@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/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 { generateText, streamText } from 'ai';
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
- // src/types.ts
330
- var MCPLinkEventType = /* @__PURE__ */ ((MCPLinkEventType2) => {
331
- MCPLinkEventType2["THINKING_START"] = "thinking_start";
332
- MCPLinkEventType2["THINKING_DELTA"] = "thinking_delta";
333
- MCPLinkEventType2["THINKING_END"] = "thinking_end";
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
- generateToolsDescription(tools) {
390
- if (tools.length === 0) {
391
- return "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u5DE5\u5177\u3002";
392
- }
393
- let description = "";
394
- for (const tool of tools) {
395
- description += `### ${tool.name}
396
- `;
397
- description += `\u63CF\u8FF0: ${tool.description}
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
- description += "\n";
410
- }
411
- return description;
345
+ );
346
+ return adapter.parseResponse(response.data);
412
347
  }
413
348
  /**
414
- * 摘要化工具返回结果(用于思考阶段,避免 AI 直接格式化输出数据)
415
- * @param toolName 工具名称
416
- * @param result 工具返回的结果
417
- * @returns 摘要字符串
349
+ * 流式请求 - SSE
418
350
  */
419
- summarizeToolResult(toolName, result) {
420
- let count = 0;
421
- let resultObj = result;
422
- if (typeof result === "string") {
423
- try {
424
- resultObj = JSON.parse(result);
425
- } catch {
426
- return `[\u5DE5\u5177 ${toolName} \u8FD4\u56DE\u4E86\u6570\u636E]`;
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
- if (Array.isArray(resultObj)) {
430
- count = resultObj.length;
431
- } else if (typeof resultObj === "object" && resultObj !== null) {
432
- const obj = resultObj;
433
- for (const key of ["data", "list", "items", "records", "results"]) {
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
- matchImmediateResult(result) {
451
- const debug = process.env.DEBUG_MCPLINK === "true";
452
- if (!this.immediateResultMatchers.length) {
453
- if (debug) console.log("[MCPLink] \u26A0\uFE0F \u672A\u914D\u7F6E\u5373\u65F6\u7ED3\u679C\u5339\u914D\u5668");
454
- return false;
455
- }
456
- let resultObj = null;
457
- if (typeof result === "string") {
458
- try {
459
- const parsed = JSON.parse(result);
460
- if (typeof parsed === "object" && parsed !== null) {
461
- resultObj = parsed;
462
- if (debug) console.log("[MCPLink] \u{1F50D} \u89E3\u6790\u5DE5\u5177\u7ED3\u679C\u4E3A\u5BF9\u8C61:", Object.keys(parsed));
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
- for (const matcher of this.immediateResultMatchers) {
476
- let matched = true;
477
- for (const [key, value] of Object.entries(matcher)) {
478
- if (resultObj[key] !== value) {
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
- * MCP 工具转换为 Vercel AI SDK 格式
409
+ * 检查 tool_call 参数是否完整(可以解析为 JSON)
493
410
  */
494
- convertMCPToolsToAITools(mcpTools) {
495
- const tools = {};
496
- for (const mcpTool of mcpTools) {
497
- const zodSchema = this.jsonSchemaToZod(mcpTool.inputSchema);
498
- tools[mcpTool.name] = {
499
- description: mcpTool.description,
500
- parameters: zodSchema
501
- };
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
- * JSON Schema 到 Zod 的完整递归转换
507
- * 支持嵌套对象、对象数组、枚举等所有常见类型
421
+ * 返回并清空 pending tool_call
508
422
  */
509
- jsonSchemaToZod(schema) {
510
- return this.convertSchemaToZod(schema, schema.required || []);
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
- * 递归转换 JSON Schema 节点为 Zod 类型
437
+ * 构建请求体
438
+ * 完全开放:除必要字段外,其他参数原封不动传递
514
439
  */
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();
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
- if (description) {
579
- zodType = zodType.describe(description);
580
- }
581
- return zodType;
460
+ return body;
582
461
  }
583
462
  /**
584
- * 执行对话
463
+ * 获取请求头
585
464
  */
586
- async chat(userMessage, callbacks) {
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
- content: finalContent,
689
- toolCalls: toolCallRecords,
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
- * 将 UserMessage 转换为 Vercel AI SDK 的消息内容格式
472
+ * 获取请求端点
705
473
  */
706
- convertUserMessageToContent(message) {
707
- if (typeof message === "string") {
708
- return message;
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
- * 从 UserMessage 提取纯文本内容(用于日志等)
479
+ * 解析非流式响应
725
480
  */
726
- extractTextFromMessage(message) {
727
- if (typeof message === "string") {
728
- return message;
729
- }
730
- return message.filter((part) => part.type === "text").map((part) => part.text).join("\n");
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
- async *chatStream(userMessage, options) {
740
- const startTime = Date.now();
741
- const toolCallRecords = [];
742
- const messages = [{ role: "system", content: this.systemPrompt }];
743
- if (options?.history && options.history.length > 0) {
744
- for (const msg of options.history) {
745
- messages.push({
746
- role: msg.role,
747
- content: msg.content
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;
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 (!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
- };
515
+ if (this.isToolCallComplete(this.pendingToolCall)) {
516
+ return this.flushPendingToolCall();
1138
517
  }
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
- });
518
+ return result;
1152
519
  }
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;
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 (hasImmediateResult) {
1207
- yield {
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
- 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
- }
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
- 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
- `;
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
- 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 }
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
- yield {
1750
- type: "complete" /* COMPLETE */,
1751
- timestamp: Date.now(),
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
- async chat(message, callbacks) {
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
- return this.agent.chat(message, callbacks);
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
- * @param message 用户消息(支持字符串或多模态数组)
1886
- * @param options 可选参数
1887
- * @param options.allowedTools 允许使用的工具名称列表
1888
- * @param options.history 历史消息列表
741
+ * 流式对话 - 公共方法
742
+ * 直接发起流式请求并返回结果
1889
743
  */
1890
- async *chatStream(message, options) {
744
+ async chatStream(messages, onStream) {
1891
745
  if (!this.initialized) {
1892
746
  await this.initialize();
1893
747
  }
1894
- if (this.detectedNativeSupport) {
1895
- yield* this.agent.chatStream(message, options);
1896
- } else {
1897
- yield* this.promptBasedAgent.chatStream(message, options);
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
- getToolCallingMode() {
1904
- return this.detectedNativeSupport ? "native" : "prompt-based";
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
- setSystemPrompt(prompt) {
1954
- this.config.systemPrompt = prompt;
1955
- this.agent = new Agent(this.model, this.mcpManager, {
1956
- systemPrompt: prompt,
1957
- maxIterations: this.config.maxIterations,
1958
- immediateResultMatchers: this.config.immediateResultMatchers,
1959
- parallelToolCalls: this.config.parallelToolCalls,
1960
- enableThinkingPhase: this.config.enableThinkingPhase,
1961
- thinkingPhasePrompt: this.config.thinkingPhasePrompt,
1962
- thinkingMaxTokens: this.config.thinkingMaxTokens
1963
- });
1964
- this.promptBasedAgent = new PromptBasedAgent(this.model, this.mcpManager, {
1965
- systemPrompt: prompt,
1966
- maxIterations: this.config.maxIterations,
1967
- immediateResultMatchers: this.config.immediateResultMatchers,
1968
- parallelToolCalls: this.config.parallelToolCalls,
1969
- enableThinkingPhase: this.config.enableThinkingPhase,
1970
- thinkingPhasePrompt: this.config.thinkingPhasePrompt,
1971
- thinkingMaxTokens: this.config.thinkingMaxTokens
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
- * 更新 AI 模型
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 { Agent, DEFAULT_SYSTEM_PROMPT, DEFAULT_THINKING_PHASE_PROMPT, MCPLink, MCPLinkEventType, MCPManager, PromptBasedAgent };
958
+ export { HttpClient, MCPLink, MCPManager, OpenAIAdapter, collectStandardResponse, openaiAdapter, toStandardStream };
2002
959
  //# sourceMappingURL=index.js.map
2003
960
  //# sourceMappingURL=index.js.map