@n0ts123/mcplink-core 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,244 @@ 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
  }
491
- /**
492
- * 将 MCP 工具转换为 Vercel AI SDK 格式
493
- */
494
- convertMCPToolsToAITools(mcpTools) {
495
- const tools = {};
496
- for (const mcpTool of mcpTools) {
497
- const zodSchema = this.jsonSchemaToZod(mcpTool.inputSchema);
498
- tools[mcpTool.name] = {
499
- description: mcpTool.description,
500
- parameters: zodSchema
501
- };
502
- }
503
- return tools;
504
- }
505
- /**
506
- * JSON Schema 到 Zod 的完整递归转换
507
- * 支持嵌套对象、对象数组、枚举等所有常见类型
508
- */
509
- jsonSchemaToZod(schema) {
510
- return this.convertSchemaToZod(schema, schema.required || []);
511
- }
512
- /**
513
- * 递归转换 JSON Schema 节点为 Zod 类型
514
- */
515
- convertSchemaToZod(schema, parentRequired = [], key) {
516
- const type = schema.type;
517
- const description = schema.description;
518
- const enumValues = schema.enum;
519
- let zodType;
520
- if (enumValues && enumValues.length > 0) {
521
- if (enumValues.every((v) => typeof v === "string")) {
522
- zodType = z.enum(enumValues);
523
- } else if (enumValues.every((v) => typeof v === "number")) {
524
- const literals = enumValues.map((v) => z.literal(v));
525
- zodType = literals.length === 1 ? literals[0] : z.union([literals[0], literals[1], ...literals.slice(2)]);
526
- } else {
527
- zodType = z.unknown();
528
- }
529
- } else {
530
- switch (type) {
531
- case "string":
532
- zodType = z.string();
533
- break;
534
- case "number":
535
- zodType = z.number();
536
- break;
537
- case "integer":
538
- zodType = z.number().int();
539
- break;
540
- case "boolean":
541
- zodType = z.boolean();
542
- break;
543
- case "null":
544
- zodType = z.null();
545
- break;
546
- case "object": {
547
- const properties = schema.properties;
548
- const required = schema.required || [];
549
- if (properties) {
550
- const shape = {};
551
- for (const [propKey, propSchema] of Object.entries(properties)) {
552
- let propZod = this.convertSchemaToZod(propSchema, required, propKey);
553
- if (!required.includes(propKey)) {
554
- propZod = propZod.optional();
555
- }
556
- shape[propKey] = propZod;
557
- }
558
- zodType = z.object(shape);
559
- } else {
560
- zodType = z.record(z.unknown());
561
- }
562
- break;
563
- }
564
- case "array": {
565
- const items = schema.items;
566
- if (items) {
567
- const itemsRequired = items.required || [];
568
- zodType = z.array(this.convertSchemaToZod(items, itemsRequired));
569
- } else {
570
- zodType = z.array(z.unknown());
571
- }
572
- break;
573
- }
574
- default:
575
- zodType = z.unknown();
401
+ };
402
+
403
+ // src/adapters/openai.ts
404
+ var OpenAIAdapter = class {
405
+ name = "openai";
406
+ /**
407
+ * 构建请求体
408
+ * 完全开放:除必要字段外,其他参数原封不动传递
409
+ */
410
+ buildRequestBody(config, messages, tools) {
411
+ const { baseURL, apiKey, model, headers, timeout, ...customParams } = config;
412
+ const openaiMessages = messages.map((msg) => this.convertMessage(msg));
413
+ const openaiTools = tools?.map((tool) => ({
414
+ type: "function",
415
+ function: {
416
+ name: tool.name,
417
+ description: tool.description,
418
+ parameters: tool.parameters
576
419
  }
420
+ }));
421
+ const body = {
422
+ model,
423
+ messages: openaiMessages,
424
+ // 用户自定义参数(包括 enable_thinking 等)
425
+ ...customParams
426
+ };
427
+ if (openaiTools && openaiTools.length > 0) {
428
+ body.tools = openaiTools;
577
429
  }
578
- if (description) {
579
- zodType = zodType.describe(description);
580
- }
581
- return zodType;
430
+ return body;
582
431
  }
583
432
  /**
584
- * 执行对话
433
+ * 获取请求头
585
434
  */
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;
435
+ getHeaders(config) {
687
436
  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
437
+ "Content-Type": "application/json",
438
+ "Authorization": `Bearer ${config.apiKey}`
701
439
  };
702
440
  }
703
441
  /**
704
- * 将 UserMessage 转换为 Vercel AI SDK 的消息内容格式
442
+ * 获取请求端点
705
443
  */
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
- });
444
+ getEndpoint(baseURL) {
445
+ const normalized = baseURL.replace(/\/$/, "");
446
+ return `${normalized}/chat/completions`;
722
447
  }
723
448
  /**
724
- * 从 UserMessage 提取纯文本内容(用于日志等)
449
+ * 解析非流式响应
725
450
  */
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");
451
+ parseResponse(data) {
452
+ const response = data;
453
+ const choice = response.choices[0];
454
+ const message = choice?.message;
455
+ const toolCalls = message?.tool_calls?.map((tc) => ({
456
+ id: tc.id,
457
+ name: tc.function.name,
458
+ arguments: JSON.parse(tc.function.arguments)
459
+ }));
460
+ return {
461
+ content: message?.content || "",
462
+ toolCalls: toolCalls?.length ? toolCalls : void 0
463
+ };
731
464
  }
732
465
  /**
733
- * 流式对话 - 返回事件生成器
734
- * @param userMessage 用户消息(支持字符串或多模态数组)
735
- * @param options 可选参数
736
- * @param options.allowedTools 允许使用的工具名称列表,为空或不传则使用所有工具
737
- * @param options.history 历史消息列表
466
+ * 解析流式数据块
738
467
  */
