@n0ts123/mcplink-core 0.0.12 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +490 -709
- package/dist/index.d.ts +295 -411
- package/dist/index.js +503 -1546
- 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,285 @@ 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
|
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
// src/adapters/openai.ts
|
|
404
|
+
var OpenAIAdapter = class {
|
|
405
|
+
name = "openai";
|
|
406
|
+
// 用于累积 tool_call 参数的状态
|
|
407
|
+
pendingToolCall = null;
|
|
491
408
|
/**
|
|
492
|
-
*
|
|
409
|
+
* 检查 tool_call 参数是否完整(可以解析为 JSON)
|
|
493
410
|
*/
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
};
|
|
411
|
+
isToolCallComplete(tc) {
|
|
412
|
+
if (!tc.arguments) return false;
|
|
413
|
+
try {
|
|
414
|
+
JSON.parse(tc.arguments);
|
|
415
|
+
return true;
|
|
416
|
+
} catch {
|
|
417
|
+
return false;
|
|
502
418
|
}
|
|
503
|
-
return tools;
|
|
504
419
|
}
|
|
505
420
|
/**
|
|
506
|
-
*
|
|
507
|
-
* 支持嵌套对象、对象数组、枚举等所有常见类型
|
|
421
|
+
* 返回并清空 pending tool_call
|
|
508
422
|
*/
|
|
509
|
-
|
|
510
|
-
|
|
423
|
+
flushPendingToolCall() {
|
|
424
|
+
if (!this.pendingToolCall) return null;
|
|
425
|
+
const tc = this.pendingToolCall;
|
|
426
|
+
this.pendingToolCall = null;
|
|
427
|
+
return {
|
|
428
|
+
type: "tool_call",
|
|
429
|
+
toolCall: {
|
|
430
|
+
id: tc.id,
|
|
431
|
+
name: tc.name,
|
|
432
|
+
arguments: tc.arguments ? JSON.parse(tc.arguments) : {}
|
|
433
|
+
}
|
|
434
|
+
};
|
|
511
435
|
}
|
|
512
436
|
/**
|
|
513
|
-
*
|
|
437
|
+
* 构建请求体
|
|
438
|
+
* 完全开放:除必要字段外,其他参数原封不动传递
|
|
514
439
|
*/
|
|
515
|
-
|
|
516
|
-
const
|
|
517
|
-
const
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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();
|
|
440
|
+
buildRequestBody(config, messages, tools) {
|
|
441
|
+
const { baseURL, apiKey, model, headers, timeout, ...customParams } = config;
|
|
442
|
+
const openaiMessages = messages.map((msg) => this.convertMessage(msg));
|
|
443
|
+
const openaiTools = tools?.map((tool) => ({
|
|
444
|
+
type: "function",
|
|
445
|
+
function: {
|
|
446
|
+
name: tool.name,
|
|
447
|
+
description: tool.description,
|
|
448
|
+
parameters: tool.parameters
|
|
576
449
|
}
|
|
450
|
+
}));
|
|
451
|
+
const body = {
|
|
452
|
+
model,
|
|
453
|
+
messages: openaiMessages,
|
|
454
|
+
// 用户自定义参数(包括 enable_thinking 等)
|
|
455
|
+
...customParams
|
|
456
|
+
};
|
|
457
|
+
if (openaiTools && openaiTools.length > 0) {
|
|
458
|
+
body.tools = openaiTools;
|
|
577
459
|
}
|
|
578
|
-
|
|
579
|
-
zodType = zodType.describe(description);
|
|
580
|
-
}
|
|
581
|
-
return zodType;
|
|
460
|
+
return body;
|
|
582
461
|
}
|
|
583
462
|
/**
|
|
584
|
-
*
|
|
463
|
+
* 获取请求头
|
|
585
464
|
*/
|
|
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;
|
|
465
|
+
getHeaders(config) {
|
|
687
466
|
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
|
|
467
|
+
"Content-Type": "application/json",
|
|
468
|
+
"Authorization": `Bearer ${config.apiKey}`
|
|
701
469
|
};
|
|
702
470
|
}
|
|
703
471
|
/**
|
|
704
|
-
*
|
|
472
|
+
* 获取请求端点
|
|
705
473
|
*/
|
|
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
|
-
});
|
|
474
|
+
getEndpoint(baseURL) {
|
|
475
|
+
const normalized = baseURL.replace(/\/$/, "");
|
|
476
|
+
return `${normalized}/chat/completions`;
|
|
722
477
|
}
|
|
723
478
|
/**
|
|
724
|
-
*
|
|
479
|
+
* 解析非流式响应
|
|
725
480
|
*/
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
481
|
+
parseResponse(data) {
|
|
482
|
+
const response = data;
|
|
483
|
+
const choice = response.choices[0];
|
|
484
|
+
const message = choice?.message;
|
|
485
|
+
const toolCalls = message?.tool_calls?.map((tc) => ({
|
|
486
|
+
id: tc.id,
|
|
487
|
+
name: tc.function.name,
|
|
488
|
+
arguments: JSON.parse(tc.function.arguments)
|
|
489
|
+
}));
|
|
490
|
+
return {
|
|
491
|
+
content: message?.content || "",
|
|
492
|
+
toolCalls: toolCalls?.length ? toolCalls : void 0
|
|
493
|
+
};
|
|
731
494
|
}
|
|
732
495
|
/**
|
|
733
|
-
*
|
|
734
|
-
* @param userMessage 用户消息(支持字符串或多模态数组)
|
|
735
|
-
* @param options 可选参数
|
|
736
|
-
* @param options.allowedTools 允许使用的工具名称列表,为空或不传则使用所有工具
|
|
737
|
-
* @param options.history 历史消息列表
|
|
496
|
+
* 解析流式数据块
|
|
738
497
|
*/
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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;
|
|
1105
|
-
return {
|
|
1106
|
-
toolCallId: toolCall.toolCallId,
|
|
1107
|
-
toolName: toolCall.toolName,
|
|
1108
|
-
args: toolCall.args,
|
|
1109
|
-
result,
|
|
1110
|
-
isError,
|
|
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
|
-
}
|
|
498
|
+
parseStreamChunk(line) {
|
|
499
|
+
try {
|
|
500
|
+
const data = JSON.parse(line);
|
|
501
|
+
const choice = data.choices[0];
|
|
502
|
+
const delta = choice?.delta;
|
|
503
|
+
if (delta?.content) {
|
|
504
|
+
return { type: "text", content: delta.content };
|
|
505
|
+
}
|
|
506
|
+
if (delta?.tool_calls) {
|
|
507
|
+
const tc = delta.tool_calls[0];
|
|
508
|
+
if (tc.id && tc.function?.name && (!this.pendingToolCall || this.pendingToolCall.id !== tc.id)) {
|
|
509
|
+
const result = this.flushPendingToolCall();
|
|
510
|
+
this.pendingToolCall = {
|
|
511
|
+
id: tc.id,
|
|
512
|
+
name: tc.function.name,
|
|
513
|
+
arguments: tc.function.arguments || ""
|
|
1126
514
|
};
|
|
1127
|
-
if (
|
|
1128
|
-
|
|
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
|
-
};
|
|
515
|
+
if (this.isToolCallComplete(this.pendingToolCall)) {
|
|
516
|
+
return this.flushPendingToolCall();
|
|
1138
517
|
}
|
|
1139
|
-
|
|
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
|
-
});
|
|
518
|
+
return result;
|
|
1152
519
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
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;
|
|
520
|
+
if (this.pendingToolCall && tc.function?.arguments) {
|
|
521
|
+
this.pendingToolCall.arguments += tc.function.arguments;
|
|
522
|
+
if (this.isToolCallComplete(this.pendingToolCall)) {
|
|
523
|
+
return this.flushPendingToolCall();
|
|
1166
524
|
}
|
|
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
|
|
1177
|
-
}
|
|
1178
|
-
};
|
|
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
525
|
}
|
|
1205
526
|
}
|
|
1206
|
-
if (
|
|
1207
|
-
|
|
1208
|
-
type: "iteration_end" /* ITERATION_END */,
|
|
1209
|
-
timestamp: Date.now(),
|
|
1210
|
-
data: { iteration }
|
|
1211
|
-
};
|
|
1212
|
-
break;
|
|
527
|
+
if (choice?.finish_reason && this.pendingToolCall) {
|
|
528
|
+
return this.flushPendingToolCall();
|
|
1213
529
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
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
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
if (matched) {
|
|
1313
|
-
if (debug) console.log("[MCPLink] \u2705 \u5373\u65F6\u7ED3\u679C\u5339\u914D\u6210\u529F:", JSON.stringify(matcher));
|
|
1314
|
-
return true;
|
|
530
|
+
if (choice?.finish_reason) {
|
|
531
|
+
return { type: "done" };
|
|
1315
532
|
}
|
|
533
|
+
return null;
|
|
534
|
+
} catch {
|
|
535
|
+
return null;
|
|
1316
536
|
}
|
|
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
537
|
}
|
|
1320
538
|
/**
|
|
1321
|
-
*
|
|
539
|
+
* 转换消息格式
|
|
1322
540
|
*/
|
|
1323
|
-
|
|
1324
|
-
if (
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
}
|
|
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
|
-
}
|
|
1389
|
-
/**
|
|
1390
|
-
* 解析工具调用
|
|
1391
|
-
*/
|
|
1392
|
-
parseToolCall(text) {
|
|
1393
|
-
const tagMatch = text.match(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/i);
|
|
1394
|
-
if (tagMatch) {
|
|
1395
|
-
try {
|
|
1396
|
-
const json = JSON.parse(tagMatch[1].trim());
|
|
1397
|
-
if (json.name) return { name: json.name, arguments: json.arguments || {} };
|
|
1398
|
-
} catch {
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
const codeMatch = text.match(/```(?:json)?\s*\n?\s*(\{[\s\S]*?"name"[\s\S]*?\})\s*\n?\s*```/i);
|
|
1402
|
-
if (codeMatch) {
|
|
1403
|
-
try {
|
|
1404
|
-
const json = JSON.parse(codeMatch[1].trim());
|
|
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;
|
|
541
|
+
convertMessage(msg) {
|
|
542
|
+
if (msg.role !== "tool") {
|
|
543
|
+
const result = {
|
|
544
|
+
role: msg.role,
|
|
545
|
+
content: msg.content
|
|
546
|
+
};
|
|
547
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
548
|
+
result.tool_calls = msg.toolCalls.map((tc) => ({
|
|
549
|
+
id: tc.id,
|
|
550
|
+
type: "function",
|
|
551
|
+
function: {
|
|
552
|
+
name: tc.name,
|
|
553
|
+
arguments: JSON.stringify(tc.arguments)
|
|
1449
554
|
}
|
|
1450
|
-
}
|
|
1451
|
-
content = content.slice(0, MAX_ASSISTANT_MESSAGE_LENGTH) + "...";
|
|
1452
|
-
}
|
|
1453
|
-
return { role: msg.role, content: content.trim() || msg.content.slice(0, 500) };
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
/**
|
|
1457
|
-
* 将 UserMessage 转换为 Vercel AI SDK 的消息内容格式
|
|
1458
|
-
*/
|
|
1459
|
-
convertUserMessageToContent(message) {
|
|
1460
|
-
if (typeof message === "string") {
|
|
1461
|
-
return message;
|
|
1462
|
-
}
|
|
1463
|
-
return message.map((part) => {
|
|
1464
|
-
switch (part.type) {
|
|
1465
|
-
case "text":
|
|
1466
|
-
return { type: "text", text: part.text };
|
|
1467
|
-
case "image":
|
|
1468
|
-
return { type: "image", image: part.image, mimeType: part.mimeType };
|
|
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 });
|
|
555
|
+
}));
|
|
1502
556
|
}
|
|
557
|
+
return result;
|
|
1503
558
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
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 }
|
|
559
|
+
if (msg.toolResults && msg.toolResults.length > 0) {
|
|
560
|
+
const tr = msg.toolResults[0];
|
|
561
|
+
return {
|
|
562
|
+
role: "tool",
|
|
563
|
+
tool_call_id: tr.toolCallId,
|
|
564
|
+
content: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
|
|
1516
565
|
};
|
|
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
566
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
data: { totalDuration: Date.now() - startTime, totalIterations: iteration }
|
|
567
|
+
return {
|
|
568
|
+
role: "user",
|
|
569
|
+
content: msg.content
|
|
1753
570
|
};
|
|
1754
571
|
}
|
|
1755
572
|
};
|
|
573
|
+
var openaiAdapter = new OpenAIAdapter();
|
|
1756
574
|
|
|
1757
575
|
// 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
576
|
var MCPLink = class {
|
|
1814
|
-
model;
|
|
1815
|
-
mcpManager;
|
|
1816
|
-
agent;
|
|
1817
|
-
promptBasedAgent;
|
|
1818
577
|
config;
|
|
578
|
+
mcpManager;
|
|
579
|
+
httpClient;
|
|
580
|
+
adapter;
|
|
1819
581
|
initialized = false;
|
|
1820
|
-
detectedNativeSupport;
|
|
1821
582
|
constructor(config) {
|
|
1822
583
|
this.config = config;
|
|
1823
|
-
this.model = config.model;
|
|
1824
584
|
this.mcpManager = new MCPManager();
|
|
585
|
+
this.httpClient = new HttpClient();
|
|
586
|
+
if (typeof config.adapter === "string") {
|
|
587
|
+
this.adapter = this.getAdapterByType(config.adapter);
|
|
588
|
+
} else if (config.adapter) {
|
|
589
|
+
this.adapter = config.adapter;
|
|
590
|
+
} else {
|
|
591
|
+
this.adapter = openaiAdapter;
|
|
592
|
+
}
|
|
1825
593
|
if (config.mcpServers) {
|
|
1826
594
|
for (const [id, serverConfig] of Object.entries(config.mcpServers)) {
|
|
1827
595
|
this.mcpManager.addServer(id, serverConfig);
|
|
1828
596
|
}
|
|
1829
597
|
}
|
|
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
598
|
}
|
|
1857
599
|
/**
|
|
1858
600
|
* 初始化 - 连接所有 MCP 服务器
|
|
1859
601
|
*/
|
|
1860
602
|
async initialize() {
|
|
1861
|
-
if (this.initialized)
|
|
1862
|
-
return;
|
|
1863
|
-
}
|
|
603
|
+
if (this.initialized) return;
|
|
1864
604
|
await this.mcpManager.startAll();
|
|
1865
605
|
this.initialized = true;
|
|
1866
606
|
}
|
|
@@ -1872,132 +612,349 @@ var MCPLink = class {
|
|
|
1872
612
|
this.initialized = false;
|
|
1873
613
|
}
|
|
1874
614
|
/**
|
|
1875
|
-
*
|
|
1876
|
-
|
|
1877
|
-
|
|
615
|
+
* 对话 - 极简设计
|
|
616
|
+
*
|
|
617
|
+
* 流程:
|
|
618
|
+
* 1. 发送消息给 AI
|
|
619
|
+
* 2. AI 返回文本/工具调用
|
|
620
|
+
* 3. 如果有工具调用,执行 MCP 工具
|
|
621
|
+
* 4. 返回结果(包括新的消息历史,用户可选择是否继续)
|
|
622
|
+
*
|
|
623
|
+
* 用户需要自己:
|
|
624
|
+
* - 维护 messages 历史
|
|
625
|
+
* - 处理流式响应(通过 onStream)
|
|
626
|
+
* - 决定是否继续迭代(如果返回了 toolCalls)
|
|
627
|
+
*/
|
|
628
|
+
async chat(options) {
|
|
1878
629
|
if (!this.initialized) {
|
|
1879
630
|
await this.initialize();
|
|
1880
631
|
}
|
|
1881
|
-
|
|
632
|
+
const startTime = Date.now();
|
|
633
|
+
const maxIterations = this.config.maxIterations ?? 10;
|
|
634
|
+
let iterations = 0;
|
|
635
|
+
let messages = [...options.messages];
|
|
636
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
637
|
+
const tools = mcpTools.map((t) => ({
|
|
638
|
+
name: t.name,
|
|
639
|
+
description: t.description,
|
|
640
|
+
parameters: t.inputSchema
|
|
641
|
+
}));
|
|
642
|
+
while (iterations < maxIterations) {
|
|
643
|
+
iterations++;
|
|
644
|
+
const response = options.stream !== false ? await this.streamChat(messages, tools, options.onStream) : await this.singleChat(messages, tools);
|
|
645
|
+
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
646
|
+
messages.push({
|
|
647
|
+
role: "assistant",
|
|
648
|
+
content: response.content
|
|
649
|
+
});
|
|
650
|
+
return {
|
|
651
|
+
content: response.content,
|
|
652
|
+
messages,
|
|
653
|
+
iterations,
|
|
654
|
+
duration: Date.now() - startTime
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
const assistantMessage = {
|
|
658
|
+
role: "assistant",
|
|
659
|
+
content: response.content,
|
|
660
|
+
toolCalls: response.toolCalls
|
|
661
|
+
};
|
|
662
|
+
const toolResults = [];
|
|
663
|
+
for (const tc of response.toolCalls) {
|
|
664
|
+
let result;
|
|
665
|
+
let isError = false;
|
|
666
|
+
try {
|
|
667
|
+
result = await this.mcpManager.callTool(tc.name, tc.arguments);
|
|
668
|
+
} catch (error) {
|
|
669
|
+
result = error instanceof Error ? error.message : String(error);
|
|
670
|
+
isError = true;
|
|
671
|
+
}
|
|
672
|
+
toolResults.push({
|
|
673
|
+
toolCallId: tc.id,
|
|
674
|
+
toolName: tc.name,
|
|
675
|
+
result,
|
|
676
|
+
isError
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
messages.push(assistantMessage);
|
|
680
|
+
messages.push({
|
|
681
|
+
role: "tool",
|
|
682
|
+
content: "",
|
|
683
|
+
toolResults
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
return {
|
|
687
|
+
content: messages[messages.length - 1]?.content || "",
|
|
688
|
+
messages,
|
|
689
|
+
iterations,
|
|
690
|
+
duration: Date.now() - startTime
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* 单次 AI 调用(非流式)
|
|
695
|
+
*/
|
|
696
|
+
async singleChat(messages, tools) {
|
|
697
|
+
const response = await this.httpClient.chat(
|
|
698
|
+
this.config.ai,
|
|
699
|
+
this.adapter,
|
|
700
|
+
messages,
|
|
701
|
+
tools
|
|
702
|
+
);
|
|
703
|
+
return {
|
|
704
|
+
content: response.content,
|
|
705
|
+
toolCalls: response.toolCalls
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* 流式 AI 调用
|
|
710
|
+
*/
|
|
711
|
+
async streamChat(messages, tools, onStream) {
|
|
712
|
+
let content = "";
|
|
713
|
+
const toolCalls = [];
|
|
714
|
+
for await (const event of this.httpClient.streamChat(
|
|
715
|
+
this.config.ai,
|
|
716
|
+
this.adapter,
|
|
717
|
+
messages,
|
|
718
|
+
tools
|
|
719
|
+
)) {
|
|
720
|
+
const shouldContinue = onStream?.(event);
|
|
721
|
+
if (shouldContinue === false) {
|
|
722
|
+
break;
|
|
723
|
+
}
|
|
724
|
+
switch (event.type) {
|
|
725
|
+
case "text":
|
|
726
|
+
content += event.content;
|
|
727
|
+
break;
|
|
728
|
+
case "tool_call":
|
|
729
|
+
toolCalls.push(event.toolCall);
|
|
730
|
+
break;
|
|
731
|
+
case "error":
|
|
732
|
+
throw event.error;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
content,
|
|
737
|
+
toolCalls: toolCalls.length ? toolCalls : void 0
|
|
738
|
+
};
|
|
1882
739
|
}
|
|
1883
740
|
/**
|
|
1884
|
-
* 流式对话
|
|
1885
|
-
*
|
|
1886
|
-
* @param options 可选参数
|
|
1887
|
-
* @param options.allowedTools 允许使用的工具名称列表
|
|
1888
|
-
* @param options.history 历史消息列表
|
|
741
|
+
* 流式对话 - 公共方法
|
|
742
|
+
* 直接发起流式请求并返回结果
|
|
1889
743
|
*/
|
|
1890
|
-
async
|
|
744
|
+
async chatStream(messages, onStream) {
|
|
1891
745
|
if (!this.initialized) {
|
|
1892
746
|
await this.initialize();
|
|
1893
747
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
748
|
+
const startTime = Date.now();
|
|
749
|
+
const mcpTools = this.mcpManager.getAllTools();
|
|
750
|
+
const tools = mcpTools.map((t) => ({
|
|
751
|
+
name: t.name,
|
|
752
|
+
description: t.description,
|
|
753
|
+
parameters: t.inputSchema
|
|
754
|
+
}));
|
|
755
|
+
const response = await this.streamChat(messages, tools, onStream);
|
|
756
|
+
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
757
|
+
const toolResults = [];
|
|
758
|
+
for (const tc of response.toolCalls) {
|
|
759
|
+
let result;
|
|
760
|
+
let isError = false;
|
|
761
|
+
try {
|
|
762
|
+
result = await this.mcpManager.callTool(tc.name, tc.arguments);
|
|
763
|
+
} catch (error) {
|
|
764
|
+
result = error instanceof Error ? error.message : String(error);
|
|
765
|
+
isError = true;
|
|
766
|
+
}
|
|
767
|
+
toolResults.push({
|
|
768
|
+
toolCallId: tc.id,
|
|
769
|
+
toolName: tc.name,
|
|
770
|
+
result,
|
|
771
|
+
isError
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
const assistantMessage = {
|
|
775
|
+
role: "assistant",
|
|
776
|
+
content: response.content,
|
|
777
|
+
toolCalls: response.toolCalls
|
|
778
|
+
};
|
|
779
|
+
messages.push(assistantMessage);
|
|
780
|
+
messages.push({
|
|
781
|
+
role: "tool",
|
|
782
|
+
content: "",
|
|
783
|
+
toolResults
|
|
784
|
+
});
|
|
785
|
+
return {
|
|
786
|
+
content: response.content,
|
|
787
|
+
messages,
|
|
788
|
+
iterations: 1,
|
|
789
|
+
duration: Date.now() - startTime
|
|
790
|
+
};
|
|
1898
791
|
}
|
|
792
|
+
messages.push({
|
|
793
|
+
role: "assistant",
|
|
794
|
+
content: response.content
|
|
795
|
+
});
|
|
796
|
+
return {
|
|
797
|
+
content: response.content,
|
|
798
|
+
messages,
|
|
799
|
+
iterations: 1,
|
|
800
|
+
duration: Date.now() - startTime
|
|
801
|
+
};
|
|
1899
802
|
}
|
|
1900
803
|
/**
|
|
1901
|
-
*
|
|
804
|
+
* 根据类型获取适配器
|
|
1902
805
|
*/
|
|
1903
|
-
|
|
1904
|
-
|
|
806
|
+
getAdapterByType(type) {
|
|
807
|
+
switch (type) {
|
|
808
|
+
case "openai":
|
|
809
|
+
return openaiAdapter;
|
|
810
|
+
default:
|
|
811
|
+
throw new Error(`Unknown adapter type: ${type}`);
|
|
812
|
+
}
|
|
1905
813
|
}
|
|
1906
814
|
// ============ MCP 服务器管理 ============
|
|
1907
|
-
/**
|
|
1908
|
-
* 添加 MCP 服务器
|
|
1909
|
-
*/
|
|
1910
815
|
addMCPServer(id, config) {
|
|
1911
816
|
this.mcpManager.addServer(id, config);
|
|
1912
817
|
}
|
|
1913
|
-
/**
|
|
1914
|
-
* 移除 MCP 服务器
|
|
1915
|
-
*/
|
|
1916
818
|
async removeMCPServer(id) {
|
|
1917
819
|
await this.mcpManager.removeServer(id);
|
|
1918
820
|
}
|
|
1919
|
-
/**
|
|
1920
|
-
* 启动指定 MCP 服务器
|
|
1921
|
-
*/
|
|
1922
821
|
async startMCPServer(id) {
|
|
1923
822
|
await this.mcpManager.startServer(id);
|
|
1924
823
|
}
|
|
1925
|
-
/**
|
|
1926
|
-
* 停止指定 MCP 服务器
|
|
1927
|
-
*/
|
|
1928
824
|
async stopMCPServer(id) {
|
|
1929
825
|
await this.mcpManager.stopServer(id);
|
|
1930
826
|
}
|
|
1931
|
-
/**
|
|
1932
|
-
* 获取所有 MCP 服务器状态
|
|
1933
|
-
*/
|
|
1934
827
|
getMCPServerStatuses() {
|
|
1935
828
|
return this.mcpManager.getServerStatuses();
|
|
1936
829
|
}
|
|
1937
|
-
/**
|
|
1938
|
-
* 获取所有可用工具
|
|
1939
|
-
*/
|
|
1940
830
|
getTools() {
|
|
1941
831
|
return this.mcpManager.getAllTools();
|
|
1942
832
|
}
|
|
1943
|
-
/**
|
|
1944
|
-
* 手动调用工具
|
|
1945
|
-
*/
|
|
1946
833
|
async callTool(toolName, args) {
|
|
1947
834
|
return this.mcpManager.callTool(toolName, args);
|
|
1948
835
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// src/standard-stream.ts
|
|
839
|
+
async function* toStandardStream(rawStream, options) {
|
|
840
|
+
let iteration = 0;
|
|
841
|
+
options.maxIterations ?? 10;
|
|
842
|
+
const startTime = Date.now();
|
|
843
|
+
let hasTextStarted = false;
|
|
844
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
845
|
+
for await (const event of rawStream) {
|
|
846
|
+
const shouldContinue = options.onRawEvent?.(event);
|
|
847
|
+
if (shouldContinue === false) break;
|
|
848
|
+
switch (event.type) {
|
|
849
|
+
case "text":
|
|
850
|
+
if (!hasTextStarted) {
|
|
851
|
+
hasTextStarted = true;
|
|
852
|
+
yield { type: "text_start" };
|
|
853
|
+
}
|
|
854
|
+
if (event.content) {
|
|
855
|
+
yield { type: "text_delta", content: event.content };
|
|
856
|
+
}
|
|
857
|
+
break;
|
|
858
|
+
case "tool_call": {
|
|
859
|
+
if (hasTextStarted) {
|
|
860
|
+
hasTextStarted = false;
|
|
861
|
+
yield { type: "text_end" };
|
|
862
|
+
}
|
|
863
|
+
const tc = event.toolCall;
|
|
864
|
+
pendingToolCalls.set(tc.id, { ...tc, argsBuffer: "" });
|
|
865
|
+
yield {
|
|
866
|
+
type: "tool_call_start",
|
|
867
|
+
toolCallId: tc.id,
|
|
868
|
+
toolName: tc.name,
|
|
869
|
+
toolArgs: tc.arguments
|
|
870
|
+
};
|
|
871
|
+
yield {
|
|
872
|
+
type: "tool_executing",
|
|
873
|
+
toolCallId: tc.id,
|
|
874
|
+
toolName: tc.name
|
|
875
|
+
};
|
|
876
|
+
const toolStartTime = Date.now();
|
|
877
|
+
let result;
|
|
878
|
+
let isError = false;
|
|
879
|
+
try {
|
|
880
|
+
result = await options.executeTool(tc.name, tc.arguments);
|
|
881
|
+
} catch (error) {
|
|
882
|
+
result = error instanceof Error ? error.message : String(error);
|
|
883
|
+
isError = true;
|
|
884
|
+
}
|
|
885
|
+
const duration = Date.now() - toolStartTime;
|
|
886
|
+
yield {
|
|
887
|
+
type: "tool_result",
|
|
888
|
+
toolCallId: tc.id,
|
|
889
|
+
toolName: tc.name,
|
|
890
|
+
toolResult: result,
|
|
891
|
+
duration,
|
|
892
|
+
isError
|
|
893
|
+
};
|
|
894
|
+
pendingToolCalls.delete(tc.id);
|
|
895
|
+
break;
|
|
896
|
+
}
|
|
897
|
+
case "done":
|
|
898
|
+
if (hasTextStarted) {
|
|
899
|
+
hasTextStarted = false;
|
|
900
|
+
yield { type: "text_end" };
|
|
901
|
+
}
|
|
902
|
+
break;
|
|
903
|
+
case "error":
|
|
904
|
+
yield { type: "error", error: event.error };
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
1973
907
|
}
|
|
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
|
-
});
|
|
908
|
+
if (hasTextStarted) {
|
|
909
|
+
yield { type: "text_end" };
|
|
1998
910
|
}
|
|
1999
|
-
|
|
911
|
+
yield {
|
|
912
|
+
type: "complete",
|
|
913
|
+
totalIterations: iteration,
|
|
914
|
+
totalDuration: Date.now() - startTime
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
async function collectStandardResponse(stream) {
|
|
918
|
+
const response = {
|
|
919
|
+
content: "",
|
|
920
|
+
toolCalls: [],
|
|
921
|
+
iterations: 0,
|
|
922
|
+
duration: 0
|
|
923
|
+
};
|
|
924
|
+
const toolCallMap = /* @__PURE__ */ new Map();
|
|
925
|
+
for await (const event of stream) {
|
|
926
|
+
switch (event.type) {
|
|
927
|
+
case "text_delta":
|
|
928
|
+
response.content += event.content;
|
|
929
|
+
break;
|
|
930
|
+
case "tool_call_start": {
|
|
931
|
+
const tc = {
|
|
932
|
+
id: event.toolCallId,
|
|
933
|
+
name: event.toolName,
|
|
934
|
+
arguments: event.toolArgs
|
|
935
|
+
};
|
|
936
|
+
response.toolCalls.push(tc);
|
|
937
|
+
toolCallMap.set(event.toolCallId, tc);
|
|
938
|
+
break;
|
|
939
|
+
}
|
|
940
|
+
case "tool_result": {
|
|
941
|
+
const tc = toolCallMap.get(event.toolCallId);
|
|
942
|
+
if (tc) {
|
|
943
|
+
tc.result = event.toolResult;
|
|
944
|
+
tc.duration = event.duration;
|
|
945
|
+
tc.isError = event.isError;
|
|
946
|
+
}
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
case "complete":
|
|
950
|
+
response.iterations = event.totalIterations;
|
|
951
|
+
response.duration = event.totalDuration;
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
return response;
|
|
956
|
+
}
|
|
2000
957
|
|
|
2001
|
-
export {
|
|
958
|
+
export { HttpClient, MCPLink, MCPManager, OpenAIAdapter, collectStandardResponse, openaiAdapter, toStandardStream };
|
|
2002
959
|
//# sourceMappingURL=index.js.map
|
|
2003
960
|
//# sourceMappingURL=index.js.map
|