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