739
- 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;
468
+ parseStreamChunk(line) {
469
+ try {
470
+ const data = JSON.parse(line);
471
+ const choice = data.choices[0];
472
+ const delta = choice?.delta;
473
+ if (delta?.content) {
474
+ return { type: "text", content: delta.content };
475
+ }
476
+ if (delta?.tool_calls) {
477
+ const tc = delta.tool_calls[0];
478
+ if (tc.id && tc.function?.name) {
1105
479
  return {
1106
- 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
- }
1126
- };
1127
- if (!r.isError && this.matchImmediateResult(r.result)) {
1128
- hasImmediateResult = true;
1129
- yield {
1130
- type: "immediate_result" /* IMMEDIATE_RESULT */,
1131
- timestamp: Date.now(),
1132
- data: {
1133
- toolName: r.toolName,
1134
- toolCallId: r.toolCallId,
1135
- immediateResult: r.result
1136
- }
1137
- };
1138
- }
1139
- toolResults.push({
1140
- toolCallId: r.toolCallId,
1141
- toolName: r.toolName,
1142
- result: r.result,
1143
- isError: r.isError,
1144
- duration: r.duration
1145
- });
1146
- toolCallRecords.push({
1147
- name: r.toolName,
1148
- arguments: r.args,
1149
- result: r.result,
1150
- duration: r.duration
1151
- });
1152
- }
1153
- } else {
1154
- for (const toolCall of toolCalls) {
1155
- const toolName = toolCall.toolName;
1156
- const toolArgs = toolCall.args;
1157
- const toolCallId = toolCall.toolCallId;
1158
- const toolStartTime = Date.now();
1159
- let result;
1160
- let isError = false;
1161
- try {
1162
- result = await this.mcpManager.callTool(toolName, toolArgs);
1163
- } catch (error) {
1164
- result = error instanceof Error ? error.message : String(error);
1165
- isError = true;
1166
- }
1167
- const duration = Date.now() - toolStartTime;
1168
- yield {
1169
- type: "tool_result" /* TOOL_RESULT */,
1170
- timestamp: Date.now(),
1171
- data: {
1172
- toolName,
1173
- toolResult: result,
1174
- toolCallId,
1175
- duration,
1176
- isError
480
+ type: "tool_call",
481
+ toolCall: {
482
+ id: tc.id,
483
+ name: tc.function.name,
484
+ arguments: tc.function.arguments ? JSON.parse(tc.function.arguments) : {}
1177
485
  }
1178
486
  };
1179
- if (!isError && this.matchImmediateResult(result)) {
1180
- hasImmediateResult = true;
1181
- yield {
1182
- type: "immediate_result" /* IMMEDIATE_RESULT */,
1183
- timestamp: Date.now(),
1184
- data: {
1185
- toolName,
1186
- toolCallId,
1187
- immediateResult: result
1188
- }
1189
- };
1190
- }
1191
- toolResults.push({
1192
- toolCallId,
1193
- toolName,
1194
- result,
1195
- isError,
1196
- duration
1197
- });
1198
- toolCallRecords.push({
1199
- name: toolName,
1200
- arguments: toolArgs,
1201
- result,
1202
- duration
1203
- });
1204
487
  }
1205
488
  }
1206
- if (hasImmediateResult) {
1207
- yield {
1208
- type: "iteration_end" /* ITERATION_END */,
1209
- timestamp: Date.now(),
1210
- data: { iteration }
1211
- };
1212
- break;
1213
- }
1214
- messages.push({
1215
- role: "assistant",
1216
- content: [
1217
- ...fullText ? [{ type: "text", text: fullText }] : [],
1218
- ...toolCalls.map((tc) => ({
1219
- type: "tool-call",
1220
- toolCallId: tc.toolCallId,
1221
- toolName: tc.toolName,
1222
- args: tc.args
1223
- }))
1224
- ]
1225
- });
1226
- for (const tr of toolResults) {
1227
- messages.push({
1228
- role: "tool",
1229
- content: [
1230
- {
1231
- type: "tool-result",
1232
- toolCallId: tr.toolCallId,
1233
- toolName: tr.toolName,
1234
- result: tr.result
1235
- }
1236
- ]
1237
- });
1238
- }
1239
- yield {
1240
- type: "iteration_end" /* ITERATION_END */,
1241
- timestamp: Date.now(),
1242
- data: { iteration }
1243
- };
1244
- }
1245
- const totalDuration = Date.now() - startTime;
1246
- yield {
1247
- type: "complete" /* COMPLETE */,
1248
- timestamp: Date.now(),
1249
- data: {
1250
- totalIterations: iteration,
1251
- totalDuration
1252
- }
1253
- };
1254
- }
1255
- };
1256
- var PromptBasedAgent = class {
1257
- model;
1258
- mcpManager;
1259
- systemPrompt;
1260
- maxIterations;
1261
- immediateResultMatchers;
1262
- parallelToolCalls;
1263
- // PromptBasedAgent 本身通过 prompt 实现思考,此配置保留以保持接口一致
1264
- enableThinkingPhase;
1265
- constructor(model, mcpManager, options = {}) {
1266
- this.model = model;
1267
- this.mcpManager = mcpManager;
1268
- this.systemPrompt = options.systemPrompt || "";
1269
- this.maxIterations = options.maxIterations || 10;
1270
- this.immediateResultMatchers = options.immediateResultMatchers || [];
1271
- this.parallelToolCalls = options.parallelToolCalls ?? true;
1272
- this.enableThinkingPhase = options.enableThinkingPhase ?? false;
1273
- }
1274
- /**
1275
- * 检查工具返回结果是否匹配即时结果匹配器
1276
- * @param result 工具返回的结果
1277
- * @returns 如果匹配返回 true,否则返回 false
1278
- */
1279
- matchImmediateResult(result) {
1280
- const debug = process.env.DEBUG_MCPLINK === "true";
1281
- if (!this.immediateResultMatchers.length) {
1282
- if (debug) console.log("[MCPLink] \u26A0\uFE0F \u672A\u914D\u7F6E\u5373\u65F6\u7ED3\u679C\u5339\u914D\u5668");
1283
- return false;
1284
- }
1285
- let resultObj = null;
1286
- if (typeof result === "string") {
1287
- try {
1288
- const parsed = JSON.parse(result);
1289
- if (typeof parsed === "object" && parsed !== null) {
1290
- resultObj = parsed;
1291
- if (debug) console.log("[MCPLink] \u{1F50D} \u89E3\u6790\u5DE5\u5177\u7ED3\u679C\u4E3A\u5BF9\u8C61:", Object.keys(parsed));
1292
- }
1293
- } catch {
1294
- if (debug) console.log("[MCPLink] \u26A0\uFE0F \u5DE5\u5177\u7ED3\u679C\u4E0D\u662F\u6709\u6548 JSON");
1295
- }
1296
- } else if (typeof result === "object" && result !== null) {
1297
- resultObj = result;
1298
- if (debug) console.log("[MCPLink] \u{1F50D} \u5DE5\u5177\u7ED3\u679C\u662F\u5BF9\u8C61:", Object.keys(result));
1299
- }
1300
- if (!resultObj) {
1301
- if (debug) console.log("[MCPLink] \u26A0\uFE0F \u65E0\u6CD5\u89E3\u6790\u5DE5\u5177\u7ED3\u679C\u4E3A\u5BF9\u8C61");
1302
- return false;
1303
- }
1304
- for (const matcher of this.immediateResultMatchers) {
1305
- let matched = true;
1306
- for (const [key, value] of Object.entries(matcher)) {
1307
- if (resultObj[key] !== value) {
1308
- matched = false;
1309
- break;
1310
- }
489
+ if (choice?.finish_reason) {
490
+ return { type: "done" };
1311
491
  }
1312
- if (matched) {
1313
- if (debug) console.log("[MCPLink] \u2705 \u5373\u65F6\u7ED3\u679C\u5339\u914D\u6210\u529F:", JSON.stringify(matcher));
1314
- return true;
1315
- }
1316
- }
1317
- if (debug) console.log("[MCPLink] \u274C \u5373\u65F6\u7ED3\u679C\u672A\u5339\u914D\uFF0C\u671F\u671B:", JSON.stringify(this.immediateResultMatchers), "\u5B9E\u9645:", JSON.stringify(resultObj));
1318
- return false;
1319
- }
1320
- /**
1321
- * 生成工具列表描述
1322
- */
1323
- generateToolsDescription(tools) {
1324
- if (tools.length === 0) {
1325
- return "\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u5DE5\u5177\u3002";
1326
- }
1327
- let description = "";
1328
- for (const tool of tools) {
1329
- description += `### ${tool.name}
1330
- `;
1331
- description += `\u63CF\u8FF0: ${tool.description}
1332
- `;
1333
- description += `\u53C2\u6570: ${JSON.stringify(tool.inputSchema, null, 2)}
1334
-
1335
- `;
492
+ return null;
493
+ } catch {
494
+ return null;
1336
495
  }
1337
- return description;
1338
- }
1339
- /**
1340
- * 内置系统提示词 - 强调格式约束
1341
- */
1342
- BUILT_IN_PROMPT = `
1343
- ## \u5DE5\u5177\u8C03\u7528\u683C\u5F0F\uFF08\u5FC5\u987B\u4E25\u683C\u9075\u5B88\uFF09
1344
-
1345
- \u5F53\u4F60\u9700\u8981\u83B7\u53D6\u6570\u636E\u6216\u6267\u884C\u64CD\u4F5C\u65F6\uFF0C**\u53EA\u80FD**\u4F7F\u7528\u4EE5\u4E0B\u683C\u5F0F\uFF1A
1346
-
1347
- <tool_call>
1348
- {"name": "\u5DE5\u5177\u540D\u79F0", "arguments": {"\u53C2\u6570\u540D": "\u503C"}}
1349
- </tool_call>
1350
-
1351
- ### \u5DE5\u4F5C\u6D41\u7A0B
1352
- 1. \u5206\u6790\u7528\u6237\u9700\u6C42
1353
- 2. \u5982\u9700\u6570\u636E\uFF0C\u8F93\u51FA <tool_call>...</tool_call> \u540E**\u7ACB\u5373\u505C\u6B62**
1354
- 3. \u7CFB\u7EDF\u4F1A\u6267\u884C\u5DE5\u5177\u5E76\u8FD4\u56DE\u771F\u5B9E\u7ED3\u679C
1355
- 4. \u6536\u5230\u7ED3\u679C\u540E\uFF0C\u7528\u4E2D\u6587\u6574\u7406\u56DE\u590D\u7528\u6237
1356
-
1357
- ### \u4E25\u683C\u7981\u6B62
1358
- - \u274C \u81EA\u5DF1\u7F16\u5199\u5DE5\u5177\u8FD4\u56DE\u7ED3\u679C\uFF08\u5982 \`\u7ED3\u679C:{...}\` \u6216 \`{"code":200...}\`\uFF09
1359
- - \u274C \u6A21\u62DF\u5DE5\u5177\u8C03\u7528\uFF08\u5982 \`RPCCall:\`\u3001\`FunctionCall:\`\uFF09
1360
- - \u274C \u5728\u6CA1\u6709\u771F\u5B9E\u5DE5\u5177\u7ED3\u679C\u7684\u60C5\u51B5\u4E0B\u7F16\u9020\u6570\u636E
1361
- - \u274C \u4E00\u6B21\u8F93\u51FA\u4E2D\u540C\u65F6\u5305\u542B\u5DE5\u5177\u8C03\u7528\u548C\u6700\u7EC8\u56DE\u590D
1362
-
1363
- ### \u6B63\u786E\u793A\u4F8B
1364
- \u7528\u6237: "\u67E5\u8BE2\u6211\u7684\u8BA2\u5355"
1365
- \u4F60\u7684\u8F93\u51FA:
1366
- <tool_call>
1367
- {"name": "get_orders", "arguments": {"token": "xxx"}}
1368
- </tool_call>
1369
-
1370
- \uFF08\u7136\u540E\u505C\u6B62\uFF0C\u7B49\u5F85\u7CFB\u7EDF\u8FD4\u56DE\u771F\u5B9E\u7ED3\u679C\uFF09
1371
-
1372
- ### \u56DE\u590D\u683C\u5F0F
1373
- - \u4F7F\u7528\u4E2D\u6587
1374
- - \u4F7F\u7528 Markdown \u683C\u5F0F\u7F8E\u5316\u8F93\u51FA
1375
- - \u5217\u8868\u6570\u636E\u6BCF\u9879\u72EC\u5360\u4E00\u884C
1376
- `;
1377
- /**
1378
- * 构建完整的系统提示词
1379
- */
1380
- buildSystemPrompt(tools) {
1381
- const toolsDescription = this.generateToolsDescription(tools);
1382
- const userPrompt = this.systemPrompt || "\u4F60\u662F\u4E00\u4E2A\u667A\u80FD\u52A9\u624B\u3002";
1383
- return `${userPrompt}
1384
-
1385
- ## \u53EF\u7528\u5DE5\u5177
1386
- ${toolsDescription}
1387
- ${this.BUILT_IN_PROMPT}`;
1388
496
  }
1389
497
  /**
1390
- * 解析工具调用
498
+ * 转换消息格式
1391
499
  */
1392
- 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;
500
+ convertMessage(msg) {
501
+ if (msg.role !== "tool") {
502
+ const result = {
503
+ role: msg.role,
504
+ content: msg.content
505
+ };
506
+ if (msg.role === "assistant" && msg.toolCalls) {
507
+ result.tool_calls = msg.toolCalls.map((tc) => ({
508
+ id: tc.id,
509
+ type: "function",
510
+ function: {
511
+ name: tc.name,
512
+ arguments: JSON.stringify(tc.arguments)
1449
513
  }
1450
- }
1451
- content = content.slice(0, MAX_ASSISTANT_MESSAGE_LENGTH) + "...";
514
+ }));
1452
515
  }
1453
- return { 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;
516
+ return result;
1462
517
  }
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 });
1502
- }
1503
- }
1504
- const userContent = this.convertUserMessageToContent(userMessage);
1505
- messages.push({ role: "user", content: userContent });
1506
- const textPreview = this.extractTextFromMessage(userMessage);
1507
- console.log(`[PromptBasedAgent] \u{1F4DD} \u7528\u6237\u6D88\u606F: "${textPreview.slice(0, 50)}${textPreview.length > 50 ? "..." : ""}"`);
1508
- console.log(`[PromptBasedAgent] \u{1F4CA} \u603B\u6D88\u606F\u6570: ${messages.length}`);
1509
- let iteration = 0;
1510
- while (iteration < this.maxIterations) {
1511
- iteration++;
1512
- yield {
1513
- type: "iteration_start" /* ITERATION_START */,
1514
- timestamp: Date.now(),
1515
- data: { iteration, maxIterations: this.maxIterations }
518
+ if (msg.toolResults && msg.toolResults.length > 0) {
519
+ const tr = msg.toolResults[0];
520
+ return {
521
+ role: "tool",
522
+ tool_call_id: tr.toolCallId,
523
+ content: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result)
1516
524
  };
1517
- console.log(`[PromptBasedAgent] \u{1F916} \u8C03\u7528\u6A21\u578B\uFF0C\u8FED\u4EE3 ${iteration}/${this.maxIterations}...`);
1518
- const modelStartTime = Date.now();
1519
- const stream = streamText({
1520
- model: this.model,
1521
- messages,
1522
- // 设置请求超时
1523
- experimental_telemetry: {
1524
- isEnabled: false
1525
- // 禁用遥测以减少开销
1526
- },
1527
- // 禁用 Qwen 思考模式 - 使用 openai 命名空间因为这是通过 OpenAI 兼容接口调用
1528
- providerOptions: {
1529
- openai: {
1530
- enable_thinking: false
1531
- }
1532
- }
1533
- });
1534
- let fullResponse = "";
1535
- let buffer = "";
1536
- let inThinking = false;
1537
- let inToolCall = false;
1538
- let thinkingStarted = false;
1539
- let thinkingEnded = false;
1540
- let textStarted = false;
1541
- let firstChunkReceived = false;
1542
- const FIRST_CHUNK_TIMEOUT = 12e4;
1543
- let timeoutId = null;
1544
- new Promise((_, reject) => {
1545
- timeoutId = setTimeout(() => {
1546
- reject(new Error(`\u6A21\u578B\u54CD\u5E94\u8D85\u65F6 (${FIRST_CHUNK_TIMEOUT / 1e3}\u79D2\u65E0\u54CD\u5E94)`));
1547
- }, FIRST_CHUNK_TIMEOUT);
1548
- });
1549
- try {
1550
- for await (const chunk of stream.fullStream) {
1551
- if (!firstChunkReceived) {
1552
- firstChunkReceived = true;
1553
- if (timeoutId) {
1554
- clearTimeout(timeoutId);
1555
- timeoutId = null;
1556
- }
1557
- console.log(`[PromptBasedAgent] \u26A1 \u9996\u4E2A chunk \u5230\u8FBE\uFF0C\u8017\u65F6: ${Date.now() - modelStartTime}ms`);
1558
- }
1559
- if (chunk.type === "reasoning") {
1560
- if (!thinkingStarted) {
1561
- thinkingStarted = true;
1562
- yield { type: "thinking_start" /* THINKING_START */, timestamp: Date.now(), data: {} };
1563
- }
1564
- if (chunk.textDelta) {
1565
- yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: chunk.textDelta } };
1566
- }
1567
- continue;
1568
- }
1569
- if (chunk.type === "text-delta") {
1570
- const delta = chunk.textDelta;
1571
- buffer += delta;
1572
- fullResponse += delta;
1573
- while (buffer.length > 0) {
1574
- if (!inThinking && !inToolCall) {
1575
- const thinkStart = buffer.indexOf("<think>");
1576
- if (thinkStart !== -1) {
1577
- if (thinkStart > 0) {
1578
- const before = buffer.substring(0, thinkStart);
1579
- if (before.trim() && thinkingEnded) {
1580
- if (!textStarted) {
1581
- textStarted = true;
1582
- yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
1583
- }
1584
- yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: before } };
1585
- }
1586
- }
1587
- inThinking = true;
1588
- if (!thinkingStarted) {
1589
- thinkingStarted = true;
1590
- yield { type: "thinking_start" /* THINKING_START */, timestamp: Date.now(), data: {} };
1591
- }
1592
- buffer = buffer.substring(thinkStart + 7);
1593
- continue;
1594
- }
1595
- const toolStart = buffer.indexOf("<tool_call>");
1596
- if (toolStart !== -1) {
1597
- if (toolStart > 0) {
1598
- const before = buffer.substring(0, toolStart);
1599
- if (before.trim() && thinkingEnded) {
1600
- if (!textStarted) {
1601
- textStarted = true;
1602
- yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
1603
- }
1604
- yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: before } };
1605
- }
1606
- }
1607
- inToolCall = true;
1608
- buffer = buffer.substring(toolStart + 11);
1609
- continue;
1610
- }
1611
- if (!buffer.includes("<")) {
1612
- if (buffer.trim() && (thinkingEnded || !thinkingStarted)) {
1613
- if (!textStarted) {
1614
- textStarted = true;
1615
- yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
1616
- }
1617
- yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: buffer } };
1618
- }
1619
- buffer = "";
1620
- }
1621
- break;
1622
- }
1623
- if (inThinking) {
1624
- const thinkEnd = buffer.indexOf("</think>");
1625
- if (thinkEnd !== -1) {
1626
- const content = buffer.substring(0, thinkEnd);
1627
- if (content) {
1628
- yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content } };
1629
- }
1630
- yield { type: "thinking_end" /* THINKING_END */, timestamp: Date.now(), data: {} };
1631
- thinkingEnded = true;
1632
- inThinking = false;
1633
- buffer = buffer.substring(thinkEnd + 8);
1634
- continue;
1635
- }
1636
- if (buffer.length > 10 && !buffer.includes("<")) {
1637
- const safe = buffer.substring(0, buffer.length - 10);
1638
- yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: safe } };
1639
- buffer = buffer.substring(safe.length);
1640
- }
1641
- break;
1642
- }
1643
- if (inToolCall) {
1644
- const toolEnd = buffer.indexOf("</tool_call>");
1645
- if (toolEnd !== -1) {
1646
- inToolCall = false;
1647
- buffer = buffer.substring(toolEnd + 12);
1648
- continue;
1649
- }
1650
- break;
1651
- }
1652
- break;
1653
- }
1654
- }
1655
- if (chunk.type === "finish") {
1656
- if (buffer.trim()) {
1657
- if (inThinking) {
1658
- yield { type: "thinking_delta" /* THINKING_DELTA */, timestamp: Date.now(), data: { content: buffer } };
1659
- yield { type: "thinking_end" /* THINKING_END */, timestamp: Date.now(), data: {} };
1660
- thinkingEnded = true;
1661
- } else if (!inToolCall) {
1662
- if (!textStarted) {
1663
- textStarted = true;
1664
- yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
1665
- }
1666
- yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: buffer } };
1667
- }
1668
- }
1669
- if (textStarted) {
1670
- yield { type: "text_end" /* TEXT_END */, timestamp: Date.now(), data: {} };
1671
- }
1672
- }
1673
- }
1674
- } finally {
1675
- if (timeoutId) {
1676
- clearTimeout(timeoutId);
1677
- }
1678
- }
1679
- const toolCall = this.parseToolCall(fullResponse);
1680
- if (toolCall) {
1681
- const toolCallId = `tool-${Date.now()}`;
1682
- yield {
1683
- type: "tool_call_start" /* TOOL_CALL_START */,
1684
- timestamp: Date.now(),
1685
- data: { toolName: toolCall.name, toolCallId, toolArgs: toolCall.arguments }
1686
- };
1687
- yield {
1688
- type: "tool_executing" /* TOOL_EXECUTING */,
1689
- timestamp: Date.now(),
1690
- data: { toolName: toolCall.name, toolCallId, toolArgs: toolCall.arguments }
1691
- };
1692
- const toolStartTime = Date.now();
1693
- let result;
1694
- let isError = false;
1695
- try {
1696
- result = await this.mcpManager.callTool(toolCall.name, toolCall.arguments);
1697
- } catch (error) {
1698
- result = error instanceof Error ? error.message : String(error);
1699
- isError = true;
1700
- }
1701
- const duration = Date.now() - toolStartTime;
1702
- yield {
1703
- type: "tool_result" /* TOOL_RESULT */,
1704
- timestamp: Date.now(),
1705
- data: { toolName: toolCall.name, toolResult: result, toolCallId, duration, isError }
1706
- };
1707
- if (!isError && this.matchImmediateResult(result)) {
1708
- yield {
1709
- type: "immediate_result" /* IMMEDIATE_RESULT */,
1710
- timestamp: Date.now(),
1711
- data: {
1712
- toolName: toolCall.name,
1713
- toolCallId,
1714
- immediateResult: result
1715
- }
1716
- };
1717
- yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
1718
- yield {
1719
- type: "complete" /* COMPLETE */,
1720
- timestamp: Date.now(),
1721
- data: { totalDuration: Date.now() - startTime, totalIterations: iteration }
1722
- };
1723
- return;
1724
- }
1725
- messages.push({ role: "assistant", content: fullResponse });
1726
- const resultStr = typeof result === "string" ? result : JSON.stringify(result, null, 2);
1727
- messages.push({
1728
- role: "user",
1729
- content: `\u5DE5\u5177 ${toolCall.name} \u8FD4\u56DE\u7ED3\u679C\uFF1A
1730
- ${resultStr}
1731
-
1732
- \u8BF7\u6839\u636E\u7ED3\u679C\u7528\u4E2D\u6587\u56DE\u590D\u7528\u6237\u3002`
1733
- });
1734
- yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
1735
- continue;
1736
- }
1737
- console.log(`[PromptBasedAgent] \u2705 \u6A21\u578B\u54CD\u5E94\u5B8C\u6210\uFF0C\u8017\u65F6: ${Date.now() - modelStartTime}ms\uFF0C\u54CD\u5E94\u957F\u5EA6: ${fullResponse.length}`);
1738
- if (!textStarted && fullResponse.trim()) {
1739
- let cleanText = fullResponse.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, "").trim();
1740
- if (cleanText) {
1741
- yield { type: "text_start" /* TEXT_START */, timestamp: Date.now(), data: {} };
1742
- yield { type: "text_delta" /* TEXT_DELTA */, timestamp: Date.now(), data: { content: cleanText } };
1743
- yield { type: "text_end" /* TEXT_END */, timestamp: Date.now(), data: {} };
1744
- }
1745
- }
1746
- yield { type: "iteration_end" /* ITERATION_END */, timestamp: Date.now(), data: { iteration } };
1747
- break;
1748
525
  }
