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
package/src/core/agent-chat.js
CHANGED
|
@@ -1,85 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* AgentChatHandler 聊天处理器
|
|
3
3
|
* 使用 AI SDK 的 ToolLoopAgent 处理工具调用循环
|
|
4
|
+
*
|
|
5
|
+
* 职责委托:
|
|
6
|
+
* - ChatSession: 会话管理、消息历史、队列
|
|
7
|
+
* - ToolExecutor: 工具发现、执行、验证
|
|
8
|
+
* - ContextCompressor: 上下文压缩
|
|
4
9
|
*/
|
|
5
10
|
|
|
6
11
|
const { EventEmitter } = require('../utils/event-emitter');
|
|
7
12
|
const { logger } = require('../utils/logger');
|
|
8
|
-
const {
|
|
13
|
+
const {
|
|
14
|
+
tool: aiTool,
|
|
15
|
+
ToolLoopAgent,
|
|
16
|
+
isLoopFinished,
|
|
17
|
+
generateText,
|
|
18
|
+
streamText,
|
|
19
|
+
RetryError,
|
|
20
|
+
APICallError,
|
|
21
|
+
} = require('ai');
|
|
9
22
|
const { prepareMessagesForAPI, cleanResponse } = require('../utils');
|
|
10
23
|
const { ChatQueueManager } = require('../utils/chat-queue');
|
|
11
24
|
const fs = require('fs/promises');
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
'deepseek-chat': 128000,
|
|
17
|
-
'deepseek-coder': 128000,
|
|
18
|
-
'deepseek-reasoner': 128000,
|
|
19
|
-
// MiniMax
|
|
20
|
-
'MiniMax-M2.7': 110000,
|
|
21
|
-
// OpenAI
|
|
22
|
-
'gpt-4': 100000,
|
|
23
|
-
'gpt-4o': 100000,
|
|
24
|
-
'gpt-4o-mini': 100000,
|
|
25
|
-
'gpt-4-turbo': 100000,
|
|
26
|
-
// Anthropic
|
|
27
|
-
'claude-3-5-sonnet': 150000,
|
|
28
|
-
'claude-3-opus': 150000,
|
|
29
|
-
'claude-3-sonnet': 150000,
|
|
30
|
-
'glm-5.1': 200000,
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 纯 JavaScript token 计数器
|
|
35
|
-
* 参考 Claude Code 的实现,使用 bytes/token 比率估算
|
|
36
|
-
* - 普通文本:4 bytes ≈ 1 token
|
|
37
|
-
* - JSON 等密集文本:2 bytes ≈ 1 token
|
|
38
|
-
*/
|
|
39
|
-
class SimpleTokenizer {
|
|
40
|
-
constructor() {
|
|
41
|
-
// 无需初始化
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 估算文本的 token 数
|
|
46
|
-
* @param {string} text
|
|
47
|
-
* @param {number} [bytesPerToken=4] - bytes per token 比率
|
|
48
|
-
* @returns {number}
|
|
49
|
-
*/
|
|
50
|
-
encode(text, bytesPerToken = 4) {
|
|
51
|
-
if (!text || typeof text !== 'string') {
|
|
52
|
-
return 0;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// 清理文本
|
|
56
|
-
const cleanText = text
|
|
57
|
-
.replace(/\0+/g, '')
|
|
58
|
-
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
|
|
59
|
-
.slice(0, 100000);
|
|
60
|
-
|
|
61
|
-
if (!cleanText) {
|
|
62
|
-
return 0;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return Math.ceil(cleanText.length / bytesPerToken);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 估算 JSON 文本的 token 数(更密集)
|
|
70
|
-
* @param {string} text
|
|
71
|
-
* @returns {number}
|
|
72
|
-
*/
|
|
73
|
-
encodeForJSON(text) {
|
|
74
|
-
return this.encode(text, 2);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// 全局 tokenizer 实例
|
|
79
|
-
const _globalTokenizer = new SimpleTokenizer();
|
|
80
|
-
|
|
81
|
-
// 压缩超时时间(毫秒)
|
|
82
|
-
const COMPRESSION_TIMEOUT = 120000;
|
|
25
|
+
// 新模块
|
|
26
|
+
const { ChatSession } = require('./chat-session');
|
|
27
|
+
const { ToolExecutor } = require('./tool-executor');
|
|
28
|
+
const { ContextCompressor } = require('./context-compressor');
|
|
83
29
|
|
|
84
30
|
class AgentChatHandler extends EventEmitter {
|
|
85
31
|
/**
|
|
@@ -92,62 +38,58 @@ class AgentChatHandler extends EventEmitter {
|
|
|
92
38
|
this.agent = agent;
|
|
93
39
|
this.config = config;
|
|
94
40
|
|
|
41
|
+
// AI 配置
|
|
95
42
|
this.model = config.model || 'deepseek-chat';
|
|
96
43
|
this.provider = config.provider || 'deepseek';
|
|
97
44
|
this.apiKey = config.apiKey;
|
|
98
45
|
this.baseURL = config.baseURL;
|
|
99
46
|
this.providerOptions = config.providerOptions || {};
|
|
47
|
+
this.providerOptions.maxOutputTokens = config.providerOptions?.maxOutputTokens || 8192;
|
|
48
|
+
this.providerOptions.temperature = config.providerOptions?.temperature || 0.3;
|
|
100
49
|
|
|
101
50
|
this._systemPrompt = config.systemPrompt || 'You are a helpful assistant.';
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const modelKey = Object.keys(MODEL_CONTEXT_LIMITS).find((k) =>
|
|
109
|
-
this.model.toLowerCase().includes(k.toLowerCase())
|
|
110
|
-
);
|
|
111
|
-
const defaultLimit = modelKey ? MODEL_CONTEXT_LIMITS[modelKey] : 40000;
|
|
112
|
-
this._maxContextTokens = config.maxContextTokens || defaultLimit;
|
|
113
|
-
this._compressionThreshold = config.compressionThreshold || 0.6; // 60% 就压缩,早触发
|
|
114
|
-
this._keepRecentMessages = config.keepRecentMessages || 20; // 保留最近 20 条
|
|
115
|
-
this._enableSmartCompress = config.enableSmartCompress !== false; // 默认开启智能摘要
|
|
116
|
-
this._encoder = null;
|
|
117
|
-
this._compressionCount = 0; // 压缩次数统计
|
|
118
|
-
this._compressionInProgress = false; // 压缩锁,防止并发压缩
|
|
119
|
-
this._compressionPromise = null; // 正在进行的压缩 Promise
|
|
120
|
-
|
|
121
|
-
// 工具结果压缩配置
|
|
122
|
-
this._maxToolResultSize = config.maxToolResultSize || 4000; // 工具结果超过此大小则压缩(字节)
|
|
123
|
-
|
|
124
|
-
// 初始化编码器
|
|
125
|
-
// 使用纯 JS tokenizer
|
|
126
|
-
this._encoder = _globalTokenizer;
|
|
127
|
-
|
|
128
|
-
// Session 历史存储配置
|
|
129
|
-
this._sessionHistoryKey = config.sessionHistoryKey || 'chat_history';
|
|
130
|
-
|
|
131
|
-
// Per-Session 队列管理
|
|
132
|
-
this._sessionQueues = new Map(); // sessionId -> Queue of {message, options, resolve, reject}
|
|
133
|
-
this._processingSessions = new Set(); // sessionId -> processing flag
|
|
134
|
-
|
|
135
|
-
// 初始化队列管理器
|
|
136
|
-
this.queueManager = new ChatQueueManager({
|
|
51
|
+
this._maxSteps = config.maxSteps || 20;
|
|
52
|
+
|
|
53
|
+
// 委托给新模块
|
|
54
|
+
// ChatSession: 会话、消息、队列
|
|
55
|
+
this._chatSession = new ChatSession({
|
|
56
|
+
agent,
|
|
137
57
|
maxConcurrent: config.maxConcurrent || 1,
|
|
138
58
|
retryAttempts: config.retryAttempts || 3,
|
|
139
59
|
retryDelay: config.retryDelay || 1000,
|
|
140
60
|
});
|
|
141
61
|
|
|
142
|
-
//
|
|
143
|
-
this.
|
|
62
|
+
// ToolExecutor: 工具管理
|
|
63
|
+
this._toolExecutor = new ToolExecutor({
|
|
64
|
+
agent,
|
|
65
|
+
framework: agent?.framework,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ContextCompressor: 压缩
|
|
69
|
+
this._contextCompressor = new ContextCompressor({
|
|
70
|
+
agent,
|
|
71
|
+
framework: agent?.framework,
|
|
72
|
+
model: this.model,
|
|
73
|
+
maxContextTokens: config.maxContextTokens,
|
|
74
|
+
keepRecentMessages: config.keepRecentMessages || 20,
|
|
75
|
+
enableSmartCompress: config.enableSmartCompress !== false,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// 上下文限制
|
|
79
|
+
this._maxContextTokens = this._contextCompressor._maxContextTokens;
|
|
80
|
+
|
|
81
|
+
// ChatQueueManager: 队列管理
|
|
82
|
+
this.queueManager = new ChatQueueManager({
|
|
83
|
+
maxConcurrent: config.maxConcurrent || 1,
|
|
84
|
+
retryAttempts: config.retryAttempts || 3,
|
|
85
|
+
retryDelay: config.retryDelay || 1000,
|
|
86
|
+
});
|
|
144
87
|
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// }
|
|
88
|
+
// AI client
|
|
89
|
+
this._aiClient = null;
|
|
90
|
+
this._aiProvider = null;
|
|
149
91
|
|
|
150
|
-
// Session Memory prepareStep
|
|
92
|
+
// Session Memory prepareStep
|
|
151
93
|
this._sessionMemoryPrepareStep = null;
|
|
152
94
|
if (agent?.framework) {
|
|
153
95
|
const memoryPlugin = agent.framework.pluginManager.get('memory');
|
|
@@ -156,12 +98,32 @@ class AgentChatHandler extends EventEmitter {
|
|
|
156
98
|
logger.debug('Session memory prepareStep loaded');
|
|
157
99
|
}
|
|
158
100
|
}
|
|
101
|
+
|
|
102
|
+
// 转发 ChatSession 事件
|
|
103
|
+
this._setupEventForwarding();
|
|
104
|
+
|
|
105
|
+
// 转发 queueManager 事件到 ChatSession(让 SessionScope 能收到 stream:chunk)
|
|
106
|
+
this.queueManager.on('stream:chunk', (data) => {
|
|
107
|
+
this._chatSession.emit('stream:chunk', data);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 转发 message:complete 和 message:error 到 ChatSession(让 SessionScope 能收到)
|
|
111
|
+
this.on('message:complete', (data) => {
|
|
112
|
+
this._chatSession.emit('message:complete', data);
|
|
113
|
+
});
|
|
114
|
+
this.on('message:error', (data) => {
|
|
115
|
+
this._chatSession.emit('message:error', data);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// 设置消息处理器(使用 bind 保持 this 引用)
|
|
119
|
+
this._chatSession.setMessageProcessor(this._processMessage.bind(this));
|
|
159
120
|
}
|
|
160
121
|
|
|
161
122
|
/**
|
|
162
|
-
*
|
|
123
|
+
* 设置事件转发
|
|
124
|
+
* @private
|
|
163
125
|
*/
|
|
164
|
-
|
|
126
|
+
_setupEventForwarding() {
|
|
165
127
|
const events = [
|
|
166
128
|
'queue:added',
|
|
167
129
|
'queue:processing',
|
|
@@ -175,125 +137,97 @@ class AgentChatHandler extends EventEmitter {
|
|
|
175
137
|
];
|
|
176
138
|
|
|
177
139
|
events.forEach((eventName) => {
|
|
178
|
-
this.
|
|
179
|
-
// 转发事件,保持 this 指向 AgentChatHandler
|
|
140
|
+
this._chatSession.on(eventName, (data) => {
|
|
180
141
|
this.emit(eventName, data);
|
|
181
142
|
});
|
|
182
143
|
});
|
|
183
144
|
|
|
184
|
-
//
|
|
185
|
-
this.
|
|
145
|
+
// 转发工具事件
|
|
146
|
+
this._toolExecutor.on('tool:call', (data) => this.emit('tool:call', data));
|
|
147
|
+
this._toolExecutor.on('tool:result', (data) => this.emit('tool:result', data));
|
|
148
|
+
this._toolExecutor.on('tool:error', (data) => this.emit('tool:error', data));
|
|
186
149
|
}
|
|
187
150
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
151
|
+
// ==================== 工具管理(委托给 ToolExecutor) ====================
|
|
152
|
+
|
|
153
|
+
registerTool(tool) {
|
|
154
|
+
this._toolExecutor.registerTool(tool);
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getTool(name) {
|
|
159
|
+
return this._toolExecutor.getTool(name);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
getTools() {
|
|
163
|
+
return this._toolExecutor.getAllTools();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ==================== 会话管理(委托给 ChatSession) ====================
|
|
167
|
+
|
|
194
168
|
createSessionScope(sessionId) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// 创建包装 handler,自动过滤 sessionId
|
|
199
|
-
const wrapHandler = (handler) => {
|
|
200
|
-
return (data) => {
|
|
201
|
-
// 检查 data 中的 sessionId
|
|
202
|
-
if (data && data.sessionId !== sessionId) {
|
|
203
|
-
return; // 不是当前 scope 的 session,跳过
|
|
204
|
-
}
|
|
205
|
-
handler(data);
|
|
206
|
-
};
|
|
207
|
-
};
|
|
169
|
+
return this._chatSession.createSessionScope(sessionId);
|
|
170
|
+
}
|
|
208
171
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* 监听一次性事件
|
|
225
|
-
*/
|
|
226
|
-
once(event, handler) {
|
|
227
|
-
const wrapped = wrapHandler(handler);
|
|
228
|
-
wrappedHandlers.set(handler, wrapped);
|
|
229
|
-
handlers.add({ event, handler, wrapped });
|
|
230
|
-
scope.once(event, wrapped);
|
|
231
|
-
return this;
|
|
232
|
-
},
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* 取消监听(精确移除)
|
|
236
|
-
*/
|
|
237
|
-
off(event, handler) {
|
|
238
|
-
const wrapped = wrappedHandlers.get(handler);
|
|
239
|
-
if (wrapped) {
|
|
240
|
-
scope.off(event, wrapped);
|
|
241
|
-
wrappedHandlers.delete(handler);
|
|
242
|
-
}
|
|
243
|
-
// 从 handlers 中移除
|
|
244
|
-
for (const item of handlers) {
|
|
245
|
-
if (item.event === event && item.handler === handler) {
|
|
246
|
-
handlers.delete(item);
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return this;
|
|
251
|
-
},
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* 移除当前 scope 注册的所有监听器
|
|
255
|
-
*/
|
|
256
|
-
removeAllListeners() {
|
|
257
|
-
for (const { event, wrapped } of handlers) {
|
|
258
|
-
scope.off(event, wrapped);
|
|
259
|
-
}
|
|
260
|
-
handlers.clear();
|
|
261
|
-
return this;
|
|
262
|
-
},
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* 获取当前绑定的 sessionId
|
|
266
|
-
*/
|
|
267
|
-
getSessionId() {
|
|
268
|
-
return sessionId;
|
|
269
|
-
},
|
|
270
|
-
};
|
|
172
|
+
getSessionMessageStore(sessionId) {
|
|
173
|
+
return this._chatSession.getSessionMessageStore(sessionId);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ==================== 压缩(委托给 ContextCompressor) ====================
|
|
177
|
+
|
|
178
|
+
async _compressContext(sessionId, messages, messageStore) {
|
|
179
|
+
return this._contextCompressor.compress(sessionId, messages, messageStore);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
_validateMessagesPairing(messages) {
|
|
183
|
+
return this._contextCompressor.validateMessagesPairing(messages);
|
|
271
184
|
}
|
|
272
185
|
|
|
273
186
|
/**
|
|
274
|
-
*
|
|
187
|
+
* 验证工具调用
|
|
188
|
+
* @private
|
|
275
189
|
*/
|
|
276
|
-
|
|
277
|
-
this.
|
|
278
|
-
|
|
279
|
-
});
|
|
190
|
+
_validateToolCalls(messages) {
|
|
191
|
+
return this._toolExecutor.validateToolCalls(messages);
|
|
192
|
+
}
|
|
280
193
|
|
|
281
|
-
|
|
282
|
-
console.log(
|
|
283
|
-
`[Queue] Processing ${data.requestId}, Active: ${data.activeCount}, Remaining: ${data.remaining}`
|
|
284
|
-
);
|
|
285
|
-
});
|
|
194
|
+
// ==================== AI 调用 ====================
|
|
286
195
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
196
|
+
setAIClient(client) {
|
|
197
|
+
this._aiClient = client;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
setSystemPrompt(prompt) {
|
|
201
|
+
this._systemPrompt = prompt;
|
|
202
|
+
}
|
|
290
203
|
|
|
291
|
-
|
|
292
|
-
|
|
204
|
+
/**
|
|
205
|
+
* 获取 AI Provider 和 Model
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
_createAIProvider() {
|
|
209
|
+
const { createAI, createModel } = require('./provider');
|
|
210
|
+
const aiProvider = createAI({
|
|
211
|
+
provider: this.provider,
|
|
212
|
+
model: this.model,
|
|
213
|
+
apiKey: this.apiKey,
|
|
214
|
+
baseURL: this.baseURL,
|
|
293
215
|
});
|
|
216
|
+
const model = createModel(this.model, aiProvider);
|
|
217
|
+
return { provider: aiProvider, model };
|
|
218
|
+
}
|
|
294
219
|
|
|
295
|
-
|
|
296
|
-
|
|
220
|
+
/**
|
|
221
|
+
* 创建 ToolLoopAgent
|
|
222
|
+
* @private
|
|
223
|
+
*/
|
|
224
|
+
_createToolLoopAgent(model, tools) {
|
|
225
|
+
return new ToolLoopAgent({
|
|
226
|
+
model,
|
|
227
|
+
instructions: this._systemPrompt,
|
|
228
|
+
tools,
|
|
229
|
+
stopWhen: isLoopFinished(),
|
|
230
|
+
prepareStep: this._createPrepareStep(),
|
|
297
231
|
});
|
|
298
232
|
}
|
|
299
233
|
|
|
@@ -351,7 +285,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
351
285
|
// 监听流式数据
|
|
352
286
|
const chunkHandler = (data) => {
|
|
353
287
|
if (data.requestId === requestId) {
|
|
354
|
-
//chunkQueue.push(data.chunk);
|
|
355
288
|
if (resolveNext) {
|
|
356
289
|
resolveNext();
|
|
357
290
|
resolveNext = null;
|
|
@@ -457,1012 +390,222 @@ class AgentChatHandler extends EventEmitter {
|
|
|
457
390
|
}
|
|
458
391
|
|
|
459
392
|
/**
|
|
460
|
-
*
|
|
461
|
-
*/
|
|
462
|
-
getQueueStatus() {
|
|
463
|
-
return this.queueManager.getStatus();
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
/**
|
|
467
|
-
* 清空队列
|
|
393
|
+
* 获取默认 sessionId
|
|
468
394
|
*/
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
this.emit('queue:cleared-manually', { count });
|
|
472
|
-
return count;
|
|
395
|
+
getDefaultSessionId() {
|
|
396
|
+
return 'default';
|
|
473
397
|
}
|
|
474
398
|
|
|
475
399
|
/**
|
|
476
|
-
* 生成请求ID
|
|
400
|
+
* 生成请求 ID
|
|
477
401
|
*/
|
|
478
402
|
generateRequestId() {
|
|
479
|
-
return `req_${Date.now()}_${Math.random().toString(36).
|
|
403
|
+
return `req_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
480
404
|
}
|
|
481
405
|
|
|
482
406
|
/**
|
|
483
|
-
*
|
|
407
|
+
* 聊天(直接非流式,使用 agent.generate)
|
|
484
408
|
*/
|
|
485
|
-
|
|
486
|
-
|
|
409
|
+
async chat(message, options = {}) {
|
|
410
|
+
const sessionId = options.sessionId || this.getDefaultSessionId();
|
|
411
|
+
const result = await this._processMessage(sessionId, message, options);
|
|
412
|
+
return { message: result.text, ...result };
|
|
487
413
|
}
|
|
488
414
|
|
|
489
415
|
/**
|
|
490
|
-
*
|
|
491
|
-
* @param {string} sessionId - 会话 ID
|
|
492
|
-
* @returns {Object} 消息存储对象
|
|
493
|
-
* @private
|
|
416
|
+
* 流式聊天
|
|
494
417
|
*/
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return {
|
|
499
|
-
messages: [],
|
|
500
|
-
historyLoaded: false,
|
|
501
|
-
compressionState: {},
|
|
502
|
-
lastUsage: null, // API 返回的真实 usage
|
|
503
|
-
save: () => {},
|
|
504
|
-
};
|
|
505
|
-
}
|
|
418
|
+
async *chatStream(message, options = {}) {
|
|
419
|
+
const sessionId = options.sessionId || this.getDefaultSessionId();
|
|
420
|
+
const framework = this.agent.framework;
|
|
506
421
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
const savedMessages = this._loadHistoryFromSession(sessionId);
|
|
510
|
-
this._sessionMessageStores.set(sessionId, {
|
|
511
|
-
sessionId, // 保存 sessionId 引用
|
|
512
|
-
messages: savedMessages,
|
|
513
|
-
historyLoaded: true, // 已加载
|
|
514
|
-
compressionState: {
|
|
515
|
-
lastCompressedAt: null,
|
|
516
|
-
lastTokenCount: 0,
|
|
517
|
-
count: 0,
|
|
518
|
-
},
|
|
519
|
-
lastUsage: null, // API 返回的真实 usage
|
|
520
|
-
save: () => {
|
|
521
|
-
// 简洁的保存方法
|
|
522
|
-
if (this.agent?.framework) {
|
|
523
|
-
const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
|
|
524
|
-
if (sessionCtx) {
|
|
525
|
-
sessionCtx.replaceMessages(this._sessionMessageStores.get(sessionId).messages);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
return this._sessionMessageStores.get(sessionId);
|
|
532
|
-
}
|
|
422
|
+
try {
|
|
423
|
+
const { messageStore, messages } = await this._prepareSession(message, sessionId);
|
|
533
424
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
* @returns {Array} 消息数组
|
|
538
|
-
* @private
|
|
539
|
-
*/
|
|
540
|
-
_loadHistoryFromSession(sessionId) {
|
|
541
|
-
if (!sessionId || !this.agent?.framework) return [];
|
|
425
|
+
if (!this._aiClient) {
|
|
426
|
+
throw new Error('AI client not configured.');
|
|
427
|
+
}
|
|
542
428
|
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
429
|
+
const tools = this._getAITools(aiTool);
|
|
430
|
+
const agent = new ToolLoopAgent({
|
|
431
|
+
model: this._aiClient,
|
|
432
|
+
instructions: this._systemPrompt,
|
|
433
|
+
tools,
|
|
434
|
+
stopWhen: isLoopFinished(),
|
|
435
|
+
prepareStep: this._createPrepareStep(),
|
|
436
|
+
});
|
|
546
437
|
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
// 忽略加载错误
|
|
551
|
-
}
|
|
552
|
-
return [];
|
|
553
|
-
}
|
|
438
|
+
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
439
|
+
return agent.stream({ messages, ...this.providerOptions });
|
|
440
|
+
});
|
|
554
441
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
* @param {Array} messages - 消息数组
|
|
559
|
-
* @private
|
|
560
|
-
*/
|
|
561
|
-
_saveHistoryToSession(sessionId, messages) {
|
|
562
|
-
if (!sessionId || !this.agent?.framework || !messages) return;
|
|
442
|
+
const stream = result.fullStream;
|
|
443
|
+
let fullText = '';
|
|
444
|
+
const iterator = stream[Symbol.asyncIterator] ? stream : stream.fullStream;
|
|
563
445
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
446
|
+
for await (const part of iterator || stream) {
|
|
447
|
+
if (part.type === 'text-delta') {
|
|
448
|
+
const text = part.text || part.textDelta || '';
|
|
449
|
+
fullText += text;
|
|
450
|
+
yield { type: 'text', text };
|
|
451
|
+
} else if (part.type === 'reasoning') {
|
|
452
|
+
yield { type: 'thinking', text: part.text };
|
|
453
|
+
} else if (part.type === 'tool-call') {
|
|
454
|
+
yield { type: 'tool-call', toolName: part.toolName, input: part.input };
|
|
455
|
+
} else if (part.type === 'tool-result') {
|
|
456
|
+
yield { type: 'tool-result', toolName: part.toolName, result: part.output };
|
|
457
|
+
} else if (part.type === 'error') {
|
|
458
|
+
yield { type: 'error', error: part.error };
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const finishMessages = (await result.response).messages;
|
|
463
|
+
messages.push(...finishMessages);
|
|
464
|
+
const usage = await result.totalUsage;
|
|
465
|
+
if (usage) {
|
|
466
|
+
this._updateMessageStoreUsage(messageStore, usage);
|
|
467
|
+
}
|
|
567
468
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
sessionCtx.replaceMessages(cleanedMessages);
|
|
469
|
+
const userMsg = messages[messages.length - finishMessages.length - 1];
|
|
470
|
+
this.emit('message', { content: fullText, sessionId: sessionId, userMessage: userMsg });
|
|
571
471
|
} catch (err) {
|
|
572
|
-
//
|
|
573
|
-
|
|
574
|
-
|
|
472
|
+
// 使用 AI SDK 的错误类型判断
|
|
473
|
+
// RetryError: SDK 内部抛出的重试失败错误,name 可能是 'AI_RetryError' 或 'RetryError'
|
|
474
|
+
const errName = err?.name || '';
|
|
475
|
+
const isRetryError =
|
|
476
|
+
err instanceof RetryError ||
|
|
477
|
+
errName === 'AI_RetryError' ||
|
|
478
|
+
errName === 'RetryError' ||
|
|
479
|
+
err.reason === 'maxRetriesExceeded';
|
|
480
|
+
|
|
481
|
+
// APICallError: 可重试的 API 调用错误
|
|
482
|
+
const isAPICallError = err instanceof APICallError;
|
|
483
|
+
const isRetryable = isAPICallError && err.isRetryable === true;
|
|
575
484
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
* @param {Object} options - 选项
|
|
581
|
-
* @returns {Promise}
|
|
582
|
-
*/
|
|
583
|
-
enqueue(sessionId, message, options = {}) {
|
|
584
|
-
if (!this._sessionQueues.has(sessionId)) {
|
|
585
|
-
this._sessionQueues.set(sessionId, []);
|
|
586
|
-
}
|
|
587
|
-
const queue = this._sessionQueues.get(sessionId);
|
|
485
|
+
const isTransientError =
|
|
486
|
+
isRetryError ||
|
|
487
|
+
isRetryable ||
|
|
488
|
+
(err.message && err.message.includes('Invalid JSON response'));
|
|
588
489
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
490
|
+
if (isTransientError) {
|
|
491
|
+
yield { type: 'error', error: 'AI 服务暂时不可用,请稍后重试。' };
|
|
492
|
+
} else {
|
|
493
|
+
logger.error(`[AgentChat] Stream error: ${err.message}`);
|
|
494
|
+
this.emit('error', { error: err.message });
|
|
495
|
+
yield { type: 'error', error: err.message };
|
|
496
|
+
}
|
|
497
|
+
} finally {
|
|
498
|
+
const messageStore = this._getSessionMessageStore(sessionId);
|
|
499
|
+
messageStore.save();
|
|
594
500
|
}
|
|
595
|
-
|
|
596
|
-
// 直接处理
|
|
597
|
-
return this._processWithSession(sessionId, message, options);
|
|
598
501
|
}
|
|
599
502
|
|
|
600
503
|
/**
|
|
601
|
-
*
|
|
602
|
-
* @param {string} sessionId - 会话 ID
|
|
603
|
-
* @param {string|Object} message - 消息
|
|
604
|
-
* @param {Object} options - 选项
|
|
605
|
-
* @returns {Promise}
|
|
606
|
-
* @private
|
|
504
|
+
* 清除历史
|
|
607
505
|
*/
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
this._processingSessions.delete(sessionId);
|
|
614
|
-
// 处理队列中的下一条
|
|
615
|
-
setImmediate(() => this._drainQueue(sessionId));
|
|
506
|
+
clearHistory(sessionId) {
|
|
507
|
+
if (sessionId) {
|
|
508
|
+
const store = this._chatSession.getSessionMessageStore(sessionId);
|
|
509
|
+
store.messages = [];
|
|
510
|
+
store.historyLoaded = false;
|
|
616
511
|
}
|
|
617
512
|
}
|
|
618
513
|
|
|
619
514
|
/**
|
|
620
|
-
*
|
|
621
|
-
* @param {string} sessionId - 会话 ID
|
|
622
|
-
* @private
|
|
515
|
+
* 销毁
|
|
623
516
|
*/
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
this._processWithSession(sessionId, item.message, item.options)
|
|
630
|
-
.then(item.resolve)
|
|
631
|
-
.catch(item.reject);
|
|
517
|
+
destroy() {
|
|
518
|
+
if (this._chatSession) {
|
|
519
|
+
this._chatSession.removeAllListeners();
|
|
520
|
+
}
|
|
521
|
+
this.removeAllListeners();
|
|
632
522
|
}
|
|
633
523
|
|
|
634
524
|
/**
|
|
635
|
-
*
|
|
525
|
+
* 入队消息
|
|
636
526
|
*/
|
|
637
|
-
|
|
638
|
-
|
|
527
|
+
enqueue(sessionId, message, options = {}) {
|
|
528
|
+
return this._chatSession.enqueue(sessionId, message, options);
|
|
639
529
|
}
|
|
640
530
|
|
|
641
531
|
/**
|
|
642
|
-
*
|
|
643
|
-
* @param {string} text
|
|
644
|
-
* @param {number} [bytesPerToken=4] - bytes per token 比率
|
|
645
|
-
* @returns {number}
|
|
532
|
+
* 处理消息(队列回调)
|
|
646
533
|
* @private
|
|
647
534
|
*/
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
535
|
+
async _processMessage(sessionId, message, options) {
|
|
536
|
+
const framework = this.agent.framework;
|
|
537
|
+
|
|
651
538
|
try {
|
|
652
|
-
|
|
539
|
+
const { messageStore, messages } = await this._prepareSession(message, sessionId);
|
|
540
|
+
|
|
541
|
+
if (!this._aiClient) {
|
|
542
|
+
throw new Error('AI client not configured.');
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const tools = this._getAITools(aiTool);
|
|
546
|
+
const agent = new ToolLoopAgent({
|
|
547
|
+
model: this._aiClient,
|
|
548
|
+
instructions: this._systemPrompt,
|
|
549
|
+
tools,
|
|
550
|
+
stopWhen: isLoopFinished(),
|
|
551
|
+
prepareStep: this._createPrepareStep(),
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
555
|
+
return agent.generate({ messages, ...this.providerOptions });
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
if (result.usage) {
|
|
559
|
+
this._updateMessageStoreUsage(messageStore, result.usage);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
messages.push(...result.response.messages);
|
|
563
|
+
const userMsg = messages[messages.length - result.response.messages.length - 1];
|
|
564
|
+
this.emit('message', { content: result.text, sessionId: sessionId, userMessage: userMsg });
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
success: true,
|
|
568
|
+
message: cleanResponse(result.text || ''),
|
|
569
|
+
stepCount: result.stepCount || 1,
|
|
570
|
+
};
|
|
653
571
|
} catch (err) {
|
|
654
|
-
|
|
655
|
-
|
|
572
|
+
console.log(err);
|
|
573
|
+
const errorMsg = err.message || String(err);
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
success: false,
|
|
577
|
+
message: 'AI 服务暂时不可用,请稍后重试。',
|
|
578
|
+
error: errorMsg,
|
|
579
|
+
stepCount: 0,
|
|
580
|
+
};
|
|
581
|
+
} finally {
|
|
582
|
+
const messageStore = this._getSessionMessageStore(sessionId);
|
|
583
|
+
messageStore.save();
|
|
656
584
|
}
|
|
657
585
|
}
|
|
658
586
|
|
|
659
587
|
/**
|
|
660
|
-
*
|
|
661
|
-
* 参考 Claude Code 的分块计数逻辑
|
|
662
|
-
* @param {Array} messages
|
|
663
|
-
* @returns {number}
|
|
588
|
+
* 获取 Session Message Store
|
|
664
589
|
* @private
|
|
665
590
|
*/
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
for (const msg of messages) {
|
|
669
|
-
if (!msg) continue;
|
|
670
|
-
total += this._countMessageTokens(msg);
|
|
671
|
-
}
|
|
672
|
-
return total;
|
|
591
|
+
_getSessionMessageStore(sessionId) {
|
|
592
|
+
return this._chatSession.getSessionMessageStore(sessionId);
|
|
673
593
|
}
|
|
674
594
|
|
|
675
595
|
/**
|
|
676
|
-
*
|
|
677
|
-
* @param {Object} msg - 消息对象
|
|
678
|
-
* @returns {number}
|
|
596
|
+
* 准备会话消息
|
|
679
597
|
* @private
|
|
680
598
|
*/
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (typeof msg.content === 'string') {
|
|
686
|
-
total += this._countTokens(msg.content);
|
|
687
|
-
} else if (Array.isArray(msg.content)) {
|
|
688
|
-
for (const part of msg.content) {
|
|
689
|
-
total += this._countContentBlockTokens(part);
|
|
690
|
-
}
|
|
691
|
-
} else if (msg.content && typeof msg.content === 'object') {
|
|
692
|
-
// 处理工具结果等对象类型
|
|
693
|
-
try {
|
|
694
|
-
const str = JSON.stringify(msg.content);
|
|
695
|
-
total += this._countTokens(str, 2); // JSON 用更密集的比率
|
|
696
|
-
} catch (e) {
|
|
697
|
-
// 无法序列化的内容,忽略
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
total += 4; // 结尾标记
|
|
701
|
-
return total;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
/**
|
|
705
|
-
* 计算单个内容块的 token 数
|
|
706
|
-
* 参考 Claude Code 的实现
|
|
707
|
-
* 兼容 AI SDK 6.x 标准格式
|
|
708
|
-
* @param {Object} block - 内容块
|
|
709
|
-
* @returns {number}
|
|
710
|
-
* @private
|
|
711
|
-
*/
|
|
712
|
-
_countContentBlockTokens(block) {
|
|
713
|
-
if (!block || typeof block !== 'object') {
|
|
714
|
-
return 0;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const tokenizer = this._encoder;
|
|
718
|
-
|
|
719
|
-
// 文本块
|
|
720
|
-
if (block.type === 'text') {
|
|
721
|
-
return tokenizer.encode(block.text || '');
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
// 工具调用块 - 兼容 tool-call 和 tool-use 两种类型
|
|
725
|
-
if (block.type === 'tool-call' || block.type === 'tool-use' || block.type === 'tool_call') {
|
|
726
|
-
let count = 0;
|
|
727
|
-
if (block.name || block.toolName) {
|
|
728
|
-
count += tokenizer.encode(String(block.name || block.toolName || ''));
|
|
729
|
-
}
|
|
730
|
-
if (block.input) {
|
|
731
|
-
try {
|
|
732
|
-
count += tokenizer.encode(JSON.stringify(block.input), 2);
|
|
733
|
-
} catch (e) {}
|
|
734
|
-
}
|
|
735
|
-
return count;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// 工具结果块 - 兼容 tool-result 和 tool_result 两种类型
|
|
739
|
-
if (block.type === 'tool-result' || block.type === 'tool_result') {
|
|
740
|
-
let count = 0;
|
|
741
|
-
if (block.content) {
|
|
742
|
-
if (typeof block.content === 'string') {
|
|
743
|
-
count += tokenizer.encode(block.content);
|
|
744
|
-
} else if (Array.isArray(block.content)) {
|
|
745
|
-
for (const c of block.content) {
|
|
746
|
-
if (c && c.type === 'text') {
|
|
747
|
-
count += tokenizer.encode(c.text || '');
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
if (block.toolUseId || block.tool_call_id) {
|
|
753
|
-
count += 10; // 工具结果标识
|
|
754
|
-
}
|
|
755
|
-
return count;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// 输入块 (tool_input)
|
|
759
|
-
if (block.type === 'input') {
|
|
760
|
-
try {
|
|
761
|
-
return tokenizer.encode(JSON.stringify(block), 2);
|
|
762
|
-
} catch (e) {
|
|
763
|
-
return 0;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// 其他类型,尝试 JSON 序列化
|
|
768
|
-
try {
|
|
769
|
-
return tokenizer.encode(JSON.stringify(block), 2);
|
|
770
|
-
} catch (e) {
|
|
771
|
-
return 0;
|
|
772
|
-
}
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
/**
|
|
776
|
-
* 更新 messageStore 的 lastUsage
|
|
777
|
-
* @param {Object} messageStore
|
|
778
|
-
* @param {Object} usage - API 返回的 usage
|
|
779
|
-
* @private
|
|
780
|
-
*/
|
|
781
|
-
_updateMessageStoreUsage(messageStore, usage) {
|
|
782
|
-
if (messageStore && usage) {
|
|
783
|
-
messageStore.lastUsage = {
|
|
784
|
-
inputTokens: usage.inputTokens,
|
|
785
|
-
outputTokens: usage.outputTokens,
|
|
786
|
-
inputTokenDetails: usage.inputTokenDetails || {},
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
/**
|
|
792
|
-
* 计算工具定义的 token 数(估算)
|
|
793
|
-
* @returns {number}
|
|
794
|
-
* @private
|
|
795
|
-
*/
|
|
796
|
-
_countToolsTokens() {
|
|
797
|
-
let total = 0;
|
|
798
|
-
for (const toolDef of this._tools.values()) {
|
|
799
|
-
// 工具名 + 描述
|
|
800
|
-
total += this._countTokens(String(toolDef.name || ''));
|
|
801
|
-
total += this._countTokens(String(toolDef.description || ''));
|
|
802
|
-
|
|
803
|
-
// 工具参数 schema
|
|
804
|
-
if (toolDef.inputSchema) {
|
|
805
|
-
const schemaStr =
|
|
806
|
-
typeof toolDef.inputSchema === 'string'
|
|
807
|
-
? toolDef.inputSchema
|
|
808
|
-
: JSON.stringify(toolDef.inputSchema);
|
|
809
|
-
total += this._countTokens(schemaStr);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
return total;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
/**
|
|
816
|
-
* 压缩上下文消息(智能摘要模式,支持超时控制)
|
|
817
|
-
* 策略:
|
|
818
|
-
* 1. 如果启用了智能摘要且有 AI 客户端,对早期消息进行 AI 总结
|
|
819
|
-
* 2. 否则使用简单裁剪 + 标记
|
|
820
|
-
* 3. 支持超时控制,防止压缩阻塞太久
|
|
821
|
-
* @param {string} sessionId - 会话 ID(用于压缩后同步)
|
|
822
|
-
* @param {Array} messages - 消息数组引用
|
|
823
|
-
* @param {Object} messageStore - 消息存储对象
|
|
824
|
-
* @private
|
|
825
|
-
*/
|
|
826
|
-
async _compressContext(sessionId, messages, messageStore) {
|
|
827
|
-
// 如果已经有压缩在进行,返回现有的 Promise
|
|
828
|
-
if (this._compressionInProgress && this._compressionPromise) {
|
|
829
|
-
logger.debug('Compression already in progress, waiting for it...');
|
|
830
|
-
return this._compressionPromise;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
if (messages.length <= this._keepRecentMessages) {
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
this._compressionInProgress = true;
|
|
838
|
-
|
|
839
|
-
// 创建压缩 Promise,带超时控制
|
|
840
|
-
this._compressionPromise = this._executeCompressionWithTimeout(
|
|
841
|
-
sessionId,
|
|
842
|
-
messages,
|
|
843
|
-
messageStore
|
|
844
|
-
).finally(() => {
|
|
845
|
-
this._compressionInProgress = false;
|
|
846
|
-
this._compressionPromise = null;
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
return this._compressionPromise;
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
/**
|
|
853
|
-
* 执行压缩(带超时)
|
|
854
|
-
* @param {string} sessionId - 会话 ID
|
|
855
|
-
* @param {Array} messages - 消息数组引用
|
|
856
|
-
* @param {Object} messageStore - 消息存储对象
|
|
857
|
-
* @private
|
|
858
|
-
*/
|
|
859
|
-
async _executeCompressionWithTimeout(sessionId, messages, messageStore) {
|
|
860
|
-
try {
|
|
861
|
-
return await Promise.race([
|
|
862
|
-
this._doCompress(sessionId, messages, messageStore),
|
|
863
|
-
this._createTimeoutPromise(),
|
|
864
|
-
]);
|
|
865
|
-
} catch (err) {
|
|
866
|
-
logger.warn('Compression failed:', err.message);
|
|
867
|
-
// 压缩失败时使用简单的截断策略
|
|
868
|
-
this._simpleCompress(sessionId, messages, messageStore);
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* 创建超时 Promise
|
|
874
|
-
* @private
|
|
875
|
-
*/
|
|
876
|
-
_createTimeoutPromise() {
|
|
877
|
-
return new Promise((_, reject) => {
|
|
878
|
-
setTimeout(() => {
|
|
879
|
-
reject(new Error('Compression timeout'));
|
|
880
|
-
}, COMPRESSION_TIMEOUT);
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
async compressToolResults(messages) {
|
|
885
|
-
try {
|
|
886
|
-
const recentMessages = prepareMessagesForAPI(messages.slice(-10));
|
|
887
|
-
const messagesToSummarize = messages.slice(0, -10);
|
|
888
|
-
let summaryContent = '';
|
|
889
|
-
const summaryText = await this._summarizeMessages(messagesToSummarize);
|
|
890
|
-
summaryContent = `[早期对话摘要]: ${summaryText}`;
|
|
891
|
-
const summary = {
|
|
892
|
-
role: 'assistant',
|
|
893
|
-
content: summaryContent,
|
|
894
|
-
};
|
|
895
|
-
logger.info(`AI summary generated (${summaryText.length} chars)`);
|
|
896
|
-
return [summary, ...recentMessages];
|
|
897
|
-
} catch (err) {
|
|
898
|
-
logger.warn('Tool result compression failed:', err.message);
|
|
899
|
-
return prepareMessagesForAPI(messages);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
/**
|
|
904
|
-
* 执行实际压缩逻辑
|
|
905
|
-
* @param {string} sessionId - 会话 ID
|
|
906
|
-
* @param {Array} messages - 消息数组引用
|
|
907
|
-
* @param {Object} messageStore - 消息存储对象
|
|
908
|
-
* @private
|
|
909
|
-
*/
|
|
910
|
-
async _doCompress(sessionId, messages, messageStore) {
|
|
911
|
-
const systemMessages = messages.filter((m) => m.role === 'system');
|
|
912
|
-
const otherMessages = messages.filter((m) => m.role !== 'system');
|
|
913
|
-
|
|
914
|
-
// 保留最近的 N 条非系统消息
|
|
915
|
-
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
916
|
-
const messagesToSummarize = otherMessages.slice(0, -this._keepRecentMessages);
|
|
917
|
-
|
|
918
|
-
const compressedCount = messagesToSummarize.length;
|
|
919
|
-
let summaryContent = '';
|
|
920
|
-
|
|
921
|
-
// 尝试使用 AI 总结
|
|
922
|
-
if (this._enableSmartCompress && this._aiClient) {
|
|
923
|
-
try {
|
|
924
|
-
const summaryText = await this._summarizeMessages(messagesToSummarize);
|
|
925
|
-
summaryContent = `[早期对话摘要]: ${summaryText}`;
|
|
926
|
-
logger.info(`AI summary generated (${summaryText.length} chars)`);
|
|
927
|
-
} catch (err) {
|
|
928
|
-
logger.warn('AI summary failed, using simple compression:', err.message);
|
|
929
|
-
summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
|
|
930
|
-
}
|
|
931
|
-
} else {
|
|
932
|
-
summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
const summary = {
|
|
936
|
-
role: 'assistant',
|
|
937
|
-
content: summaryContent,
|
|
938
|
-
};
|
|
939
|
-
|
|
940
|
-
// 构建保留的消息,确保 tool call 和 tool result 配对不被分离
|
|
941
|
-
const assistantToolCalls = new Map(); // toolCallId -> assistant message index
|
|
942
|
-
const toolResults = new Map(); // toolCallId -> tool result indices
|
|
943
|
-
|
|
944
|
-
// 工具类型检查辅助函数(兼容 AI SDK 6.x)
|
|
945
|
-
const isToolCall = (item) =>
|
|
946
|
-
item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
|
|
947
|
-
const isToolResult = (item) =>
|
|
948
|
-
item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
|
|
949
|
-
|
|
950
|
-
// 第一遍:找出所有 tool call 和它们的 results
|
|
951
|
-
for (let i = 0; i < recentMessages.length; i++) {
|
|
952
|
-
const msg = recentMessages[i];
|
|
953
|
-
if (msg.role === 'assistant' && msg.content) {
|
|
954
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
955
|
-
for (const item of content) {
|
|
956
|
-
if (isToolCall(item)) {
|
|
957
|
-
assistantToolCalls.set(item.toolCallId, i);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
962
|
-
// 遍历 tool message 中的所有 tool-results
|
|
963
|
-
for (const item of msg.content) {
|
|
964
|
-
if (isToolResult(item)) {
|
|
965
|
-
if (!toolResults.has(item.toolCallId)) {
|
|
966
|
-
toolResults.set(item.toolCallId, []);
|
|
967
|
-
}
|
|
968
|
-
toolResults.get(item.toolCallId).push(i);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
|
|
974
|
-
// 第二遍:找出哪些 tool call 没有对应的 result(需要保留)
|
|
975
|
-
const orphanToolCalls = new Set();
|
|
976
|
-
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
977
|
-
if (!toolResults.has(toolCallId)) {
|
|
978
|
-
orphanToolCalls.add(toolCallId);
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// 过滤时保留:有 result 的 tool call,以及没有 result 的 tool call(但要附带其 result)
|
|
983
|
-
// 第一遍:先确定哪些 assistant 消息要保留
|
|
984
|
-
const assistantIndicesToKeep = new Set();
|
|
985
|
-
for (let i = 0; i < recentMessages.length; i++) {
|
|
986
|
-
const msg = recentMessages[i];
|
|
987
|
-
if (msg.role === 'assistant') {
|
|
988
|
-
// 检查这个 assistant 消息是否有 tool call
|
|
989
|
-
let hasToolCall = false;
|
|
990
|
-
if (msg.content) {
|
|
991
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
992
|
-
for (const item of content) {
|
|
993
|
-
if (isToolCall(item)) {
|
|
994
|
-
hasToolCall = true;
|
|
995
|
-
break;
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
if (hasToolCall) {
|
|
1000
|
-
assistantIndicesToKeep.add(i);
|
|
1001
|
-
} else if (i >= recentMessages.length - 3) {
|
|
1002
|
-
// 保留最近几条 assistant 消息(它们可能包含重要上下文)
|
|
1003
|
-
assistantIndicesToKeep.add(i);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
// 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
|
|
1009
|
-
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
1010
|
-
if (!toolResults.has(toolCallId)) {
|
|
1011
|
-
assistantIndicesToKeep.add(assistantIdx);
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
// 第二遍:基于 assistant 的保留情况,确定哪些 tool result 要保留
|
|
1016
|
-
const indicesToKeep = new Set();
|
|
1017
|
-
for (let i = 0; i < recentMessages.length; i++) {
|
|
1018
|
-
const msg = recentMessages[i];
|
|
1019
|
-
if (msg.role === 'tool') {
|
|
1020
|
-
// 检查这个 tool result 是否对应一个被保留的 assistant tool_call
|
|
1021
|
-
// 如果对应的 assistant 消息被删除了,这个 tool result 也应该被删除(否则会报 orphaned error)
|
|
1022
|
-
let hasPairedAssistant = false;
|
|
1023
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
1024
|
-
for (const item of content) {
|
|
1025
|
-
if (isToolResult(item)) {
|
|
1026
|
-
// 检查这个 toolCallId 对应的 assistant 消息是否被保留
|
|
1027
|
-
const assistantIdx = assistantToolCalls.get(item.toolCallId);
|
|
1028
|
-
if (assistantIdx !== undefined && assistantIndicesToKeep.has(assistantIdx)) {
|
|
1029
|
-
hasPairedAssistant = true;
|
|
1030
|
-
break;
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
// 只有有配对的 assistant 消息被保留时,才保留这个 tool result
|
|
1035
|
-
if (hasPairedAssistant) {
|
|
1036
|
-
indicesToKeep.add(i);
|
|
1037
|
-
} else {
|
|
1038
|
-
logger.debug(`Dropping orphaned tool result at index ${i}`);
|
|
1039
|
-
}
|
|
1040
|
-
} else if (msg.role === 'assistant') {
|
|
1041
|
-
if (assistantIndicesToKeep.has(i)) {
|
|
1042
|
-
indicesToKeep.add(i);
|
|
1043
|
-
}
|
|
1044
|
-
} else {
|
|
1045
|
-
// user, system 等角色默认保留
|
|
1046
|
-
indicesToKeep.add(i);
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// 按索引排序并重建消息数组
|
|
1051
|
-
const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
|
|
1052
|
-
const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);
|
|
1053
|
-
|
|
1054
|
-
// 直接修改传入的 messages 数组
|
|
1055
|
-
messages.length = 0;
|
|
1056
|
-
messages.push(...systemMessages, summary, ...filteredRecentMessages);
|
|
1057
|
-
|
|
1058
|
-
// 更新压缩状态
|
|
1059
|
-
this._compressionCount++;
|
|
1060
|
-
if (messageStore.compressionState) {
|
|
1061
|
-
messageStore.compressionState.count++;
|
|
1062
|
-
messageStore.compressionState.lastCompressedAt = Date.now();
|
|
1063
|
-
messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
const totalTokens = this._countMessagesTokens(messages);
|
|
1067
|
-
logger.info(
|
|
1068
|
-
`Context compressed (${this._compressionCount} times). Messages: ${messages.length}, Est. tokens: ${totalTokens}`
|
|
1069
|
-
);
|
|
1070
|
-
|
|
1071
|
-
// 压缩后立即同步到 session
|
|
1072
|
-
if (sessionId) {
|
|
1073
|
-
this._saveHistoryToSession(sessionId, messages);
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
/**
|
|
1078
|
-
* 简单压缩(当 AI 压缩失败时使用)
|
|
1079
|
-
* @param {string} sessionId - 会话 ID
|
|
1080
|
-
* @param {Array} messages - 消息数组引用
|
|
1081
|
-
* @param {Object} messageStore - 消息存储对象
|
|
1082
|
-
* @private
|
|
1083
|
-
*/
|
|
1084
|
-
_simpleCompress(sessionId, messages, messageStore) {
|
|
1085
|
-
const systemMessages = messages.filter((m) => m.role === 'system');
|
|
1086
|
-
const otherMessages = messages.filter((m) => m.role !== 'system');
|
|
1087
|
-
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
1088
|
-
const compressedCount = otherMessages.length - this._keepRecentMessages;
|
|
1089
|
-
|
|
1090
|
-
const summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
|
|
1091
|
-
|
|
1092
|
-
const summary = {
|
|
1093
|
-
role: 'assistant',
|
|
1094
|
-
content: summaryContent,
|
|
1095
|
-
};
|
|
1096
|
-
|
|
1097
|
-
// 工具类型检查辅助函数(兼容 AI SDK 6.x)
|
|
1098
|
-
const isToolCall = (item) =>
|
|
1099
|
-
item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
|
|
1100
|
-
const isToolResult = (item) =>
|
|
1101
|
-
item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
|
|
1102
|
-
|
|
1103
|
-
// 构建保留的消息,确保 tool call 和 tool result 配对不被分离
|
|
1104
|
-
const assistantToolCalls = new Map(); // toolCallId -> assistant message index
|
|
1105
|
-
const toolResults = new Map(); // toolCallId -> tool result indices
|
|
1106
|
-
|
|
1107
|
-
// 第一遍:找出所有 tool call 和它们的 results
|
|
1108
|
-
for (let i = 0; i < recentMessages.length; i++) {
|
|
1109
|
-
const msg = recentMessages[i];
|
|
1110
|
-
if (msg.role === 'assistant' && msg.content) {
|
|
1111
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
1112
|
-
for (const item of content) {
|
|
1113
|
-
if (isToolCall(item)) {
|
|
1114
|
-
assistantToolCalls.set(item.toolCallId, i);
|
|
1115
|
-
}
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1119
|
-
// 遍历 tool message 中的所有 tool-results
|
|
1120
|
-
for (const item of msg.content) {
|
|
1121
|
-
if (isToolResult(item)) {
|
|
1122
|
-
if (!toolResults.has(item.toolCallId)) {
|
|
1123
|
-
toolResults.set(item.toolCallId, []);
|
|
1124
|
-
}
|
|
1125
|
-
toolResults.get(item.toolCallId).push(i);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
// 第二遍:找出哪些 tool call 没有对应的 result(需要保留)
|
|
1132
|
-
const orphanToolCalls = new Set();
|
|
1133
|
-
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
1134
|
-
if (!toolResults.has(toolCallId)) {
|
|
1135
|
-
orphanToolCalls.add(toolCallId);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
// 过滤时保留:有 result 的 tool call,以及没有 result 的 tool call
|
|
1140
|
-
const indicesToKeep = new Set();
|
|
1141
|
-
for (let i = 0; i < recentMessages.length; i++) {
|
|
1142
|
-
const msg = recentMessages[i];
|
|
1143
|
-
if (msg.role === 'tool') {
|
|
1144
|
-
// 保留所有 tool result
|
|
1145
|
-
indicesToKeep.add(i);
|
|
1146
|
-
} else if (msg.role === 'assistant') {
|
|
1147
|
-
let hasToolCall = false;
|
|
1148
|
-
if (msg.content) {
|
|
1149
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
1150
|
-
for (const item of content) {
|
|
1151
|
-
if (isToolCall(item)) {
|
|
1152
|
-
hasToolCall = true;
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
if (hasToolCall) {
|
|
1158
|
-
indicesToKeep.add(i);
|
|
1159
|
-
} else if (i >= recentMessages.length - 3) {
|
|
1160
|
-
// 保留最近几条 assistant 消息
|
|
1161
|
-
indicesToKeep.add(i);
|
|
1162
|
-
}
|
|
1163
|
-
} else {
|
|
1164
|
-
// user, system 等角色默认保留
|
|
1165
|
-
indicesToKeep.add(i);
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
|
|
1170
|
-
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
1171
|
-
if (!toolResults.has(toolCallId)) {
|
|
1172
|
-
indicesToKeep.add(assistantIdx);
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
// 按索引排序并重建消息数组
|
|
1177
|
-
const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
|
|
1178
|
-
const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);
|
|
1179
|
-
|
|
1180
|
-
// 直接修改传入的 messages 数组
|
|
1181
|
-
messages.length = 0;
|
|
1182
|
-
messages.push(...systemMessages, summary, ...filteredRecentMessages);
|
|
1183
|
-
|
|
1184
|
-
// 更新压缩状态
|
|
1185
|
-
this._compressionCount++;
|
|
1186
|
-
if (messageStore.compressionState) {
|
|
1187
|
-
messageStore.compressionState.count++;
|
|
1188
|
-
messageStore.compressionState.lastCompressedAt = Date.now();
|
|
1189
|
-
messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
logger.info(
|
|
1193
|
-
`Context simple compressed (${this._compressionCount} times). Messages: ${messages.length}`
|
|
1194
|
-
);
|
|
1195
|
-
|
|
1196
|
-
// 压缩后立即同步到 session
|
|
1197
|
-
if (sessionId) {
|
|
1198
|
-
this._saveHistoryToSession(sessionId, messages);
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
/**
|
|
1203
|
-
* 使用 AI 对消息进行总结
|
|
1204
|
-
* @param {Array} messages - 要总结的消息
|
|
1205
|
-
* @returns {Promise<string>} 总结文本
|
|
1206
|
-
* @private
|
|
1207
|
-
*/
|
|
1208
|
-
async _summarizeMessages(messages) {
|
|
1209
|
-
if (!this._aiClient || messages.length === 0) {
|
|
1210
|
-
return '(无早期对话)';
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
// 构建总结提示
|
|
1214
|
-
const conversationText = messages
|
|
1215
|
-
.map((m) => {
|
|
1216
|
-
const role = m.role === 'user' ? '用户' : '助手';
|
|
1217
|
-
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
|
|
1218
|
-
return `${role}: ${content}`;
|
|
1219
|
-
})
|
|
1220
|
-
.join('\n');
|
|
1221
|
-
|
|
1222
|
-
const summarizePrompt = `请简洁地总结以下对话的要点,保留关键信息和用户需求:
|
|
1223
|
-
|
|
1224
|
-
${conversationText}
|
|
1225
|
-
|
|
1226
|
-
总结要求:
|
|
1227
|
-
1. 提取用户的主要需求和意图
|
|
1228
|
-
2. 保留关键的技术细节或决策
|
|
1229
|
-
3. 不要超过 1000 字
|
|
1230
|
-
4. 用中文回复`;
|
|
1231
|
-
|
|
1232
|
-
// 使用 AI SDK 6.x 的 generateText
|
|
1233
|
-
const { text } = await generateText({
|
|
1234
|
-
model: this._aiClient,
|
|
1235
|
-
prompt: summarizePrompt,
|
|
1236
|
-
...this.providerOptions,
|
|
1237
|
-
});
|
|
1238
|
-
|
|
1239
|
-
return text || '(总结生成失败)';
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
/**
|
|
1243
|
-
* 检查工具结果是否需要压缩
|
|
1244
|
-
* @param {any} result - 工具返回结果
|
|
1245
|
-
* @returns {boolean}
|
|
1246
|
-
* @private
|
|
1247
|
-
*/
|
|
1248
|
-
_shouldCompressToolResult(result) {
|
|
1249
|
-
if (!result || this._maxToolResultSize <= 0) return false;
|
|
1250
|
-
|
|
1251
|
-
// 计算结果的大小
|
|
1252
|
-
let size = 0;
|
|
1253
|
-
if (typeof result === 'string') {
|
|
1254
|
-
size = result.length;
|
|
1255
|
-
} else if (typeof result === 'object') {
|
|
1256
|
-
try {
|
|
1257
|
-
size = JSON.stringify(result).length;
|
|
1258
|
-
} catch {
|
|
1259
|
-
size = String(result).length;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
return size > this._maxToolResultSize;
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
/**
|
|
1267
|
-
* 压缩工具结果
|
|
1268
|
-
* @param {any} result - 工具返回结果
|
|
1269
|
-
* @returns {Promise<any>} 压缩后的结果
|
|
1270
|
-
* @private
|
|
1271
|
-
*/
|
|
1272
|
-
async _compressToolResult(result) {
|
|
1273
|
-
if (!this._shouldCompressToolResult(result)) {
|
|
1274
|
-
return result;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
if (!this._aiClient) {
|
|
1278
|
-
logger.warn('Cannot compress tool result: no AI client');
|
|
1279
|
-
return this._fallbackCompress(result);
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
try {
|
|
1283
|
-
const originalSize =
|
|
1284
|
-
typeof result === 'string' ? result.length : JSON.stringify(result).length;
|
|
1285
|
-
const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
1286
|
-
|
|
1287
|
-
// 对于超大型内容(如网页),采用更好的截断策略
|
|
1288
|
-
const maxInputSize = 6000; // 保留给 AI 处理的输入大小
|
|
1289
|
-
const shouldTruncate = resultStr.length > maxInputSize;
|
|
1290
|
-
const truncatedContent = resultStr.substring(0, maxInputSize);
|
|
1291
|
-
const truncatedNote = shouldTruncate ? `\n\n[内容已截断,原始长度 ${originalSize} 字符]` : '';
|
|
1292
|
-
|
|
1293
|
-
// 检测内容类型
|
|
1294
|
-
const isHTML =
|
|
1295
|
-
resultStr.startsWith('<') || resultStr.includes('<html') || resultStr.includes('<!DOCTYPE');
|
|
1296
|
-
const isJSON = !isHTML && (resultStr.startsWith('{') || resultStr.startsWith('['));
|
|
1297
|
-
const contentTypeHint = isHTML ? '(HTML 网页内容)' : isJSON ? '(JSON 数据)' : '';
|
|
1298
|
-
|
|
1299
|
-
// 构建压缩提示
|
|
1300
|
-
const compressPrompt = `以下是一个工具执行结果${contentTypeHint},长度 ${originalSize} 字符。请简洁地总结其核心内容:
|
|
1301
|
-
|
|
1302
|
-
${truncatedContent}${truncatedNote}
|
|
1303
|
-
|
|
1304
|
-
请提取并保留:
|
|
1305
|
-
1. 主要标题和主题
|
|
1306
|
-
2. 关键信息点(不超过 5 个)
|
|
1307
|
-
3. 重要数据或结论
|
|
1308
|
-
|
|
1309
|
-
用简洁的中文总结,不超过 1000 字:`;
|
|
1310
|
-
|
|
1311
|
-
// 使用 AI SDK 6.x 的 generateText
|
|
1312
|
-
const { text } = await generateText({
|
|
1313
|
-
model: this._aiClient,
|
|
1314
|
-
prompt: compressPrompt,
|
|
1315
|
-
...this.providerOptions,
|
|
1316
|
-
maxTokens: 500,
|
|
1317
|
-
});
|
|
1318
|
-
|
|
1319
|
-
const summary = text || '(总结生成失败)';
|
|
1320
|
-
const compressed = `[工具结果已压缩${contentTypeHint}: ${originalSize} -> ${summary.length} 字符]\n\n${summary}`;
|
|
1321
|
-
|
|
1322
|
-
logger.info(`Tool result compressed: ${originalSize} -> ${summary.length} chars`);
|
|
1323
|
-
return compressed;
|
|
1324
|
-
} catch (err) {
|
|
1325
|
-
logger.warn('Tool result compression failed:', err.message);
|
|
1326
|
-
return this._fallbackCompress(result);
|
|
1327
|
-
}
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
/**
|
|
1331
|
-
* 回退压缩方法(当 AI 客户端不可用时)
|
|
1332
|
-
* @param {any} result - 工具返回结果
|
|
1333
|
-
* @returns {string} 压缩后的结果
|
|
1334
|
-
* @private
|
|
1335
|
-
*/
|
|
1336
|
-
_fallbackCompress(result) {
|
|
1337
|
-
const originalSize = typeof result === 'string' ? result.length : JSON.stringify(result).length;
|
|
1338
|
-
const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
1339
|
-
|
|
1340
|
-
// 简单截断策略:保留前 2000 字符
|
|
1341
|
-
const maxSize = 2000;
|
|
1342
|
-
if (resultStr.length <= maxSize) {
|
|
1343
|
-
return result;
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1346
|
-
const compressed = `[工具结果已压缩(简单截断): ${originalSize} -> ${maxSize} 字符]\n\n${resultStr.substring(0, maxSize)}\n\n...[内容已截断,原文 ${originalSize} 字符]`;
|
|
1347
|
-
logger.info(`Tool result fallback compressed: ${originalSize} -> ${maxSize} chars`);
|
|
1348
|
-
return compressed;
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
/**
|
|
1352
|
-
* 设置 AI 客户端
|
|
1353
|
-
* @param {Object} client - AI 模型客户端
|
|
1354
|
-
*/
|
|
1355
|
-
setAIClient(client) {
|
|
1356
|
-
this._aiClient = client;
|
|
1357
|
-
return this;
|
|
1358
|
-
}
|
|
1359
|
-
|
|
1360
|
-
/**
|
|
1361
|
-
* 设置系统提示
|
|
1362
|
-
* @param {string} prompt
|
|
1363
|
-
*/
|
|
1364
|
-
setSystemPrompt(prompt) {
|
|
1365
|
-
this._systemPrompt = prompt;
|
|
1366
|
-
return this;
|
|
1367
|
-
}
|
|
1368
|
-
|
|
1369
|
-
/**
|
|
1370
|
-
* 注册工具
|
|
1371
|
-
* @param {Object} toolDef - 工具定义
|
|
1372
|
-
*/
|
|
1373
|
-
registerTool(toolDef) {
|
|
1374
|
-
this._tools.set(toolDef.name, toolDef);
|
|
1375
|
-
return this;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
/**
|
|
1379
|
-
* 清空对话历史
|
|
1380
|
-
* @param {string} sessionId - 可选,指定 session 则同时清除 session 中的历史
|
|
1381
|
-
*/
|
|
1382
|
-
clearHistory(sessionId) {
|
|
1383
|
-
if (sessionId) {
|
|
1384
|
-
// 清除 Per-Session 消息存储
|
|
1385
|
-
const messageStore = this._sessionMessageStores.get(sessionId);
|
|
1386
|
-
if (messageStore) {
|
|
1387
|
-
messageStore.messages = [];
|
|
1388
|
-
messageStore.historyLoaded = false;
|
|
1389
|
-
messageStore.compressionState = {
|
|
1390
|
-
lastCompressedAt: null,
|
|
1391
|
-
lastTokenCount: 0,
|
|
1392
|
-
count: 0,
|
|
1393
|
-
};
|
|
1394
|
-
}
|
|
1395
|
-
// 清除 SessionContext 中的历史
|
|
1396
|
-
if (this.agent?.framework) {
|
|
1397
|
-
try {
|
|
1398
|
-
const sessionCtx = this.agent.framework.getSessionContext(sessionId);
|
|
1399
|
-
if (sessionCtx) {
|
|
1400
|
-
sessionCtx.clearMessages();
|
|
1401
|
-
}
|
|
1402
|
-
} catch (err) {
|
|
1403
|
-
// 忽略错误
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
} else {
|
|
1407
|
-
// 清除所有 Per-Session 消息存储
|
|
1408
|
-
this._sessionMessageStores.clear();
|
|
1409
|
-
}
|
|
1410
|
-
this._compressionCount = 0;
|
|
1411
|
-
return this;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
/**
|
|
1415
|
-
* 获取已注册的工具
|
|
1416
|
-
* @returns {Array}
|
|
1417
|
-
*/
|
|
1418
|
-
getTools() {
|
|
1419
|
-
return Array.from(this._tools.values());
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
/**
|
|
1423
|
-
* 导入 AI SDK(动态导入)
|
|
1424
|
-
* @private
|
|
1425
|
-
*/
|
|
1426
|
-
async _importAI() {
|
|
1427
|
-
try {
|
|
1428
|
-
const ai = require('ai');
|
|
1429
|
-
return {
|
|
1430
|
-
tool: ai.tool,
|
|
1431
|
-
ToolLoopAgent: ai.ToolLoopAgent,
|
|
1432
|
-
};
|
|
1433
|
-
} catch (err) {
|
|
1434
|
-
throw new Error('AI SDK not found. Please install: npm install ai');
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
/**
|
|
1439
|
-
* 发送消息(非流式)- 使用队列机制
|
|
1440
|
-
* @param {string|Object} message - 消息
|
|
1441
|
-
* @param {Object} options - 选项
|
|
1442
|
-
*/
|
|
1443
|
-
async chat(message, options = {}) {
|
|
1444
|
-
const sessionId = options.sessionId || null;
|
|
1445
|
-
return this.enqueue(sessionId, message, options);
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
/**
|
|
1449
|
-
* 准备聊天会话:刷新系统提示词、添加用户消息、压缩上下文
|
|
1450
|
-
* @param {string|Object} message - 消息
|
|
1451
|
-
* @param {string} sessionId - 会话 ID
|
|
1452
|
-
* @returns {Object} - { messageStore, messages, tools, maxSteps }
|
|
1453
|
-
* @private
|
|
1454
|
-
*/
|
|
1455
|
-
async _prepareSession(message, sessionId) {
|
|
1456
|
-
const messageStore = this._getSessionMessageStore(sessionId);
|
|
1457
|
-
const messages = messageStore.messages;
|
|
599
|
+
async _prepareSession(message, sessionId) {
|
|
600
|
+
const messageStore = this._getSessionMessageStore(sessionId);
|
|
601
|
+
const messages = messageStore.messages;
|
|
1458
602
|
|
|
1459
603
|
// 刷新系统提示词
|
|
1460
604
|
this._systemPrompt = this.agent._buildSystemPrompt();
|
|
1461
605
|
|
|
1462
606
|
// 添加用户消息
|
|
1463
607
|
const userMessage = typeof message === 'string' ? { role: 'user', content: message } : message;
|
|
1464
|
-
|
|
1465
|
-
//await fs.writeFile('system.md',this._systemPrompt)
|
|
608
|
+
|
|
1466
609
|
// 检查上下文大小,必要时压缩
|
|
1467
610
|
const messagesTokens = this._countMessagesTokens(messages);
|
|
1468
611
|
const toolsTokens = this._countToolsTokens();
|
|
@@ -1475,23 +618,8 @@ ${truncatedContent}${truncatedNote}
|
|
|
1475
618
|
`Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
|
|
1476
619
|
);
|
|
1477
620
|
await this._compressContext(sessionId, messages, messageStore);
|
|
1478
|
-
const validated = this._validateMessagesPairing(messages);
|
|
1479
|
-
if (validated.length !== messages.length) {
|
|
1480
|
-
messages.length = 0;
|
|
1481
|
-
messages.push(...validated);
|
|
1482
|
-
}
|
|
1483
621
|
}
|
|
1484
622
|
|
|
1485
|
-
// 验证消息配对
|
|
1486
|
-
const validatedMessages = this._validateMessagesPairing(messages);
|
|
1487
|
-
if (validatedMessages.length !== messages.length) {
|
|
1488
|
-
messages.length = 0;
|
|
1489
|
-
messages.push(...validatedMessages);
|
|
1490
|
-
}
|
|
1491
|
-
|
|
1492
|
-
// 验证工具调用
|
|
1493
|
-
this._validateToolCalls(messages);
|
|
1494
|
-
|
|
1495
623
|
// 最终 token 检查
|
|
1496
624
|
const finalTokens =
|
|
1497
625
|
this._countMessagesTokens(messages) +
|
|
@@ -1500,205 +628,146 @@ ${truncatedContent}${truncatedNote}
|
|
|
1500
628
|
if (finalTokens > this._maxContextTokens * 0.5) {
|
|
1501
629
|
logger.warn('Still over limit after validation, forcing compression');
|
|
1502
630
|
await this._compressContext(sessionId, messages, messageStore);
|
|
1503
|
-
const compressed = this._validateMessagesPairing(messages);
|
|
1504
|
-
messages.length = 0;
|
|
1505
|
-
messages.push(...compressed);
|
|
1506
631
|
}
|
|
632
|
+
// 验证工具调用
|
|
1507
633
|
|
|
1508
|
-
const
|
|
634
|
+
const validated = this._validateMessagesPairing(messages);
|
|
635
|
+
if (validated.length !== messages.length) {
|
|
636
|
+
messages.length = 0;
|
|
637
|
+
messages.push(...validated);
|
|
638
|
+
}
|
|
639
|
+
messages.push(userMessage);
|
|
640
|
+
this._validateToolCalls(messages);
|
|
1509
641
|
|
|
1510
|
-
return { messageStore, messages
|
|
642
|
+
return { messageStore, messages };
|
|
1511
643
|
}
|
|
1512
644
|
|
|
1513
645
|
/**
|
|
1514
|
-
*
|
|
1515
|
-
* @returns {Function}
|
|
646
|
+
* 更新消息存储的使用量
|
|
1516
647
|
* @private
|
|
1517
648
|
*/
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
return { messages: inputMessages };
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
this._validateToolCalls(inputMessages);
|
|
1526
|
-
const pairedMessages = this._validateMessagesPairing(inputMessages);
|
|
1527
|
-
if (pairedMessages.length !== inputMessages.length) {
|
|
1528
|
-
inputMessages.length = 0;
|
|
1529
|
-
inputMessages.push(...pairedMessages);
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
const tokenCount = this._countMessagesTokens(inputMessages);
|
|
1533
|
-
const tokenLimit = this._maxContextTokens * 0.75;
|
|
1534
|
-
if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
|
|
1535
|
-
return { messages: inputMessages };
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
const pruned = this._prepareMessagesForAI(inputMessages);
|
|
1539
|
-
return { messages: pruned };
|
|
1540
|
-
} catch (err) {
|
|
1541
|
-
logger.error('prepareStep error:', err.message);
|
|
1542
|
-
return { messages: inputMessages };
|
|
1543
|
-
}
|
|
1544
|
-
};
|
|
649
|
+
_updateMessageStoreUsage(messageStore, usage) {
|
|
650
|
+
if (messageStore && usage) {
|
|
651
|
+
messageStore.usage = usage;
|
|
652
|
+
}
|
|
1545
653
|
}
|
|
1546
654
|
|
|
1547
655
|
/**
|
|
1548
|
-
*
|
|
1549
|
-
*
|
|
1550
|
-
* @param {Object} options - 选项
|
|
656
|
+
* 获取 AI 工具格式
|
|
657
|
+
* 使用 AI SDK 的 tool() 格式
|
|
1551
658
|
* @private
|
|
1552
659
|
*/
|
|
1553
|
-
|
|
1554
|
-
const
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
const { tool, ToolLoopAgent } = await this._importAI();
|
|
1559
|
-
const { messageStore, messages, maxSteps } = await this._prepareSession(message, sessionId);
|
|
1560
|
-
|
|
1561
|
-
if (!this._aiClient) {
|
|
1562
|
-
throw new Error('AI client not configured.');
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
const tools = this._getAITools(tool);
|
|
1566
|
-
const agent = new ToolLoopAgent({
|
|
1567
|
-
model: this._aiClient,
|
|
1568
|
-
instructions: this._systemPrompt,
|
|
1569
|
-
tools: tools,
|
|
1570
|
-
prepareStep: this._createPrepareStep(),
|
|
1571
|
-
});
|
|
1572
|
-
|
|
1573
|
-
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
1574
|
-
return agent.generate({ messages, ...this.providerOptions });
|
|
1575
|
-
});
|
|
1576
|
-
|
|
1577
|
-
if (result.usage) {
|
|
1578
|
-
this._updateMessageStoreUsage(messageStore, result.usage);
|
|
1579
|
-
}
|
|
660
|
+
_getAITools(toolFn) {
|
|
661
|
+
const tools = {};
|
|
662
|
+
const allTools = this.agent.framework.getTools();
|
|
663
|
+
for (const toolDef of allTools) {
|
|
664
|
+
const toolName = toolDef.name;
|
|
1580
665
|
|
|
1581
|
-
|
|
1582
|
-
const
|
|
1583
|
-
|
|
666
|
+
// 使用 AI SDK 的 tool() 格式
|
|
667
|
+
const toolConfig = {
|
|
668
|
+
name: toolName,
|
|
669
|
+
description: toolDef.description || '',
|
|
670
|
+
execute: async (args) => {
|
|
671
|
+
// 清理参数
|
|
672
|
+
const cleanedArgs = this._cleanToolArgs(args);
|
|
1584
673
|
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
674
|
+
// 执行工具
|
|
675
|
+
this.emit('tool-call', { name: toolName, args: cleanedArgs });
|
|
676
|
+
try {
|
|
677
|
+
const result = await toolDef.execute(cleanedArgs, this.agent.framework);
|
|
678
|
+
this.emit('tool-result', { name: toolName, args: cleanedArgs, result });
|
|
679
|
+
// 确保返回字符串或可序列化的对象
|
|
680
|
+
if (result === null || result === undefined) {
|
|
681
|
+
return 'OK';
|
|
682
|
+
}
|
|
683
|
+
if (typeof result === 'object') {
|
|
684
|
+
return JSON.stringify(result);
|
|
685
|
+
}
|
|
686
|
+
return String(result);
|
|
687
|
+
} catch (err) {
|
|
688
|
+
this.emit('tool-error', { name: toolName, args: cleanedArgs, error: err.message });
|
|
689
|
+
// 返回错误信息字符串,而不是抛出异常
|
|
690
|
+
return JSON.stringify({ error: err.message, success: false });
|
|
691
|
+
}
|
|
692
|
+
},
|
|
1589
693
|
};
|
|
1590
|
-
} catch (err) {
|
|
1591
|
-
console.log(err);
|
|
1592
|
-
const errorMsg = err.message || String(err);
|
|
1593
694
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
695
|
+
// 支持 inputSchema 或 parameters
|
|
696
|
+
if (toolDef.inputSchema) {
|
|
697
|
+
toolConfig.inputSchema = toolDef.inputSchema;
|
|
698
|
+
} else if (toolDef.parameters) {
|
|
699
|
+
toolConfig.parameters = toolDef.parameters;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// AI SDK 6.x 使用对象形式,键为工具名
|
|
703
|
+
tools[toolName] = toolFn(toolConfig);
|
|
1603
704
|
}
|
|
705
|
+
|
|
706
|
+
return tools;
|
|
1604
707
|
}
|
|
1605
708
|
|
|
1606
709
|
/**
|
|
1607
|
-
*
|
|
1608
|
-
* @
|
|
1609
|
-
* @param {Object} options - 选项
|
|
710
|
+
* 清理工具参数
|
|
711
|
+
* @private
|
|
1610
712
|
*/
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
try {
|
|
1616
|
-
const { tool, ToolLoopAgent } = await this._importAI();
|
|
1617
|
-
const { messageStore, messages } = await this._prepareSession(message, sessionId);
|
|
713
|
+
_cleanToolArgs(args) {
|
|
714
|
+
if (!args || typeof args !== 'object') {
|
|
715
|
+
return {};
|
|
716
|
+
}
|
|
1618
717
|
|
|
1619
|
-
|
|
1620
|
-
|
|
718
|
+
const cleaned = {};
|
|
719
|
+
for (const [key, value] of Object.entries(args)) {
|
|
720
|
+
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
|
|
721
|
+
continue;
|
|
1621
722
|
}
|
|
723
|
+
cleaned[key] = value;
|
|
724
|
+
}
|
|
725
|
+
return cleaned;
|
|
726
|
+
}
|
|
1622
727
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
1634
|
-
return agent.stream({ messages, ...this.providerOptions });
|
|
1635
|
-
});
|
|
1636
|
-
|
|
1637
|
-
const stream = result.fullStream;
|
|
1638
|
-
let fullText = '';
|
|
1639
|
-
const iterator = stream[Symbol.asyncIterator] ? stream : stream.fullStream;
|
|
728
|
+
/**
|
|
729
|
+
* 为 AI SDK prepareStep 准备消息
|
|
730
|
+
* @private
|
|
731
|
+
*/
|
|
732
|
+
_createPrepareStep() {
|
|
733
|
+
return async ({ stepNumber, messages: inputMessages }) => {
|
|
734
|
+
try {
|
|
735
|
+
const tokenCount = this._countMessagesTokens(inputMessages);
|
|
736
|
+
const tokenLimit = this._maxContextTokens * 0.75;
|
|
1640
737
|
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
// this.emit('thinking', { content: part.text });
|
|
1648
|
-
yield { type: 'thinking', text: part.text };
|
|
1649
|
-
} else if (part.type === 'tool-call') {
|
|
1650
|
-
yield { type: 'tool-call', toolName: part.toolName, input: part.input };
|
|
1651
|
-
} else if (part.type === 'tool-result') {
|
|
1652
|
-
yield { type: 'tool-result', toolName: part.toolName, result: part.output };
|
|
1653
|
-
} else if (part.type === 'error') {
|
|
1654
|
-
yield { type: 'error', error: part.error };
|
|
738
|
+
if (inputMessages.length > 50 || tokenCount > tokenLimit) {
|
|
739
|
+
const pairedMessages = this._compressContextForPrepare(inputMessages);
|
|
740
|
+
if (pairedMessages.length !== inputMessages.length) {
|
|
741
|
+
inputMessages.length = 0;
|
|
742
|
+
inputMessages.push(...pairedMessages);
|
|
743
|
+
}
|
|
1655
744
|
}
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
const finishMessages = (await result.response).messages;
|
|
1659
|
-
messages.push(...finishMessages);
|
|
1660
|
-
const usage = await result.totalUsage;
|
|
1661
|
-
if (usage) {
|
|
1662
|
-
this._updateMessageStoreUsage(messageStore, usage);
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
const userMsg = messages[messages.length - finishMessages.length - 1];
|
|
1666
|
-
this.emit('message', { content: fullText, sessionId: sessionId, userMessage: userMsg });
|
|
1667
|
-
} catch (err) {
|
|
1668
|
-
const errorMsg = err.message || String(err);
|
|
1669
|
-
const isTransientError =
|
|
1670
|
-
errorMsg.includes('Invalid JSON response') ||
|
|
1671
|
-
errorMsg.includes('负载较高') ||
|
|
1672
|
-
err.name === 'AI_RetryError' ||
|
|
1673
|
-
err.name === 'AI_APICallError';
|
|
1674
745
|
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
746
|
+
const validated = this._validateMessagesPairing(inputMessages);
|
|
747
|
+
if (validated.length !== inputMessages.length) {
|
|
748
|
+
inputMessages.length = 0;
|
|
749
|
+
inputMessages.push(...validated);
|
|
750
|
+
}
|
|
751
|
+
this._validateToolCalls(inputMessages);
|
|
752
|
+
return { messages: inputMessages };
|
|
753
|
+
} catch (err) {
|
|
754
|
+
logger.error('prepareStep error:', err.message);
|
|
755
|
+
return { messages: inputMessages };
|
|
1681
756
|
}
|
|
1682
|
-
}
|
|
1683
|
-
const messageStore = this._getSessionMessageStore(sessionId);
|
|
1684
|
-
messageStore.save();
|
|
1685
|
-
}
|
|
757
|
+
};
|
|
1686
758
|
}
|
|
1687
759
|
|
|
1688
760
|
/**
|
|
1689
|
-
*
|
|
1690
|
-
* 确保 tool call 和 tool result 正确配对
|
|
1691
|
-
* @param {Array} messages - 消息列表
|
|
1692
|
-
* @returns {Array} 过滤后的消息数组
|
|
761
|
+
* 压缩上下文(用于 prepareStep)
|
|
1693
762
|
* @private
|
|
1694
763
|
*/
|
|
1695
|
-
|
|
1696
|
-
if (messages.length <=
|
|
764
|
+
async _compressContextForPrepare(messages) {
|
|
765
|
+
if (messages.length <= 30) {
|
|
1697
766
|
return messages;
|
|
1698
767
|
}
|
|
1699
768
|
|
|
1700
769
|
// 1. 确定要保留的消息范围(保留最近 N 条)
|
|
1701
|
-
const keepRecentCount = Math.min(
|
|
770
|
+
const keepRecentCount = Math.min(10, Math.floor(messages.length * 0.4));
|
|
1702
771
|
const minIndexToKeep = Math.max(0, messages.length - keepRecentCount);
|
|
1703
772
|
|
|
1704
773
|
// 2. 只从保留下来的消息(minIndexToKeep 之后)中收集 assistant 的 tool-call IDs
|
|
@@ -1793,317 +862,104 @@ ${truncatedContent}${truncatedNote}
|
|
|
1793
862
|
}
|
|
1794
863
|
|
|
1795
864
|
/**
|
|
1796
|
-
*
|
|
1797
|
-
* @param {number} stepNumber - 当前步骤号
|
|
1798
|
-
* @param {Array} messages - 消息列表
|
|
1799
|
-
* @returns {Object} { messages } 或空对象
|
|
865
|
+
* 计算 token
|
|
1800
866
|
* @private
|
|
1801
867
|
*/
|
|
1802
|
-
|
|
1803
|
-
if (
|
|
1804
|
-
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
// 工具类型检查辅助函数(兼容 AI SDK 6.x)
|
|
1808
|
-
const isToolCall = (item) =>
|
|
1809
|
-
item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
|
|
1810
|
-
const isToolResult = (item) =>
|
|
1811
|
-
item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
|
|
1812
|
-
|
|
1813
|
-
// 构建配对索引集合:确保 assistant 的 tool call 和 tool result 不会被单独裁剪
|
|
1814
|
-
const assistantIndices = new Map(); // toolCallId -> assistant index
|
|
1815
|
-
const toolResultIndices = new Map(); // toolCallId -> tool result indices
|
|
1816
|
-
|
|
1817
|
-
for (let i = 0; i < messages.length; i++) {
|
|
1818
|
-
const msg = messages[i];
|
|
1819
|
-
if (msg.role === 'assistant' && msg.content) {
|
|
1820
|
-
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
1821
|
-
for (const item of content) {
|
|
1822
|
-
if (isToolCall(item)) {
|
|
1823
|
-
assistantIndices.set(item.toolCallId, i);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
}
|
|
1827
|
-
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1828
|
-
// 遍历 tool message 中的所有 tool-results
|
|
1829
|
-
for (const item of msg.content) {
|
|
1830
|
-
if (isToolResult(item)) {
|
|
1831
|
-
if (!toolResultIndices.has(item.toolCallId)) {
|
|
1832
|
-
toolResultIndices.set(item.toolCallId, []);
|
|
1833
|
-
}
|
|
1834
|
-
toolResultIndices.get(item.toolCallId).push(i);
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
const recentCount = Math.max(10, messages.length - 10);
|
|
1841
|
-
let minIndexToKeep = messages.length - recentCount;
|
|
1842
|
-
|
|
1843
|
-
// 情况1:保留被裁剪范围内的 assistant 消息及其 tool result
|
|
1844
|
-
for (const [toolCallId, assistantIdx] of assistantIndices) {
|
|
1845
|
-
if (assistantIdx < minIndexToKeep) {
|
|
1846
|
-
minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
|
|
1847
|
-
}
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
// 情况2:保留被裁剪范围内的 tool result 及其 assistant 消息
|
|
1851
|
-
for (const [toolCallId, resultIndices] of toolResultIndices) {
|
|
1852
|
-
for (const resultIdx of resultIndices) {
|
|
1853
|
-
if (resultIdx >= minIndexToKeep) {
|
|
1854
|
-
const assistantIdx = assistantIndices.get(toolCallId);
|
|
1855
|
-
if (assistantIdx !== undefined && assistantIdx < minIndexToKeep) {
|
|
1856
|
-
minIndexToKeep = Math.min(minIndexToKeep, assistantIdx);
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
const pruned = messages.slice(minIndexToKeep);
|
|
1863
|
-
// logger.info(
|
|
1864
|
-
// `prepareStep pruned: ${messages.length} -> ${pruned.length} (step ${stepNumber}, kept from index ${minIndexToKeep})`
|
|
1865
|
-
// );
|
|
1866
|
-
return { messages: pruned };
|
|
868
|
+
_countTokens(text, bytesPerToken = 4) {
|
|
869
|
+
if (!text) return 0;
|
|
870
|
+
return Math.ceil(Buffer.byteLength(String(text), 'utf8') / bytesPerToken);
|
|
1867
871
|
}
|
|
1868
872
|
|
|
1869
873
|
/**
|
|
1870
|
-
*
|
|
1871
|
-
* 删除没有对应 assistant tool_call 的 tool result(会导致 API 报错)
|
|
1872
|
-
* 兼容 AI SDK 6.x 标准格式
|
|
1873
|
-
* @param {Array} messages - 消息列表
|
|
874
|
+
* 计算消息 token
|
|
1874
875
|
* @private
|
|
1875
876
|
*/
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
for (const msg of messages) {
|
|
1880
|
-
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1881
|
-
for (const item of msg.content) {
|
|
1882
|
-
// 兼容 tool-call 和 tool-use 两种类型
|
|
1883
|
-
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
|
|
1884
|
-
assistantToolCallIds.add(item.toolCallId);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
// 检查并删除没有配对的 tool result
|
|
1891
|
-
let removedCount = 0;
|
|
877
|
+
_countMessagesTokens(messages) {
|
|
878
|
+
if (!Array.isArray(messages)) return 0;
|
|
879
|
+
let total = 0;
|
|
1892
880
|
for (const msg of messages) {
|
|
1893
|
-
|
|
1894
|
-
const originalLength = msg.content.length;
|
|
1895
|
-
msg.content = msg.content.filter((item) => {
|
|
1896
|
-
// 兼容 tool-result 和 tool_result 两种类型
|
|
1897
|
-
if (
|
|
1898
|
-
item &&
|
|
1899
|
-
(item.type === 'tool-result' || item.type === 'tool_result') &&
|
|
1900
|
-
item.toolCallId
|
|
1901
|
-
) {
|
|
1902
|
-
if (!assistantToolCallIds.has(item.toolCallId)) {
|
|
1903
|
-
removedCount++;
|
|
1904
|
-
return false; // 删除没有配对的 tool result
|
|
1905
|
-
}
|
|
1906
|
-
}
|
|
1907
|
-
return true;
|
|
1908
|
-
});
|
|
1909
|
-
|
|
1910
|
-
// 如果所有 content 都被删除了,标记整个消息待删除
|
|
1911
|
-
if (msg.content.length === 0 && originalLength > 0) {
|
|
1912
|
-
msg._orphaned = true;
|
|
1913
|
-
}
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
|
|
1917
|
-
// 移除被标记为 orphaned 的 tool 消息
|
|
1918
|
-
const originalLength = messages.length;
|
|
1919
|
-
const filtered = messages.filter((msg) => !(msg.role === 'tool' && msg._orphaned));
|
|
1920
|
-
|
|
1921
|
-
if (removedCount > 0 || filtered.length !== originalLength) {
|
|
1922
|
-
logger.debug(
|
|
1923
|
-
`Removed ${removedCount} orphaned tool-results, ${originalLength - filtered.length} orphaned tool messages`
|
|
1924
|
-
);
|
|
881
|
+
total += this._countMessageTokens(msg);
|
|
1925
882
|
}
|
|
1926
|
-
|
|
1927
|
-
return filtered;
|
|
883
|
+
return total;
|
|
1928
884
|
}
|
|
1929
885
|
|
|
1930
886
|
/**
|
|
1931
|
-
*
|
|
1932
|
-
* 移除不完整的 JSON(如只有 "{" )的工具调用
|
|
1933
|
-
* 同时清理 tool result 中的错误信息
|
|
1934
|
-
* 兼容 AI SDK 6.x 标准格式
|
|
1935
|
-
* @param {Array} messages - 消息列表
|
|
887
|
+
* 计算单条消息 token
|
|
1936
888
|
* @private
|
|
1937
889
|
*/
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
const invalidatedToolCallIds = new Set();
|
|
1942
|
-
|
|
1943
|
-
for (const msg of messages) {
|
|
1944
|
-
// 清理 assistant 消息中的不完整 tool-call
|
|
1945
|
-
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1946
|
-
for (const item of msg.content) {
|
|
1947
|
-
// 兼容 tool-call 和 tool-use 两种类型
|
|
1948
|
-
if (item.type !== 'tool-call' && item.type !== 'tool-use') {
|
|
1949
|
-
continue;
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
const input = item.input;
|
|
1953
|
-
if (typeof input !== 'string') {
|
|
1954
|
-
continue;
|
|
1955
|
-
}
|
|
890
|
+
_countMessageTokens(msg) {
|
|
891
|
+
if (!msg) return 0;
|
|
892
|
+
let total = 4;
|
|
1956
893
|
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
`_validateToolCalls: invalid tool-call input="${input}", toolCallId=${item.toolCallId}, converting to text`
|
|
1967
|
-
);
|
|
1968
|
-
item.type = 'text';
|
|
1969
|
-
item.text = `(工具调用 ${item.toolName} 参数不完整,已跳过)`;
|
|
1970
|
-
delete item.toolCallId;
|
|
1971
|
-
delete item.toolName;
|
|
1972
|
-
delete item.input;
|
|
1973
|
-
fixedCount++;
|
|
894
|
+
if (typeof msg.content === 'string') {
|
|
895
|
+
total += this._countTokens(msg.content);
|
|
896
|
+
} else if (Array.isArray(msg.content)) {
|
|
897
|
+
for (const block of msg.content) {
|
|
898
|
+
if (block.type === 'text') {
|
|
899
|
+
total += this._countTokens(block.text || '');
|
|
900
|
+
} else if (block.type === 'tool-call' || block.type === 'tool-use') {
|
|
901
|
+
if (block.input) {
|
|
902
|
+
total += this._countTokens(JSON.stringify(block.input));
|
|
1974
903
|
}
|
|
904
|
+
} else if (block.type === 'tool-result' || block.type === 'tool_result') {
|
|
905
|
+
const content =
|
|
906
|
+
typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
|
|
907
|
+
total += this._countTokens(content);
|
|
1975
908
|
}
|
|
1976
909
|
}
|
|
1977
910
|
}
|
|
1978
911
|
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1986
|
-
// 过滤掉引用了无效 toolCallId 的 tool-result
|
|
1987
|
-
const oldLen = msg.content.length;
|
|
1988
|
-
msg.content = msg.content.filter((item) => {
|
|
1989
|
-
if (item.type !== 'tool-result' && item.type !== 'tool_result') {
|
|
1990
|
-
return true;
|
|
1991
|
-
}
|
|
1992
|
-
// 如果 tool-result 引用的 toolCallId 已被标记为无效,则移除
|
|
1993
|
-
if (item.toolCallId && invalidatedToolCallIds.has(item.toolCallId)) {
|
|
1994
|
-
logger.warn(
|
|
1995
|
-
`_validateToolCalls: removing orphaned tool-result with toolCallId=${item.toolCallId}`
|
|
1996
|
-
);
|
|
1997
|
-
fixedCount++;
|
|
1998
|
-
return false;
|
|
1999
|
-
}
|
|
2000
|
-
return true;
|
|
2001
|
-
});
|
|
912
|
+
if (msg.tool_calls) {
|
|
913
|
+
total += 15;
|
|
914
|
+
for (const tc of msg.tool_calls) {
|
|
915
|
+
if (tc.function) {
|
|
916
|
+
total += this._countTokens(tc.function.name || '');
|
|
917
|
+
total += this._countTokens(tc.function.arguments || '{}');
|
|
2002
918
|
}
|
|
2003
919
|
}
|
|
2004
920
|
}
|
|
2005
921
|
|
|
2006
|
-
|
|
2007
|
-
logger.info(`_validateToolCalls: Fixed ${fixedCount} incomplete tool calls/results`);
|
|
2008
|
-
}
|
|
922
|
+
return total;
|
|
2009
923
|
}
|
|
2010
924
|
|
|
2011
925
|
/**
|
|
2012
|
-
*
|
|
2013
|
-
* AI SDK 6.x 需要对象形式 { toolName: tool }
|
|
2014
|
-
* @param {Function} toolFn - AI SDK 的 tool 函数
|
|
926
|
+
* 计算工具 token
|
|
2015
927
|
* @private
|
|
2016
928
|
*/
|
|
2017
|
-
|
|
2018
|
-
const tools =
|
|
2019
|
-
|
|
2020
|
-
for (const [name, toolDef] of this._tools) {
|
|
2021
|
-
const toolName = toolDef.name || name;
|
|
2022
|
-
|
|
2023
|
-
// 使用 AI SDK 的 tool() 格式
|
|
2024
|
-
// 支持 inputSchema (zod schema) 或 parameters (旧格式)
|
|
2025
|
-
const toolConfig = {
|
|
2026
|
-
name: toolName,
|
|
2027
|
-
description: toolDef.description || '',
|
|
2028
|
-
execute: async (args) => {
|
|
2029
|
-
// 清理参数:移除 undefined、function 等无效值
|
|
2030
|
-
const cleanedArgs = this._cleanToolArgs(args);
|
|
929
|
+
_countToolsTokens() {
|
|
930
|
+
const tools = this._toolExecutor.getAllTools();
|
|
931
|
+
if (!tools.length) return 0;
|
|
2031
932
|
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
this.
|
|
2043
|
-
return { error: err.message };
|
|
933
|
+
let total = 0;
|
|
934
|
+
for (const tool of tools) {
|
|
935
|
+
total += 20;
|
|
936
|
+
if (tool.description) {
|
|
937
|
+
total += this._countTokens(tool.description);
|
|
938
|
+
}
|
|
939
|
+
if (tool.inputSchema) {
|
|
940
|
+
const schema = tool.inputSchema.jsonSchema || tool.inputSchema;
|
|
941
|
+
if (schema.properties) {
|
|
942
|
+
for (const [name, prop] of Object.entries(schema.properties)) {
|
|
943
|
+
total += this._countTokens(name) + 10;
|
|
2044
944
|
}
|
|
2045
|
-
}
|
|
2046
|
-
};
|
|
2047
|
-
|
|
2048
|
-
// 支持 inputSchema (zod) 或 parameters (旧格式)
|
|
2049
|
-
if (toolDef.inputSchema) {
|
|
2050
|
-
toolConfig.inputSchema = toolDef.inputSchema;
|
|
2051
|
-
} else if (toolDef.parameters) {
|
|
2052
|
-
toolConfig.parameters = toolDef.parameters;
|
|
945
|
+
}
|
|
2053
946
|
}
|
|
2054
|
-
|
|
2055
|
-
// AI SDK 6.x 使用对象形式,键为工具名
|
|
2056
|
-
tools[toolName] = toolFn(toolConfig);
|
|
2057
947
|
}
|
|
2058
|
-
|
|
2059
|
-
return tools;
|
|
948
|
+
return total;
|
|
2060
949
|
}
|
|
2061
950
|
|
|
2062
|
-
|
|
2063
|
-
* 清理工具参数,移除无效值
|
|
2064
|
-
* @param {Object} args - 原始参数
|
|
2065
|
-
* @returns {Object} 清理后的参数
|
|
2066
|
-
* @private
|
|
2067
|
-
*/
|
|
2068
|
-
_cleanToolArgs(args) {
|
|
2069
|
-
if (!args || typeof args !== 'object') {
|
|
2070
|
-
return {};
|
|
2071
|
-
}
|
|
951
|
+
// ==================== 兼容性方法 ====================
|
|
2072
952
|
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
// 跳过 undefined、function、symbol 等无效值
|
|
2076
|
-
if (value === undefined || value === null) {
|
|
2077
|
-
continue;
|
|
2078
|
-
}
|
|
2079
|
-
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
2080
|
-
continue;
|
|
2081
|
-
}
|
|
2082
|
-
// 递归清理嵌套对象
|
|
2083
|
-
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
2084
|
-
cleaned[key] = this._cleanToolArgs(value);
|
|
2085
|
-
} else if (Array.isArray(value)) {
|
|
2086
|
-
cleaned[key] = value
|
|
2087
|
-
.map((item) =>
|
|
2088
|
-
typeof item === 'object' && item !== null ? this._cleanToolArgs(item) : item
|
|
2089
|
-
)
|
|
2090
|
-
.filter((item) => item !== undefined && typeof item !== 'function');
|
|
2091
|
-
} else {
|
|
2092
|
-
cleaned[key] = value;
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
return cleaned;
|
|
953
|
+
cancelSession(sessionId) {
|
|
954
|
+
this._chatSession.cancelSession(sessionId);
|
|
2096
955
|
}
|
|
2097
956
|
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
this.
|
|
2104
|
-
this._tools.clear();
|
|
2105
|
-
this._encoder = null;
|
|
2106
|
-
this.removeAllListeners();
|
|
957
|
+
getQueueStatus(sessionId) {
|
|
958
|
+
return this._chatSession.getQueueStatus(sessionId);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
clearQueue(sessionId) {
|
|
962
|
+
this._chatSession.clearQueue(sessionId);
|
|
2107
963
|
}
|
|
2108
964
|
}
|
|
2109
965
|
|