foliko 1.0.76 → 1.0.78

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/src/core/agent.js CHANGED
@@ -3,9 +3,11 @@
3
3
  * 负责对话和推理
4
4
  */
5
5
 
6
+ const { pl } = require('zod/v4/locales');
6
7
  const { EventEmitter } = require('../utils/event-emitter');
7
8
  const { AgentChatHandler } = require('./agent-chat');
8
9
  const { SystemPromptBuilder } = require('./system-prompt-builder');
10
+ const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
9
11
  const os = require('os');
10
12
 
11
13
  class Agent extends EventEmitter {
@@ -108,9 +110,16 @@ class Agent extends EventEmitter {
108
110
  this._systemPromptBuilder.register('subagents', 600, () => this._buildSubAgentsDescription());
109
111
 
110
112
  // 7. 系统能力 (优先级 700)
111
- this._systemPromptBuilder.register('capabilities', 700, () => this._buildCapabilitiesDescription());
112
-
113
- // 8. 工具调用核心规则 (优先级 1000)
113
+ this._systemPromptBuilder.register('capabilities', 700, () =>
114
+ this._buildCapabilitiesDescription()
115
+ );
116
+ // 8. MCP 工具列表 (优先级 750)
117
+ this._systemPromptBuilder.register('mcp-tools', 750, () => this._buildMcpToolsDescription());
118
+ // 9. 扩展工具列表 (优先级 800)
119
+ this._systemPromptBuilder.register('extension-tools', 800, () =>
120
+ this._buildExtensionToolsDescription()
121
+ );
122
+ // 10. 工具调用核心规则 (优先级 1000)
114
123
  this._systemPromptBuilder.register('tool-core-rules', 1000, () => this._getToolCoreRules());
115
124
  }
116
125
 
@@ -153,26 +162,35 @@ class Agent extends EventEmitter {
153
162
  }
154
163
 
155
164
  /**
156
- * 构建工具描述
165
+ * 构建工具描述(直接调用的工具)
157
166
  */
158
167
  _buildToolsDescription() {
159
168
  if (this._tools.size === 0) {
160
169
  return '';
161
170
  }
162
171
 
163
- // 需要排除的子Agent相关工具(它们通过专门的子Agent部分提供)
164
- // const excludedTools = new Set(['subagent_call', 'subagent_list', 'subagent_reload']);
172
+ const directTools = [];
165
173
 
166
- let desc = '【可用工具】\n';
167
174
  for (const [name, tool] of this._tools) {
168
- // 跳过没有描述的工具(对LLM无帮助)
169
175
  if (!tool.description) continue;
170
- // // 跳过子Agent相关工具(通过专门的子Agent部分提供)
171
- // if (excludedTools.has(name)) continue;
172
- // // 跳过子Agent委托工具(通过专门的子Agent部分提供)
173
- // if (this._subAgents.has(name)) continue;
174
- desc += `- ${name}: ${tool.description}\n`;
176
+
177
+ // MCP ext 工具不在这里显示,它们通过专门的描述部分提供
178
+ if (name.startsWith('mcp_') || name.startsWith('ext_') || name.startsWith('workflow_')) {
179
+ continue;
180
+ }
181
+
182
+ directTools.push({ name, tool });
183
+ }
184
+
185
+ if (directTools.length === 0) {
186
+ return '';
175
187
  }
188
+
189
+ let desc = '## 可用工具\n\n';
190
+ for (const { name, tool } of directTools) {
191
+ desc += `- **${name}**: ${tool.description || '无描述'}\n`;
192
+ }
193
+
176
194
  return desc.trim();
177
195
  }
178
196
 
@@ -222,15 +240,61 @@ class Agent extends EventEmitter {
222
240
  }
223
241
 
224
242
  let desc = '【系统能力】\n';
225
- for (const plugin of keyPlugins) {
243
+ keyPlugins.map((plugin, index) => {
226
244
  const name = plugin.instance?.name || plugin.name || 'unknown';
227
245
  const description = plugin.instance?.description || '无描述';
228
- desc += `- ${name}: ${description}\n`;
246
+ desc += `${index + 1}. ${name}: ${description}\n`;
247
+ });
248
+
249
+ return desc.trim();
250
+ }
251
+
252
+ _buildExtensionToolsDescription() {
253
+ const extensionExecutor = this.framework.pluginManager.get('extension-executor');
254
+ if (!extensionExecutor) {
255
+ return '';
256
+ }
257
+
258
+ const extensions = extensionExecutor.getExtensions();
259
+ if (!extensions || extensions.length === 0) {
260
+ return '';
261
+ }
262
+
263
+ let desc = '【Extensions 扩展工具】\n\n';
264
+ desc += '你可以通过 `ext_call` 工具调用以下扩展插件的功能。\n\n';
265
+
266
+ for (const ext of extensions) {
267
+ desc += `### ${ext.name}\n\n`;
268
+ desc += `${ext.description || ''}\n\n`;
269
+ for (const tool of ext.tools) {
270
+ desc += `- **${tool.name}**: ${tool.description || '无描述'}\n`;
271
+ // 添加参数描述
272
+ if (tool.inputSchema) {
273
+ try {
274
+ desc += `**参数:**\n\n`;
275
+ desc += zodSchemaToMarkdown(tool.inputSchema) + '\n\n';
276
+ } catch (e) {
277
+ // 忽略转换错误
278
+ }
279
+ }
280
+ }
281
+ desc += '\n';
229
282
  }
230
283
 
284
+ desc += '**调用格式:**\n';
285
+ desc += '```\next_call({ plugin: "插件名", tool: "工具名", args: {参数} })\n';
286
+ desc += '```\n';
231
287
  return desc.trim();
232
288
  }
233
289
 
290
+ _buildMcpToolsDescription() {
291
+ const mcpExecutor = this.framework.pluginManager.get('mcp-executor');
292
+ if (!mcpExecutor) {
293
+ return '';
294
+ }
295
+ return mcpExecutor._buildMCPServersDescription();
296
+ }
297
+
234
298
  /**
235
299
  * 构建子Agent描述
236
300
  * 动态从配置文件读取每个子Agent的专业领域
@@ -366,8 +430,10 @@ class Agent extends EventEmitter {
366
430
  const key = line.substring(0, colonIndex).trim();
367
431
  let value = line.substring(colonIndex + 1).trim();
368
432
  // 移除可能的引号
369
- if ((value.startsWith('"') && value.endsWith('"')) ||
370
- (value.startsWith("'") && value.endsWith("'"))) {
433
+ if (
434
+ (value.startsWith('"') && value.endsWith('"')) ||
435
+ (value.startsWith("'") && value.endsWith("'"))
436
+ ) {
371
437
  value = value.slice(1, -1);
372
438
  }
373
439
  config[key] = value;
@@ -377,7 +443,7 @@ class Agent extends EventEmitter {
377
443
  if (config.description) {
378
444
  const desc = config.description;
379
445
  // 按逗号/顿号分隔
380
- const parts = desc.split(/[,,、]/).map(s => s.trim());
446
+ const parts = desc.split(/[,,、]/).map((s) => s.trim());
381
447
  // 取第一部分(通常是最核心的能力描述)
382
448
  const firstPart = parts[0];
383
449
  // 如果第一部分太长(说明是句子),取前50个字符
@@ -52,6 +52,13 @@ class Plugin {
52
52
  _framework = null;
53
53
  _subAgents = [];
54
54
 
55
+ /**
56
+ * 插件注册的工具集(供 ExtensionExecutor 自动加载)
57
+ * 格式: { toolName: { name, description, inputSchema, execute } }
58
+ * @type {Object}
59
+ */
60
+ tools = {};
61
+
55
62
  /**
56
63
  * 安装插件 - 注册工具/事件等
57
64
  * @param {Framework} framework - 框架实例
@@ -61,6 +68,42 @@ class Plugin {
61
68
  return this;
62
69
  }
63
70
 
71
+ /**
72
+ * 注册工具到扩展执行器(供 ExtensionExecutor 统一管理)
73
+ * 支持三种调用方式:
74
+ * this.registerTool('name', { description, inputSchema, execute })
75
+ * this.registerTool({ name, description, inputSchema, execute })
76
+ * this.registerTool({ name, description, inputSchema, execute, pluginName: 'xxx' })
77
+ * @param {string|Object} name - 工具名称或工具定义对象
78
+ * @param {Object} [def] - 工具定义 { description, inputSchema, execute }
79
+ */
80
+ registerTool(name, def) {
81
+ if (!this._framework) return;
82
+ // 支持 this.registerTool({ name, description, ... }) 形式
83
+ if (typeof name === 'object' && name !== null) {
84
+ def = name;
85
+ name = def.name;
86
+ }
87
+ if (!name || !def) return;
88
+
89
+ // 支持自定义插件名
90
+ const pluginName = def.pluginName || this.name;
91
+ delete def.pluginName;
92
+
93
+ const extExec = this._framework.pluginManager.get('extension-executor');
94
+ if (extExec) {
95
+ extExec.registerTool(
96
+ pluginName,
97
+ {
98
+ name: pluginName,
99
+ description: def.description || this.description,
100
+ version: this.version,
101
+ },
102
+ { name, ...def }
103
+ );
104
+ }
105
+ }
106
+
64
107
  /**
65
108
  * 启动插件 - 初始化完成后的启动
66
109
  * @param {Framework} framework - 框架实例
@@ -10,6 +10,10 @@ const path = require('path');
10
10
  const { z } = require('zod');
11
11
  const { createMCPClient } = require('@ai-sdk/mcp');
12
12
  const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
13
+ const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
14
+ const { logger } = require('../utils/logger');
15
+
16
+ const log = logger.child('MCPExecutor');
13
17
 
14
18
  /**
15
19
  * MCP 客户端包装器
@@ -495,39 +499,139 @@ class MCPExecutorPlugin extends Plugin {
495
499
  return '';
496
500
  }
497
501
 
498
- let desc = 'MCP Servers - 可用工具服务】\n';
499
- desc += '你可以通过 mcp_call 工具调用以下 MCP 服务器的工具。\n\n';
502
+ let desc = '[MCP Servers]\n\n';
503
+ desc += '你可以通过 `mcp_call` 工具调用以下 MCP 服务器的工具。\n\n';
500
504
 
501
505
  for (const [serverName, info] of this._clients) {
502
- desc += `【${serverName}】\n`;
506
+ desc += `### ${serverName}\n\n`;
503
507
  for (const tool of info.tools) {
504
- const schema = this._extractSchema(tool.inputSchema);
505
- const props = schema.properties || {};
506
- const required = schema.required || [];
508
+ desc += `#### ${tool.name}\n`;
509
+ desc += `${tool.description || '无描述'}\n\n`;
507
510
 
508
- // 简化参数列表
509
- let paramStr = '';
510
- if (Object.keys(props).length > 0) {
511
- const params = Object.entries(props).map(([key, prop]) => {
512
- const isRequired = required.includes(key);
513
- return `${key}${isRequired ? '(必填)' : ''}`;
514
- });
515
- paramStr = ` 参数: ${params.join(', ')}`;
511
+ // 使用 zodSchemaToMarkdown 生成参数文档
512
+ const schema = tool.inputSchema;
513
+ if (schema) {
514
+ try {
515
+ if (typeof schema.toJSON === 'function') {
516
+ // Zod schema 直接转换
517
+ desc += `**参数:**\n\n`;
518
+ desc += zodSchemaToMarkdown(schema) + '\n\n';
519
+ } else if (schema.jsonSchema) {
520
+ // JSON Schema 格式,尝试转换为 Zod schema
521
+ const zodSchema = this._jsonSchemaToZod(schema.jsonSchema || schema);
522
+ if (zodSchema) {
523
+ desc += `**参数:**\n\n`;
524
+ desc += zodSchemaToMarkdown(zodSchema) + '\n\n';
525
+ }
526
+ } else if (schema.properties) {
527
+ // 已经是对象格式
528
+ const zodSchema = this._jsonSchemaToZod(schema);
529
+ if (zodSchema) {
530
+ desc += `**参数:**\n\n`;
531
+ desc += zodSchemaToMarkdown(zodSchema) + '\n\n';
532
+ }
533
+ }
534
+ } catch (e) {
535
+ // 如果转换失败,使用简化格式
536
+ desc += this._fallbackParamsDesc(schema);
537
+ }
516
538
  }
517
-
518
- // 截取描述的第一句
519
- const shortDesc = tool.description?.split('.')[0] || '无描述';
520
- desc += `- ${tool.name}: ${shortDesc}${paramStr}\n`;
521
539
  }
522
- desc += '\n';
540
+ desc += '---\n\n';
523
541
  }
524
542
 
525
- desc +=
526
- '调用格式:mcp_call({ server: "服务器", tool: "工具", args_json: \'{"参数": "值"}\' })\n';
527
- desc += '复杂参数时先用 mcp_tool_schema 查询完整参数结构\n';
543
+ desc += '**调用格式:**\n';
544
+ desc += '```\nmcp_call({ server: "服务器", tool: "工具", args_json: \'{"参数": "值"}\' })\n';
545
+ desc += '```\n';
528
546
  return desc.trim();
529
547
  }
530
548
 
549
+ /**
550
+ * 将 JSON Schema 转换为 Zod schema
551
+ */
552
+ _jsonSchemaToZod(jsonSchema) {
553
+ if (!jsonSchema || !jsonSchema.properties) {
554
+ return null;
555
+ }
556
+
557
+ try {
558
+ const shape = {};
559
+ const properties = jsonSchema.properties;
560
+ const required = jsonSchema.required || [];
561
+
562
+ for (const [key, prop] of Object.entries(properties)) {
563
+ shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key));
564
+ }
565
+
566
+ return z.object(shape);
567
+ } catch (e) {
568
+ return null;
569
+ }
570
+ }
571
+
572
+ /**
573
+ * 将 JSON Schema 属性转换为 Zod 类型
574
+ */
575
+ _jsonSchemaPropToZod(prop, isRequired) {
576
+ if (prop.$ref) {
577
+ return isRequired ? z.any() : z.any().optional();
578
+ }
579
+
580
+ if (prop.enum) {
581
+ let zodType = z.string().enum(prop.enum);
582
+ if (prop.nullable) {
583
+ zodType = zodType.nullable();
584
+ }
585
+ return isRequired ? zodType : zodType.optional();
586
+ }
587
+
588
+ const type = prop.type || 'string';
589
+
590
+ switch (type) {
591
+ case 'string':
592
+ return isRequired ? z.string() : z.string().optional();
593
+ case 'number':
594
+ case 'integer':
595
+ return isRequired ? z.number() : z.number().optional();
596
+ case 'boolean':
597
+ return isRequired ? z.boolean() : z.boolean().optional();
598
+ case 'array':
599
+ return isRequired ? z.array(z.any()) : z.array(z.any()).optional();
600
+ case 'object':
601
+ if (prop.properties) {
602
+ const nested = {};
603
+ for (const [k, v] of Object.entries(prop.properties)) {
604
+ nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false);
605
+ }
606
+ return isRequired ? z.object(nested) : z.object(nested).optional();
607
+ }
608
+ return isRequired ? z.record(z.any()) : z.record(z.any()).optional();
609
+ default:
610
+ return isRequired ? z.any() : z.any().optional();
611
+ }
612
+ }
613
+
614
+ /**
615
+ * 回退的参数描述(当 zodSchemaToMarkdown 失败时)
616
+ */
617
+ _fallbackParamsDesc(schema) {
618
+ const props = schema.properties || {};
619
+ const required = schema.required || [];
620
+
621
+ if (Object.keys(props).length === 0) {
622
+ return '';
623
+ }
624
+
625
+ let desc = '**参数:**\n\n';
626
+ for (const [key, prop] of Object.entries(props)) {
627
+ const isRequired = required.includes(key);
628
+ const type = prop.type || 'any';
629
+ const desc_text = prop.description || '';
630
+ desc += `- \`${key}\`${isRequired ? ' (必填)' : ''}: ${type} ${desc_text}\n`;
631
+ }
632
+ return desc + '\n';
633
+ }
634
+
531
635
  _getParentAgent() {
532
636
  // 优先返回已缓存的主 agent
533
637
  if (this._mainAgent) {