foliko 1.0.87 → 1.1.1
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/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +288 -0
- package/.agent/agents/ambient-agent.md +57 -0
- package/.agent/agents/debugger.md +55 -0
- package/.agent/agents/email-assistant.md +49 -0
- package/.agent/agents/file-manager.md +42 -0
- package/.agent/agents/python-developer.md +60 -0
- package/.agent/agents/scheduler.md +59 -0
- package/.agent/agents/web-developer.md +45 -0
- package/.agent/data/default.json +325 -21
- package/.agent/data/plugins-state.json +194 -162
- package/.agent/data/puppeteer-sessions/undefined.json +6 -0
- package/.agent/mcp_config.json +0 -1
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins/poster-plugin/README.md +304 -0
- package/.agent/plugins/poster-plugin/fonts/NotoColorEmoji-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
- package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
- package/.agent/plugins/poster-plugin/index.js +13 -0
- package/.agent/plugins/poster-plugin/package.json +28 -0
- package/.agent/plugins/poster-plugin/src/canvas.js +161 -0
- package/.agent/plugins/poster-plugin/src/components/arrow.js +84 -0
- package/.agent/plugins/poster-plugin/src/components/avatar.js +71 -0
- package/.agent/plugins/poster-plugin/src/components/badge.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/card.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/chart.js +127 -0
- package/.agent/plugins/poster-plugin/src/components/chip.js +88 -0
- package/.agent/plugins/poster-plugin/src/components/columns.js +107 -0
- package/.agent/plugins/poster-plugin/src/components/cta.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/divider.js +55 -0
- package/.agent/plugins/poster-plugin/src/components/feature.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +112 -0
- package/.agent/plugins/poster-plugin/src/components/grid.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/imageFrame.js +155 -0
- package/.agent/plugins/poster-plugin/src/components/index.js +62 -0
- package/.agent/plugins/poster-plugin/src/components/listItem.js +146 -0
- package/.agent/plugins/poster-plugin/src/components/notification.js +123 -0
- package/.agent/plugins/poster-plugin/src/components/progress.js +79 -0
- package/.agent/plugins/poster-plugin/src/components/progressCircle.js +117 -0
- package/.agent/plugins/poster-plugin/src/components/quote.js +97 -0
- package/.agent/plugins/poster-plugin/src/components/rating.js +85 -0
- package/.agent/plugins/poster-plugin/src/components/star.js +70 -0
- package/.agent/plugins/poster-plugin/src/components/statCard.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/stepper.js +118 -0
- package/.agent/plugins/poster-plugin/src/components/table.js +159 -0
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +78 -0
- package/.agent/plugins/poster-plugin/src/components/timeline.js +105 -0
- package/.agent/plugins/poster-plugin/src/components/watermark.js +52 -0
- package/.agent/plugins/poster-plugin/src/composer.js +1904 -0
- package/.agent/plugins/poster-plugin/src/elements/artText.js +60 -0
- package/.agent/plugins/poster-plugin/src/elements/background.js +52 -0
- package/.agent/plugins/poster-plugin/src/elements/circle.js +31 -0
- package/.agent/plugins/poster-plugin/src/elements/image.js +71 -0
- package/.agent/plugins/poster-plugin/src/elements/index.js +26 -0
- package/.agent/plugins/poster-plugin/src/elements/line.js +23 -0
- package/.agent/plugins/poster-plugin/src/elements/polygon.js +63 -0
- package/.agent/plugins/poster-plugin/src/elements/rectangle.js +32 -0
- package/.agent/plugins/poster-plugin/src/elements/svg.js +92 -0
- package/.agent/plugins/poster-plugin/src/elements/text.js +107 -0
- package/.agent/plugins/poster-plugin/src/fonts.js +233 -0
- package/.agent/plugins/poster-plugin/src/index.js +1658 -0
- package/.agent/plugins/poster-plugin/src/presets.js +36 -0
- package/.agent/plugins/poster-plugin/src/templates/business.js +60 -0
- package/.agent/plugins/poster-plugin/src/templates/gradient.js +64 -0
- package/.agent/plugins/poster-plugin/src/templates/index.js +43 -0
- package/.agent/plugins/poster-plugin/src/templates/modern.js +69 -0
- package/.agent/plugins/poster-plugin/src/templates/simple.js +58 -0
- package/.agent/plugins/poster-plugin/src/templates/social.js +62 -0
- package/.agent/plugins/poster-plugin/src/templates/tech.js +84 -0
- package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/index.js +1 -1
- package/.agent/plugins.json +5 -11
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/rules/allow-rule.md +77 -0
- package/.agent/rules/log-rule.md +83 -0
- package/.agent/rules/security-rule.md +93 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/sessions/cli_default.json +419 -0
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +2195 -0
- package/.agent/skills/api-patterns/SKILL.md +81 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +55 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/clean-code/SKILL.md +201 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +311 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +237 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +81 -0
- package/.agent/workflows/simple-test.md +42 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/structured-orchestrate.md +180 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/.claude/settings.local.json +20 -8
- package/.env.example +56 -56
- package/CLAUDE.md +144 -108
- package/README.md +441 -441
- package/calc_tokens_weixin.js +81 -0
- package/cli/src/commands/chat.js +2 -1
- package/docs/CONTEXT_DESIGN.md +1596 -0
- package/examples/test-concurrent-chat.js +60 -60
- package/foliko-creative-3.png +0 -0
- package/foliko-creative-4.png +0 -0
- package/foliko-creative-5.png +0 -0
- package/package.json +2 -2
- package/plugins/default-plugins.js +2 -1
- package/plugins/extension-executor-plugin.js +91 -2
- package/plugins/file-system-plugin.js +2 -2
- package/plugins/memory-plugin.js +984 -0
- package/plugins/session-plugin.js +57 -1
- package/plugins/weixin-plugin.js +33 -23
- package/skills/find-skills/AGENTS.md +162 -162
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/poster-guide/SKILL.md +1059 -0
- package/skills/python-plugin-dev/SKILL.md +238 -238
- package/skills/skill-guide/SKILL.md +130 -108
- package/src/capabilities/skill-manager.js +99 -0
- package/src/core/agent-chat.js +620 -141
- package/src/core/agent-context.js +188 -0
- package/src/core/agent.js +6 -2
- package/src/core/context-manager.js +283 -0
- package/src/core/framework.js +264 -3
- package/src/core/plugin-manager.js +79 -2
- package/src/core/request-context.js +98 -0
- package/src/core/session-context.js +341 -0
- package/src/core/session-storage.js +274 -0
- package/src/executors/mcp-executor.js +2 -2
- package/src/utils/index.js +239 -67
- package/src/utils/plugin-helpers.js +17 -0
- package/story-cover-book-v2.png +0 -0
- package/story-cover-japanese-1.png +0 -0
- package/story-cover-japanese-2.png +0 -0
- package/story-cover-japanese-3.png +0 -0
- package/story-cover-moran.png +0 -0
- package/undefined.png +0 -0
- package//346/265/267/346/212/245/346/217/222/344/273/266.md +621 -0
- package/.agent/agents/code-assistant.json +0 -14
- package/.agent/agents/email-assistant.json +0 -14
- package/.agent/agents/file-assistant.json +0 -15
- package/.agent/agents/system-assistant.json +0 -15
- package/.agent/agents/web-assistant.json +0 -12
- package/.agent/data/ambient/goals.json +0 -50
- package/.agent/data/ambient/memories.json +0 -7
- package/.agent/data/scheduler/tasks.json +0 -1
- package/.agent/package.json +0 -8
- package/.agent/plugins/__pycache__/test_plugin.cpython-312.pyc +0 -0
- package/.agent/plugins/daytona/README.md +0 -89
- package/.agent/plugins/daytona/index.js +0 -377
- package/.agent/plugins/daytona/package.json +0 -12
- package/.agent/plugins/marknative/README.md +0 -134
- package/.agent/plugins/marknative/index.js +0 -228
- package/.agent/plugins/marknative/package.json +0 -12
- package/.agent/plugins/marknative/update-readme.js +0 -134
- package/.agent/plugins/system-info/index.js +0 -387
- package/.agent/plugins/system-info/package.json +0 -4
- package/.agent/plugins/system-info/test.js +0 -40
- package/.agent/plugins/temp-repo/LICENSE +0 -201
- package/.agent/plugins/test_plugin.py +0 -304
- package/.agent/python-scripts/test_sample.py +0 -24
- package/.agent/skills/agent-browser/SKILL.md +0 -311
- package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
- package/.agent/skills/sysinfo/SKILL.md +0 -38
- package/.agent/skills/sysinfo/system-info.sh +0 -130
- package/.agent/skills/workflow/SKILL.md +0 -324
- package/.agent/workflows/email-digest.json +0 -50
- package/.agent/workflows/file-backup.json +0 -21
- package/.agent/workflows/get-ip-notify.json +0 -32
- package/.agent/workflows/news-aggregator.json +0 -93
- package/.agent/workflows/news-dashboard-v2.json +0 -94
- package/.agent/workflows/notification-batch.json +0 -32
- package/examples/test-chat-debug.js +0 -102
- package/examples/test-chat-result.js +0 -76
- package/examples/test-chat-stream-diff.js +0 -63
- /package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/README.md +0 -0
- /package/.agent/plugins/{temp-repo/puppeteer-plugin → puppeteer-plugin}/package.json +0 -0
package/src/core/agent-chat.js
CHANGED
|
@@ -7,6 +7,23 @@ const { EventEmitter } = require('../utils/event-emitter');
|
|
|
7
7
|
const { logger } = require('../utils/logger');
|
|
8
8
|
const { generateText, stepCountIs } = require('ai');
|
|
9
9
|
const { prepareMessagesForAPI } = require('../utils');
|
|
10
|
+
const fs = require('fs/promises');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 从消息中提取 toolCallId(兼容两种格式)
|
|
14
|
+
* 格式1: msg.toolCallId (旧格式)
|
|
15
|
+
* 格式2: msg.content[0].toolCallId (AI SDK CoreMessage 格式)
|
|
16
|
+
* @param {Object} msg - 消息对象
|
|
17
|
+
* @returns {string|null}
|
|
18
|
+
*/
|
|
19
|
+
function extractToolCallId(msg) {
|
|
20
|
+
if (msg.toolCallId) return msg.toolCallId;
|
|
21
|
+
if (Array.isArray(msg.content) && msg.content.length > 0) {
|
|
22
|
+
const firstContent = msg.content[0];
|
|
23
|
+
if (firstContent && firstContent.toolCallId) return firstContent.toolCallId;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
10
27
|
|
|
11
28
|
// 模型上下文限制表(留 15-20% 余量给 system prompt 和输出)
|
|
12
29
|
const MODEL_CONTEXT_LIMITS = {
|
|
@@ -14,7 +31,7 @@ const MODEL_CONTEXT_LIMITS = {
|
|
|
14
31
|
'deepseek-chat': 28000,
|
|
15
32
|
'deepseek-coder': 28000,
|
|
16
33
|
// MiniMax
|
|
17
|
-
'MiniMax-M2.7':
|
|
34
|
+
'MiniMax-M2.7': 130800,
|
|
18
35
|
// OpenAI
|
|
19
36
|
'gpt-4': 100000,
|
|
20
37
|
'gpt-4o': 100000,
|
|
@@ -76,7 +93,7 @@ class SimpleTokenizer {
|
|
|
76
93
|
const _globalTokenizer = new SimpleTokenizer();
|
|
77
94
|
|
|
78
95
|
// 压缩超时时间(毫秒)
|
|
79
|
-
const COMPRESSION_TIMEOUT =
|
|
96
|
+
const COMPRESSION_TIMEOUT = 120000; // 2分钟
|
|
80
97
|
|
|
81
98
|
class AgentChatHandler extends EventEmitter {
|
|
82
99
|
/**
|
|
@@ -96,7 +113,8 @@ class AgentChatHandler extends EventEmitter {
|
|
|
96
113
|
this.providerOptions = config.providerOptions || {};
|
|
97
114
|
|
|
98
115
|
this._systemPrompt = config.systemPrompt || 'You are a helpful assistant.';
|
|
99
|
-
this._messages = [];
|
|
116
|
+
// this._messages = []; // 移除共享消息存储!改用 Per-Session
|
|
117
|
+
this._sessionMessageStores = new Map(); // sessionId → { messages: [], historyLoaded: false, compressionState: {} }
|
|
100
118
|
this._tools = new Map();
|
|
101
119
|
this._maxSteps = 20; // 降低默认步骤数,减少上下文消耗
|
|
102
120
|
|
|
@@ -127,10 +145,58 @@ class AgentChatHandler extends EventEmitter {
|
|
|
127
145
|
// Per-Session 队列管理
|
|
128
146
|
this._sessionQueues = new Map(); // sessionId -> Queue of {message, options, resolve, reject}
|
|
129
147
|
this._processingSessions = new Set(); // sessionId -> processing flag
|
|
148
|
+
|
|
149
|
+
// Session Memory prepareStep(从 memory 插件获取)
|
|
150
|
+
this._sessionMemoryPrepareStep = null;
|
|
151
|
+
if (agent?.framework) {
|
|
152
|
+
const memoryPlugin = agent.framework.pluginManager.get('memory');
|
|
153
|
+
if (memoryPlugin?.instance?.getPrepareStep) {
|
|
154
|
+
this._sessionMemoryPrepareStep = memoryPlugin.instance.getPrepareStep();
|
|
155
|
+
logger.debug('Session memory prepareStep loaded');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 获取 Per-Session 的消息存储
|
|
162
|
+
* @param {string} sessionId - 会话 ID
|
|
163
|
+
* @returns {Object} 消息存储对象
|
|
164
|
+
* @private
|
|
165
|
+
*/
|
|
166
|
+
_getSessionMessageStore(sessionId) {
|
|
167
|
+
if (!sessionId) {
|
|
168
|
+
// 无 session 的单次请求,使用临时存储
|
|
169
|
+
return { messages: [], historyLoaded: false, compressionState: {}, save: () => {} };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (!this._sessionMessageStores.has(sessionId)) {
|
|
173
|
+
// 自动从 session 加载消息
|
|
174
|
+
const savedMessages = this._loadHistoryFromSession(sessionId);
|
|
175
|
+
this._sessionMessageStores.set(sessionId, {
|
|
176
|
+
sessionId, // 保存 sessionId 引用
|
|
177
|
+
messages: savedMessages,
|
|
178
|
+
historyLoaded: true, // 已加载
|
|
179
|
+
compressionState: {
|
|
180
|
+
lastCompressedAt: null,
|
|
181
|
+
lastTokenCount: 0,
|
|
182
|
+
count: 0,
|
|
183
|
+
},
|
|
184
|
+
save: () => {
|
|
185
|
+
// 简洁的保存方法
|
|
186
|
+
if (this.agent?.framework) {
|
|
187
|
+
const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
|
|
188
|
+
if (sessionCtx) {
|
|
189
|
+
sessionCtx.replaceMessages(this._sessionMessageStores.get(sessionId).messages);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
return this._sessionMessageStores.get(sessionId);
|
|
130
196
|
}
|
|
131
197
|
|
|
132
198
|
/**
|
|
133
|
-
* 从
|
|
199
|
+
* 从 SessionContext 加载聊天历史
|
|
134
200
|
* @param {string} sessionId - 会话 ID
|
|
135
201
|
* @returns {Array} 消息数组
|
|
136
202
|
* @private
|
|
@@ -139,11 +205,10 @@ class AgentChatHandler extends EventEmitter {
|
|
|
139
205
|
if (!sessionId || !this.agent?.framework) return [];
|
|
140
206
|
|
|
141
207
|
try {
|
|
142
|
-
const
|
|
143
|
-
if (!
|
|
208
|
+
const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
|
|
209
|
+
if (!sessionCtx) return [];
|
|
144
210
|
|
|
145
|
-
const messages =
|
|
146
|
-
// 直接返回,保存时已清理,数据应该是干净的
|
|
211
|
+
const messages = sessionCtx.getMessages();
|
|
147
212
|
return messages || [];
|
|
148
213
|
} catch (err) {
|
|
149
214
|
// 忽略加载错误
|
|
@@ -152,7 +217,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
152
217
|
}
|
|
153
218
|
|
|
154
219
|
/**
|
|
155
|
-
* 保存聊天历史到
|
|
220
|
+
* 保存聊天历史到 SessionContext
|
|
156
221
|
* @param {string} sessionId - 会话 ID
|
|
157
222
|
* @param {Array} messages - 消息数组
|
|
158
223
|
* @private
|
|
@@ -161,12 +226,12 @@ class AgentChatHandler extends EventEmitter {
|
|
|
161
226
|
if (!sessionId || !this.agent?.framework || !messages) return;
|
|
162
227
|
|
|
163
228
|
try {
|
|
164
|
-
const
|
|
165
|
-
if (!
|
|
229
|
+
const sessionCtx = this.agent.framework.getOrCreateSessionContext(sessionId);
|
|
230
|
+
if (!sessionCtx) return;
|
|
166
231
|
|
|
167
232
|
// 清理消息格式后整体替换
|
|
168
233
|
const cleanedMessages = prepareMessagesForAPI(messages);
|
|
169
|
-
|
|
234
|
+
sessionCtx.replaceMessages(cleanedMessages);
|
|
170
235
|
} catch (err) {
|
|
171
236
|
// 忽略保存错误
|
|
172
237
|
}
|
|
@@ -269,9 +334,48 @@ class AgentChatHandler extends EventEmitter {
|
|
|
269
334
|
total += this._countTokens(msg.content);
|
|
270
335
|
} else if (Array.isArray(msg.content)) {
|
|
271
336
|
for (const part of msg.content) {
|
|
272
|
-
if (part
|
|
337
|
+
if (!part || typeof part !== 'object') continue;
|
|
338
|
+
|
|
339
|
+
// 处理普通文本
|
|
340
|
+
if (part.text) {
|
|
273
341
|
total += this._countTokens(String(part.text));
|
|
274
342
|
}
|
|
343
|
+
|
|
344
|
+
// 处理 tool-result 的 output 字段
|
|
345
|
+
if (part.type === 'tool-result' && part.output) {
|
|
346
|
+
if (typeof part.output === 'string') {
|
|
347
|
+
total += this._countTokens(part.output);
|
|
348
|
+
} else if (part.output && typeof part.output === 'object') {
|
|
349
|
+
// 处理 { type: 'text', value: '...' } 或 { type: 'text', html: '...' } 等
|
|
350
|
+
if (part.output.value) {
|
|
351
|
+
total += this._countTokens(String(part.output.value));
|
|
352
|
+
}
|
|
353
|
+
if (part.output.html) {
|
|
354
|
+
total += this._countTokens(part.output.html);
|
|
355
|
+
}
|
|
356
|
+
// 其他字段也序列化计算
|
|
357
|
+
try {
|
|
358
|
+
const others = {};
|
|
359
|
+
for (const [k, v] of Object.entries(part.output)) {
|
|
360
|
+
if (k !== 'value' && k !== 'html') others[k] = v;
|
|
361
|
+
}
|
|
362
|
+
if (Object.keys(others).length > 0) {
|
|
363
|
+
total += this._countTokens(JSON.stringify(others));
|
|
364
|
+
}
|
|
365
|
+
} catch (e) {
|
|
366
|
+
// 忽略
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 处理 tool-use 的 input 字段
|
|
372
|
+
if (part.type === 'tool_use' && part.input) {
|
|
373
|
+
try {
|
|
374
|
+
total += this._countTokens(JSON.stringify(part.input));
|
|
375
|
+
} catch (e) {
|
|
376
|
+
// 忽略
|
|
377
|
+
}
|
|
378
|
+
}
|
|
275
379
|
}
|
|
276
380
|
} else if (msg.content && typeof msg.content === 'object') {
|
|
277
381
|
// 处理工具结果等对象类型
|
|
@@ -287,16 +391,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
287
391
|
return total;
|
|
288
392
|
}
|
|
289
393
|
|
|
290
|
-
/**
|
|
291
|
-
* 检查是否需要压缩上下文
|
|
292
|
-
* @returns {boolean}
|
|
293
|
-
* @private
|
|
294
|
-
*/
|
|
295
|
-
_shouldCompress() {
|
|
296
|
-
const totalTokens = this._countMessagesTokens(this._messages);
|
|
297
|
-
return totalTokens > this._maxContextTokens * this._compressionThreshold;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
394
|
/**
|
|
301
395
|
* 计算工具定义的 token 数(估算)
|
|
302
396
|
* @returns {number}
|
|
@@ -321,21 +415,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
321
415
|
return total;
|
|
322
416
|
}
|
|
323
417
|
|
|
324
|
-
/**
|
|
325
|
-
* 检查是否需要压缩(包括工具定义)
|
|
326
|
-
* @returns {boolean}
|
|
327
|
-
* @private
|
|
328
|
-
*/
|
|
329
|
-
_shouldCompressWithTools() {
|
|
330
|
-
const messagesTokens = this._countMessagesTokens(this._messages);
|
|
331
|
-
const toolsTokens = this._countToolsTokens();
|
|
332
|
-
const systemPromptTokens = this._countTokens(this._systemPrompt);
|
|
333
|
-
const total = messagesTokens + toolsTokens + systemPromptTokens;
|
|
334
|
-
|
|
335
|
-
// 如果总token数超过上下文限制的 85%,就压缩
|
|
336
|
-
return total > this._maxContextTokens * 0.85;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
418
|
/**
|
|
340
419
|
* 压缩上下文消息(智能摘要模式,支持超时控制)
|
|
341
420
|
* 策略:
|
|
@@ -343,23 +422,29 @@ class AgentChatHandler extends EventEmitter {
|
|
|
343
422
|
* 2. 否则使用简单裁剪 + 标记
|
|
344
423
|
* 3. 支持超时控制,防止压缩阻塞太久
|
|
345
424
|
* @param {string} sessionId - 会话 ID(用于压缩后同步)
|
|
425
|
+
* @param {Array} messages - 消息数组引用
|
|
426
|
+
* @param {Object} messageStore - 消息存储对象
|
|
346
427
|
* @private
|
|
347
428
|
*/
|
|
348
|
-
async _compressContext(sessionId) {
|
|
429
|
+
async _compressContext(sessionId, messages, messageStore) {
|
|
349
430
|
// 如果已经有压缩在进行,返回现有的 Promise
|
|
350
431
|
if (this._compressionInProgress && this._compressionPromise) {
|
|
351
432
|
logger.debug('Compression already in progress, waiting for it...');
|
|
352
433
|
return this._compressionPromise;
|
|
353
434
|
}
|
|
354
435
|
|
|
355
|
-
if (
|
|
436
|
+
if (messages.length <= this._keepRecentMessages) {
|
|
356
437
|
return;
|
|
357
438
|
}
|
|
358
439
|
|
|
359
440
|
this._compressionInProgress = true;
|
|
360
441
|
|
|
361
442
|
// 创建压缩 Promise,带超时控制
|
|
362
|
-
this._compressionPromise = this._executeCompressionWithTimeout(
|
|
443
|
+
this._compressionPromise = this._executeCompressionWithTimeout(
|
|
444
|
+
sessionId,
|
|
445
|
+
messages,
|
|
446
|
+
messageStore
|
|
447
|
+
).finally(() => {
|
|
363
448
|
this._compressionInProgress = false;
|
|
364
449
|
this._compressionPromise = null;
|
|
365
450
|
});
|
|
@@ -370,15 +455,20 @@ class AgentChatHandler extends EventEmitter {
|
|
|
370
455
|
/**
|
|
371
456
|
* 执行压缩(带超时)
|
|
372
457
|
* @param {string} sessionId - 会话 ID
|
|
458
|
+
* @param {Array} messages - 消息数组引用
|
|
459
|
+
* @param {Object} messageStore - 消息存储对象
|
|
373
460
|
* @private
|
|
374
461
|
*/
|
|
375
|
-
async _executeCompressionWithTimeout(sessionId) {
|
|
462
|
+
async _executeCompressionWithTimeout(sessionId, messages, messageStore) {
|
|
376
463
|
try {
|
|
377
|
-
return await Promise.race([
|
|
464
|
+
return await Promise.race([
|
|
465
|
+
this._doCompress(sessionId, messages, messageStore),
|
|
466
|
+
this._createTimeoutPromise(),
|
|
467
|
+
]);
|
|
378
468
|
} catch (err) {
|
|
379
469
|
logger.warn('Compression failed:', err.message);
|
|
380
470
|
// 压缩失败时使用简单的截断策略
|
|
381
|
-
this._simpleCompress(sessionId);
|
|
471
|
+
this._simpleCompress(sessionId, messages, messageStore);
|
|
382
472
|
}
|
|
383
473
|
}
|
|
384
474
|
|
|
@@ -416,11 +506,13 @@ class AgentChatHandler extends EventEmitter {
|
|
|
416
506
|
/**
|
|
417
507
|
* 执行实际压缩逻辑
|
|
418
508
|
* @param {string} sessionId - 会话 ID
|
|
509
|
+
* @param {Array} messages - 消息数组引用
|
|
510
|
+
* @param {Object} messageStore - 消息存储对象
|
|
419
511
|
* @private
|
|
420
512
|
*/
|
|
421
|
-
async _doCompress(sessionId) {
|
|
422
|
-
const systemMessages =
|
|
423
|
-
const otherMessages =
|
|
513
|
+
async _doCompress(sessionId, messages, messageStore) {
|
|
514
|
+
const systemMessages = messages.filter((m) => m.role === 'system');
|
|
515
|
+
const otherMessages = messages.filter((m) => m.role !== 'system');
|
|
424
516
|
|
|
425
517
|
// 保留最近的 N 条非系统消息
|
|
426
518
|
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
@@ -448,29 +540,120 @@ class AgentChatHandler extends EventEmitter {
|
|
|
448
540
|
content: summaryContent,
|
|
449
541
|
};
|
|
450
542
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
);
|
|
543
|
+
// 构建保留的消息,确保 tool call 和 tool result 配对不被分离
|
|
544
|
+
const assistantToolCalls = new Map(); // toolCallId -> assistant message index
|
|
545
|
+
const toolResults = new Map(); // toolCallId -> tool result indices
|
|
546
|
+
|
|
547
|
+
// 第一遍:找出所有 tool call 和它们的 results
|
|
548
|
+
for (let i = 0; i < recentMessages.length; i++) {
|
|
549
|
+
const msg = recentMessages[i];
|
|
550
|
+
if (msg.role === 'assistant' && msg.content) {
|
|
551
|
+
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
552
|
+
for (const item of content) {
|
|
553
|
+
if (item.type === 'tool-call' && item.toolCallId) {
|
|
554
|
+
assistantToolCalls.set(item.toolCallId, i);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
559
|
+
// 遍历 tool message 中的所有 tool-results
|
|
560
|
+
for (const item of msg.content) {
|
|
561
|
+
if (item && item.type === 'tool-result' && item.toolCallId) {
|
|
562
|
+
if (!toolResults.has(item.toolCallId)) {
|
|
563
|
+
toolResults.set(item.toolCallId, []);
|
|
564
|
+
}
|
|
565
|
+
toolResults.get(item.toolCallId).push(i);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// 第二遍:找出哪些 tool call 没有对应的 result(需要保留)
|
|
572
|
+
const orphanToolCalls = new Set();
|
|
573
|
+
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
574
|
+
if (!toolResults.has(toolCallId)) {
|
|
575
|
+
orphanToolCalls.add(toolCallId);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// 过滤时保留:有 result 的 tool call,以及没有 result 的 tool call(但要附带其 result)
|
|
580
|
+
const indicesToKeep = new Set();
|
|
581
|
+
for (let i = 0; i < recentMessages.length; i++) {
|
|
582
|
+
const msg = recentMessages[i];
|
|
583
|
+
if (msg.role === 'tool') {
|
|
584
|
+
// 保留所有 tool result(它们是必要的)
|
|
585
|
+
indicesToKeep.add(i);
|
|
586
|
+
} else if (msg.role === 'assistant') {
|
|
587
|
+
// 检查这个 assistant 消息是否有 tool call
|
|
588
|
+
let hasToolCall = false;
|
|
589
|
+
if (msg.content) {
|
|
590
|
+
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
591
|
+
for (const item of content) {
|
|
592
|
+
if (item.type === 'tool-call' && item.toolCallId) {
|
|
593
|
+
hasToolCall = true;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (hasToolCall) {
|
|
599
|
+
indicesToKeep.add(i);
|
|
600
|
+
} else if (i >= recentMessages.length - 3) {
|
|
601
|
+
// 保留最近几条 assistant 消息(它们可能包含重要上下文)
|
|
602
|
+
indicesToKeep.add(i);
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
// user, system 等角色默认保留
|
|
606
|
+
indicesToKeep.add(i);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
|
|
611
|
+
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
612
|
+
if (!toolResults.has(toolCallId)) {
|
|
613
|
+
indicesToKeep.add(assistantIdx);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 按索引排序并重建消息数组
|
|
618
|
+
const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
|
|
619
|
+
const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);
|
|
620
|
+
|
|
621
|
+
// 清理孤立 tool results(tool call 已被总结,但 result 还在)
|
|
622
|
+
this._cleanOrphanedToolResults(filteredRecentMessages);
|
|
623
|
+
|
|
624
|
+
// 直接修改传入的 messages 数组
|
|
625
|
+
messages.length = 0;
|
|
626
|
+
messages.push(...systemMessages, summary, ...filteredRecentMessages);
|
|
627
|
+
|
|
628
|
+
// 更新压缩状态
|
|
454
629
|
this._compressionCount++;
|
|
455
|
-
|
|
630
|
+
if (messageStore.compressionState) {
|
|
631
|
+
messageStore.compressionState.count++;
|
|
632
|
+
messageStore.compressionState.lastCompressedAt = Date.now();
|
|
633
|
+
messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const totalTokens = this._countMessagesTokens(messages);
|
|
456
637
|
logger.info(
|
|
457
|
-
`Context compressed (${this._compressionCount} times). Messages: ${
|
|
638
|
+
`Context compressed (${this._compressionCount} times). Messages: ${messages.length}, Est. tokens: ${totalTokens}`
|
|
458
639
|
);
|
|
459
640
|
|
|
460
641
|
// 压缩后立即同步到 session
|
|
461
642
|
if (sessionId) {
|
|
462
|
-
this._saveHistoryToSession(sessionId,
|
|
643
|
+
this._saveHistoryToSession(sessionId, messages);
|
|
463
644
|
}
|
|
464
645
|
}
|
|
465
646
|
|
|
466
647
|
/**
|
|
467
648
|
* 简单压缩(当 AI 压缩失败时使用)
|
|
468
649
|
* @param {string} sessionId - 会话 ID
|
|
650
|
+
* @param {Array} messages - 消息数组引用
|
|
651
|
+
* @param {Object} messageStore - 消息存储对象
|
|
469
652
|
* @private
|
|
470
653
|
*/
|
|
471
|
-
_simpleCompress(sessionId) {
|
|
472
|
-
const systemMessages =
|
|
473
|
-
const otherMessages =
|
|
654
|
+
_simpleCompress(sessionId, messages, messageStore) {
|
|
655
|
+
const systemMessages = messages.filter((m) => m.role === 'system');
|
|
656
|
+
const otherMessages = messages.filter((m) => m.role !== 'system');
|
|
474
657
|
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
475
658
|
const compressedCount = otherMessages.length - this._keepRecentMessages;
|
|
476
659
|
|
|
@@ -481,18 +664,137 @@ class AgentChatHandler extends EventEmitter {
|
|
|
481
664
|
content: summaryContent,
|
|
482
665
|
};
|
|
483
666
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
);
|
|
667
|
+
// 构建保留的消息,确保 tool call 和 tool result 配对不被分离
|
|
668
|
+
const assistantToolCalls = new Map(); // toolCallId -> assistant message index
|
|
669
|
+
const toolResults = new Map(); // toolCallId -> tool result indices
|
|
670
|
+
|
|
671
|
+
// 第一遍:找出所有 tool call 和它们的 results
|
|
672
|
+
for (let i = 0; i < recentMessages.length; i++) {
|
|
673
|
+
const msg = recentMessages[i];
|
|
674
|
+
if (msg.role === 'assistant' && msg.content) {
|
|
675
|
+
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
676
|
+
for (const item of content) {
|
|
677
|
+
if (item.type === 'tool-call' && item.toolCallId) {
|
|
678
|
+
assistantToolCalls.set(item.toolCallId, i);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
683
|
+
// 遍历 tool message 中的所有 tool-results
|
|
684
|
+
for (const item of msg.content) {
|
|
685
|
+
if (item && item.type === 'tool-result' && item.toolCallId) {
|
|
686
|
+
if (!toolResults.has(item.toolCallId)) {
|
|
687
|
+
toolResults.set(item.toolCallId, []);
|
|
688
|
+
}
|
|
689
|
+
toolResults.get(item.toolCallId).push(i);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// 第二遍:找出哪些 tool call 没有对应的 result(需要保留)
|
|
696
|
+
const orphanToolCalls = new Set();
|
|
697
|
+
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
698
|
+
if (!toolResults.has(toolCallId)) {
|
|
699
|
+
orphanToolCalls.add(toolCallId);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// 过滤时保留:有 result 的 tool call,以及没有 result 的 tool call
|
|
704
|
+
const indicesToKeep = new Set();
|
|
705
|
+
for (let i = 0; i < recentMessages.length; i++) {
|
|
706
|
+
const msg = recentMessages[i];
|
|
707
|
+
if (msg.role === 'tool') {
|
|
708
|
+
// 保留所有 tool result
|
|
709
|
+
indicesToKeep.add(i);
|
|
710
|
+
} else if (msg.role === 'assistant') {
|
|
711
|
+
let hasToolCall = false;
|
|
712
|
+
if (msg.content) {
|
|
713
|
+
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
714
|
+
for (const item of content) {
|
|
715
|
+
if (item.type === 'tool-call' && item.toolCallId) {
|
|
716
|
+
hasToolCall = true;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
if (hasToolCall) {
|
|
722
|
+
indicesToKeep.add(i);
|
|
723
|
+
} else if (i >= recentMessages.length - 3) {
|
|
724
|
+
// 保留最近几条 assistant 消息
|
|
725
|
+
indicesToKeep.add(i);
|
|
726
|
+
}
|
|
727
|
+
} else {
|
|
728
|
+
// user, system 等角色默认保留
|
|
729
|
+
indicesToKeep.add(i);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// 如果有孤儿的 tool call(没有 result),保留该 assistant 消息
|
|
734
|
+
for (const [toolCallId, assistantIdx] of assistantToolCalls) {
|
|
735
|
+
if (!toolResults.has(toolCallId)) {
|
|
736
|
+
indicesToKeep.add(assistantIdx);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// 按索引排序并重建消息数组
|
|
741
|
+
const sortedIndices = Array.from(indicesToKeep).sort((a, b) => a - b);
|
|
742
|
+
const filteredRecentMessages = sortedIndices.map((i) => recentMessages[i]);
|
|
743
|
+
|
|
744
|
+
// 清理孤立 tool results(tool call 已被总结,但 result 还在)
|
|
745
|
+
this._cleanOrphanedToolResults(filteredRecentMessages);
|
|
746
|
+
|
|
747
|
+
// 直接修改传入的 messages 数组
|
|
748
|
+
messages.length = 0;
|
|
749
|
+
messages.push(...systemMessages, summary, ...filteredRecentMessages);
|
|
750
|
+
|
|
751
|
+
// 更新压缩状态
|
|
487
752
|
this._compressionCount++;
|
|
753
|
+
if (messageStore.compressionState) {
|
|
754
|
+
messageStore.compressionState.count++;
|
|
755
|
+
messageStore.compressionState.lastCompressedAt = Date.now();
|
|
756
|
+
messageStore.compressionState.lastTokenCount = this._countMessagesTokens(messages);
|
|
757
|
+
}
|
|
488
758
|
|
|
489
759
|
logger.info(
|
|
490
|
-
`Context simple compressed (${this._compressionCount} times). Messages: ${
|
|
760
|
+
`Context simple compressed (${this._compressionCount} times). Messages: ${messages.length}`
|
|
491
761
|
);
|
|
492
762
|
|
|
493
763
|
// 压缩后立即同步到 session
|
|
494
764
|
if (sessionId) {
|
|
495
|
-
this._saveHistoryToSession(sessionId,
|
|
765
|
+
this._saveHistoryToSession(sessionId, messages);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* 清理孤立的 tool results(对应的 tool call 已被压缩删除)
|
|
771
|
+
* @param {Array} messages - 消息数组
|
|
772
|
+
* @private
|
|
773
|
+
*/
|
|
774
|
+
_cleanOrphanedToolResults(messages) {
|
|
775
|
+
// 构建当前消息中存在的 toolCallId 集合
|
|
776
|
+
const validToolCallIds = new Set();
|
|
777
|
+
for (const msg of messages) {
|
|
778
|
+
if (msg.role === 'assistant' && msg.content) {
|
|
779
|
+
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
780
|
+
for (const item of content) {
|
|
781
|
+
if (item.type === 'tool-call' && item.toolCallId) {
|
|
782
|
+
validToolCallIds.add(item.toolCallId);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// 过滤掉孤立 tool results
|
|
789
|
+
for (const msg of messages) {
|
|
790
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
791
|
+
msg.content = msg.content.filter((item) => {
|
|
792
|
+
if (item && item.type === 'tool-result' && item.toolCallId) {
|
|
793
|
+
return validToolCallIds.has(item.toolCallId);
|
|
794
|
+
}
|
|
795
|
+
return true;
|
|
796
|
+
});
|
|
797
|
+
}
|
|
496
798
|
}
|
|
497
799
|
}
|
|
498
800
|
|
|
@@ -677,23 +979,34 @@ ${truncatedContent}${truncatedNote}
|
|
|
677
979
|
* @param {string} sessionId - 可选,指定 session 则同时清除 session 中的历史
|
|
678
980
|
*/
|
|
679
981
|
clearHistory(sessionId) {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
982
|
+
if (sessionId) {
|
|
983
|
+
// 清除 Per-Session 消息存储
|
|
984
|
+
const messageStore = this._sessionMessageStores.get(sessionId);
|
|
985
|
+
if (messageStore) {
|
|
986
|
+
messageStore.messages = [];
|
|
987
|
+
messageStore.historyLoaded = false;
|
|
988
|
+
messageStore.compressionState = {
|
|
989
|
+
lastCompressedAt: null,
|
|
990
|
+
lastTokenCount: 0,
|
|
991
|
+
count: 0,
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
// 清除 SessionContext 中的历史
|
|
995
|
+
if (this.agent?.framework) {
|
|
996
|
+
try {
|
|
997
|
+
const sessionCtx = this.agent.framework.getSessionContext(sessionId);
|
|
998
|
+
if (sessionCtx) {
|
|
999
|
+
sessionCtx.clearMessages();
|
|
691
1000
|
}
|
|
1001
|
+
} catch (err) {
|
|
1002
|
+
// 忽略错误
|
|
692
1003
|
}
|
|
693
|
-
} catch (err) {
|
|
694
|
-
// 忽略错误
|
|
695
1004
|
}
|
|
1005
|
+
} else {
|
|
1006
|
+
// 清除所有 Per-Session 消息存储
|
|
1007
|
+
this._sessionMessageStores.clear();
|
|
696
1008
|
}
|
|
1009
|
+
this._compressionCount = 0;
|
|
697
1010
|
return this;
|
|
698
1011
|
}
|
|
699
1012
|
|
|
@@ -739,19 +1052,13 @@ ${truncatedContent}${truncatedNote}
|
|
|
739
1052
|
*/
|
|
740
1053
|
async _doChat(message, options = {}) {
|
|
741
1054
|
const sessionId = options.sessionId || null;
|
|
742
|
-
const context = { sessionId, isStream: false };
|
|
743
1055
|
const framework = this.agent.framework;
|
|
744
1056
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
const savedMessages = this._loadHistoryFromSession(sessionId);
|
|
749
|
-
if (savedMessages.length > 0) {
|
|
750
|
-
this._messages = savedMessages;
|
|
751
|
-
logger.info(`Loaded ${savedMessages.length} messages from session ${sessionId}`);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
1057
|
+
// 获取 Per-Session 的消息存储(自动从 session 加载)
|
|
1058
|
+
const messageStore = this._getSessionMessageStore(sessionId);
|
|
1059
|
+
const messages = messageStore.messages;
|
|
754
1060
|
|
|
1061
|
+
try {
|
|
755
1062
|
// 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
|
|
756
1063
|
// 解决上下文过长时 LLM 不调用工具的问题
|
|
757
1064
|
this._systemPrompt = this.agent._buildSystemPrompt();
|
|
@@ -760,11 +1067,10 @@ ${truncatedContent}${truncatedNote}
|
|
|
760
1067
|
|
|
761
1068
|
const userMessage =
|
|
762
1069
|
typeof message === 'string' ? { role: 'user', content: message } : message;
|
|
763
|
-
|
|
764
|
-
this._messages.push(userMessage);
|
|
1070
|
+
messages.push(userMessage);
|
|
765
1071
|
|
|
766
1072
|
// 检查是否需要压缩上下文(包括工具定义)
|
|
767
|
-
const messagesTokens = this._countMessagesTokens(
|
|
1073
|
+
const messagesTokens = this._countMessagesTokens(messages);
|
|
768
1074
|
const toolsTokens = this._countToolsTokens();
|
|
769
1075
|
const systemPromptTokens = this._countTokens(this._systemPrompt);
|
|
770
1076
|
const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
|
|
@@ -774,55 +1080,76 @@ ${truncatedContent}${truncatedNote}
|
|
|
774
1080
|
logger.info(
|
|
775
1081
|
`Context large (${totalTokens}/${this._maxContextTokens} tokens = msgs:${messagesTokens} + tools:${toolsTokens} + sys:${systemPromptTokens}), compressing...`
|
|
776
1082
|
);
|
|
777
|
-
//
|
|
778
|
-
await this._compressContext(sessionId);
|
|
1083
|
+
// 使用带超时控制的压缩(传入 messages 引用)
|
|
1084
|
+
await this._compressContext(sessionId, messages, messageStore);
|
|
779
1085
|
}
|
|
780
1086
|
|
|
781
1087
|
const maxSteps = options.maxSteps || this._maxSteps;
|
|
782
1088
|
const tools = this._getAITools(tool);
|
|
783
1089
|
|
|
1090
|
+
// 准备传给 agent 的消息
|
|
1091
|
+
const prepareStepChainStream = async ({ stepNumber, messages }) => {
|
|
1092
|
+
try {
|
|
1093
|
+
// 1. 验证消息格式
|
|
1094
|
+
if (!Array.isArray(messages)) {
|
|
1095
|
+
logger.warn('prepareStep received non-array messages');
|
|
1096
|
+
return messages;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// 2. 消息数量超过阈值才修剪
|
|
1100
|
+
if (messages.length <= 50) {
|
|
1101
|
+
return messages;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// 3. 保留配对完整的消息
|
|
1105
|
+
const pruned = this._prepareMessagesForAI(messages);
|
|
1106
|
+
return pruned;
|
|
1107
|
+
} catch (err) {
|
|
1108
|
+
logger.error('prepareStep error:', err.message);
|
|
1109
|
+
return messages; // 出错时返回原消息
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
|
|
784
1113
|
if (!this._aiClient) {
|
|
785
1114
|
throw new Error('AI client not configured.');
|
|
786
1115
|
}
|
|
787
1116
|
|
|
788
|
-
//
|
|
789
|
-
// ToolLoopAgent 会自动处理消息格式,不需要手动 prune
|
|
1117
|
+
// 不使用 prepareStep,消息格式已在 _saveHistoryToSession 中处理
|
|
790
1118
|
const agent = new ToolLoopAgent({
|
|
791
1119
|
model: this._aiClient,
|
|
792
1120
|
instructions: this._systemPrompt,
|
|
793
1121
|
tools: tools,
|
|
794
|
-
|
|
795
|
-
prepareStep:
|
|
796
|
-
this._prepareStepForPruning(stepNumber, messages),
|
|
1122
|
+
stopWhen: stepCountIs(30),
|
|
1123
|
+
prepareStep: prepareStepChainStream,
|
|
797
1124
|
});
|
|
798
1125
|
|
|
799
|
-
// 使用
|
|
800
|
-
const result = await framework.
|
|
801
|
-
return agent.generate({ messages
|
|
1126
|
+
// 使用 runInSession 让工具执行时能获取 sessionId(Per-Session 隔离)
|
|
1127
|
+
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
1128
|
+
return agent.generate({ messages, ...this.providerOptions });
|
|
802
1129
|
});
|
|
803
1130
|
|
|
804
|
-
|
|
1131
|
+
messages.push(...result.response.messages);
|
|
1132
|
+
|
|
1133
|
+
// 触发 agent:message 事件,让 memory 插件可以自动提取记忆
|
|
1134
|
+
const userMsg = messages[messages.length - result.response.messages.length - 1];
|
|
1135
|
+
this.emit('message', { userMessage: userMsg, assistantResponse: result.text });
|
|
805
1136
|
|
|
806
1137
|
// 生成后检查:如果消息太长,下次需要压缩
|
|
807
|
-
const afterTokens = this._countMessagesTokens(
|
|
1138
|
+
const afterTokens = this._countMessagesTokens(messages);
|
|
808
1139
|
if (afterTokens > this._maxContextTokens * 0.8) {
|
|
809
1140
|
logger.info(`After generation: ${afterTokens} tokens, will compress on next turn`);
|
|
810
1141
|
}
|
|
811
1142
|
|
|
812
|
-
// 保存聊天历史到 session
|
|
813
|
-
if (sessionId) {
|
|
814
|
-
this._saveHistoryToSession(sessionId, this._messages);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
1143
|
return {
|
|
818
1144
|
success: true,
|
|
819
1145
|
message: result.text || '',
|
|
820
1146
|
stepCount: result.stepCount || 1,
|
|
821
1147
|
};
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1148
|
+
} finally {
|
|
1149
|
+
// 校验并修复消息中的不完整工具调用
|
|
1150
|
+
this._validateToolCalls(messages);
|
|
1151
|
+
// 确保保存聊天历史到 session(无论成功还是失败)
|
|
1152
|
+
messageStore.save();
|
|
826
1153
|
}
|
|
827
1154
|
}
|
|
828
1155
|
|
|
@@ -833,19 +1160,13 @@ ${truncatedContent}${truncatedNote}
|
|
|
833
1160
|
*/
|
|
834
1161
|
async *chatStream(message, options = {}) {
|
|
835
1162
|
const sessionId = options.sessionId || null;
|
|
836
|
-
const context = { sessionId, isStream: true };
|
|
837
1163
|
const framework = this.agent.framework;
|
|
838
1164
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const savedMessages = this._loadHistoryFromSession(sessionId);
|
|
843
|
-
if (savedMessages.length > 0) {
|
|
844
|
-
this._messages = savedMessages;
|
|
845
|
-
logger.info(`Loaded ${savedMessages.length} messages from session ${sessionId}`);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
1165
|
+
// 获取 Per-Session 的消息存储
|
|
1166
|
+
const messageStore = this._getSessionMessageStore(sessionId);
|
|
1167
|
+
const messages = messageStore.messages;
|
|
848
1168
|
|
|
1169
|
+
try {
|
|
849
1170
|
// 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
|
|
850
1171
|
// 解决上下文过长时 LLM 不调用工具的问题
|
|
851
1172
|
this._systemPrompt = this.agent._buildSystemPrompt();
|
|
@@ -854,23 +1175,21 @@ ${truncatedContent}${truncatedNote}
|
|
|
854
1175
|
|
|
855
1176
|
const userMessage =
|
|
856
1177
|
typeof message === 'string' ? { role: 'user', content: message } : message;
|
|
857
|
-
|
|
858
|
-
this._messages.push(userMessage);
|
|
1178
|
+
messages.push(userMessage);
|
|
859
1179
|
|
|
860
1180
|
// 检查是否需要压缩上下文(包括工具定义)
|
|
861
|
-
const messagesTokens = this._countMessagesTokens(
|
|
1181
|
+
const messagesTokens = this._countMessagesTokens(messages);
|
|
862
1182
|
const toolsTokens = this._countToolsTokens();
|
|
863
1183
|
const systemPromptTokens = this._countTokens(this._systemPrompt);
|
|
864
1184
|
const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
|
|
865
1185
|
const limit = this._maxContextTokens * 0.7; // 降低到 70%
|
|
866
|
-
//console.log(messagesTokens,toolsTokens,systemPromptTokens,totalTokens,this._maxContextTokens)
|
|
867
1186
|
// 对于流式调用,如果上下文太大,先压缩再开始
|
|
868
1187
|
if (totalTokens > limit) {
|
|
869
1188
|
logger.info(
|
|
870
1189
|
`Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
|
|
871
1190
|
);
|
|
872
1191
|
// 流式调用时等待压缩完成(使用带超时控制的压缩)
|
|
873
|
-
await this._compressContext(sessionId);
|
|
1192
|
+
await this._compressContext(sessionId, messages, messageStore);
|
|
874
1193
|
}
|
|
875
1194
|
|
|
876
1195
|
const maxSteps = options.maxSteps || this._maxSteps;
|
|
@@ -880,20 +1199,40 @@ ${truncatedContent}${truncatedNote}
|
|
|
880
1199
|
}
|
|
881
1200
|
|
|
882
1201
|
// 准备传给 agent 的消息
|
|
883
|
-
|
|
1202
|
+
const prepareStepChainStream = async ({ stepNumber, messages }) => {
|
|
1203
|
+
try {
|
|
1204
|
+
// 1. 验证消息格式
|
|
1205
|
+
if (!Array.isArray(messages)) {
|
|
1206
|
+
logger.warn('prepareStep received non-array messages');
|
|
1207
|
+
return messages;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// 2. 消息数量超过阈值才修剪
|
|
1211
|
+
if (messages.length <= 50) {
|
|
1212
|
+
return messages;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// 3. 保留配对完整的消息
|
|
1216
|
+
const pruned = this._prepareMessagesForAI(messages);
|
|
1217
|
+
return pruned;
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
logger.error('prepareStep error:', err.message);
|
|
1220
|
+
return messages; // 出错时返回原消息
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1223
|
+
|
|
884
1224
|
const agent = new ToolLoopAgent({
|
|
885
1225
|
model: this._aiClient,
|
|
886
1226
|
instructions: this._systemPrompt,
|
|
887
1227
|
tools: tools,
|
|
888
|
-
|
|
889
|
-
prepareStep:
|
|
890
|
-
this._prepareStepForPruning(stepNumber, messages),
|
|
1228
|
+
stopWhen: stepCountIs(30),
|
|
1229
|
+
prepareStep: prepareStepChainStream,
|
|
891
1230
|
});
|
|
892
1231
|
|
|
893
|
-
// 使用
|
|
894
|
-
const result = await framework.
|
|
1232
|
+
// 使用 runInSession 让工具执行时能获取 sessionId(Per-Session 隔离)
|
|
1233
|
+
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
895
1234
|
return agent.stream({
|
|
896
|
-
messages
|
|
1235
|
+
messages,
|
|
897
1236
|
...this.providerOptions,
|
|
898
1237
|
});
|
|
899
1238
|
});
|
|
@@ -921,18 +1260,106 @@ ${truncatedContent}${truncatedNote}
|
|
|
921
1260
|
}
|
|
922
1261
|
|
|
923
1262
|
const finishMessages = (await result.response).messages;
|
|
924
|
-
|
|
1263
|
+
messages.push(...finishMessages);
|
|
925
1264
|
|
|
926
|
-
//
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
}
|
|
1265
|
+
// 触发 agent:message 事件,让 memory 插件可以自动提取记忆
|
|
1266
|
+
const userMsg = messages[messages.length - finishMessages.length - 1];
|
|
1267
|
+
this.emit('message', { userMessage: userMsg, assistantResponse: fullText });
|
|
930
1268
|
} catch (err) {
|
|
931
1269
|
this.emit('error', { error: err.message });
|
|
932
1270
|
yield { type: 'error', error: err.message };
|
|
1271
|
+
} finally {
|
|
1272
|
+
// 校验并修复消息中的不完整工具调用
|
|
1273
|
+
this._validateToolCalls(messages);
|
|
1274
|
+
// 确保保存聊天历史到 session(无论成功还是失败)
|
|
1275
|
+
messageStore.save();
|
|
933
1276
|
}
|
|
934
1277
|
}
|
|
935
1278
|
|
|
1279
|
+
/**
|
|
1280
|
+
* 为 AI SDK prepareStep 准备消息
|
|
1281
|
+
* 确保 tool call 和 tool result 正确配对
|
|
1282
|
+
* @param {Array} messages - 消息列表
|
|
1283
|
+
* @returns {Array} 过滤后的消息数组
|
|
1284
|
+
* @private
|
|
1285
|
+
*/
|
|
1286
|
+
_prepareMessagesForAI(messages) {
|
|
1287
|
+
if (messages.length <= 10) {
|
|
1288
|
+
return messages;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
// 收集所有 assistant 的 tool-call
|
|
1292
|
+
const assistantToolCalls = new Map(); // toolCallId -> message index
|
|
1293
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1294
|
+
const msg = messages[i];
|
|
1295
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1296
|
+
for (const item of msg.content) {
|
|
1297
|
+
if (item.type === 'tool-call' && item.toolCallId) {
|
|
1298
|
+
assistantToolCalls.set(item.toolCallId, i);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// 收集配对的 tool results
|
|
1305
|
+
const pairedToolResults = new Set(); // 保留的 tool result indices
|
|
1306
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1307
|
+
const msg = messages[i];
|
|
1308
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1309
|
+
for (const item of msg.content) {
|
|
1310
|
+
if (item.type === 'tool-result' && item.toolCallId) {
|
|
1311
|
+
if (assistantToolCalls.has(item.toolCallId)) {
|
|
1312
|
+
pairedToolResults.add(i);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// 过滤:保留最近的消息,但确保 tool call/result 配对完整
|
|
1320
|
+
const recentCount = Math.max(20, messages.length - 10);
|
|
1321
|
+
const minIndexToKeep = messages.length - recentCount;
|
|
1322
|
+
|
|
1323
|
+
const result = [];
|
|
1324
|
+
for (let i = minIndexToKeep; i < messages.length; i++) {
|
|
1325
|
+
const msg = messages[i];
|
|
1326
|
+
|
|
1327
|
+
if (msg.role === 'tool') {
|
|
1328
|
+
// 只保留有配对的 tool messages
|
|
1329
|
+
if (pairedToolResults.has(i)) {
|
|
1330
|
+
result.push(msg);
|
|
1331
|
+
}
|
|
1332
|
+
} else if (msg.role === 'assistant') {
|
|
1333
|
+
// 检查是否有未配对的 tool-call
|
|
1334
|
+
if (Array.isArray(msg.content)) {
|
|
1335
|
+
const hasUnpairedToolCall = msg.content.some(
|
|
1336
|
+
(item) => item.type === 'tool-call' && !assistantToolCalls.has(item.toolCallId)
|
|
1337
|
+
);
|
|
1338
|
+
if (hasUnpairedToolCall) {
|
|
1339
|
+
// 过滤掉未配对的 tool-call
|
|
1340
|
+
const filteredContent = msg.content.filter((item) => {
|
|
1341
|
+
if (item.type === 'tool-call') {
|
|
1342
|
+
return assistantToolCalls.has(item.toolCallId);
|
|
1343
|
+
}
|
|
1344
|
+
return true;
|
|
1345
|
+
});
|
|
1346
|
+
if (filteredContent.length > 0) {
|
|
1347
|
+
result.push({ ...msg, content: filteredContent });
|
|
1348
|
+
}
|
|
1349
|
+
} else {
|
|
1350
|
+
result.push(msg);
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
result.push(msg);
|
|
1354
|
+
}
|
|
1355
|
+
} else {
|
|
1356
|
+
result.push(msg);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
return result;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
936
1363
|
/**
|
|
937
1364
|
* 修剪消息确保配对的 tool call/result 不被拆分
|
|
938
1365
|
* @param {number} stepNumber - 当前步骤号
|
|
@@ -959,11 +1386,16 @@ ${truncatedContent}${truncatedNote}
|
|
|
959
1386
|
}
|
|
960
1387
|
}
|
|
961
1388
|
}
|
|
962
|
-
if (msg.role === 'tool' && msg.
|
|
963
|
-
|
|
964
|
-
|
|
1389
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1390
|
+
// 遍历 tool message 中的所有 tool-results
|
|
1391
|
+
for (const item of msg.content) {
|
|
1392
|
+
if (item && item.type === 'tool-result' && item.toolCallId) {
|
|
1393
|
+
if (!toolResultIndices.has(item.toolCallId)) {
|
|
1394
|
+
toolResultIndices.set(item.toolCallId, []);
|
|
1395
|
+
}
|
|
1396
|
+
toolResultIndices.get(item.toolCallId).push(i);
|
|
1397
|
+
}
|
|
965
1398
|
}
|
|
966
|
-
toolResultIndices.get(msg.toolCallId).push(i);
|
|
967
1399
|
}
|
|
968
1400
|
}
|
|
969
1401
|
|
|
@@ -996,6 +1428,49 @@ ${truncatedContent}${truncatedNote}
|
|
|
996
1428
|
return { messages: pruned };
|
|
997
1429
|
}
|
|
998
1430
|
|
|
1431
|
+
/**
|
|
1432
|
+
* 校验并修复消息中的工具调用参数
|
|
1433
|
+
* 移除不完整的 JSON(如只有 "{" )的工具调用
|
|
1434
|
+
* @param {Array} messages - 消息列表
|
|
1435
|
+
* @private
|
|
1436
|
+
*/
|
|
1437
|
+
_validateToolCalls(messages) {
|
|
1438
|
+
let fixedCount = 0;
|
|
1439
|
+
|
|
1440
|
+
for (const msg of messages) {
|
|
1441
|
+
if (msg.role !== 'assistant' || !Array.isArray(msg.content)) {
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
for (const item of msg.content) {
|
|
1446
|
+
if (item.type !== 'tool-call') {
|
|
1447
|
+
continue;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
const input = item.input;
|
|
1451
|
+
if (typeof input !== 'string') {
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// 检查 input 是否是有效的 JSON(不是不完整的)
|
|
1456
|
+
const trimmed = input.trim();
|
|
1457
|
+
if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
|
|
1458
|
+
// 不完整的 JSON,移除这个 tool-call
|
|
1459
|
+
item.type = 'text';
|
|
1460
|
+
item.text = `(工具调用 ${item.toolName} 参数不完整,已跳过)`;
|
|
1461
|
+
delete item.toolCallId;
|
|
1462
|
+
delete item.toolName;
|
|
1463
|
+
delete item.input;
|
|
1464
|
+
fixedCount++;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
if (fixedCount > 0) {
|
|
1470
|
+
logger.info(`Fixed ${fixedCount} incomplete tool calls`);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
999
1474
|
/**
|
|
1000
1475
|
* 获取 AI SDK 格式的工具
|
|
1001
1476
|
* AI SDK 6.x 需要对象形式 { toolName: tool }
|
|
@@ -1022,7 +1497,10 @@ ${truncatedContent}${truncatedNote}
|
|
|
1022
1497
|
try {
|
|
1023
1498
|
const result = await toolDef.execute(cleanedArgs, this.agent.framework);
|
|
1024
1499
|
this.emit('tool-result', { name: toolName, args: cleanedArgs, result });
|
|
1025
|
-
|
|
1500
|
+
if (result !== null && typeof result === 'object') {
|
|
1501
|
+
return JSON.stringify(result);
|
|
1502
|
+
}
|
|
1503
|
+
return String(result);
|
|
1026
1504
|
} catch (err) {
|
|
1027
1505
|
this.emit('tool-error', { name: toolName, args: cleanedArgs, error: err.message });
|
|
1028
1506
|
return { error: err.message };
|
|
@@ -1084,7 +1562,8 @@ ${truncatedContent}${truncatedNote}
|
|
|
1084
1562
|
* 销毁
|
|
1085
1563
|
*/
|
|
1086
1564
|
destroy() {
|
|
1087
|
-
this.
|
|
1565
|
+
this._sessionMessageStores.clear();
|
|
1566
|
+
this._sessionMessageStores = new Map();
|
|
1088
1567
|
this._tools.clear();
|
|
1089
1568
|
this._encoder = null;
|
|
1090
1569
|
this.removeAllListeners();
|