1749
- yield {
1750
- type: "complete" /* COMPLETE */,
1751
- timestamp: Date.now(),
1752
- data: { totalDuration: Date.now() - startTime, totalIterations: iteration }
526
+ return {
527
+ role: "user",
528
+ content: msg.content
1753
529
  };
1754
530
  }
1755
531
  };
532
+ var openaiAdapter = new OpenAIAdapter();
1756
533
 
1757
534
  // src/MCPLink.ts
1758
- var NATIVE_FUNCTION_CALLING_PATTERNS = [
1759
- // OpenAI GPT 系列 - 支持原生 function calling
1760
- /^gpt/i,
1761
- // OpenAI o1/o3 需要特殊处理,暂用 PromptBased
1762
- // /^o1/i,
1763
- // /^o3/i,
1764
- // Anthropic Claude - 支持原生 function calling
1765
- /^claude/i,
1766
- // Google Gemini 稳定版 - 支持原生 function calling
1767
- // 注意:gemini-*-preview/thinking 版本需要特殊处理,不在此列表
1768
- /^gemini-[\d.]+-flash$/i,
1769
- /^gemini-[\d.]+-pro$/i,
1770
- /^gemini-pro$/i,
1771
- /^gemini-flash$/i,
1772
- // Mistral - 支持原生 function calling
1773
- /^mistral/i,
1774
- /^mixtral/i,
1775
- // Cohere Command-R - 支持原生 function calling
1776
- /^command-r/i
1777
- ];
1778
- var PROMPT_BASED_PATTERNS = [
1779
- // DeepSeek(不支持原生 function calling)
1780
- /deepseek/i,
1781
- // OpenAI o1/o3 思考模型
1782
- /^o1/i,
1783
- /^o3/i,
1784
- // Gemini 思考/预览版本 - 需要 thought_signature,暂用 PromptBased
1785
- /gemini.*preview/i,
1786
- /gemini.*thinking/i,
1787
- /gemini.*exp/i,
1788
- // 开源模型(大多数不支持原生 function calling)
1789
- /^llama/i,
1790
- /^phi-/i,
1791
- /^qwen/i,
1792
- /^yi-/i,
1793
- /^glm/i,
1794
- /^baichuan/i
1795
- ];
1796
- function detectNativeToolSupport(modelId) {
1797
- console.log(`[MCPLink] \u{1F50D} \u68C0\u6D4B\u6A21\u578B: "${modelId}"`);
1798
- for (const pattern of PROMPT_BASED_PATTERNS) {
1799
- if (pattern.test(modelId)) {
1800
- console.log(`[MCPLink] \u2705 Model "${modelId}" -> PromptBasedAgent (matched: ${pattern})`);
1801
- return false;
1802
- }
1803
- }
1804
- for (const pattern of NATIVE_FUNCTION_CALLING_PATTERNS) {
1805
- if (pattern.test(modelId)) {
1806
- console.log(`[MCPLink] \u2705 Model "${modelId}" -> Agent (\u539F\u751F\u6A21\u5F0F, matched: ${pattern})`);
1807
- return true;
1808
- }
1809
- }
1810
- console.log(`[MCPLink] \u26A0\uFE0F Model "${modelId}" -> PromptBasedAgent (\u672A\u77E5\u6A21\u578B\uFF0C\u9ED8\u8BA4)`);
1811
- return false;
1812
- }
1813
535
  var MCPLink = class {
1814
- model;
1815
- mcpManager;
1816
- agent;
1817
- promptBasedAgent;
1818
536
  config;
537
+ mcpManager;
538
+ httpClient;
539
+ adapter;
1819
540
  initialized = false;
1820
- detectedNativeSupport;
1821
541
  constructor(config) {
1822
542
  this.config = config;
1823
- this.model = config.model;
1824
543
  this.mcpManager = new MCPManager();
544
+ this.httpClient = new HttpClient();
545
+ if (typeof config.adapter === "string") {
546
+ this.adapter = this.getAdapterByType(config.adapter);
547
+ } else if (config.adapter) {
548
+ this.adapter = config.adapter;
549
+ } else {
550
+ this.adapter = openaiAdapter;
551
+ }
1825
552
  if (config.mcpServers) {
1826
553
  for (const [id, serverConfig] of Object.entries(config.mcpServers)) {
1827
554
  this.mcpManager.addServer(id, serverConfig);
1828
555
  }
1829
556
  }
1830
- this.agent = new Agent(this.model, this.mcpManager, {
1831
- systemPrompt: config.systemPrompt,
1832
- maxIterations: config.maxIterations,
1833
- immediateResultMatchers: config.immediateResultMatchers,
1834
- parallelToolCalls: config.parallelToolCalls,
1835
- enableThinkingPhase: config.enableThinkingPhase,
1836
- thinkingPhasePrompt: config.thinkingPhasePrompt,
1837
- thinkingMaxTokens: config.thinkingMaxTokens
1838
- });
1839
- this.promptBasedAgent = new PromptBasedAgent(this.model, this.mcpManager, {
1840
- systemPrompt: config.systemPrompt,
1841
- maxIterations: config.maxIterations,
1842
- immediateResultMatchers: config.immediateResultMatchers,
1843
- parallelToolCalls: config.parallelToolCalls,
1844
- enableThinkingPhase: config.enableThinkingPhase,
1845
- thinkingPhasePrompt: config.thinkingPhasePrompt,
1846
- thinkingMaxTokens: config.thinkingMaxTokens
1847
- });
1848
- if (config.usePromptBasedTools === true) {
1849
- this.detectedNativeSupport = false;
1850
- } else if (config.usePromptBasedTools === false) {
1851
- this.detectedNativeSupport = true;
1852
- } else {
1853
- const modelNameToCheck = config.modelName || config.model.modelId;
1854
- this.detectedNativeSupport = detectNativeToolSupport(modelNameToCheck);
1855
- }
1856
557
  }
1857
558
  /**
1858
559
  * 初始化 - 连接所有 MCP 服务器
1859
560
  */
1860
561
  async initialize() {
1861
- if (this.initialized) {
1862
- return;
1863
- }
562
+ if (this.initialized) return;
1864
563
  await this.mcpManager.startAll();
1865
564
  this.initialized = true;
1866
565
  }
@@ -1872,132 +571,349 @@ var MCPLink = class {
1872
571
  this.initialized = false;
1873
572
  }
1874
573
  /**
1875
- * 发起对话
1876
- */
1877
- async chat(message, callbacks) {
574
+ * 对话 - 极简设计
575
+ *
576
+ * 流程:
577
+ * 1. 发送消息给 AI
578
+ * 2. AI 返回文本/工具调用
579
+ * 3. 如果有工具调用,执行 MCP 工具
580
+ * 4. 返回结果(包括新的消息历史,用户可选择是否继续)
581
+ *
582
+ * 用户需要自己:
583
+ * - 维护 messages 历史
584
+ * - 处理流式响应(通过 onStream)
585
+ * - 决定是否继续迭代(如果返回了 toolCalls)
586
+ */
587
+ async chat(options) {
1878
588
  if (!this.initialized) {
1879
589
  await this.initialize();
1880
590
  }
1881
- return this.agent.chat(message, callbacks);
591
+ const startTime = Date.now();
592
+ const maxIterations = this.config.maxIterations ?? 10;
593
+ let iterations = 0;
594
+ let messages = [...options.messages];
595
+ const mcpTools = this.mcpManager.getAllTools();
596
+ const tools = mcpTools.map((t) => ({
597
+ name: t.name,
598
+ description: t.description,
599
+ parameters: t.inputSchema
600
+ }));
601
+ while (iterations < maxIterations) {
602
+ iterations++;
603
+ const response = options.stream !== false ? await this.streamChat(messages, tools, options.onStream) : await this.singleChat(messages, tools);
604
+ if (!response.toolCalls || response.toolCalls.length === 0) {
605
+ messages.push({
606
+ role: "assistant",
607
+ content: response.content
608
+ });
609
+ return {
610
+ content: response.content,
611
+ messages,
612
+ iterations,
613
+ duration: Date.now() - startTime
614
+ };
615
+ }
616
+ const assistantMessage = {
617
+ role: "assistant",
618
+ content: response.content,
619
+ toolCalls: response.toolCalls
620
+ };
621
+ const toolResults = [];
622
+ for (const tc of response.toolCalls) {
623
+ let result;
624
+ let isError = false;
625
+ try {
626
+ result = await this.mcpManager.callTool(tc.name, tc.arguments);
627
+ } catch (error) {
628
+ result = error instanceof Error ? error.message : String(error);
629
+ isError = true;
630
+ }
631
+ toolResults.push({
632
+ toolCallId: tc.id,
633
+ toolName: tc.name,
634
+ result,
635
+ isError
636
+ });
637
+ }
638
+ messages.push(assistantMessage);
639
+ messages.push({
640
+ role: "tool",
641
+ content: "",
642
+ toolResults
643
+ });
644
+ }
645
+ return {
646
+ content: messages[messages.length - 1]?.content || "",
647
+ messages,
648
+ iterations,
649
+ duration: Date.now() - startTime
650
+ };
651
+ }
652
+ /**
653
+ * 单次 AI 调用(非流式)
654
+ */
655
+ async singleChat(messages, tools) {
656
+ const response = await this.httpClient.chat(
657
+ this.config.ai,
658
+ this.adapter,
659
+ messages,
660
+ tools
661
+ );
662
+ return {
663
+ content: response.content,
664
+ toolCalls: response.toolCalls
665
+ };
666
+ }
667
+ /**
668
+ * 流式 AI 调用
669
+ */
670
+ async streamChat(messages, tools, onStream) {
671
+ let content = "";
672
+ const toolCalls = [];
673
+ for await (const event of this.httpClient.streamChat(
674
+ this.config.ai,
675
+ this.adapter,
676
+ messages,
677
+ tools
678
+ )) {
679
+ const shouldContinue = onStream?.(event);
680
+ if (shouldContinue === false) {
681
+ break;
682
+ }
683
+ switch (event.type) {
684
+ case "text":
685
+ content += event.content;
686
+ break;
687
+ case "tool_call":
688
+ toolCalls.push(event.toolCall);
689
+ break;
690
+ case "error":
691
+ throw event.error;
692
+ }
693
+ }
694
+ return {
695
+ content,
696
+ toolCalls: toolCalls.length ? toolCalls : void 0
697
+ };
1882
698
  }
1883
699
  /**
1884
- * 流式对话
1885
- * @param message 用户消息(支持字符串或多模态数组)
1886
- * @param options 可选参数
1887
- * @param options.allowedTools 允许使用的工具名称列表
1888
- * @param options.history 历史消息列表
700
+ * 流式对话 - 公共方法
701
+ * 直接发起流式请求并返回结果
1889
702
  */
1890
- async *chatStream(message, options) {
703
+ async chatStream(messages, onStream) {
1891
704
  if (!this.initialized) {
1892
705
  await this.initialize();
1893
706
  }
1894
- if (this.detectedNativeSupport) {
1895
- yield* this.agent.chatStream(message, options);
1896
- } else {
1897
- yield* this.promptBasedAgent.chatStream(message, options);
707
+ const startTime = Date.now();
708
+ const mcpTools = this.mcpManager.getAllTools();
709
+ const tools = mcpTools.map((t) => ({
710
+ name: t.name,
711
+ description: t.description,
712
+ parameters: t.inputSchema
713
+ }));
714
+ const response = await this.streamChat(messages, tools, onStream);
715
+ if (response.toolCalls && response.toolCalls.length > 0) {
716
+ const toolResults = [];
717
+ for (const tc of response.toolCalls) {
718
+ let result;
719
+ let isError = false;
720
+ try {
721
+ result = await this.mcpManager.callTool(tc.name, tc.arguments);
722
+ } catch (error) {
723
+ result = error instanceof Error ? error.message : String(error);
724
+ isError = true;
725
+ }
726
+ toolResults.push({
727
+ toolCallId: tc.id,
728
+ toolName: tc.name,
729
+ result,
730
+ isError
731
+ });
732
+ }
733
+ const assistantMessage = {
734
+ role: "assistant",
735
+ content: response.content,
736
+ toolCalls: response.toolCalls
737
+ };
738
+ messages.push(assistantMessage);
739
+ messages.push({
740
+ role: "tool",
741
+ content: "",
742
+ toolResults
743
+ });
744
+ return {
745
+ content: response.content,
746
+ messages,
747
+ iterations: 1,
748
+ duration: Date.now() - startTime
749
+ };
1898
750
  }
751
+ messages.push({
752
+ role: "assistant",
753
+ content: response.content
754
+ });
755
+ return {
756
+ content: response.content,
757
+ messages,
758
+ iterations: 1,
759
+ duration: Date.now() - startTime
760
+ };
1899
761
  }
1900
762
  /**
1901
- * 获取当前使用的模式
763
+ * 根据类型获取适配器
1902
764
  */
1903
- getToolCallingMode() {
1904
- return this.detectedNativeSupport ? "native" : "prompt-based";
765
+ getAdapterByType(type) {
766
+ switch (type) {
767
+ case "openai":
768
+ return openaiAdapter;
769
+ default:
770
+ throw new Error(`Unknown adapter type: ${type}`);
771
+ }
1905
772
  }
1906
773
  // ============ MCP 服务器管理 ============
1907
- /**
1908
- * 添加 MCP 服务器
1909
- */
1910
774
  addMCPServer(id, config) {
1911
775
  this.mcpManager.addServer(id, config);
1912
776
  }
1913
- /**
1914
- * 移除 MCP 服务器
1915
- */
1916
777
  async removeMCPServer(id) {
1917
778
  await this.mcpManager.removeServer(id);
1918
779
  }
1919
- /**
1920
- * 启动指定 MCP 服务器
1921
- */
1922
780
  async startMCPServer(id) {
1923
781
  await this.mcpManager.startServer(id);
1924
782
  }
1925
- /**
1926
- * 停止指定 MCP 服务器
1927
- */
1928
783
  async stopMCPServer(id) {
1929
784
  await this.mcpManager.stopServer(id);
1930
785
  }
1931
- /**
1932
- * 获取所有 MCP 服务器状态
1933
- */
1934
786
  getMCPServerStatuses() {
1935
787
  return this.mcpManager.getServerStatuses();
1936
788
  }
1937
- /**
1938
- * 获取所有可用工具
1939
- */
1940
789
  getTools() {
1941
790
  return this.mcpManager.getAllTools();
1942
791
  }
1943
- /**
1944
- * 手动调用工具
1945
- */
1946
792
  async callTool(toolName, args) {
1947
793
  return this.mcpManager.callTool(toolName, args);
1948
794
  }
1949
- // ============ 配置管理 ============
1950
- /**
1951
- * 更新系统提示词
1952
- */
1953
- 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
- });
795
+ };
796
+
797
+ // src/standard-stream.ts
798
+ async function* toStandardStream(rawStream, options) {
799
+ let iteration = 0;
800
+ options.maxIterations ?? 10;
801
+ const startTime = Date.now();
802
+ let hasTextStarted = false;
803
+ const pendingToolCalls = /* @__PURE__ */ new Map();
804
+ for await (const event of rawStream) {
805
+ const shouldContinue = options.onRawEvent?.(event);
806
+ if (shouldContinue === false) break;
807
+ switch (event.type) {
808
+ case "text":
809
+ if (!hasTextStarted) {
810
+ hasTextStarted = true;
811
+ yield { type: "text_start" };
812
+ }
813
+ if (event.content) {
814
+ yield { type: "text_delta", content: event.content };
815
+ }
816
+ break;
817
+ case "tool_call": {
818
+ if (hasTextStarted) {
819
+ hasTextStarted = false;
820
+ yield { type: "text_end" };
821
+ }
822
+ const tc = event.toolCall;
823
+ pendingToolCalls.set(tc.id, { ...tc, argsBuffer: "" });
824
+ yield {
825
+ type: "tool_call_start",
826
+ toolCallId: tc.id,
827
+ toolName: tc.name,
828
+ toolArgs: tc.arguments
829
+ };
830
+ yield {
831
+ type: "tool_executing",
832
+ toolCallId: tc.id,
833
+ toolName: tc.name
834
+ };
835
+ const toolStartTime = Date.now();
836
+ let result;
837
+ let isError = false;
838
+ try {
839
+ result = await options.executeTool(tc.name, tc.arguments);
840
+ } catch (error) {
841
+ result = error instanceof Error ? error.message : String(error);
842
+ isError = true;
843
+ }
844
+ const duration = Date.now() - toolStartTime;
845
+ yield {
846
+ type: "tool_result",
847
+ toolCallId: tc.id,
848
+ toolName: tc.name,
849
+ toolResult: result,
850
+ duration,
851
+ isError
852
+ };
853
+ pendingToolCalls.delete(tc.id);
854
+ break;
855
+ }
856
+ case "done":
857
+ if (hasTextStarted) {
858
+ hasTextStarted = false;
859
+ yield { type: "text_end" };
860
+ }
861
+ break;
862
+ case "error":
863
+ yield { type: "error", error: event.error };
864
+ return;
865
+ }
1973
866
  }
1974
- /**
1975
- * 更新 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
- });
867
+ if (hasTextStarted) {
868
+ yield { type: "text_end" };
1998
869
  }
1999
- };
870
+ yield {
871
+ type: "complete",
872
+ totalIterations: iteration,
873
+ totalDuration: Date.now() - startTime
874
+ };
875
+ }
876
+ async function collectStandardResponse(stream) {
877
+ const response = {
878
+ content: "",
879
+ toolCalls: [],
880
+ iterations: 0,
881
+ duration: 0
882
+ };
883
+ const toolCallMap = /* @__PURE__ */ new Map();
884
+ for await (const event of stream) {
885
+ switch (event.type) {
886
+ case "text_delta":
887
+ response.content += event.content;
888
+ break;
889
+ case "tool_call_start": {
890
+ const tc = {
891
+ id: event.toolCallId,
892
+ name: event.toolName,
893
+ arguments: event.toolArgs
894
+ };
895
+ response.toolCalls.push(tc);
896
+ toolCallMap.set(event.toolCallId, tc);
897
+ break;
898
+ }
899
+ case "tool_result": {
900
+ const tc = toolCallMap.get(event.toolCallId);
901
+ if (tc) {
902
+ tc.result = event.toolResult;
903
+ tc.duration = event.duration;
904
+ tc.isError = event.isError;
905
+ }
906
+ break;
907
+ }
908
+ case "complete":
909
+ response.iterations = event.totalIterations;
910
+ response.duration = event.totalDuration;
911
+ break;
912
+ }
913
+ }
914
+ return response;
915
+ }
2000
916
 
2001
- export { Agent, DEFAULT_SYSTEM_PROMPT, DEFAULT_THINKING_PHASE_PROMPT, MCPLink, MCPLinkEventType, MCPManager, PromptBasedAgent };
917
+ export { HttpClient, MCPLink, MCPManager, OpenAIAdapter, collectStandardResponse, openaiAdapter, toStandardStream };
2002
918
  //# sourceMappingURL=index.js.map
2003
919
  //# sourceMappingURL=index.js.map