foliko 1.1.13 → 1.1.15
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/.agent/data/plugins-state.json +1 -1
- package/.agent/data/weixin/images/file_1776188148383jpg +0 -0
- package/.agent/data/weixin/images/file_1776188458326.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188689423.jpg +0 -0
- package/.agent/data/weixin/images/file_1776188813604.jpg +0 -0
- package/.agent/data/weixin/images/file_1776189097450.jpg +0 -0
- package/.agent/data/weixin/videos/file_1776188318431.mp4 +0 -0
- package/.agent/mcp_config.json +7 -0
- package/.agent/memory/feedback/mnxe0cxc-14l6q5.md +17 -0
- package/.agent/memory/feedback/mnxe11pa-nxf577.md +9 -0
- package/.agent/memory/feedback/mnxe1an2-84faff.md +9 -0
- package/.agent/memory/feedback/mnxgcfj0-qg3wjc.md +9 -0
- package/.agent/memory/feedback/mnxgcn3y-40mqss.md +9 -0
- package/.agent/memory/feedback/mnxgcxq9-jm7ydl.md +9 -0
- package/.agent/memory/feedback/mnxgdyfj-pzjvkb.md +9 -0
- package/.agent/memory/feedback/mnxge3z1-7vyit1.md +9 -0
- package/.agent/memory/feedback/mnxhrg28-41hhjr.md +9 -0
- package/.agent/memory/feedback/mnxhrx0e-yth94k.md +9 -0
- package/.agent/memory/feedback/mnxhs3jd-rvx8aq.md +9 -0
- package/.agent/memory/feedback/mnxhs7p7-g5rtn9.md +9 -0
- package/.agent/memory/feedback/mnxhslx5-oqwuhr.md +9 -0
- package/.agent/memory/feedback/mnxhsvd6-nuyvvc.md +9 -0
- package/.agent/memory/project/mnxegq6z-5fc64w.md +22 -0
- package/.agent/memory/project/mnxh2w4r-le9hur.md +17 -0
- package/.agent/memory/project/mnxhq2yv-9qa8ay.md +31 -0
- package/.agent/memory/project/mnxhql11-iaun2o.md +34 -0
- package/.agent/memory/project/mnxhr78p-jpg7eq.md +23 -0
- package/.agent/memory/reference/mnxe0oa9-p6wzk6.md +27 -0
- package/.agent/memory/reference/mnxehcll-kcrmpf.md +29 -0
- package/.agent/memory/reference/mnxei0ts-jw091y.md +18 -0
- package/.agent/memory/reference/mnxfnrr4-rski36.md +40 -0
- package/.agent/memory/reference/mnxfo6n5-af9zls.md +18 -0
- package/.agent/memory/reference/mnxh2ady-u6cmvk.md +61 -0
- package/.agent/memory/reference/mnxhqdqh-ucsbsk.md +31 -0
- package/.agent/memory/reference/mnxiixyp-rz2gvw.md +34 -0
- package/.agent/memory/user/mnxhqxk3-vjjhlf.md +23 -0
- package/.agent/sessions/cli_default.json +11 -639
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +25 -0
- package/.claude/settings.local.json +23 -1
- package/cli/src/commands/chat.js +9 -15
- package/cli/src/ui/chat-ui.js +40 -71
- package/package.json +4 -2
- package/plugins/default-plugins.js +5 -5
- package/plugins/file-system-plugin.js +1 -1
- package/plugins/memory-plugin.js +12 -12
- package/plugins/plugin-manager-plugin.js +1 -0
- package/plugins/subagent-plugin.js +55 -1
- package/plugins/telegram-plugin.js +9 -6
- package/plugins/weixin-plugin.js +75 -78
- package/src/core/agent-chat.js +468 -1612
- package/src/core/agent.js +53 -134
- package/src/core/chat-session.js +423 -0
- package/src/core/context-compressor.js +473 -0
- package/src/core/context-manager.js +0 -48
- package/src/core/framework.js +95 -68
- package/src/core/index.js +11 -0
- package/src/core/notification-manager.js +125 -0
- package/src/core/subagent.js +295 -0
- package/src/core/token-counter.js +190 -0
- package/src/core/tool-executor.js +270 -0
- package/src/executors/mcp-executor.js +14 -1
- package/src/utils/download.js +596 -0
- package/system.md +312 -2373
- package/.agent/agents/code-assistant.json +0 -17
- package/.agent/agents/email-assistant.json +0 -14
- package/.agent/agents/file-assistant.json +0 -18
- package/.agent/agents/orchestrator-demo.md +0 -53
- package/.agent/agents/orchestrator.json +0 -7
- package/.agent/agents/poster-expert.md +0 -228
- package/.agent/agents/system-assistant.json +0 -15
- package/.agent/agents/web-assistant.json +0 -12
- package/.agent/memory/feedback/mnv3nu27-3o15pf.md +0 -9
- package/.agent/memory/feedback/mnv3o078-b959yj.md +0 -9
- package/.agent/memory/feedback/mnv3o6ej-u0fif5.md +0 -9
- package/.agent/memory/feedback/mnv3obgl-bkkjoj.md +0 -9
- package/.agent/memory/feedback/mnv4a3js-dv6onx.md +0 -9
- package/.agent/memory/feedback/mnv4aacm-sxxowp.md +0 -9
- package/.agent/memory/feedback/mnv4ahto-w40ffm.md +0 -9
- package/.agent/memory/feedback/mnv4anvp-3cs06y.md +0 -9
- package/.agent/memory/feedback/mnvzgvtd-0o2900.md +0 -9
- package/.agent/memory/feedback/mnvzhajn-swbx61.md +0 -15
- package/.agent/memory/feedback/mnvzhgsp-p5vog3.md +0 -9
- package/.agent/memory/feedback/mnvzho0c-fgql7q.md +0 -14
- package/.agent/memory/feedback/mnvzhtzq-ufr5at.md +0 -9
- package/.agent/memory/feedback/mnvzhyb3-9byq2z.md +0 -9
- package/.agent/memory/feedback/mnvzi7hp-hyeafp.md +0 -9
- package/.agent/memory/feedback/mnvzibph-z7rwp5.md +0 -9
- package/.agent/memory/feedback/mnvzilys-7h176w.md +0 -14
- package/.agent/memory/feedback/mnvziuh5-zjshci.md +0 -9
- package/.agent/memory/feedback/mnw07wde-6zqsc8.md +0 -9
- package/.agent/memory/feedback/mnw084bp-j0ba2a.md +0 -9
- package/.agent/memory/user/mnv3n62r-y0h79j.md +0 -21
- package/.agent/memory/user/mnv3n9yf-ead4g8.md +0 -13
- package/.agent/memory/user/mnv3ne3j-82tq1k.md +0 -19
- package/.agent/memory/user/mnv3nhgm-g2s2us.md +0 -11
- package/.agent/memory/user/mnv3nl9u-ejd998.md +0 -16
- package/.agent/memory/user/mnv3nofp-ya5szl.md +0 -10
- package/.agent/memory/user/mnv49qne-bhk0ki.md +0 -9
- package/.agent/memory/user/mnv49w3y-rzr8ju.md +0 -13
- package/.agent/sessions/test.json +0 -16
- package/plugins/python-plugin-loader.js.bak +0 -856
- package/src/core/agent-context.js +0 -188
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TokenCounter - Token 计算工具
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 计算文本的 token 数量
|
|
6
|
+
* 2. 计算消息数组的 token 总数
|
|
7
|
+
* 3. 计算工具定义的 token 总数
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 简单的中英文混合 tokenizer
|
|
12
|
+
* 粗略估计:中文每个字符 2 字节,英文每个单词约 1.5 字节
|
|
13
|
+
* @param {string} text - 文本
|
|
14
|
+
* @param {number} bytesPerToken - 每 token 字节数,默认 4
|
|
15
|
+
* @returns {number} token 数量
|
|
16
|
+
*/
|
|
17
|
+
function encode(text, bytesPerToken = 4) {
|
|
18
|
+
if (!text) return 0;
|
|
19
|
+
const bytes = Buffer.byteLength(String(text), 'utf8');
|
|
20
|
+
return Math.ceil(bytes / bytesPerToken);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 计算 JSON 字符串的 token 数量
|
|
25
|
+
* @param {string} text - JSON 字符串
|
|
26
|
+
* @returns {number} token 数量
|
|
27
|
+
*/
|
|
28
|
+
function encodeForJSON(text) {
|
|
29
|
+
if (!text) return 0;
|
|
30
|
+
// JSON 字符串需要额外计算引号和转义
|
|
31
|
+
const encoded = encode(text);
|
|
32
|
+
return encoded + Math.ceil(Buffer.byteLength(JSON.stringify(text), 'utf8') / 100);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
class TokenCounter {
|
|
36
|
+
/**
|
|
37
|
+
* @param {Object} config - 配置
|
|
38
|
+
* @param {Object} config.toolSchema - 工具 schema(用于工具 token 计算)
|
|
39
|
+
*/
|
|
40
|
+
constructor(config = {}) {
|
|
41
|
+
this.toolSchema = config.toolSchema || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 计算文本 token
|
|
46
|
+
* @param {string} text - 文本
|
|
47
|
+
* @param {number} bytesPerToken - 每 token 字节数
|
|
48
|
+
* @returns {number} token 数量
|
|
49
|
+
*/
|
|
50
|
+
countText(text, bytesPerToken = 4) {
|
|
51
|
+
return encode(text, bytesPerToken);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 计算消息数组的 token 总数
|
|
56
|
+
* @param {Array} messages - 消息数组
|
|
57
|
+
* @returns {number} token 总数
|
|
58
|
+
*/
|
|
59
|
+
countMessages(messages) {
|
|
60
|
+
if (!Array.isArray(messages)) return 0;
|
|
61
|
+
return messages.reduce((sum, msg) => sum + this.countMessage(msg), 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 计算单条消息的 token
|
|
66
|
+
* @param {Object} msg - 消息
|
|
67
|
+
* @returns {number} token 数量
|
|
68
|
+
*/
|
|
69
|
+
countMessage(msg) {
|
|
70
|
+
if (!msg) return 0;
|
|
71
|
+
|
|
72
|
+
let total = 0;
|
|
73
|
+
|
|
74
|
+
// 角色和格式开销
|
|
75
|
+
total += 4;
|
|
76
|
+
|
|
77
|
+
if (typeof msg.content === 'string') {
|
|
78
|
+
total += this.countText(msg.content);
|
|
79
|
+
} else if (Array.isArray(msg.content)) {
|
|
80
|
+
for (const block of msg.content) {
|
|
81
|
+
total += this.countContentBlock(block);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// tool_calls 开销
|
|
86
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
|
|
87
|
+
total += 15; // overhead per tool_calls block
|
|
88
|
+
for (const tc of msg.tool_calls) {
|
|
89
|
+
if (tc.function) {
|
|
90
|
+
total += this.countText(tc.function.name) + this.countText(tc.function.arguments);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// tool_call_id 开销
|
|
96
|
+
if (msg.tool_call_id) {
|
|
97
|
+
total += 15;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return total;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 计算 content block 的 token
|
|
105
|
+
* @param {Object} block - content block
|
|
106
|
+
* @returns {number} token 数量
|
|
107
|
+
*/
|
|
108
|
+
countContentBlock(block) {
|
|
109
|
+
if (!block) return 0;
|
|
110
|
+
|
|
111
|
+
switch (block.type) {
|
|
112
|
+
case 'text':
|
|
113
|
+
return this.countText(block.text);
|
|
114
|
+
case 'tool-call':
|
|
115
|
+
case 'tool-use':
|
|
116
|
+
if (block.input) {
|
|
117
|
+
return this.countText(JSON.stringify(block.input));
|
|
118
|
+
}
|
|
119
|
+
return 0;
|
|
120
|
+
case 'tool-result':
|
|
121
|
+
case 'tool_result':
|
|
122
|
+
if (block.content) {
|
|
123
|
+
const content =
|
|
124
|
+
typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
|
|
125
|
+
return this.countText(content);
|
|
126
|
+
}
|
|
127
|
+
return 0;
|
|
128
|
+
case 'image':
|
|
129
|
+
// 图片按 token 估算
|
|
130
|
+
return 85;
|
|
131
|
+
default:
|
|
132
|
+
return this.countText(JSON.stringify(block));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 计算工具定义的 token 总数
|
|
138
|
+
* @param {Array} tools - 工具数组
|
|
139
|
+
* @returns {number} token 总数
|
|
140
|
+
*/
|
|
141
|
+
countTools(tools) {
|
|
142
|
+
if (!tools || !Array.isArray(tools)) return 0;
|
|
143
|
+
|
|
144
|
+
let total = 0;
|
|
145
|
+
for (const tool of tools) {
|
|
146
|
+
// 工具名和描述
|
|
147
|
+
total += 20; // overhead
|
|
148
|
+
|
|
149
|
+
if (tool.description) {
|
|
150
|
+
total += this.countText(tool.description);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 参数
|
|
154
|
+
if (tool.inputSchema) {
|
|
155
|
+
const schema =
|
|
156
|
+
tool.inputSchema.jsonSchema || tool.inputSchema.inputSchema || tool.inputSchema;
|
|
157
|
+
if (schema.properties) {
|
|
158
|
+
for (const [name, prop] of Object.entries(schema.properties)) {
|
|
159
|
+
total += this.countText(name) + 10;
|
|
160
|
+
if (prop.description) {
|
|
161
|
+
total += this.countText(prop.description);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return total;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 估算完整请求的 token(消息 + 工具 + 系统提示)
|
|
173
|
+
* @param {Object} params - 请求参数
|
|
174
|
+
* @returns {Object} - { messagesTokens, toolsTokens, systemPromptTokens, total }
|
|
175
|
+
*/
|
|
176
|
+
estimateRequest({ messages, tools, systemPrompt }) {
|
|
177
|
+
const messagesTokens = this.countMessages(messages);
|
|
178
|
+
const toolsTokens = this.countTools(tools);
|
|
179
|
+
const systemPromptTokens = systemPrompt ? this.countText(systemPrompt) : 0;
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
messagesTokens,
|
|
183
|
+
toolsTokens,
|
|
184
|
+
systemPromptTokens,
|
|
185
|
+
total: messagesTokens + toolsTokens + systemPromptTokens,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = { TokenCounter, encode, encodeForJSON };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolExecutor - 工具执行器
|
|
3
|
+
*
|
|
4
|
+
* 职责:
|
|
5
|
+
* 1. 工具发现和注册
|
|
6
|
+
* 2. 工具执行
|
|
7
|
+
* 3. 工具调用验证
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { EventEmitter } = require('../utils/event-emitter');
|
|
11
|
+
const { logger } = require('../utils/logger');
|
|
12
|
+
|
|
13
|
+
class ToolExecutor extends EventEmitter {
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} config - 配置
|
|
16
|
+
*/
|
|
17
|
+
constructor(config = {}) {
|
|
18
|
+
super();
|
|
19
|
+
|
|
20
|
+
this.config = config;
|
|
21
|
+
this.agent = config.agent;
|
|
22
|
+
this.framework = config.framework;
|
|
23
|
+
|
|
24
|
+
// 工具注册表: name -> toolDef
|
|
25
|
+
this._tools = new Map();
|
|
26
|
+
|
|
27
|
+
// 工具调用统计
|
|
28
|
+
this._toolStats = {
|
|
29
|
+
totalCalls: 0,
|
|
30
|
+
failedCalls: 0,
|
|
31
|
+
lastCall: null,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 注册工具
|
|
37
|
+
* @param {Object} tool - 工具定义
|
|
38
|
+
*/
|
|
39
|
+
registerTool(tool) {
|
|
40
|
+
if (!tool || !tool.name) {
|
|
41
|
+
logger.warn('ToolExecutor', 'Ignoring tool with no name');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this._tools.set(tool.name, tool);
|
|
45
|
+
logger.debug('ToolExecutor', `Registered tool: ${tool.name}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 批量注册工具
|
|
50
|
+
* @param {Array} tools - 工具数组
|
|
51
|
+
*/
|
|
52
|
+
registerTools(tools) {
|
|
53
|
+
if (!Array.isArray(tools)) return;
|
|
54
|
+
for (const tool of tools) {
|
|
55
|
+
this.registerTool(tool);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 注销工具
|
|
61
|
+
* @param {string} name - 工具名
|
|
62
|
+
*/
|
|
63
|
+
unregisterTool(name) {
|
|
64
|
+
this._tools.delete(name);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 获取工具
|
|
69
|
+
* @param {string} name - 工具名
|
|
70
|
+
* @returns {Object|null}
|
|
71
|
+
*/
|
|
72
|
+
getTool(name) {
|
|
73
|
+
return this._tools.get(name) || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 获取所有工具
|
|
78
|
+
* @returns {Array}
|
|
79
|
+
*/
|
|
80
|
+
getAllTools() {
|
|
81
|
+
return Array.from(this._tools.values());
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 检查工具是否存在
|
|
86
|
+
* @param {string} name - 工具名
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
*/
|
|
89
|
+
hasTool(name) {
|
|
90
|
+
return this._tools.has(name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 执行工具
|
|
95
|
+
* @param {string} name - 工具名
|
|
96
|
+
* @param {Object} args - 参数
|
|
97
|
+
* @param {Object} options - 选项
|
|
98
|
+
* @returns {Promise}
|
|
99
|
+
*/
|
|
100
|
+
async executeTool(name, args = {}, options = {}) {
|
|
101
|
+
const tool = this._tools.get(name);
|
|
102
|
+
if (!tool) {
|
|
103
|
+
const error = `Tool '${name}' not found`;
|
|
104
|
+
logger.warn('ToolExecutor', error);
|
|
105
|
+
throw new Error(error);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this._toolStats.totalCalls++;
|
|
109
|
+
this._toolStats.lastCall = {
|
|
110
|
+
name,
|
|
111
|
+
args,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
this.emit('tool:call', { name, args, source: options.source });
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
// 统一 execute 签名为 (args, framework)
|
|
119
|
+
const result = await tool.execute(args, this.framework);
|
|
120
|
+
|
|
121
|
+
this.emit('tool:result', { name, args, result, source: options.source });
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
} catch (err) {
|
|
125
|
+
this._toolStats.failedCalls++;
|
|
126
|
+
logger.error('ToolExecutor', `Tool '${name}' failed:`, err.message);
|
|
127
|
+
|
|
128
|
+
this.emit('tool:error', {
|
|
129
|
+
name,
|
|
130
|
+
args,
|
|
131
|
+
error: err.message,
|
|
132
|
+
source: options.source,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 批量执行工具
|
|
141
|
+
* @param {Array} tools - 工具调用数组 [{name, args}, ...]
|
|
142
|
+
* @param {Object} options - 选项
|
|
143
|
+
* @returns {Promise<Array>}
|
|
144
|
+
*/
|
|
145
|
+
async executeTools(tools, options = {}) {
|
|
146
|
+
if (!Array.isArray(tools)) {
|
|
147
|
+
throw new Error('tools must be an array');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const results = [];
|
|
151
|
+
for (const toolCall of tools) {
|
|
152
|
+
try {
|
|
153
|
+
const result = await this.executeTool(toolCall.name, toolCall.args || {}, options);
|
|
154
|
+
results.push({ success: true, name: toolCall.name, result });
|
|
155
|
+
} catch (err) {
|
|
156
|
+
results.push({ success: false, name: toolCall.name, error: err.message });
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return results;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* 获取工具的 AI 格式(用于发送给 AI)
|
|
164
|
+
* @returns {Object} AI SDK 格式的工具对象
|
|
165
|
+
*/
|
|
166
|
+
getToolsForAI() {
|
|
167
|
+
const tools = {};
|
|
168
|
+
for (const [name, tool] of this._tools) {
|
|
169
|
+
if (!tool.description) continue;
|
|
170
|
+
|
|
171
|
+
tools[name] = {
|
|
172
|
+
description: tool.description,
|
|
173
|
+
inputSchema: tool.inputSchema,
|
|
174
|
+
execute: tool.execute ? tool.execute.bind(tool) : undefined,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return tools;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 验证工具调用的参数
|
|
182
|
+
* @param {Array} messages - 消息数组
|
|
183
|
+
* @returns {Array} 验证后的消息
|
|
184
|
+
*/
|
|
185
|
+
validateToolCalls(messages) {
|
|
186
|
+
let fixedCount = 0;
|
|
187
|
+
// 收集被跳过的 toolCallId,用于清理对应的 tool-result
|
|
188
|
+
const invalidatedToolCallIds = new Set();
|
|
189
|
+
|
|
190
|
+
for (const msg of messages) {
|
|
191
|
+
// 清理 assistant 消息中的不完整 tool-call
|
|
192
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
193
|
+
for (const item of msg.content) {
|
|
194
|
+
// 兼容 tool-call 和 tool-use 两种类型
|
|
195
|
+
if (item.type !== 'tool-call' && item.type !== 'tool-use') {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const input = item.input;
|
|
200
|
+
if (typeof input !== 'string') {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// 检查 input 是否是有效的 JSON(不是不完整的)
|
|
205
|
+
const trimmed = input.trim();
|
|
206
|
+
if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
|
|
207
|
+
// 不完整的 JSON,移除这个 tool-call
|
|
208
|
+
// 记录 toolCallId,以便后续清理对应的 tool-result
|
|
209
|
+
if (item.toolCallId) {
|
|
210
|
+
invalidatedToolCallIds.add(item.toolCallId);
|
|
211
|
+
}
|
|
212
|
+
logger.warn(
|
|
213
|
+
`_validateToolCalls: invalid tool-call input="${input}", toolCallId=${item.toolCallId}, converting to text`
|
|
214
|
+
);
|
|
215
|
+
item.type = 'text';
|
|
216
|
+
item.text = `(工具调用 ${item.toolName} 参数不完整,已跳过)`;
|
|
217
|
+
delete item.toolCallId;
|
|
218
|
+
delete item.toolName;
|
|
219
|
+
delete item.input;
|
|
220
|
+
fixedCount++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 如果有无效的 tool-call,清理对应的 tool-result
|
|
227
|
+
if (invalidatedToolCallIds.size > 0) {
|
|
228
|
+
logger.warn(
|
|
229
|
+
`_validateToolCalls: removing ${invalidatedToolCallIds.size} tool-results with invalidated toolCallIds`
|
|
230
|
+
);
|
|
231
|
+
for (const msg of messages) {
|
|
232
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
233
|
+
// 过滤掉引用了无效 toolCallId 的 tool-result
|
|
234
|
+
const oldLen = msg.content.length;
|
|
235
|
+
msg.content = msg.content.filter((item) => {
|
|
236
|
+
if (item.type !== 'tool-result' && item.type !== 'tool_result') {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
// 如果 tool-result 引用的 toolCallId 已被标记为无效,则移除
|
|
240
|
+
if (item.toolCallId && invalidatedToolCallIds.has(item.toolCallId)) {
|
|
241
|
+
logger.warn(
|
|
242
|
+
`_validateToolCalls: removing orphaned tool-result with toolCallId=${item.toolCallId}`
|
|
243
|
+
);
|
|
244
|
+
fixedCount++;
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
return true;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (fixedCount > 0) {
|
|
254
|
+
logger.info(`_validateToolCalls: Fixed ${fixedCount} incomplete tool calls/results`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 获取工具统计
|
|
260
|
+
* @returns {Object}
|
|
261
|
+
*/
|
|
262
|
+
getStats() {
|
|
263
|
+
return {
|
|
264
|
+
...this._toolStats,
|
|
265
|
+
toolCount: this._tools.size,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = { ToolExecutor };
|
|
@@ -37,6 +37,10 @@ class MCPClientWrapper {
|
|
|
37
37
|
async connect() {
|
|
38
38
|
if (this.connected) return;
|
|
39
39
|
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timeoutMs = this.timeout || 30000;
|
|
42
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
43
|
+
|
|
40
44
|
try {
|
|
41
45
|
// 动态导入 @ai-sdk/mcp
|
|
42
46
|
let createMCPClient;
|
|
@@ -63,7 +67,10 @@ class MCPClientWrapper {
|
|
|
63
67
|
args: shellResolve ? [shellResolve, ...this.args] : this.args,
|
|
64
68
|
env: { ...process.env, ...this.env },
|
|
65
69
|
});
|
|
66
|
-
this.client = await createMCPClient({
|
|
70
|
+
this.client = await createMCPClient({
|
|
71
|
+
transport,
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
67
74
|
} catch (transportErr) {
|
|
68
75
|
log.error(` Transport error:`, transportErr.message);
|
|
69
76
|
throw transportErr;
|
|
@@ -85,8 +92,14 @@ class MCPClientWrapper {
|
|
|
85
92
|
this.connected = true;
|
|
86
93
|
log.info(` Connected to ${this.serverName} with ${this.tools.length} tools`);
|
|
87
94
|
} catch (err) {
|
|
95
|
+
if (controller.signal.aborted) {
|
|
96
|
+
log.error(` Connection timeout (${timeoutMs}ms) for ${this.serverName}`);
|
|
97
|
+
throw new Error(`连接超时 (${timeoutMs}ms): ${this.serverName}`);
|
|
98
|
+
}
|
|
88
99
|
log.error(` Failed to connect to ${this.serverName}:`, err.message);
|
|
89
100
|
// 不抛出错误,让框架继续运行
|
|
101
|
+
} finally {
|
|
102
|
+
clearTimeout(timeoutId);
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
|