@n0ts123/mcplink-core 0.0.11 → 0.0.13
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 +490 -709
- package/dist/index.d.ts +290 -415
- package/dist/index.js +473 -1529
- package/dist/index.js.map +1 -1
- package/package.json +2 -5
package/dist/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
2
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
3
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
|
-
import
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
export { createOpenAI } from '@ai-sdk/openai';
|
|
7
|
-
export { createAnthropic } from '@ai-sdk/anthropic';
|
|
4
|
+
import axios from 'axios';
|
|
8
5
|
|
|
9
6
|
// src/MCPManager.ts
|
|
10
7
|
var RECONNECT_ERROR_KEYWORDS = [
|
|
@@ -325,1514 +322,244 @@ var MCPManager = class {
|
|
|
325
322
|
this.servers.delete(id);
|
|
326
323
|
}
|
|
327
324
|
};
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
MCPLinkEventType2["TEXT_START"] = "text_start";
|
|
335
|
-
MCPLinkEventType2["TEXT_DELTA"] = "text_delta";
|
|
336
|
-
MCPLinkEventType2["TEXT_END"] = "text_end";
|
|
337
|
-
MCPLinkEventType2["TOOL_CALL_START"] = "tool_call_start";
|
|
338
|
-
MCPLinkEventType2["TOOL_CALL_DELTA"] = "tool_call_delta";
|
|
339
|
-
MCPLinkEventType2["TOOL_EXECUTING"] = "tool_executing";
|
|
340
|
-
MCPLinkEventType2["TOOL_RESULT"] = "tool_result";
|
|
341
|
-
MCPLinkEventType2["IMMEDIATE_RESULT"] = "immediate_result";
|
|
342
|
-
MCPLinkEventType2["ITERATION_START"] = "iteration_start";
|
|
343
|
-
MCPLinkEventType2["ITERATION_END"] = "iteration_end";
|
|
344
|
-
MCPLinkEventType2["COMPLETE"] = "complete";
|
|
345
|
-
MCPLinkEventType2["ERROR"] = "error";
|
|
346
|
-
return MCPLinkEventType2;
|
|
347
|
-
})(MCPLinkEventType || {});
|
|
348
|
-
|
|
349
|
-
// src/Agent.ts
|
|
350
|
-
var DEFAULT_SYSTEM_PROMPT = `\u4F60\u662F\u4E00\u4E2A\u4E13\u4E1A\u3001\u53CB\u597D\u7684\u667A\u80FD\u52A9\u624B\u3002
|
|
351
|
-
|
|
352
|
-
## \u56DE\u590D\u8981\u6C42
|
|
353
|
-
- \u7B80\u6D01\u6E05\u6670\uFF0C\u91CD\u70B9\u7A81\u51FA
|
|
354
|
-
- \u7528\u5217\u8868\u5448\u73B0\u5173\u952E\u4FE1\u606F
|
|
355
|
-
- \u8BED\u6C14\u793C\u8C8C\u81EA\u7136\uFF0C\u50CF\u4E13\u4E1A\u52A9\u624B
|
|
356
|
-
- \u6709\u7ED3\u8BBA\u65F6\u76F4\u63A5\u7ED9\u51FA\uFF0C\u9700\u8981\u8865\u5145\u4FE1\u606F\u65F6\u7B80\u5355\u8BE2\u95EE`;
|
|
357
|
-
var DEFAULT_THINKING_PHASE_PROMPT = `
|
|
358
|
-
---
|
|
359
|
-
\u8FD9\u662F\u4F60\u7684\u5185\u5FC3\u72EC\u767D\uFF0C\u7528\u6237\u770B\u4E0D\u5230\u3002
|
|
360
|
-
|
|
361
|
-
\u5224\u65AD\u5F53\u524D\u72B6\u6001\uFF1A\u6211\u62FF\u5230\u4E86\u4EC0\u4E48\uFF1F\u4EFB\u52A1\u5B8C\u6210\u4E86\u5417\uFF1F\u8FD8\u9700\u8981\u67E5\u4EC0\u4E48\uFF1F
|
|
362
|
-
|
|
363
|
-
\u91CD\u8981\uFF1A\u8FD9\u91CC\u53EA\u662F\u601D\u8003\u5224\u65AD\uFF0C\u4E0D\u8981\u5728\u8FD9\u91CC\u5199\u56DE\u590D\u5185\u5BB9\uFF08\u56DE\u590D\u662F\u4E0B\u4E00\u6B65\u7684\u4E8B\uFF09\u3002
|
|
364
|
-
---`;
|
|
365
|
-
var Agent = class {
|
|
366
|
-
model;
|
|
367
|
-
mcpManager;
|
|
368
|
-
systemPrompt;
|
|
369
|
-
maxIterations;
|
|
370
|
-
immediateResultMatchers;
|
|
371
|
-
parallelToolCalls;
|
|
372
|
-
enableThinkingPhase;
|
|
373
|
-
thinkingPhasePrompt;
|
|
374
|
-
thinkingMaxTokens;
|
|
375
|
-
constructor(model, mcpManager, options = {}) {
|
|
376
|
-
this.model = model;
|
|
377
|
-
this.mcpManager = mcpManager;
|
|
378
|
-
this.systemPrompt = options.systemPrompt || DEFAULT_SYSTEM_PROMPT;
|
|
379
|
-
this.maxIterations = options.maxIterations || 10;
|
|
380
|
-
this.immediateResultMatchers = options.immediateResultMatchers || [];
|
|
381
|
-
this.parallelToolCalls = options.parallelToolCalls ?? true;
|
|
382
|
-
this.enableThinkingPhase = options.enableThinkingPhase ?? false;
|
|
383
|
-
this.thinkingPhasePrompt = options.thinkingPhasePrompt || DEFAULT_THINKING_PHASE_PROMPT;
|
|
384
|
-
this.thinkingMaxTokens = options.thinkingMaxTokens ?? 1e3;
|
|
325
|
+
var HttpClient = class {
|
|
326
|
+
client;
|
|
327
|
+
constructor() {
|
|
328
|
+
this.client = axios.create({
|
|
329
|
+
timeout: 12e4
|
|
330
|
+
});
|
|
385
331
|
}
|
|
386
332
|
/**
|
|
387
|
-
*
|
|
333
|
+
* 非流式请求
|
|
388
334
|
*/
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
`;
|
|
399
|
-
if (tool.inputSchema.properties) {
|
|
400
|
-
description += `\u53C2\u6570:
|
|
401
|
-
`;
|
|
402
|
-
for (const [key, prop] of Object.entries(tool.inputSchema.properties)) {
|
|
403
|
-
const propInfo = prop;
|
|
404
|
-
const required = tool.inputSchema.required?.includes(key) ? "\u5FC5\u586B" : "\u53EF\u9009";
|
|
405
|
-
description += ` - ${key} (${propInfo.type || "any"}, ${required}): ${propInfo.description || ""}
|
|
406
|
-
`;
|
|
407
|
-
}
|
|
335
|
+
async chat(aiConfig, adapter, messages, tools) {
|
|
336
|
+
const body = adapter.buildRequestBody(aiConfig, messages, tools);
|
|
337
|
+
const headers = { ...adapter.getHeaders(aiConfig), ...aiConfig.headers };
|
|
338
|
+
const response = await this.client.post(
|
|
339
|
+
adapter.getEndpoint(aiConfig.baseURL),
|
|
340
|
+
body,
|
|
341
|
+
{
|
|
342
|
+
headers,
|
|
343
|
+
timeout: aiConfig.timeout || 12e4
|
|
408
344
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
return description;
|
|
345
|
+
);
|
|
346
|
+
return adapter.parseResponse(response.data);
|
|
412
347
|
}
|
|
413
348
|
/**
|
|
414
|
-
*
|
|
415
|
-
* @param toolName 工具名称
|
|
416
|
-
* @param result 工具返回的结果
|
|
417
|
-
* @returns 摘要字符串
|
|
349
|
+
* 流式请求 - SSE
|
|
418
350
|
*/
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
351
|
+
async *streamChat(aiConfig, adapter, messages, tools) {
|
|
352
|
+
const body = adapter.buildRequestBody(aiConfig, messages, tools);
|
|
353
|
+
const streamBody = { ...body, stream: true };
|
|
354
|
+
const headers = { ...adapter.getHeaders(aiConfig), ...aiConfig.headers };
|
|
355
|
+
const response = await this.client.post(
|
|
356
|
+
adapter.getEndpoint(aiConfig.baseURL),
|
|
357
|
+
streamBody,
|
|
358
|
+
{
|
|
359
|
+
headers,
|
|
360
|
+
timeout: aiConfig.timeout || 12e4,
|
|
361
|
+
responseType: "stream"
|
|
427
362
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (Array.isArray(obj[key])) {
|
|
435
|
-
count = obj[key].length;
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
363
|
+
);
|
|
364
|
+
const stream = response.data;
|
|
365
|
+
for await (const event of this.parseSSE(stream)) {
|
|
366
|
+
const parsed = adapter.parseStreamChunk(event);
|
|
367
|
+
if (parsed) {
|
|
368
|
+
yield parsed;
|
|
438
369
|
}
|
|
439
370
|
}
|
|
440
|
-
if (count > 0) {
|
|
441
|
-
return `[\u5DE5\u5177 ${toolName} \u8FD4\u56DE\u4E86\u6570\u636E\uFF0C\u5305\u542B ${count} \u6761\u8BB0\u5F55]`;
|
|
442
|
-
}
|
|
443
|
-
return `[\u5DE5\u5177 ${toolName} \u8FD4\u56DE\u4E86\u6570\u636E]`;
|
|
444
371
|
}
|
|
445
372
|
/**
|
|
446
|
-
*
|
|
447
|
-
* @param result 工具返回的结果
|
|
448
|
-
* @returns 如果匹配返回 true,否则返回 false
|
|
373
|
+
* 解析 SSE 流
|
|
449
374
|
*/
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
375
|
+
async *parseSSE(stream) {
|
|
376
|
+
let buffer = "";
|
|
377
|
+
for await (const chunk of stream) {
|
|
378
|
+
buffer += chunk.toString();
|
|
379
|
+
const lines = buffer.split("\n");
|
|
380
|
+
buffer = lines.pop() || "";
|
|
381
|
+
for (const line of lines) {
|
|
382
|
+
const trimmed = line.trim();
|
|
383
|
+
if (trimmed.startsWith("data: ")) {
|
|
384
|
+
const data = trimmed.slice(6);
|
|
385
|
+
if (data === "[DONE]") {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (data) {
|
|
389
|
+
yield data;
|
|
390
|
+
}
|
|
463
391
|
}
|
|
464
|
-
} catch {
|
|
465
|
-
if (debug) console.log("[MCPLink] \u26A0\uFE0F \u5DE5\u5177\u7ED3\u679C\u4E0D\u662F\u6709\u6548 JSON");
|
|
466
392
|
}
|
|
467
|
-
} else if (typeof result === "object" && result !== null) {
|
|
468
|
-
resultObj = result;
|
|
469
|
-
if (debug) console.log("[MCPLink] \u{1F50D} \u5DE5\u5177\u7ED3\u679C\u662F\u5BF9\u8C61:", Object.keys(result));
|
|
470
|
-
}
|
|
471
|
-
if (!resultObj) {
|
|
472
|
-
if (debug) console.log("[MCPLink] \u26A0\uFE0F \u65E0\u6CD5\u89E3\u6790\u5DE5\u5177\u7ED3\u679C\u4E3A\u5BF9\u8C61");
|
|
473
|
-
return false;
|
|
474
393
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
matched = false;
|
|
480
|
-
break;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
if (matched) {
|
|
484
|
-
if (debug) console.log("[MCPLink] \u2705 \u5373\u65F6\u7ED3\u679C\u5339\u914D\u6210\u529F:", JSON.stringify(matcher));
|
|
485
|
-
return true;
|
|
394
|
+
if (buffer.trim().startsWith("data: ")) {
|
|
395
|
+
const data = buffer.trim().slice(6);
|
|
396
|
+
if (data && data !== "[DONE]") {
|
|
397
|
+
yield data;
|
|
486
398
|
}
|
|
487
399
|
}
|
|
488
|
-
if (debug) console.log("[MCPLink] \u274C \u5373\u65F6\u7ED3\u679C\u672A\u5339\u914D\uFF0C\u671F\u671B:", JSON.stringify(this.immediateResultMatchers), "\u5B9E\u9645:", JSON.stringify(resultObj));
|
|
489
|
-
return false;
|
|
490
|
-
}
|
|
491
|
-
/**
|
|
492
|
-
* 将 MCP 工具转换为 Vercel AI SDK 格式
|
|
493
|
-
*/
|
|
494
|
-
convertMCPToolsToAITools(mcpTools) {
|
|
495
|
-
const tools = {};
|
|
496
|
-
for (const mcpTool of mcpTools) {
|
|
497
|
-
const zodSchema = this.jsonSchemaToZod(mcpTool.inputSchema);
|
|
498
|
-
tools[mcpTool.name] = {
|
|
499
|
-
description: mcpTool.description,
|
|
500
|
-
parameters: zodSchema
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
return tools;
|
|
504
400
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
} else if (enumValues.every((v) => typeof v === "number")) {
|
|
524
|
-
const literals = enumValues.map((v) => z.literal(v));
|
|
525
|
-
zodType = literals.length === 1 ? literals[0] : z.union([literals[0], literals[1], ...literals.slice(2)]);
|
|
526
|
-
} else {
|
|
527
|
-
zodType = z.unknown();
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
switch (type) {
|
|
531
|
-
case "string":
|
|
532
|
-
zodType = z.string();
|
|
533
|
-
break;
|
|
534
|
-
case "number":
|
|
535
|
-
zodType = z.number();
|
|
536
|
-
break;
|
|
537
|
-
case "integer":
|
|
538
|
-
zodType = z.number().int();
|
|
539
|
-
break;
|
|
540
|
-
case "boolean":
|
|
541
|
-
zodType = z.boolean();
|
|
542
|
-
break;
|
|
543
|
-
case "null":
|
|
544
|
-
zodType = z.null();
|
|
545
|
-
break;
|
|
546
|
-
case "object": {
|
|
547
|
-
const properties = schema.properties;
|
|
548
|
-
const required = schema.required || [];
|
|
549
|
-
if (properties) {
|
|
550
|
-
const shape = {};
|
|
551
|
-
for (const [propKey, propSchema] of Object.entries(properties)) {
|
|
552
|
-
let propZod = this.convertSchemaToZod(propSchema, required, propKey);
|
|
553
|
-
if (!required.includes(propKey)) {
|
|
554
|
-
propZod = propZod.optional();
|
|
555
|
-
}
|
|
556
|
-
shape[propKey] = propZod;
|
|
557
|
-
}
|
|
558
|
-
zodType = z.object(shape);
|
|
559
|
-
} else {
|
|
560
|
-
zodType = z.record(z.unknown());
|
|
561
|
-
}
|
|
562
|
-
break;
|
|
563
|
-
}
|
|
564
|
-
case "array": {
|
|
565
|
-
const items = schema.items;
|
|
566
|
-
if (items) {
|
|
567
|
-
const itemsRequired = items.required || [];
|
|
568
|
-
zodType = z.array(this.convertSchemaToZod(items, itemsRequired));
|
|
569
|
-
} else {
|
|
570
|
-
zodType = z.array(z.unknown());
|
|
571
|
-
}
|
|
572
|
-
break;
|
|
573
|
-
}
|
|
574
|
-
default:
|
|
575
|
-
zodType = z.unknown();
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// src/adapters/openai.ts
|
|
404
|
+
var OpenAIAdapter = class {
|
|
405
|
+
name = "openai";
|
|
406
|
+
/**
|
|
407
|
+
* 构建请求体
|
|
408
|
+
* 完全开放:除必要字段外,其他参数原封不动传递
|
|
409
|
+
*/
|
|
410
|
+
buildRequestBody(config, messages, tools) {
|
|
411
|
+
const { baseURL, apiKey, model, headers, timeout, ...customParams } = config;
|
|
412
|
+
const openaiMessages = messages.map((msg) => this.convertMessage(msg));
|
|
413
|
+
const openaiTools = tools?.map((tool) => ({
|
|
414
|
+
type: "function",
|
|
415
|
+
function: {
|
|
416
|
+
name: tool.name,
|
|
417
|
+
description: tool.description,
|
|
418
|
+
parameters: tool.parameters
|
|
576
419
|
}
|
|
420
|
+
}));
|
|
421
|
+
const body = {
|
|
422
|
+
model,
|
|
423
|
+
messages: openaiMessages,
|
|
424
|
+
// 用户自定义参数(包括 enable_thinking 等)
|
|
425
|
+
...customParams
|
|
426
|
+
};
|
|
427
|
+
if (openaiTools && openaiTools.length > 0) {
|
|
428
|
+
body.tools = openaiTools;
|
|
577
429
|
}
|
|
578
|
-
|
|
579
|
-
zodType = zodType.describe(description);
|
|
580
|
-
}
|
|
581
|
-
return zodType;
|
|
430
|
+
return body;
|
|
582
431
|
}
|
|
583
432
|
/**
|
|
584
|
-
*
|
|
433
|
+
* 获取请求头
|
|
585
434
|
*/
|
|
586
|
-
|
|
587
|
-
const startTime = Date.now();
|
|
588
|
-
const toolCallRecords = [];
|
|
589
|
-
let totalPromptTokens = 0;
|
|
590
|
-
let totalCompletionTokens = 0;
|
|
591
|
-
const messages = [
|
|
592
|
-
{ role: "system", content: this.systemPrompt },
|
|
593
|
-
{ role: "user", content: userMessage }
|
|
594
|
-
];
|
|
595
|
-
const mcpTools = this.mcpManager.getAllTools();
|
|
596
|
-
const tools = this.convertMCPToolsToAITools(mcpTools);
|
|
597
|
-
let iteration = 0;
|
|
598
|
-
let finalContent = "";
|
|
599
|
-
while (iteration < this.maxIterations) {
|
|
600
|
-
iteration++;
|
|
601
|
-
callbacks?.onIterationStart?.(iteration);
|
|
602
|
-
const response = await generateText({
|
|
603
|
-
model: this.model,
|
|
604
|
-
messages,
|
|
605
|
-
tools: Object.keys(tools).length > 0 ? tools : void 0,
|
|
606
|
-
maxSteps: 1
|
|
607
|
-
// 每次只执行一步,方便我们控制流程
|
|
608
|
-
});
|
|
609
|
-
if (response.usage) {
|
|
610
|
-
totalPromptTokens += response.usage.promptTokens;
|
|
611
|
-
totalCompletionTokens += response.usage.completionTokens;
|
|
612
|
-
}
|
|
613
|
-
const toolCalls = response.toolCalls || [];
|
|
614
|
-
if (toolCalls.length === 0) {
|
|
615
|
-
finalContent = response.text || "";
|
|
616
|
-
callbacks?.onTextDelta?.(finalContent);
|
|
617
|
-
callbacks?.onIterationEnd?.(iteration);
|
|
618
|
-
break;
|
|
619
|
-
}
|
|
620
|
-
const toolResults = [];
|
|
621
|
-
for (const toolCall of toolCalls) {
|
|
622
|
-
const toolName = toolCall.toolName;
|
|
623
|
-
const toolArgs = toolCall.args;
|
|
624
|
-
const toolCallId = toolCall.toolCallId;
|
|
625
|
-
callbacks?.onToolCallStart?.(toolName, toolArgs);
|
|
626
|
-
const toolStartTime = Date.now();
|
|
627
|
-
let result;
|
|
628
|
-
let isError = false;
|
|
629
|
-
try {
|
|
630
|
-
result = await this.mcpManager.callTool(toolName, toolArgs);
|
|
631
|
-
} catch (error) {
|
|
632
|
-
result = error instanceof Error ? error.message : String(error);
|
|
633
|
-
isError = true;
|
|
634
|
-
}
|
|
635
|
-
const duration2 = Date.now() - toolStartTime;
|
|
636
|
-
callbacks?.onToolResult?.(toolName, result, duration2);
|
|
637
|
-
toolResults.push({
|
|
638
|
-
toolCallId,
|
|
639
|
-
toolName,
|
|
640
|
-
result,
|
|
641
|
-
isError,
|
|
642
|
-
duration: duration2
|
|
643
|
-
});
|
|
644
|
-
toolCallRecords.push({
|
|
645
|
-
name: toolName,
|
|
646
|
-
arguments: toolArgs,
|
|
647
|
-
result,
|
|
648
|
-
duration: duration2
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
messages.push({
|
|
652
|
-
role: "assistant",
|
|
653
|
-
content: [
|
|
654
|
-
{ type: "text", text: response.text || "" },
|
|
655
|
-
...toolCalls.map((tc) => ({
|
|
656
|
-
type: "tool-call",
|
|
657
|
-
toolCallId: tc.toolCallId,
|
|
658
|
-
toolName: tc.toolName,
|
|
659
|
-
args: tc.args
|
|
660
|
-
}))
|
|
661
|
-
]
|
|
662
|
-
});
|
|
663
|
-
for (const tr of toolResults) {
|
|
664
|
-
messages.push({
|
|
665
|
-
role: "tool",
|
|
666
|
-
content: [
|
|
667
|
-
{
|
|
668
|
-
type: "tool-result",
|
|
669
|
-
toolCallId: tr.toolCallId,
|
|
670
|
-
toolName: tr.toolName,
|
|
671
|
-
result: tr.result
|
|
672
|
-
}
|
|
673
|
-
]
|
|
674
|
-
});
|
|
675
|
-
}
|
|
676
|
-
callbacks?.onIterationEnd?.(iteration);
|
|
677
|
-
}
|
|
678
|
-
const duration = Date.now() - startTime;
|
|
435
|
+
getHeaders(config) {
|
|
679
436
|
return {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
messages: messages.map((m) => ({
|
|
683
|
-
role: m.role,
|
|
684
|
-
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content)
|
|
685
|
-
})),
|
|
686
|
-
usage: {
|
|
687
|
-
promptTokens: totalPromptTokens,
|
|
688
|
-
completionTokens: totalCompletionTokens,
|
|
689
|
-
totalTokens: totalPromptTokens + totalCompletionTokens
|
|
690
|
-
},
|
|
691
|
-
iterations: iteration,
|
|
692
|
-
duration
|
|
437
|
+
"Content-Type": "application/json",
|
|
438
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
693
439
|
};
|
|
694
440
|
}
|
|
695
441
|
/**
|
|
696
|
-
*
|
|
442
|
+
* 获取请求端点
|
|
697
443
|
*/
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
return message.map((part) => {
|
|
703
|
-
switch (part.type) {
|
|
704
|
-
case "text":
|
|
705
|
-
return { type: "text", text: part.text };
|
|
706
|
-
case "image":
|
|
707
|
-
return { type: "image", image: part.image, mimeType: part.mimeType };
|
|
708
|
-
case "file":
|
|
709
|
-
return { type: "file", data: part.data, mimeType: part.mimeType };
|
|
710
|
-
default:
|
|
711
|
-
return { type: "text", text: "" };
|
|
712
|
-
}
|
|
713
|
-
});
|
|
444
|
+
getEndpoint(baseURL) {
|
|
445
|
+
const normalized = baseURL.replace(/\/$/, "");
|
|
446
|
+
return `${normalized}/chat/completions`;
|
|
714
447
|
}
|
|
715
448
|
/**
|
|
716
|
-
*
|
|
449
|
+
* 解析非流式响应
|
|
717
450
|
*/
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
451
|
+
parseResponse(data) {
|
|
452
|
+
const response = data;
|
|
453
|
+
const choice = response.choices[0];
|
|
454
|
+
const message = choice?.message;
|
|
455
|
+
const toolCalls = message?.tool_calls?.map((tc) => ({
|
|
456
|
+
id: tc.id,
|
|
457
|
+
name: tc.function.name,
|
|
458
|
+
arguments: JSON.parse(tc.function.arguments)
|
|
459
|
+
}));
|
|
460
|
+
return {
|
|
461
|
+
content: message?.content || "",
|
|
462
|
+
toolCalls: toolCalls?.length ? toolCalls : void 0
|
|
463
|
+
};
|
|
723
464
|
}
|
|
724
465
|
/**
|
|
725
|
-
*
|
|
726
|
-
* @param userMessage 用户消息(支持字符串或多模态数组)
|
|
727
|
-
* @param options 可选参数
|
|
728
|
-
* @param options.allowedTools 允许使用的工具名称列表,为空或不传则使用所有工具
|
|
729
|
-
* @param options.history 历史消息列表
|
|
466
|
+
* 解析流式数据块
|
|
730
467
|
*/
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
const userContent = this.convertUserMessageToContent(userMessage);
|
|
744
|
-
messages.push({ role: "user", content: userContent });
|
|
745
|
-
let mcpTools = this.mcpManager.getAllTools();
|
|
746
|
-
if (options?.allowedTools && options.allowedTools.length > 0) {
|
|
747
|
-
mcpTools = mcpTools.filter((tool) => options.allowedTools.includes(tool.name));
|
|
748
|
-
}
|
|
749
|
-
const tools = this.convertMCPToolsToAITools(mcpTools);
|
|
750
|
-
const hasTools = Object.keys(tools).length > 0;
|
|
751
|
-
let iteration = 0;
|
|
752
|
-
while (iteration < this.maxIterations) {
|
|
753
|
-
iteration++;
|
|
754
|
-
yield {
|
|
755
|
-
type: "iteration_start" /* ITERATION_START */,
|
|
756
|
-
timestamp: Date.now(),
|
|
757
|
-
data: { iteration, maxIterations: this.maxIterations }
|
|
758
|
-
};
|
|
759
|
-
if (this.enableThinkingPhase && hasTools) {
|
|
760
|
-
yield {
|
|
761
|
-
type: "thinking_start" /* THINKING_START */,
|
|
762
|
-
timestamp: Date.now(),
|
|
763
|
-
data: {}
|
|
764
|
-
};
|
|
765
|
-
const toolsDescription = this.generateToolsDescription(mcpTools);
|
|
766
|
-
const thinkingSystemPrompt = `## \u4F60\u7684\u89D2\u8272
|
|
767
|
-
\u4F60\u73B0\u5728\u662F\u300C\u5185\u90E8\u601D\u8003\u8005\u300D\uFF0C\u8FD9\u6BB5\u601D\u8003\u7528\u6237\u770B\u4E0D\u5230\u3002
|
|
768
|
-
\u4F60\u7684\u4EFB\u52A1\u662F\uFF1A\u5206\u6790\u5F53\u524D\u72B6\u6001\uFF0C\u5224\u65AD\u4E0B\u4E00\u6B65\u8BE5\u505A\u4EC0\u4E48\u3002
|
|
769
|
-
|
|
770
|
-
## \u53C2\u8003\u4FE1\u606F\uFF08\u53EF\u80FD\u5305\u542B\u91CD\u8981\u914D\u7F6E\u5982\u8BA4\u8BC1\u4FE1\u606F\uFF09
|
|
771
|
-
${this.systemPrompt}
|
|
772
|
-
|
|
773
|
-
## \u53EF\u7528\u5DE5\u5177
|
|
774
|
-
${toolsDescription}
|
|
775
|
-
${this.thinkingPhasePrompt}`;
|
|
776
|
-
const summarizedMessages = messages.slice(1).map((msg) => {
|
|
777
|
-
if (msg.role === "tool" && Array.isArray(msg.content)) {
|
|
778
|
-
const summarizedContent = msg.content.map((item) => {
|
|
779
|
-
if (item.type === "tool-result") {
|
|
780
|
-
return {
|
|
781
|
-
...item,
|
|
782
|
-
result: this.summarizeToolResult(item.toolName, item.result)
|
|
783
|
-
};
|
|
784
|
-
}
|
|
785
|
-
return item;
|
|
786
|
-
});
|
|
787
|
-
return { ...msg, content: summarizedContent };
|
|
788
|
-
}
|
|
789
|
-
return msg;
|
|
790
|
-
});
|
|
791
|
-
const thinkingMessages = [
|
|
792
|
-
{
|
|
793
|
-
role: "system",
|
|
794
|
-
content: thinkingSystemPrompt
|
|
795
|
-
},
|
|
796
|
-
...summarizedMessages
|
|
797
|
-
];
|
|
798
|
-
const thinkingStream = streamText({
|
|
799
|
-
model: this.model,
|
|
800
|
-
messages: thinkingMessages,
|
|
801
|
-
maxTokens: this.thinkingMaxTokens
|
|
802
|
-
// 不传 tools,强制 AI 输出文本思考
|
|
803
|
-
});
|
|
804
|
-
let thinkingContent = "";
|
|
805
|
-
for await (const chunk of thinkingStream.fullStream) {
|
|
806
|
-
if (chunk.type === "text-delta") {
|
|
807
|
-
thinkingContent += chunk.textDelta;
|
|
808
|
-
yield {
|
|
809
|
-
type: "thinking_delta" /* THINKING_DELTA */,
|
|
810
|
-
timestamp: Date.now(),
|
|
811
|
-
data: { content: chunk.textDelta }
|
|
812
|
-
};
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
yield {
|
|
816
|
-
type: "thinking_end" /* THINKING_END */,
|
|
817
|
-
timestamp: Date.now(),
|
|
818
|
-
data: {}
|
|
819
|
-
};
|
|
820
|
-
if (thinkingContent) {
|
|
821
|
-
messages.push({
|
|
822
|
-
role: "assistant",
|
|
823
|
-
content: `[\u5185\u90E8\u51B3\u7B56]
|
|
824
|
-
${thinkingContent}`
|
|
825
|
-
});
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
const stream = streamText({
|
|
829
|
-
model: this.model,
|
|
830
|
-
messages,
|
|
831
|
-
tools: hasTools ? tools : void 0,
|
|
832
|
-
maxSteps: 1
|
|
833
|
-
});
|
|
834
|
-
let fullText = "";
|
|
835
|
-
let reasoningText = "";
|
|
836
|
-
const toolCalls = [];
|
|
837
|
-
let currentToolCall = null;
|
|
838
|
-
let hasStartedText = false;
|
|
839
|
-
let hasStartedReasoning = false;
|
|
840
|
-
const sentToolCallStarts = /* @__PURE__ */ new Set();
|
|
841
|
-
let isInsideThinkTag = false;
|
|
842
|
-
let textBuffer = "";
|
|
843
|
-
for await (const chunk of stream.fullStream) {
|
|
844
|
-
switch (chunk.type) {
|
|
845
|
-
case "reasoning":
|
|
846
|
-
if (!hasStartedReasoning) {
|
|
847
|
-
hasStartedReasoning = true;
|
|
848
|
-
yield {
|
|
849
|
-
type: "thinking_start" /* THINKING_START */,
|
|
850
|
-
timestamp: Date.now(),
|
|
851
|
-
data: {}
|
|
852
|
-
};
|
|
853
|
-
}
|
|
854
|
-
reasoningText += chunk.textDelta;
|
|
855
|
-
yield {
|
|
856
|
-
type: "thinking_delta" /* THINKING_DELTA */,
|
|
857
|
-
timestamp: Date.now(),
|
|
858
|
-
data: { content: chunk.textDelta }
|
|
859
|
-
};
|
|
860
|
-
break;
|
|
861
|
-
case "text-delta":
|
|
862
|
-
const delta = chunk.textDelta;
|
|
863
|
-
textBuffer += delta;
|
|
864
|
-
if (!isInsideThinkTag) {
|
|
865
|
-
const thinkStartMatch = textBuffer.match(/<think>/i);
|
|
866
|
-
if (thinkStartMatch) {
|
|
867
|
-
const beforeThink = textBuffer.substring(0, thinkStartMatch.index);
|
|
868
|
-
if (beforeThink.trim()) {
|
|
869
|
-
if (!hasStartedText) {
|
|
870
|
-
hasStartedText = true;
|
|
871
|
-
yield {
|
|
872
|
-
type: "text_start" /* TEXT_START */,
|
|
873
|
-
timestamp: Date.now(),
|
|
874
|
-
data: {}
|
|
875
|
-
};
|
|
876
|
-
}
|
|
877
|
-
fullText += beforeThink;
|
|
878
|
-
yield {
|
|
879
|
-
type: "text_delta" /* TEXT_DELTA */,
|
|
880
|
-
timestamp: Date.now(),
|
|
881
|
-
data: { content: beforeThink }
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
isInsideThinkTag = true;
|
|
885
|
-
if (!hasStartedReasoning) {
|
|
886
|
-
hasStartedReasoning = true;
|
|
887
|
-
yield {
|
|
888
|
-
type: "thinking_start" /* THINKING_START */,
|
|
889
|
-
timestamp: Date.now(),
|
|
890
|
-
data: {}
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
textBuffer = textBuffer.substring(thinkStartMatch.index + 7);
|
|
894
|
-
} else if (!textBuffer.includes("<")) {
|
|
895
|
-
if (hasStartedReasoning && !hasStartedText) {
|
|
896
|
-
yield {
|
|
897
|
-
type: "thinking_end" /* THINKING_END */,
|
|
898
|
-
timestamp: Date.now(),
|
|
899
|
-
data: {}
|
|
900
|
-
};
|
|
901
|
-
}
|
|
902
|
-
if (!hasStartedText) {
|
|
903
|
-
hasStartedText = true;
|
|
904
|
-
yield {
|
|
905
|
-
type: "text_start" /* TEXT_START */,
|
|
906
|
-
timestamp: Date.now(),
|
|
907
|
-
data: {}
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
fullText += textBuffer;
|
|
911
|
-
yield {
|
|
912
|
-
type: "text_delta" /* TEXT_DELTA */,
|
|
913
|
-
timestamp: Date.now(),
|
|
914
|
-
data: { content: textBuffer }
|
|
915
|
-
};
|
|
916
|
-
textBuffer = "";
|
|
917
|
-
}
|
|
918
|
-
} else {
|
|
919
|
-
const thinkEndMatch = textBuffer.match(/<\/think>/i);
|
|
920
|
-
if (thinkEndMatch) {
|
|
921
|
-
const thinkContent = textBuffer.substring(0, thinkEndMatch.index);
|
|
922
|
-
if (thinkContent) {
|
|
923
|
-
reasoningText += thinkContent;
|
|
924
|
-
yield {
|
|
925
|
-
type: "thinking_delta" /* THINKING_DELTA */,
|
|
926
|
-
timestamp: Date.now(),
|
|
927
|
-
data: { content: thinkContent }
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
yield {
|
|
931
|
-
type: "thinking_end" /* THINKING_END */,
|
|
932
|
-
timestamp: Date.now(),
|
|
933
|
-
data: {}
|
|
934
|
-
};
|
|
935
|
-
isInsideThinkTag = false;
|
|
936
|
-
textBuffer = textBuffer.substring(thinkEndMatch.index + 8);
|
|
937
|
-
} else if (!textBuffer.includes("<")) {
|
|
938
|
-
reasoningText += textBuffer;
|
|
939
|
-
yield {
|
|
940
|
-
type: "thinking_delta" /* THINKING_DELTA */,
|
|
941
|
-
timestamp: Date.now(),
|
|
942
|
-
data: { content: textBuffer }
|
|
943
|
-
};
|
|
944
|
-
textBuffer = "";
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
break;
|
|
948
|
-
case "tool-call":
|
|
949
|
-
if (!sentToolCallStarts.has(chunk.toolCallId)) {
|
|
950
|
-
yield {
|
|
951
|
-
type: "tool_call_start" /* TOOL_CALL_START */,
|
|
952
|
-
timestamp: Date.now(),
|
|
953
|
-
data: {
|
|
954
|
-
toolName: chunk.toolName,
|
|
955
|
-
toolCallId: chunk.toolCallId,
|
|
956
|
-
toolArgs: chunk.args
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
sentToolCallStarts.add(chunk.toolCallId);
|
|
960
|
-
toolCalls.push({
|
|
961
|
-
toolCallId: chunk.toolCallId,
|
|
962
|
-
toolName: chunk.toolName,
|
|
963
|
-
args: chunk.args
|
|
964
|
-
});
|
|
965
|
-
}
|
|
966
|
-
break;
|
|
967
|
-
case "tool-call-streaming-start":
|
|
968
|
-
currentToolCall = {
|
|
969
|
-
toolCallId: chunk.toolCallId,
|
|
970
|
-
toolName: chunk.toolName,
|
|
971
|
-
argsText: ""
|
|
972
|
-
};
|
|
973
|
-
if (!sentToolCallStarts.has(chunk.toolCallId)) {
|
|
974
|
-
yield {
|
|
975
|
-
type: "tool_call_start" /* TOOL_CALL_START */,
|
|
976
|
-
timestamp: Date.now(),
|
|
977
|
-
data: {
|
|
978
|
-
toolName: chunk.toolName,
|
|
979
|
-
toolCallId: chunk.toolCallId
|
|
980
|
-
}
|
|
981
|
-
};
|
|
982
|
-
sentToolCallStarts.add(chunk.toolCallId);
|
|
983
|
-
}
|
|
984
|
-
break;
|
|
985
|
-
case "tool-call-delta":
|
|
986
|
-
if (currentToolCall) {
|
|
987
|
-
currentToolCall.argsText += chunk.argsTextDelta;
|
|
988
|
-
yield {
|
|
989
|
-
type: "tool_call_delta" /* TOOL_CALL_DELTA */,
|
|
990
|
-
timestamp: Date.now(),
|
|
991
|
-
data: {
|
|
992
|
-
toolCallId: currentToolCall.toolCallId,
|
|
993
|
-
argsTextDelta: chunk.argsTextDelta
|
|
994
|
-
}
|
|
995
|
-
};
|
|
996
|
-
}
|
|
997
|
-
break;
|
|
998
|
-
case "finish":
|
|
999
|
-
if (textBuffer) {
|
|
1000
|
-
if (isInsideThinkTag) {
|
|
1001
|
-
reasoningText += textBuffer;
|
|
1002
|
-
yield {
|
|
1003
|
-
type: "thinking_delta" /* THINKING_DELTA */,
|
|
1004
|
-
timestamp: Date.now(),
|
|
1005
|
-
data: { content: textBuffer }
|
|
1006
|
-
};
|
|
1007
|
-
} else {
|
|
1008
|
-
if (!hasStartedText) {
|
|
1009
|
-
hasStartedText = true;
|
|
1010
|
-
yield {
|
|
1011
|
-
type: "text_start" /* TEXT_START */,
|
|
1012
|
-
timestamp: Date.now(),
|
|
1013
|
-
data: {}
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
fullText += textBuffer;
|
|
1017
|
-
yield {
|
|
1018
|
-
type: "text_delta" /* TEXT_DELTA */,
|
|
1019
|
-
timestamp: Date.now(),
|
|
1020
|
-
data: { content: textBuffer }
|
|
1021
|
-
};
|
|
1022
|
-
}
|
|
1023
|
-
textBuffer = "";
|
|
1024
|
-
}
|
|
1025
|
-
if (isInsideThinkTag || hasStartedReasoning && !hasStartedText) {
|
|
1026
|
-
yield {
|
|
1027
|
-
type: "thinking_end" /* THINKING_END */,
|
|
1028
|
-
timestamp: Date.now(),
|
|
1029
|
-
data: {}
|
|
1030
|
-
};
|
|
1031
|
-
isInsideThinkTag = false;
|
|
1032
|
-
}
|
|
1033
|
-
if (hasStartedText) {
|
|
1034
|
-
yield {
|
|
1035
|
-
type: "text_end" /* TEXT_END */,
|
|
1036
|
-
timestamp: Date.now(),
|
|
1037
|
-
data: {}
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
break;
|
|
1041
|
-
case "error":
|
|
1042
|
-
yield {
|
|
1043
|
-
type: "error" /* ERROR */,
|
|
1044
|
-
timestamp: Date.now(),
|
|
1045
|
-
data: { error: chunk.error }
|
|
1046
|
-
};
|
|
1047
|
-
break;
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
if (toolCalls.length === 0) {
|
|
1051
|
-
yield {
|
|
1052
|
-
type: "iteration_end" /* ITERATION_END */,
|
|
1053
|
-
timestamp: Date.now(),
|
|
1054
|
-
data: { iteration }
|
|
1055
|
-
};
|
|
1056
|
-
break;
|
|
1057
|
-
}
|
|
1058
|
-
const toolResults = [];
|
|
1059
|
-
for (const toolCall of toolCalls) {
|
|
1060
|
-
yield {
|
|
1061
|
-
type: "tool_executing" /* TOOL_EXECUTING */,
|
|
1062
|
-
timestamp: Date.now(),
|
|
1063
|
-
data: {
|
|
1064
|
-
toolName: toolCall.toolName,
|
|
1065
|
-
toolCallId: toolCall.toolCallId,
|
|
1066
|
-
toolArgs: toolCall.args
|
|
1067
|
-
}
|
|
1068
|
-
};
|
|
1069
|
-
}
|
|
1070
|
-
let hasImmediateResult = false;
|
|
1071
|
-
if (this.parallelToolCalls && toolCalls.length > 1) {
|
|
1072
|
-
const executePromises = toolCalls.map(async (toolCall) => {
|
|
1073
|
-
const toolStartTime = Date.now();
|
|
1074
|
-
let result;
|
|
1075
|
-
let isError = false;
|
|
1076
|
-
try {
|
|
1077
|
-
result = await this.mcpManager.callTool(toolCall.toolName, toolCall.args);
|
|
1078
|
-
} catch (error) {
|
|
1079
|
-
result = error instanceof Error ? error.message : String(error);
|
|
1080
|
-
isError = true;
|
|
1081
|
-
}
|
|
1082
|
-
const duration = Date.now() - toolStartTime;
|
|
468
|
+
parseStreamChunk(line) {
|
|
469
|
+
try {
|
|
470
|
+
const data = JSON.parse(line);
|
|
471
|
+
const choice = data.choices[0];
|
|
472
|
+
const delta = choice?.delta;
|
|
473
|
+
if (delta?.content) {
|
|
474
|
+
return { type: "text", content: delta.content };
|
|
475
|
+
}
|
|
476
|
+
if (delta?.tool_calls) {
|
|
477
|
+
const tc = delta.tool_calls[0];
|
|
478
|
+
if (tc.id && tc.function?.name) {
|
|
1083
479
|
return {
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
duration
|
|
1090
|
-
};
|
|
1091
|
-
});
|
|
1092
|
-
const results = await Promise.all(executePromises);
|
|
1093
|
-
for (const r of results) {
|
|
1094
|
-
yield {
|
|
1095
|
-
type: "tool_result" /* TOOL_RESULT */,
|
|
1096
|
-
timestamp: Date.now(),
|
|
1097
|
-
data: {
|
|
1098
|
-
toolName: r.toolName,
|
|
1099
|
-
toolResult: r.result,
|
|
1100
|
-
toolCallId: r.toolCallId,
|
|
1101
|
-
duration: r.duration,
|
|
1102
|
-
isError: r.isError
|
|
480
|
+
type: "tool_call",
|
|
481
|
+
toolCall: {
|
|
482
|
+
id: tc.id,
|
|
483
|
+
name: tc.function.name,
|
|
484
|
+
arguments: tc.function.arguments ? JSON.parse(tc.function.arguments) : {}
|
|
1103
485
|
}
|
|
1104
486
|
};
|
|
1105
|
-
if (!r.isError && this.matchImmediateResult(r.result)) {
|
|
1106
|
-
hasImmediateResult = true;
|
|
1107
|
-
yield {
|
|
1108
|
-
type: "immediate_result" /* IMMEDIATE_RESULT */,
|
|
1109
|
-
timestamp: Date.now(),
|
|
1110
|
-
data: {
|
|
1111
|
-
toolName: r.toolName,
|
|
1112
|
-
toolCallId: r.toolCallId,
|
|
1113
|
-
immediateResult: r.result
|
|
1114
|
-
}
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
toolResults.push({
|
|
1118
|
-
toolCallId: r.toolCallId,
|
|
1119
|
-
toolName: r.toolName,
|
|
1120
|
-
result: r.result,
|
|
1121
|
-
isError: r.isError,
|
|
1122
|
-
duration: r.duration
|
|
1123
|
-
});
|
|
1124
|
-
toolCallRecords.push({
|
|
1125
|
-
name: r.toolName,
|
|
1126
|
-
arguments: r.args,
|
|
1127
|
-
result: r.result,
|
|
1128
|
-
duration: r.duration
|
|
1129
|
-
});
|
|
1130
|
-
}
|
|
1131
|
-
} else {
|
|
1132
|
-
for (const toolCall of toolCalls) {
|
|
1133
|
-
const toolName = toolCall.toolName;
|
|
1134
|
-
const toolArgs = toolCall.args;
|
|
1135
|
-
const toolCallId = toolCall.toolCallId;
|
|
1136
|
-
const toolStartTime = Date.now();
|
|
1137
|
-
let result;
|
|
1138
|
-
let isError = false;
|
|
1139
|
-
try {
|
|
1140
|
-
result = await this.mcpManager.callTool(toolName, toolArgs);
|
|
1141
|
-
} catch (error) {
|
|
1142
|
-
result = error instanceof Error ? error.message : String(error);
|
|
1143
|
-
isError = true;
|
|
1144
|
-
}
|
|
1145
|
-
const duration = Date.now() - toolStartTime;
|
|
1146
|
-
yield {
|
|
1147
|
-
type: "tool_result" /* TOOL_RESULT */,
|
|
1148
|
-
timestamp: Date.now(),
|
|
1149
|
-
data: {
|
|
1150
|
-
toolName,
|
|
1151
|
-
toolResult: result,
|
|
1152
|
-
toolCallId,
|
|
1153
|
-
duration,
|
|
1154
|
-
isError
|
|
1155
|
-
}
|
|
1156
|
-
};
|
|
1157
|
-
if (!isError && this.matchImmediateResult(result)) {
|
|
1158
|
-
hasImmediateResult = true;
|
|
1159
|
-
yield {
|
|
1160
|
-
type: "immediate_result" /* IMMEDIATE_RESULT */,
|
|
1161
|
-
timestamp: Date.now(),
|
|
1162
|
-
data: {
|
|
1163
|
-
toolName,
|
|
1164
|
-
toolCallId,
|
|
1165
|
-
immediateResult: result
|
|
1166
|
-
}
|
|
1167
|
-
};
|
|
1168
|
-
}
|
|
1169
|
-
toolResults.push({
|
|
1170
|
-
toolCallId,
|
|
1171
|
-
toolName,
|
|
1172
|
-
result,
|
|
1173
|
-
isError,
|
|
1174
|
-
duration
|
|
1175
|
-
});
|
|
1176
|
-
toolCallRecords.push({
|
|
1177
|
-
name: toolName,
|
|
1178
|
-
arguments: toolArgs,
|
|
1179
|
-
result,
|
|
1180
|
-
duration
|
|
1181
|
-
});
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
if (hasImmediateResult) {
|
|
1185
|
-
yield {
|
|
1186
|
-
type: "iteration_end" /* ITERATION_END */,
|
|
1187
|
-
timestamp: Date.now(),
|
|
1188
|
-
data: { iteration }
|
|
1189
|
-
};
|
|
1190
|
-
break;
|
|
1191
|
-
}
|
|
1192
|
-
messages.push({
|
|
1193
|
-
role: "assistant",
|
|
1194
|
-
content: [
|
|
1195
|
-
...fullText ? [{ type: "text", text: fullText }] : [],
|
|
1196
|
-
...toolCalls.map((tc) => ({
|
|
1197
|
-
type: "tool-call",
|
|
1198
|
-
toolCallId: tc.toolCallId,
|
|
1199
|
-
toolName: tc.toolName,
|
|
1200
|
-
args: tc.args
|
|
1201
|
-
}))
|
|
1202
|
-
]
|
|
1203
|
-
});
|
|
1204
|
-
for (const tr of toolResults) {
|
|
1205
|
-
messages.push({
|
|
1206
|
-
role: "tool",
|
|
1207
|
-
content: [
|
|
1208
|
-
{
|
|
1209
|
-
type: "tool-result",
|
|
1210
|
-
toolCallId: tr.toolCallId,
|
|
1211
|
-
toolName: tr.toolName,
|
|
1212
|
-
result: tr.result
|
|
1213
|
-
}
|
|
1214
|
-
]
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
yield {
|
|
1218
|
-
type: "iteration_end" /* ITERATION_END */,
|
|
1219
|
-
timestamp: Date.now(),
|
|
1220
|
-
data: { iteration }
|
|
1221
|
-
};
|
|
1222
|
-
}
|
|
1223
|
-
const totalDuration = Date.now() - startTime;
|
|
1224
|
-
yield {
|
|
1225
|
-
type: "complete" /* COMPLETE */,
|
|
1226
|
-
timestamp: Date.now(),
|
|
1227
|
-
data: {
|
|
1228
|
-
totalIterations: iteration,
|
|
1229
|
-
totalDuration
|
|
1230
|
-
}
|
|
1231
|
-
};
|
|
1232
|
-
}
|
|
1233
|
-
};
|
|
1234
|
-
var PromptBasedAgent = class {
|
|
1235
|
-
model;
|
|
1236
|
-
mcpManager;
|
|
1237
|
-
systemPrompt;
|
|
1238
|
-
maxIterations;
|
|
1239
|
-
immediateResultMatchers;
|
|
1240
|
-
parallelToolCalls;
|
|
1241
|
-
// PromptBasedAgent 本身通过 prompt 实现思考,此配置保留以保持接口一致
|
|
1242
|
-
enableThinkingPhase;
|
|
1243
|
-
constructor(model, mcpManager, options = {}) {
|
|
1244
|
-
this.model = model;
|
|
1245
|
-
this.mcpManager = mcpManager;
|
|
1246
|
-
this.systemPrompt = options.systemPrompt || "";
|
|
1247
|
-
this.maxIterations = options.maxIterations || 10;
|
|
1248
|
-
this.immediateResultMatchers = options.immediateResultMatchers || [];
|
|
1249
|
-
this.parallelToolCalls = options.parallelToolCalls ?? true;
|
|
1250
|
-
this.enableThinkingPhase = options.enableThinkingPhase ?? false;
|
|
1251
|
-
}
|
|
1252
|
-
/**
|
|
1253
|
-
* 检查工具返回结果是否匹配即时结果匹配器
|
|
1254
|
-
* @param result 工具返回的结果
|
|
1255
|
-
* @returns 如果匹配返回 true,否则返回 false
|
|
1256
|
-
*/
|
|
1257
|
-
matchImmediateResult(result) {
|
|
1258
|
-
const debug = process.env.DEBUG_MCPLINK === "true";
|
|
1259
|
-
if (!this.immediateResultMatchers.length) {
|
|
1260
|
-
if (debug) console.log("[MCPLink] \u26A0\uFE0F \u672A\u914D\u7F6E\u5373\u65F6\u7ED3\u679C\u5339\u914D\u5668");
|
|
1261
|
-
return false;
|
|
1262
|
-
}
|
|
1263
|
-
let resultObj = null;
|
|
1264
|
-
if (typeof result === "string") {
|
|
1265
|
-
try {
|
|
1266
|
-
const parsed = JSON.parse(result);
|
|
1267
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
1268
|
-
resultObj = parsed;
|
|
1269
|
-
if (debug) console.log("[MCPLink] \u{1F50D} \u89E3\u6790\u5DE5\u5177\u7ED3\u679C\u4E3A\u5BF9\u8C61:", Object.keys(parsed));
|
|
1270
|
-
}
|
|
1271
|
-
} catch {
|
|
1272
|
-
if (debug) console.log("[MCPLink] \u26A0\uFE0F \u5DE5\u5177\u7ED3\u679C\u4E0D\u662F\u6709\u6548 JSON");
|
|
1273
|
-
}
|
|
1274
|
-
} else if (typeof result === "object" && result !== null) {
|
|
1275
|
-
resultObj = result;
|
|
1276
|
-
if (debug) console.log("[MCPLink] \u{1F50D} \u5DE5\u5177\u7ED3\u679C\u662F\u5BF9\u8C61:", Object.keys(result));
|
|
1277
|
-
}
|
|
1278
|
-
if (!resultObj) {
|
|
1279
|
-
if (debug) console.log("[MCPLink] \u26A0\uFE0F \u65E0\u6CD5\u89E3\u6790\u5DE5\u5177\u7ED3\u679C\u4E3A\u5BF9\u8C61");
|
|
1280
|
-
return false;
|
|
1281
|
-
}
|
|
1282
|
-
for (const matcher of this.immediateResultMatchers) {
|
|
1283
|
-
let matched = true;
|
|
1284
|
-
for (const [key, value] of Object.entries(matcher)) {
|
|
1285
|
-
if (resultObj[key] !== value) {
|
|
1286
|
-
matched = false;
|
|
1287
|
-
break;
|
|
1288
487
|
}
|
|
1289
488
|
}
|
|
1290
|
-
if (
|
|
1291
|
-
|
|
1292
|
-
return true;
|
|
489
|
+
if (choice?.finish_reason) {
|
|
490
|
+
return { type: "done" };
|
|
1293
491
|
}
|
|
492
|
+
return null;
|
|
493
|
+
} catch {
|
|
494
|
+
return null;
|
|
1294
495
|
}
|
|
1295
|
-
if (debug) console.log("[MCPLink] \u274C \u5373\u65F6\u7ED3\u679C\u672A\u5339\u914D\uFF0C\u671F\u671B:", JSON.stringify(this.immediateResultMatchers), "\u5B9E\u9645:", JSON.stringify(resultObj));
|
|
1296
|
-
return false;
|
|
1297
496
|
}
|
|
1298
497
|
/**
|
|
1299
|
-
*
|
|
498
|
+
* 转换消息格式
|
|
1300
499
|
*/
|
|
1301
|
-
|
|
1302
|
-
if (
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
}
|
|
1315
|
-
return description;
|
|
1316
|
-
}
|
|
1317
|
-
/**
|
|
1318
|
-
* 内置系统提示词 - 强调格式约束
|
|
1319
|
-
*/
|
|
1320
|
-
BUILT_IN_PROMPT = `
|
|
1321
|
-
## \u5DE5\u5177\u8C03\u7528\u683C\u5F0F\uFF08\u5FC5\u987B\u4E25\u683C\u9075\u5B88\uFF09
|
|
1322
|
-
|
|
1323
|
-
\u5F53\u4F60\u9700\u8981\u83B7\u53D6\u6570\u636E\u6216\u6267\u884C\u64CD\u4F5C\u65F6\uFF0C**\u53EA\u80FD**\u4F7F\u7528\u4EE5\u4E0B\u683C\u5F0F\uFF1A
|
|
1324
|
-
|
|
1325
|
-
<tool_call>
|
|
1326
|
-
{"name": "\u5DE5\u5177\u540D\u79F0", "arguments": {"\u53C2\u6570\u540D": "\u503C"}}
|
|
1327
|
-
</tool_call>
|
|
1328
|
-
|
|
1329
|
-
### \u5DE5\u4F5C\u6D41\u7A0B
|
|
1330
|
-
1. \u5206\u6790\u7528\u6237\u9700\u6C42
|
|
1331
|
-
2. \u5982\u9700\u6570\u636E\uFF0C\u8F93\u51FA <tool_call>...</tool_call> \u540E**\u7ACB\u5373\u505C\u6B62**
|
|
1332
|
-
3. \u7CFB\u7EDF\u4F1A\u6267\u884C\u5DE5\u5177\u5E76\u8FD4\u56DE\u771F\u5B9E\u7ED3\u679C
|
|
1333
|
-
4. \u6536\u5230\u7ED3\u679C\u540E\uFF0C\u7528\u4E2D\u6587\u6574\u7406\u56DE\u590D\u7528\u6237
|
|
1334
|
-
|
|
1335
|
-
### \u4E25\u683C\u7981\u6B62
|
|
1336
|
-
- \u274C \u81EA\u5DF1\u7F16\u5199\u5DE5\u5177\u8FD4\u56DE\u7ED3\u679C\uFF08\u5982 \`\u7ED3\u679C:{...}\` \u6216 \`{"code":200...}\`\uFF09
|
|
1337
|
-
- \u274C \u6A21\u62DF\u5DE5\u5177\u8C03\u7528\uFF08\u5982 \`RPCCall:\`\u3001\`FunctionCall:\`\uFF09
|
|
1338
|
-
- \u274C \u5728\u6CA1\u6709\u771F\u5B9E\u5DE5\u5177\u7ED3\u679C\u7684\u60C5\u51B5\u4E0B\u7F16\u9020\u6570\u636E
|
|
1339
|
-
- \u274C \u4E00\u6B21\u8F93\u51FA\u4E2D\u540C\u65F6\u5305\u542B\u5DE5\u5177\u8C03\u7528\u548C\u6700\u7EC8\u56DE\u590D
|
|
1340
|
-
|
|
1341
|
-
### \u6B63\u786E\u793A\u4F8B
|
|
1342
|
-
\u7528\u6237: "\u67E5\u8BE2\u6211\u7684\u8BA2\u5355"
|
|
1343
|
-
\u4F60\u7684\u8F93\u51FA:
|
|
1344
|
-
<tool_call>
|
|
1345
|
-
{"name": "get_orders", "arguments": {"token": "xxx"}}
|
|
1346
|
-
</tool_call>
|
|
1347
|
-
|
|
1348
|
-
\uFF08\u7136\u540E\u505C\u6B62\uFF0C\u7B49\u5F85\u7CFB\u7EDF\u8FD4\u56DE\u771F\u5B9E\u7ED3\u679C\uFF09
|
|
1349
|
-
|
|
1350
|
-
### \u56DE\u590D\u683C\u5F0F
|
|
1351
|
-
- \u4F7F\u7528\u4E2D\u6587
|
|
1352
|
-
- \u4F7F\u7528 Markdown \u683C\u5F0F\u7F8E\u5316\u8F93\u51FA
|
|
1353
|
-
- \u5217\u8868\u6570\u636E\u6BCF\u9879\u72EC\u5360\u4E00\u884C
|
|
1354
|
-
`;
|
|
1355
|
-
/**
|
|
1356
|
-
* 构建完整的系统提示词
|
|
1357
|
-
*/
|
|
1358
|
-
buildSystemPrompt(tools) {
|
|
1359
|
-
const toolsDescription = this.generateToolsDescription(tools);
|
|
1360
|
-
const userPrompt = this.systemPrompt || "\u4F60\u662F\u4E00\u4E2A\u667A\u80FD\u52A9\u624B\u3002";
|
|
1361
|
-
return `${userPrompt}
|
|
1362
|
-
|
|
1363
|
-
## \u53EF\u7528\u5DE5\u5177
|
|
1364
|
-
${toolsDescription}
|
|
1365
|
-
${this.BUILT_IN_PROMPT}`;
|
|
1366
|
-
}
|
|
1367
|
-
/**
|
|
1368
|
-
* 解析工具调用
|
|
1369
|
-
*/
|
|
1370
|
-
parseToolCall(text) {
|
|
1371
|
-
const tagMatch = text.match(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/i);
|
|
1372
|
-
if (tagMatch) {
|
|
1373
|
-
try {
|
|
1374
|
-
const json = JSON.parse(tagMatch[1].trim());
|
|
1375
|
-
if (json.name) return { name: json.name, arguments: json.arguments || {} };
|
|
1376
|
-
} catch {
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
const codeMatch = text.match(/```(?:json)?\s*\n?\s*(\{[\s\S]*?"name"[\s\S]*?\})\s*\n?\s*```/i);
|
|
1380
|
-
if (codeMatch) {
|
|
1381
|
-
try {
|
|
1382
|
-
const json = JSON.parse(codeMatch[1].trim());
|
|
1383
|
-
if (json.name) return { name: json.name, arguments: json.arguments || {} };
|
|
1384
|
-
} catch {
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
const jsonMatch = text.match(/\{\s*"name"\s*:\s*"([^"]+)"[\s\S]*?"arguments"\s*:\s*(\{[\s\S]*?\})\s*\}/i);
|
|
1388
|
-
if (jsonMatch) {
|
|
1389
|
-
try {
|
|
1390
|
-
const fullMatch = jsonMatch[0];
|
|
1391
|
-
const json = JSON.parse(fullMatch);
|
|
1392
|
-
if (json.name) return { name: json.name, arguments: json.arguments || {} };
|
|
1393
|
-
} catch {
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
return null;
|
|
1397
|
-
}
|
|
1398
|
-
/**
|
|
1399
|
-
* 智能压缩历史消息
|
|
1400
|
-
* - 用户消息完整保留
|
|
1401
|
-
* - AI 回复保留关键信息(ID、名称、数量、价格等)
|
|
1402
|
-
* - 去除冗长的 JSON 原始数据
|
|
1403
|
-
*/
|
|
1404
|
-
compressHistory(history) {
|
|
1405
|
-
const MAX_USER_MESSAGE_LENGTH = 500;
|
|
1406
|
-
const MAX_ASSISTANT_MESSAGE_LENGTH = 1500;
|
|
1407
|
-
const recentHistory = history.slice(-20);
|
|
1408
|
-
return recentHistory.map((msg) => {
|
|
1409
|
-
if (msg.role === "user") {
|
|
1410
|
-
if (msg.content.length <= MAX_USER_MESSAGE_LENGTH) {
|
|
1411
|
-
return msg;
|
|
1412
|
-
}
|
|
1413
|
-
return {
|
|
1414
|
-
role: msg.role,
|
|
1415
|
-
content: msg.content.slice(0, MAX_USER_MESSAGE_LENGTH) + "..."
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
let content = msg.content;
|
|
1419
|
-
content = content.replace(/```json\n[\s\S]*?\n```/g, "[\u5DE5\u5177\u8FD4\u56DE\u6570\u636E]");
|
|
1420
|
-
content = content.replace(/## .*?\(原始JSON\)[\s\S]*?(?=##|$)/g, "");
|
|
1421
|
-
if (content.length > MAX_ASSISTANT_MESSAGE_LENGTH) {
|
|
1422
|
-
const tableMatch = content.match(/\|[\s\S]*?\|/g);
|
|
1423
|
-
if (tableMatch) {
|
|
1424
|
-
const tables = tableMatch.join("\n");
|
|
1425
|
-
if (tables.length < MAX_ASSISTANT_MESSAGE_LENGTH) {
|
|
1426
|
-
content = content.slice(0, MAX_ASSISTANT_MESSAGE_LENGTH - tables.length) + "\n" + tables;
|
|
500
|
+
convertMessage(msg) {
|
|
501
|
+
if (msg.role !== "tool") {
|
|
502
|
+
const result = {
|
|
503
|
+
role: msg.role,
|
|
504
|
+
content: msg.content
|
|
505
|
+
};
|
|
506
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
507
|
+
result.tool_calls = msg.toolCalls.map((tc) => ({
|
|
508
|
+
id: tc.id,
|
|
509
|
+
type: "function",
|
|
510
|
+
function: {
|
|
511
|
+
name: tc.name,
|
|
512
|
+
arguments: JSON.stringify(tc.arguments)
|
|
1427
513
|
}
|
|
1428
|
-
}
|
|
1429
|
-
content = content.slice(0, MAX_ASSISTANT_MESSAGE_LENGTH) + "...";
|
|
1430
|
-
}
|
|
1431
|
-
return { role: msg.role, content: content.trim() || msg.content.slice(0, 500) };
|
|
1432
|
-
});
|
|
1433
|
-
}
|
|
1434
|
-
/**
|
|
1435
|
-
* 将 UserMessage 转换为 Vercel AI SDK 的消息内容格式
|
|
1436
|
-
*/
|
|
1437
|
-
convertUserMessageToContent(message) {
|
|
1438
|
-
if (typeof message === "string") {
|
|
1439
|
-
return message;
|
|
1440
|
-
}
|
|
1441
|
-
return message.map((part) => {
|
|
1442
|
-
switch (part.type) {
|
|
1443
|
-
case "text":
|
|
1444
|
-
return { type: "text", text: part.text };
|
|
1445
|
-
case "image":
|
|
1446
|
-
return { type: "image", image: part.image, mimeType: part.mimeType };
|
|
1447
|
-
case "file":
|
|
1448
|
-
return { type: "file", data: part.data, mimeType: part.mimeType };
|
|
1449
|
-
default:
|
|
1450
|
-
return { type: "text", text: "" };
|
|
1451
|
-
}
|
|
1452
|
-
});
|
|
1453
|
-
}
|
|
1454
|
-
/**
|
|
1455
|
-
* 从 UserMessage 提取纯文本内容(用于日志等)
|
|
1456
|
-
*/
|
|
1457
|
-
extractTextFromMessage(message) {
|
|
1458
|
-
if (typeof message === "string") {
|
|
1459
|
-
return message;
|
|
1460
|
-
}
|
|
1461
|
-
return message.filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
1462
|
-
}
|
|
1463
|
-
/**
|
|
1464
|
-
* 流式对话(支持多模态消息)
|
|
1465
|
-
*/
|
|
1466
|
-
async *chatStream(userMessage, options) {
|
|
1467
|
-
const startTime = Date.now();
|
|
1468
|
-
let mcpTools = this.mcpManager.getAllTools();
|
|
1469
|
-
if (options?.allowedTools?.length) {
|
|
1470
|
-
mcpTools = mcpTools.filter((t) => options.allowedTools.includes(t.name));
|
|
1471
|
-
}
|
|
1472
|
-
const messages = [
|
|
1473
|
-
{ role: "system", content: this.buildSystemPrompt(mcpTools) }
|
|
1474
|
-
];
|
|
1475
|
-
if (options?.history?.length) {
|
|
1476
|
-
const compressedHistory = this.compressHistory(options.history);
|
|
1477
|
-
console.log(`[PromptBasedAgent] \u{1F4DA} \u5386\u53F2\u6D88\u606F: ${options.history.length} \u6761 -> \u538B\u7F29\u540E: ${compressedHistory.length} \u6761`);
|
|
1478
|
-
for (const msg of compressedHistory) {
|
|
1479
|
-
messages.push({ role: msg.role, content: msg.content });
|
|
514
|
+
}));
|
|
1480
515
|
}
|
|
516
|
+
return result;
|
|
1481
517
|
}
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
while (iteration < this.maxIterations) {
|
|
1489
|
-
iteration++;
|
|
1490
|
-
yield {
|
|
1491
|
-
type: "iteration_start" /* ITERATION_START */,
|
|
1492
|
-
timestamp: Date.now(),
|
|
1493
|
-
data: { iteration, maxIterations: this.maxIterations }
|
|
518
|
+
if (msg.toolResults && msg.toolResults.length > 0) {
|
|
519
|
+
const tr = msg.toolResults[0];
|
|
520
|
+
return {
|
|
521
|
+
role: "tool",
|
|
522
|
+
tool_call_id: tr.toolCallId,
|
|
523
|
+
content: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
|
|
1494
524
|
};
|
|
1495
|
-
console.log(`[PromptBasedAgent] \u{1F916} \u8C03\u7528\u6A21\u578B\uFF0C\u8FED\u4EE3 ${iteration}/${this.maxIterations}...`);
|
|
1496
|
-
const modelStartTime = Date.now();
|
|
1497
|
-
const stream = streamText({
|
|
1498
|
-
model: this.model,
|
|
1499
|
-
messages,
|
|
1500
|
-
// 设置请求超时
|
|
1501
|
-
experimental_telemetry: {
|
|
1502
|
-
isEnabled: false
|
|
1503
|
-
// 禁用遥测以减少开销
|
|
1504
|
-
}
|
|
1505
|
-
});
|
|
1506
|
-
let fullResponse = "";
|
|
1507
|
-
let buffer = "";
|
|
1508
|
-
let inThinking = false;
|
|
1509
|
-
let inToolCall = false;
|
|
1510
|
-
let thinkingStarted = false;
|
|
1511
|
-
let thinkingEnded = false;
|
|
1512
|
-
let textStarted = false;
|
|
1513
|
-
let firstChunkReceived = false;
|
|
1514
|
-
const FIRST_CHUNK_TIMEOUT = 12e4;
|
|
1515
|
-
let timeoutId = null;
|
|
1516
|
-
new Promise((_, reject) => {
|
|
1517
|
-
timeoutId = setTimeout(() => {
|
|
1518
|
-
reject(new Error(`\u6A21\u578B\u54CD\u5E94\u8D85\u65F6 (${FIRST_CHUNK_TIMEOUT / 1e3}\u79D2\u65E0\u54CD\u5E94)`));
|
|
1519
|
-
}, FIRST_CHUNK_TIMEOUT);
|
|
1520
|
-
});
|
|
1521
|
-
try {
|
|
1522
|
-
for await (const chunk of stream.fullStream) {
|
|
1523
|
-
if (!firstChunkReceived) {
|
|
1524
|
-
firstChunkReceived = true;
|
|
1525
|
-
if (timeoutId) {
|
|
1526
|
-
clearTimeout(timeoutId);
|
|
1527
|
-
timeoutId = null;
|
|
1528
|
-
}
|
|
1529
|
-
console.log(`[PromptBasedAgent] \u26A1 \u9996\u4E2A chunk \u5230\u8FBE\uFF0C\u8017\u65F6: ${Date.now() - modelStartTime}ms`);
|
|
1530
|
-
}
|
|
1531
|
-
if (chunk.type === "reasoning") {
|
|
1532
|
-
if (!thinkingStarted) {
|
|
1533
|
-
thinkingStarted = true;
|
|
1534
|
-
yield { type: "thinking_start" /* THINKING_START */, timestamp: Date.now(), data: {} };
|
|
1535
|
-
}
|
|
1536
|
-
if (chunk.textDelta) {
|
|
1537
|
-
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: chunk.textDelta } };
|
|
1538
|
-
}
|
|
1539
|
-
continue;
|
|
1540
|
-
}
|
|
1541
|
-
if (chunk.type === "text-delta") {
|
|
1542
|
-
const delta = chunk.textDelta;
|
|
1543
|
-
buffer += delta;
|
|
1544
|
-
fullResponse += delta;
|
|
1545
|
-
while (buffer.length > 0) {
|
|
1546
|
-
if (!inThinking && !inToolCall) {
|
|
1547
|
-
const thinkStart = buffer.indexOf("<think>");
|
|
1548
|
-
if (thinkStart !== -1) {
|
|
1549
|
-
if (thinkStart > 0) {
|
|
1550
|
-
const before = buffer.substring(0, thinkStart);
|
|
1551
|
-
if (before.trim() && thinkingEnded) {
|
|
1552
|
-
if (!textStarted) {
|
|
1553
|
-
textStarted = true;
|
|
1554
|
-
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1555
|
-
}
|
|
1556
|
-
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: before } };
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
inThinking = true;
|
|
1560
|
-
if (!thinkingStarted) {
|
|
1561
|
-
thinkingStarted = true;
|
|
1562
|
-
yield { type: "thinking_start" /* THINKING_START */, timestamp: Date.now(), data: {} };
|
|
1563
|
-
}
|
|
1564
|
-
buffer = buffer.substring(thinkStart + 7);
|
|
1565
|
-
continue;
|
|
1566
|
-
}
|
|
1567
|
-
const toolStart = buffer.indexOf("<tool_call>");
|
|
1568
|
-
if (toolStart !== -1) {
|
|
1569
|
-
if (toolStart > 0) {
|
|
1570
|
-
const before = buffer.substring(0, toolStart);
|
|
1571
|
-
if (before.trim() && thinkingEnded) {
|
|
1572
|
-
if (!textStarted) {
|
|
1573
|
-
textStarted = true;
|
|
1574
|
-
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1575
|
-
}
|
|
1576
|
-
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: before } };
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
inToolCall = true;
|
|
1580
|
-
buffer = buffer.substring(toolStart + 11);
|
|
1581
|
-
continue;
|
|
1582
|
-
}
|
|
1583
|
-
if (!buffer.includes("<")) {
|
|
1584
|
-
if (buffer.trim() && (thinkingEnded || !thinkingStarted)) {
|
|
1585
|
-
if (!textStarted) {
|
|
1586
|
-
textStarted = true;
|
|
1587
|
-
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1588
|
-
}
|
|
1589
|
-
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: buffer } };
|
|
1590
|
-
}
|
|
1591
|
-
buffer = "";
|
|
1592
|
-
}
|
|
1593
|
-
break;
|
|
1594
|
-
}
|
|
1595
|
-
if (inThinking) {
|
|
1596
|
-
const thinkEnd = buffer.indexOf("</think>");
|
|
1597
|
-
if (thinkEnd !== -1) {
|
|
1598
|
-
const content = buffer.substring(0, thinkEnd);
|
|
1599
|
-
if (content) {
|
|
1600
|
-
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content } };
|
|
1601
|
-
}
|
|
1602
|
-
yield { type: "thinking_end" /* THINKING_END */, timestamp: Date.now(), data: {} };
|
|
1603
|
-
thinkingEnded = true;
|
|
1604
|
-
inThinking = false;
|
|
1605
|
-
buffer = buffer.substring(thinkEnd + 8);
|
|
1606
|
-
continue;
|
|
1607
|
-
}
|
|
1608
|
-
if (buffer.length > 10 && !buffer.includes("<")) {
|
|
1609
|
-
const safe = buffer.substring(0, buffer.length - 10);
|
|
1610
|
-
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: safe } };
|
|
1611
|
-
buffer = buffer.substring(safe.length);
|
|
1612
|
-
}
|
|
1613
|
-
break;
|
|
1614
|
-
}
|
|
1615
|
-
if (inToolCall) {
|
|
1616
|
-
const toolEnd = buffer.indexOf("</tool_call>");
|
|
1617
|
-
if (toolEnd !== -1) {
|
|
1618
|
-
inToolCall = false;
|
|
1619
|
-
buffer = buffer.substring(toolEnd + 12);
|
|
1620
|
-
continue;
|
|
1621
|
-
}
|
|
1622
|
-
break;
|
|
1623
|
-
}
|
|
1624
|
-
break;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
if (chunk.type === "finish") {
|
|
1628
|
-
if (buffer.trim()) {
|
|
1629
|
-
if (inThinking) {
|
|
1630
|
-
yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: buffer } };
|
|
1631
|
-
yield { type: "thinking_end" /* THINKING_END */, timestamp: Date.now(), data: {} };
|
|
1632
|
-
thinkingEnded = true;
|
|
1633
|
-
} else if (!inToolCall) {
|
|
1634
|
-
if (!textStarted) {
|
|
1635
|
-
textStarted = true;
|
|
1636
|
-
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1637
|
-
}
|
|
1638
|
-
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: buffer } };
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
if (textStarted) {
|
|
1642
|
-
yield { type: "text_end" /* TEXT_END */, timestamp: Date.now(), data: {} };
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
} finally {
|
|
1647
|
-
if (timeoutId) {
|
|
1648
|
-
clearTimeout(timeoutId);
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
|
-
const toolCall = this.parseToolCall(fullResponse);
|
|
1652
|
-
if (toolCall) {
|
|
1653
|
-
const toolCallId = `tool-${Date.now()}`;
|
|
1654
|
-
yield {
|
|
1655
|
-
type: "tool_call_start" /* TOOL_CALL_START */,
|
|
1656
|
-
timestamp: Date.now(),
|
|
1657
|
-
data: { toolName: toolCall.name, toolCallId, toolArgs: toolCall.arguments }
|
|
1658
|
-
};
|
|
1659
|
-
yield {
|
|
1660
|
-
type: "tool_executing" /* TOOL_EXECUTING */,
|
|
1661
|
-
timestamp: Date.now(),
|
|
1662
|
-
data: { toolName: toolCall.name, toolCallId, toolArgs: toolCall.arguments }
|
|
1663
|
-
};
|
|
1664
|
-
const toolStartTime = Date.now();
|
|
1665
|
-
let result;
|
|
1666
|
-
let isError = false;
|
|
1667
|
-
try {
|
|
1668
|
-
result = await this.mcpManager.callTool(toolCall.name, toolCall.arguments);
|
|
1669
|
-
} catch (error) {
|
|
1670
|
-
result = error instanceof Error ? error.message : String(error);
|
|
1671
|
-
isError = true;
|
|
1672
|
-
}
|
|
1673
|
-
const duration = Date.now() - toolStartTime;
|
|
1674
|
-
yield {
|
|
1675
|
-
type: "tool_result" /* TOOL_RESULT */,
|
|
1676
|
-
timestamp: Date.now(),
|
|
1677
|
-
data: { toolName: toolCall.name, toolResult: result, toolCallId, duration, isError }
|
|
1678
|
-
};
|
|
1679
|
-
if (!isError && this.matchImmediateResult(result)) {
|
|
1680
|
-
yield {
|
|
1681
|
-
type: "immediate_result" /* IMMEDIATE_RESULT */,
|
|
1682
|
-
timestamp: Date.now(),
|
|
1683
|
-
data: {
|
|
1684
|
-
toolName: toolCall.name,
|
|
1685
|
-
toolCallId,
|
|
1686
|
-
immediateResult: result
|
|
1687
|
-
}
|
|
1688
|
-
};
|
|
1689
|
-
yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
|
|
1690
|
-
yield {
|
|
1691
|
-
type: "complete" /* COMPLETE */,
|
|
1692
|
-
timestamp: Date.now(),
|
|
1693
|
-
data: { totalDuration: Date.now() - startTime, totalIterations: iteration }
|
|
1694
|
-
};
|
|
1695
|
-
return;
|
|
1696
|
-
}
|
|
1697
|
-
messages.push({ role: "assistant", content: fullResponse });
|
|
1698
|
-
const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
1699
|
-
messages.push({
|
|
1700
|
-
role: "user",
|
|
1701
|
-
content: `\u5DE5\u5177 ${toolCall.name} \u8FD4\u56DE\u7ED3\u679C\uFF1A
|
|
1702
|
-
${resultStr}
|
|
1703
|
-
|
|
1704
|
-
\u8BF7\u6839\u636E\u7ED3\u679C\u7528\u4E2D\u6587\u56DE\u590D\u7528\u6237\u3002`
|
|
1705
|
-
});
|
|
1706
|
-
yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
|
|
1707
|
-
continue;
|
|
1708
|
-
}
|
|
1709
|
-
console.log(`[PromptBasedAgent] \u2705 \u6A21\u578B\u54CD\u5E94\u5B8C\u6210\uFF0C\u8017\u65F6: ${Date.now() - modelStartTime}ms\uFF0C\u54CD\u5E94\u957F\u5EA6: ${fullResponse.length}`);
|
|
1710
|
-
if (!textStarted && fullResponse.trim()) {
|
|
1711
|
-
let cleanText = fullResponse.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, "").trim();
|
|
1712
|
-
if (cleanText) {
|
|
1713
|
-
yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
|
|
1714
|
-
yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: cleanText } };
|
|
1715
|
-
yield { type: "text_end" /* TEXT_END */, timestamp: Date.now(), data: {} };
|
|
1716
|
-
}
|
|
1717
|
-
}
|
|
1718
|
-
yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
|
|
1719
|
-
break;
|
|
1720
525
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
data: { totalDuration: Date.now() - startTime, totalIterations: iteration }
|
|
526
|
+
return {
|
|
527
|
+
role: "user",
|
|
528
|
+
content: msg.content
|
|
1725
529
|
};
|
|
1726
530
|
}
|
|
1727
531
|
};
|
|
532
|
+
var openaiAdapter = new OpenAIAdapter();
|
|
1728
533
|
|
|
1729
534
|
// src/MCPLink.ts
|
|
1730
|
-
var NATIVE_FUNCTION_CALLING_PATTERNS = [
|
|
1731
|
-
// OpenAI GPT 系列 - 支持原生 function calling
|
|
1732
|
-
/^gpt/i,
|
|
1733
|
-
// OpenAI o1/o3 需要特殊处理,暂用 PromptBased
|
|
1734
|
-
// /^o1/i,
|
|
1735
|
-
// /^o3/i,
|
|
1736
|
-
// Anthropic Claude - 支持原生 function calling
|
|
1737
|
-
/^claude/i,
|
|
1738
|
-
// Google Gemini 稳定版 - 支持原生 function calling
|
|
1739
|
-
// 注意:gemini-*-preview/thinking 版本需要特殊处理,不在此列表
|
|
1740
|
-
/^gemini-[\d.]+-flash$/i,
|
|
1741
|
-
/^gemini-[\d.]+-pro$/i,
|
|
1742
|
-
/^gemini-pro$/i,
|
|
1743
|
-
/^gemini-flash$/i,
|
|
1744
|
-
// Mistral - 支持原生 function calling
|
|
1745
|
-
/^mistral/i,
|
|
1746
|
-
/^mixtral/i,
|
|
1747
|
-
// Cohere Command-R - 支持原生 function calling
|
|
1748
|
-
/^command-r/i
|
|
1749
|
-
];
|
|
1750
|
-
var PROMPT_BASED_PATTERNS = [
|
|
1751
|
-
// DeepSeek(不支持原生 function calling)
|
|
1752
|
-
/deepseek/i,
|
|
1753
|
-
// OpenAI o1/o3 思考模型
|
|
1754
|
-
/^o1/i,
|
|
1755
|
-
/^o3/i,
|
|
1756
|
-
// Gemini 思考/预览版本 - 需要 thought_signature,暂用 PromptBased
|
|
1757
|
-
/gemini.*preview/i,
|
|
1758
|
-
/gemini.*thinking/i,
|
|
1759
|
-
/gemini.*exp/i,
|
|
1760
|
-
// 开源模型(大多数不支持原生 function calling)
|
|
1761
|
-
/^llama/i,
|
|
1762
|
-
/^phi-/i,
|
|
1763
|
-
/^qwen/i,
|
|
1764
|
-
/^yi-/i,
|
|
1765
|
-
/^glm/i,
|
|
1766
|
-
/^baichuan/i
|
|
1767
|
-
];
|
|
1768
|
-
function detectNativeToolSupport(modelId) {
|
|
1769
|
-
console.log(`[MCPLink] \u{1F50D} \u68C0\u6D4B\u6A21\u578B: "${modelId}"`);
|
|
1770
|
-
for (const pattern of PROMPT_BASED_PATTERNS) {
|
|
1771
|
-
if (pattern.test(modelId)) {
|
|
1772
|
-
console.log(`[MCPLink] \u2705 Model "${modelId}" -> PromptBasedAgent (matched: ${pattern})`);
|
|
1773
|
-
return false;
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
for (const pattern of NATIVE_FUNCTION_CALLING_PATTERNS) {
|
|
1777
|
-
if (pattern.test(modelId)) {
|
|
1778
|
-
console.log(`[MCPLink] \u2705 Model "${modelId}" -> Agent (\u539F\u751F\u6A21\u5F0F, matched: ${pattern})`);
|
|
1779
|
-
return true;
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
console.log(`[MCPLink] \u26A0\uFE0F Model "${modelId}" -> PromptBasedAgent (\u672A\u77E5\u6A21\u578B\uFF0C\u9ED8\u8BA4)`);
|
|
1783
|
-
return false;
|
|
1784
|
-
}
|
|
1785
535
|
var MCPLink = class {
|
|
1786
|
-
model;
|
|
1787
|
-
mcpManager;
|
|
1788
|
-
agent;
|
|
1789
|
-
promptBasedAgent;
|
|
1790
536
|
config;
|
|
537
|
+
mcpManager;
|
|
538
|
+
httpClient;
|
|
539
|
+
adapter;
|
|
1791
540
|
initialized = false;
|
|
1792
|
-
detectedNativeSupport;
|
|
1793
541
|
constructor(config) {
|
|
1794
542
|
this.config = config;
|
|
1795
|
-
this.model = config.model;
|
|
1796
543
|
this.mcpManager = new MCPManager();
|
|
544
|
+
this.httpClient = new HttpClient();
|
|
545
|
+
if (typeof config.adapter === "string") {
|
|
546
|
+
this.adapter = this.getAdapterByType(config.adapter);
|
|
547
|
+
} else if (config.adapter) {
|
|
548
|
+
this.adapter = config.adapter;
|
|
549
|
+
} else {
|
|
550
|
+
this.adapter = openaiAdapter;
|
|
551
|
+
}
|
|
1797
552
|
if (config.mcpServers) {
|
|
1798
553
|
for (const [id, serverConfig] of Object.entries(config.mcpServers)) {
|
|
1799
554
|
this.mcpManager.addServer(id, serverConfig);
|
|
1800
555
|
}
|
|
1801
556
|
}
|
|
1802
|
-
this.agent = new Agent(this.model, this.mcpManager, {
|
|
1803
|
-
systemPrompt: config.systemPrompt,
|
|
1804
|
-
maxIterations: config.maxIterations,
|
|
1805
|
-
immediateResultMatchers: config.immediateResultMatchers,
|
|
1806
|
-
parallelToolCalls: config.parallelToolCalls,
|
|
1807
|
-
enableThinkingPhase: config.enableThinkingPhase,
|
|
1808
|
-
thinkingPhasePrompt: config.thinkingPhasePrompt,
|
|
1809
|
-
thinkingMaxTokens: config.thinkingMaxTokens
|
|
1810
|
-
});
|
|
1811
|
-
this.promptBasedAgent = new PromptBasedAgent(this.model, this.mcpManager, {
|
|
1812
|
-
systemPrompt: config.systemPrompt,
|
|
1813
|
-
maxIterations: config.maxIterations,
|
|
1814
|
-
immediateResultMatchers: config.immediateResultMatchers,
|
|
1815
|
-
parallelToolCalls: config.parallelToolCalls,
|
|
1816
|
-
enableThinkingPhase: config.enableThinkingPhase,
|
|
1817
|
-
thinkingPhasePrompt: config.thinkingPhasePrompt,
|
|
1818
|
-
thinkingMaxTokens: config.thinkingMaxTokens
|
|
1819
|
-
});
|
|
1820
|
-
if (config.usePromptBasedTools === true) {
|
|
1821
|
-
this.detectedNativeSupport = false;
|
|
1822
|
-
} else if (config.usePromptBasedTools === false) {
|
|
1823
|
-
this.detectedNativeSupport = true;
|
|
1824
|
-
} else {
|
|
1825
|
-
const modelNameToCheck = config.modelName || config.model.modelId;
|
|
1826
|
-
this.detectedNativeSupport = detectNativeToolSupport(modelNameToCheck);
|
|
1827
|
-
}
|
|
1828
557
|
}
|
|
1829
558
|
/**
|
|
1830
559
|
* 初始化 - 连接所有 MCP 服务器
|
|
1831
560
|
*/
|
|
1832
561
|
async initialize() {
|
|
1833
|
-
if (this.initialized)
|
|
1834
|
-
return;
|
|
1835
|
-
}
|
|
562
|
+
if (this.initialized) return;
|
|
1836
563
|
await this.mcpManager.startAll();
|
|
1837
564
|
this.initialized = true;
|
|
1838
565
|
}
|
|
@@ -1844,132 +571,349 @@ var MCPLink = class {
|
|
|
1844
571
|
this.initialized = false;
|
|
1845
572
|
}
|
|
1846
573
|
/**
|
|
1847
|
-
*
|
|
1848
|
-
|
|
1849
|
-
|
|
574
|
+
* 对话 - 极简设计
|
|
575
|
+
*
|
|
576
|
+
* 流程:
|
|
577
|
+
* 1. 发送消息给 AI
|
|
578
|
+
* 2. AI 返回文本/工具调用
|
|
579
|
+
* 3. 如果有工具调用,执行 MCP 工具
|
|
580
|
+
* 4. 返回结果(包括新的消息历史,用户可选择是否继续)
|
|
581
|
+
*
|
|
582
|
+
* 用户需要自己:
|
|
583
|
+
* - 维护 messages 历史
|
|
584
|
+
* - 处理流式响应(通过 onStream)
|
|
585
|
+
* - 决定是否继续迭代(如果返回了 toolCalls)
|
|
586
|
+
*/
|
|
587
|
+
async chat(options) {
|
|
1850
588
|
if (!this.initialized) {
|
|
1851
589
|
await this.initialize();
|
|
1852
590
|
}
|
|
1853
|
-
|
|
591
|
+
const startTime = Date.now();
|
|
592
|
+
const maxIterations = this.config.maxIterations ?? 10;
|
|
593
|
+
let iterations = 0;
|
|
594
|
+
let messages = [...options.messages];
|
|
595
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
596
|
+
const tools = mcpTools.map((t) => ({
|
|
597
|
+
name: t.name,
|
|
598
|
+
description: t.description,
|
|
599
|
+
parameters: t.inputSchema
|
|
600
|
+
}));
|
|
601
|
+
while (iterations < maxIterations) {
|
|
602
|
+
iterations++;
|
|
603
|
+
const response = options.stream !== false ? await this.streamChat(messages, tools, options.onStream) : await this.singleChat(messages, tools);
|
|
604
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
605
|
+
messages.push({
|
|
606
|
+
role: "assistant",
|
|
607
|
+
content: response.content
|
|
608
|
+
});
|
|
609
|
+
return {
|
|
610
|
+
content: response.content,
|
|
611
|
+
messages,
|
|
612
|
+
iterations,
|
|
613
|
+
duration: Date.now() - startTime
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const assistantMessage = {
|
|
617
|
+
role: "assistant",
|
|
618
|
+
content: response.content,
|
|
619
|
+
toolCalls: response.toolCalls
|
|
620
|
+
};
|
|
621
|
+
const toolResults = [];
|
|
622
|
+
for (const tc of response.toolCalls) {
|
|
623
|
+
let result;
|
|
624
|
+
let isError = false;
|
|
625
|
+
try {
|
|
626
|
+
result = await this.mcpManager.callTool(tc.name, tc.arguments);
|
|
627
|
+
} catch (error) {
|
|
628
|
+
result = error instanceof Error ? error.message : String(error);
|
|
629
|
+
isError = true;
|
|
630
|
+
}
|
|
631
|
+
toolResults.push({
|
|
632
|
+
toolCallId: tc.id,
|
|
633
|
+
toolName: tc.name,
|
|
634
|
+
result,
|
|
635
|
+
isError
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
messages.push(assistantMessage);
|
|
639
|
+
messages.push({
|
|
640
|
+
role: "tool",
|
|
641
|
+
content: "",
|
|
642
|
+
toolResults
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
content: messages[messages.length - 1]?.content || "",
|
|
647
|
+
messages,
|
|
648
|
+
iterations,
|
|
649
|
+
duration: Date.now() - startTime
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* 单次 AI 调用(非流式)
|
|
654
|
+
*/
|
|
655
|
+
async singleChat(messages, tools) {
|
|
656
|
+
const response = await this.httpClient.chat(
|
|
657
|
+
this.config.ai,
|
|
658
|
+
this.adapter,
|
|
659
|
+
messages,
|
|
660
|
+
tools
|
|
661
|
+
);
|
|
662
|
+
return {
|
|
663
|
+
content: response.content,
|
|
664
|
+
toolCalls: response.toolCalls
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* 流式 AI 调用
|
|
669
|
+
*/
|
|
670
|
+
async streamChat(messages, tools, onStream) {
|
|
671
|
+
let content = "";
|
|
672
|
+
const toolCalls = [];
|
|
673
|
+
for await (const event of this.httpClient.streamChat(
|
|
674
|
+
this.config.ai,
|
|
675
|
+
this.adapter,
|
|
676
|
+
messages,
|
|
677
|
+
tools
|
|
678
|
+
)) {
|
|
679
|
+
const shouldContinue = onStream?.(event);
|
|
680
|
+
if (shouldContinue === false) {
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
switch (event.type) {
|
|
684
|
+
case "text":
|
|
685
|
+
content += event.content;
|
|
686
|
+
break;
|
|
687
|
+
case "tool_call":
|
|
688
|
+
toolCalls.push(event.toolCall);
|
|
689
|
+
break;
|
|
690
|
+
case "error":
|
|
691
|
+
throw event.error;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return {
|
|
695
|
+
content,
|
|
696
|
+
toolCalls: toolCalls.length ? toolCalls : void 0
|
|
697
|
+
};
|
|
1854
698
|
}
|
|
1855
699
|
/**
|
|
1856
|
-
* 流式对话
|
|
1857
|
-
*
|
|
1858
|
-
* @param options 可选参数
|
|
1859
|
-
* @param options.allowedTools 允许使用的工具名称列表
|
|
1860
|
-
* @param options.history 历史消息列表
|
|
700
|
+
* 流式对话 - 公共方法
|
|
701
|
+
* 直接发起流式请求并返回结果
|
|
1861
702
|
*/
|
|
1862
|
-
async
|
|
703
|
+
async chatStream(messages, onStream) {
|
|
1863
704
|
if (!this.initialized) {
|
|
1864
705
|
await this.initialize();
|
|
1865
706
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
707
|
+
const startTime = Date.now();
|
|
708
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
709
|
+
const tools = mcpTools.map((t) => ({
|
|
710
|
+
name: t.name,
|
|
711
|
+
description: t.description,
|
|
712
|
+
parameters: t.inputSchema
|
|
713
|
+
}));
|
|
714
|
+
const response = await this.streamChat(messages, tools, onStream);
|
|
715
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
716
|
+
const toolResults = [];
|
|
717
|
+
for (const tc of response.toolCalls) {
|
|
718
|
+
let result;
|
|
719
|
+
let isError = false;
|
|
720
|
+
try {
|
|
721
|
+
result = await this.mcpManager.callTool(tc.name, tc.arguments);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
result = error instanceof Error ? error.message : String(error);
|
|
724
|
+
isError = true;
|
|
725
|
+
}
|
|
726
|
+
toolResults.push({
|
|
727
|
+
toolCallId: tc.id,
|
|
728
|
+
toolName: tc.name,
|
|
729
|
+
result,
|
|
730
|
+
isError
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
const assistantMessage = {
|
|
734
|
+
role: "assistant",
|
|
735
|
+
content: response.content,
|
|
736
|
+
toolCalls: response.toolCalls
|
|
737
|
+
};
|
|
738
|
+
messages.push(assistantMessage);
|
|
739
|
+
messages.push({
|
|
740
|
+
role: "tool",
|
|
741
|
+
content: "",
|
|
742
|
+
toolResults
|
|
743
|
+
});
|
|
744
|
+
return {
|
|
745
|
+
content: response.content,
|
|
746
|
+
messages,
|
|
747
|
+
iterations: 1,
|
|
748
|
+
duration: Date.now() - startTime
|
|
749
|
+
};
|
|
1870
750
|
}
|
|
751
|
+
messages.push({
|
|
752
|
+
role: "assistant",
|
|
753
|
+
content: response.content
|
|
754
|
+
});
|
|
755
|
+
return {
|
|
756
|
+
content: response.content,
|
|
757
|
+
messages,
|
|
758
|
+
iterations: 1,
|
|
759
|
+
duration: Date.now() - startTime
|
|
760
|
+
};
|
|
1871
761
|
}
|
|
1872
762
|
/**
|
|
1873
|
-
*
|
|
763
|
+
* 根据类型获取适配器
|
|
1874
764
|
*/
|
|
1875
|
-
|
|
1876
|
-
|
|
765
|
+
getAdapterByType(type) {
|
|
766
|
+
switch (type) {
|
|
767
|
+
case "openai":
|
|
768
|
+
return openaiAdapter;
|
|
769
|
+
default:
|
|
770
|
+
throw new Error(`Unknown adapter type: ${type}`);
|
|
771
|
+
}
|
|
1877
772
|
}
|
|
1878
773
|
// ============ MCP 服务器管理 ============
|
|
1879
|
-
/**
|
|
1880
|
-
* 添加 MCP 服务器
|
|
1881
|
-
*/
|
|
1882
774
|
addMCPServer(id, config) {
|
|
1883
775
|
this.mcpManager.addServer(id, config);
|
|
1884
776
|
}
|
|
1885
|
-
/**
|
|
1886
|
-
* 移除 MCP 服务器
|
|
1887
|
-
*/
|
|
1888
777
|
async removeMCPServer(id) {
|
|
1889
778
|
await this.mcpManager.removeServer(id);
|
|
1890
779
|
}
|
|
1891
|
-
/**
|
|
1892
|
-
* 启动指定 MCP 服务器
|
|
1893
|
-
*/
|
|
1894
780
|
async startMCPServer(id) {
|
|
1895
781
|
await this.mcpManager.startServer(id);
|
|
1896
782
|
}
|
|
1897
|
-
/**
|
|
1898
|
-
* 停止指定 MCP 服务器
|
|
1899
|
-
*/
|
|
1900
783
|
async stopMCPServer(id) {
|
|
1901
784
|
await this.mcpManager.stopServer(id);
|
|
1902
785
|
}
|
|
1903
|
-
/**
|
|
1904
|
-
* 获取所有 MCP 服务器状态
|
|
1905
|
-
*/
|
|
1906
786
|
getMCPServerStatuses() {
|
|
1907
787
|
return this.mcpManager.getServerStatuses();
|
|
1908
788
|
}
|
|
1909
|
-
/**
|
|
1910
|
-
* 获取所有可用工具
|
|
1911
|
-
*/
|
|
1912
789
|
getTools() {
|
|
1913
790
|
return this.mcpManager.getAllTools();
|
|
1914
791
|
}
|
|
1915
|
-
/**
|
|
1916
|
-
* 手动调用工具
|
|
1917
|
-
*/
|
|
1918
792
|
async callTool(toolName, args) {
|
|
1919
793
|
return this.mcpManager.callTool(toolName, args);
|
|
1920
794
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
// src/standard-stream.ts
|
|
798
|
+
async function* toStandardStream(rawStream, options) {
|
|
799
|
+
let iteration = 0;
|
|
800
|
+
options.maxIterations ?? 10;
|
|
801
|
+
const startTime = Date.now();
|
|
802
|
+
let hasTextStarted = false;
|
|
803
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
804
|
+
for await (const event of rawStream) {
|
|
805
|
+
const shouldContinue = options.onRawEvent?.(event);
|
|
806
|
+
if (shouldContinue === false) break;
|
|
807
|
+
switch (event.type) {
|
|
808
|
+
case "text":
|
|
809
|
+
if (!hasTextStarted) {
|
|
810
|
+
hasTextStarted = true;
|
|
811
|
+
yield { type: "text_start" };
|
|
812
|
+
}
|
|
813
|
+
if (event.content) {
|
|
814
|
+
yield { type: "text_delta", content: event.content };
|
|
815
|
+
}
|
|
816
|
+
break;
|
|
817
|
+
case "tool_call": {
|
|
818
|
+
if (hasTextStarted) {
|
|
819
|
+
hasTextStarted = false;
|
|
820
|
+
yield { type: "text_end" };
|
|
821
|
+
}
|
|
822
|
+
const tc = event.toolCall;
|
|
823
|
+
pendingToolCalls.set(tc.id, { ...tc, argsBuffer: "" });
|
|
824
|
+
yield {
|
|
825
|
+
type: "tool_call_start",
|
|
826
|
+
toolCallId: tc.id,
|
|
827
|
+
toolName: tc.name,
|
|
828
|
+
toolArgs: tc.arguments
|
|
829
|
+
};
|
|
830
|
+
yield {
|
|
831
|
+
type: "tool_executing",
|
|
832
|
+
toolCallId: tc.id,
|
|
833
|
+
toolName: tc.name
|
|
834
|
+
};
|
|
835
|
+
const toolStartTime = Date.now();
|
|
836
|
+
let result;
|
|
837
|
+
let isError = false;
|
|
838
|
+
try {
|
|
839
|
+
result = await options.executeTool(tc.name, tc.arguments);
|
|
840
|
+
} catch (error) {
|
|
841
|
+
result = error instanceof Error ? error.message : String(error);
|
|
842
|
+
isError = true;
|
|
843
|
+
}
|
|
844
|
+
const duration = Date.now() - toolStartTime;
|
|
845
|
+
yield {
|
|
846
|
+
type: "tool_result",
|
|
847
|
+
toolCallId: tc.id,
|
|
848
|
+
toolName: tc.name,
|
|
849
|
+
toolResult: result,
|
|
850
|
+
duration,
|
|
851
|
+
isError
|
|
852
|
+
};
|
|
853
|
+
pendingToolCalls.delete(tc.id);
|
|
854
|
+
break;
|
|
855
|
+
}
|
|
856
|
+
case "done":
|
|
857
|
+
if (hasTextStarted) {
|
|
858
|
+
hasTextStarted = false;
|
|
859
|
+
yield { type: "text_end" };
|
|
860
|
+
}
|
|
861
|
+
break;
|
|
862
|
+
case "error":
|
|
863
|
+
yield { type: "error", error: event.error };
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
1945
866
|
}
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
*/
|
|
1949
|
-
setModel(model) {
|
|
1950
|
-
this.model = model;
|
|
1951
|
-
this.config.model = model;
|
|
1952
|
-
this.agent = new Agent(this.model, this.mcpManager, {
|
|
1953
|
-
systemPrompt: this.config.systemPrompt,
|
|
1954
|
-
maxIterations: this.config.maxIterations,
|
|
1955
|
-
immediateResultMatchers: this.config.immediateResultMatchers,
|
|
1956
|
-
parallelToolCalls: this.config.parallelToolCalls,
|
|
1957
|
-
enableThinkingPhase: this.config.enableThinkingPhase,
|
|
1958
|
-
thinkingPhasePrompt: this.config.thinkingPhasePrompt,
|
|
1959
|
-
thinkingMaxTokens: this.config.thinkingMaxTokens
|
|
1960
|
-
});
|
|
1961
|
-
this.promptBasedAgent = new PromptBasedAgent(this.model, this.mcpManager, {
|
|
1962
|
-
systemPrompt: this.config.systemPrompt,
|
|
1963
|
-
maxIterations: this.config.maxIterations,
|
|
1964
|
-
immediateResultMatchers: this.config.immediateResultMatchers,
|
|
1965
|
-
parallelToolCalls: this.config.parallelToolCalls,
|
|
1966
|
-
enableThinkingPhase: this.config.enableThinkingPhase,
|
|
1967
|
-
thinkingPhasePrompt: this.config.thinkingPhasePrompt,
|
|
1968
|
-
thinkingMaxTokens: this.config.thinkingMaxTokens
|
|
1969
|
-
});
|
|
867
|
+
if (hasTextStarted) {
|
|
868
|
+
yield { type: "text_end" };
|
|
1970
869
|
}
|
|
1971
|
-
|
|
870
|
+
yield {
|
|
871
|
+
type: "complete",
|
|
872
|
+
totalIterations: iteration,
|
|
873
|
+
totalDuration: Date.now() - startTime
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
async function collectStandardResponse(stream) {
|
|
877
|
+
const response = {
|
|
878
|
+
content: "",
|
|
879
|
+
toolCalls: [],
|
|
880
|
+
iterations: 0,
|
|
881
|
+
duration: 0
|
|
882
|
+
};
|
|
883
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
884
|
+
for await (const event of stream) {
|
|
885
|
+
switch (event.type) {
|
|
886
|
+
case "text_delta":
|
|
887
|
+
response.content += event.content;
|
|
888
|
+
break;
|
|
889
|
+
case "tool_call_start": {
|
|
890
|
+
const tc = {
|
|
891
|
+
id: event.toolCallId,
|
|
892
|
+
name: event.toolName,
|
|
893
|
+
arguments: event.toolArgs
|
|
894
|
+
};
|
|
895
|
+
response.toolCalls.push(tc);
|
|
896
|
+
toolCallMap.set(event.toolCallId, tc);
|
|
897
|
+
break;
|
|
898
|
+
}
|
|
899
|
+
case "tool_result": {
|
|
900
|
+
const tc = toolCallMap.get(event.toolCallId);
|
|
901
|
+
if (tc) {
|
|
902
|
+
tc.result = event.toolResult;
|
|
903
|
+
tc.duration = event.duration;
|
|
904
|
+
tc.isError = event.isError;
|
|
905
|
+
}
|
|
906
|
+
break;
|
|
907
|
+
}
|
|
908
|
+
case "complete":
|
|
909
|
+
response.iterations = event.totalIterations;
|
|
910
|
+
response.duration = event.totalDuration;
|
|
911
|
+
break;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return response;
|
|
915
|
+
}
|
|
1972
916
|
|
|
1973
|
-
export {
|
|
917
|
+
export { HttpClient, MCPLink, MCPManager, OpenAIAdapter, collectStandardResponse, openaiAdapter, toStandardStream };
|
|
1974
918
|
//# sourceMappingURL=index.js.map
|
|
1975
919
|
//# sourceMappingURL=index.js.map
|