foliko 1.0.74 → 1.0.76
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 +29 -0
- package/.agent/data/plugins-state.json +255 -0
- package/.agent/mcp_config.json +4 -0
- package/.agent/mcp_config_updated.json +12 -0
- package/.agent/plugins.json +5 -0
- 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/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 +11 -1
- package/.editorconfig +56 -0
- package/.husky/pre-commit +4 -0
- package/.lintstagedrc +7 -0
- package/.prettierignore +29 -0
- package/.prettierrc +11 -0
- package/CLAUDE.md +2 -0
- package/README.md +64 -55
- package/SPEC.md +102 -61
- package/cli/bin/foliko.js +11 -11
- package/cli/src/commands/chat.js +143 -141
- package/cli/src/commands/list.js +93 -90
- package/cli/src/index.js +75 -75
- package/cli/src/ui/chat-ui.js +201 -199
- package/cli/src/utils/ansi.js +40 -40
- package/cli/src/utils/markdown.js +292 -296
- package/docker-compose.yml +1 -1
- package/docs/ai-sdk-optimization.md +655 -643
- package/docs/features.md +80 -80
- package/docs/quick-reference.md +49 -46
- package/docs/user-manual.md +411 -380
- package/examples/ambient-example.js +194 -196
- package/examples/basic.js +50 -45
- package/examples/bootstrap.js +121 -112
- package/examples/mcp-example.js +19 -16
- package/examples/skill-example.js +20 -20
- package/examples/test-chat.js +137 -135
- package/examples/test-mcp.js +85 -79
- package/examples/test-reload.js +59 -61
- package/examples/test-telegram.js +50 -50
- package/examples/test-tg-bot.js +45 -42
- package/examples/test-tg-simple.js +47 -46
- package/examples/test-tg.js +62 -62
- package/examples/test-think.js +43 -37
- package/examples/test-web-plugin.js +103 -98
- package/examples/test-weixin-feishu.js +103 -100
- package/examples/workflow.js +158 -158
- package/package.json +37 -3
- package/plugins/ai-plugin.js +102 -100
- package/plugins/ambient-agent/EventWatcher.js +113 -0
- package/plugins/ambient-agent/ExplorerLoop.js +640 -0
- package/plugins/ambient-agent/GoalManager.js +197 -0
- package/plugins/ambient-agent/Reflector.js +95 -0
- package/plugins/ambient-agent/StateStore.js +90 -0
- package/plugins/ambient-agent/constants.js +101 -0
- package/plugins/ambient-agent/index.js +579 -0
- package/plugins/audit-plugin.js +187 -187
- package/plugins/default-plugins.js +662 -649
- package/plugins/email/constants.js +64 -0
- package/plugins/email/handlers.js +461 -0
- package/plugins/email/index.js +278 -0
- package/plugins/email/monitor.js +269 -0
- package/plugins/email/parser.js +138 -0
- package/plugins/email/reply.js +151 -0
- package/plugins/email/utils.js +124 -0
- package/plugins/feishu-plugin.js +481 -477
- package/plugins/file-system-plugin.js +826 -476
- package/plugins/install-plugin.js +199 -197
- package/plugins/python-executor-plugin.js +367 -365
- package/plugins/python-plugin-loader.js +481 -479
- package/plugins/rules-plugin.js +294 -292
- package/plugins/scheduler-plugin.js +691 -689
- package/plugins/session-plugin.js +369 -367
- package/plugins/shell-executor-plugin.js +197 -197
- package/plugins/storage-plugin.js +240 -238
- package/plugins/subagent-plugin.js +845 -785
- package/plugins/telegram-plugin.js +482 -475
- package/plugins/think-plugin.js +345 -343
- package/plugins/tools-plugin.js +196 -194
- package/plugins/web-plugin.js +606 -604
- package/plugins/weixin-plugin.js +545 -538
- package/reports/system-health-report-20260401.md +79 -0
- package/skills/ambient-agent/SKILL.md +49 -39
- package/skills/foliko-dev/AGENTS.md +64 -61
- package/skills/foliko-dev/SKILL.md +125 -119
- package/skills/mcp-usage/SKILL.md +19 -17
- package/skills/python-plugin-dev/SKILL.md +16 -15
- package/skills/skill-guide/SKILL.md +12 -12
- package/skills/subagent-guide/SKILL.md +237 -0
- package/skills/workflow-guide/SKILL.md +90 -45
- package/skills/workflow-troubleshooting/DEBUGGING.md +36 -21
- package/skills/workflow-troubleshooting/SKILL.md +156 -79
- package/src/capabilities/index.js +11 -11
- package/src/capabilities/skill-manager.js +609 -595
- package/src/capabilities/workflow-engine.js +1109 -1195
- package/src/core/agent-chat.js +882 -735
- package/src/core/agent.js +892 -688
- package/src/core/framework.js +465 -431
- package/src/core/index.js +19 -19
- package/src/core/plugin-base.js +219 -219
- package/src/core/plugin-manager.js +863 -767
- package/src/core/provider.js +114 -111
- package/src/core/sub-agent-config.js +264 -0
- package/src/core/system-prompt-builder.js +120 -0
- package/src/core/tool-registry.js +517 -134
- package/src/core/tool-router.js +297 -216
- package/src/executors/executor-base.js +12 -12
- package/src/executors/mcp-executor.js +741 -729
- package/src/index.js +25 -37
- package/src/utils/circuit-breaker.js +301 -0
- package/src/utils/error-boundary.js +363 -0
- package/src/utils/error.js +374 -0
- package/src/utils/event-emitter.js +97 -97
- package/src/utils/id.js +133 -0
- package/src/utils/index.js +217 -3
- package/src/utils/logger.js +181 -0
- package/src/utils/plugin-helpers.js +90 -0
- package/src/utils/retry.js +122 -0
- package/src/utils/sandbox.js +292 -0
- package/test/tool-registry-validation.test.js +218 -0
- package/test_report.md +70 -0
- package/website/docs/api.html +169 -107
- package/website/docs/configuration.html +296 -144
- package/website/docs/plugin-development.html +154 -85
- package/website/docs/project-structure.html +110 -109
- package/website/docs/skill-development.html +117 -61
- package/website/index.html +209 -205
- package/website/script.js +136 -133
- package/website/styles.css +1 -1
- package/plugins/ambient-agent-plugin.js +0 -1565
- package/plugins/email.js +0 -1142
package/src/core/agent-chat.js
CHANGED
|
@@ -1,735 +1,882 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AgentChatHandler 聊天处理器
|
|
3
|
-
* 使用 AI SDK 的 ToolLoopAgent 处理工具调用循环
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const { EventEmitter } = require('../utils/event-emitter')
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
'deepseek-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
'gpt-
|
|
19
|
-
'gpt-4o
|
|
20
|
-
'gpt-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'claude-3-
|
|
24
|
-
'claude-3-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
*
|
|
83
|
-
* @param {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
*
|
|
133
|
-
* @
|
|
134
|
-
* @
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
*
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
*
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
this.
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AgentChatHandler 聊天处理器
|
|
3
|
+
* 使用 AI SDK 的 ToolLoopAgent 处理工具调用循环
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { EventEmitter } = require('../utils/event-emitter');
|
|
7
|
+
const { logger } = require('../utils/logger');
|
|
8
|
+
const { generateText, pruneMessages } = require('ai');
|
|
9
|
+
|
|
10
|
+
// 模型上下文限制表(留 15-20% 余量给 system prompt 和输出)
|
|
11
|
+
const MODEL_CONTEXT_LIMITS = {
|
|
12
|
+
// DeepSeek
|
|
13
|
+
'deepseek-chat': 28000,
|
|
14
|
+
'deepseek-coder': 28000,
|
|
15
|
+
// MiniMax
|
|
16
|
+
'MiniMax-M2.7': 90000,
|
|
17
|
+
// OpenAI
|
|
18
|
+
'gpt-4': 100000,
|
|
19
|
+
'gpt-4o': 100000,
|
|
20
|
+
'gpt-4o-mini': 100000,
|
|
21
|
+
'gpt-4-turbo': 100000,
|
|
22
|
+
// Anthropic
|
|
23
|
+
'claude-3-5-sonnet': 150000,
|
|
24
|
+
'claude-3-opus': 150000,
|
|
25
|
+
'claude-3-sonnet': 150000,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 纯 JavaScript token 计数器
|
|
30
|
+
* 基于字符数估算,兼容中英文
|
|
31
|
+
* 估算规则:
|
|
32
|
+
* - 英文:约 4 字符 = 1 token
|
|
33
|
+
* - 中文:约 2 字符 = 1 token
|
|
34
|
+
* - 混合文本取加权平均
|
|
35
|
+
*/
|
|
36
|
+
class SimpleTokenizer {
|
|
37
|
+
constructor() {
|
|
38
|
+
// 无需初始化
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 估算文本的 token 数
|
|
43
|
+
* @param {string} text
|
|
44
|
+
* @returns {number}
|
|
45
|
+
*/
|
|
46
|
+
encode(text) {
|
|
47
|
+
if (!text || typeof text !== 'string') {
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 清理文本
|
|
52
|
+
const cleanText = text
|
|
53
|
+
.replace(/\0+/g, '')
|
|
54
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '')
|
|
55
|
+
.slice(0, 100000);
|
|
56
|
+
|
|
57
|
+
if (!cleanText) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 分离中英文分别计数
|
|
62
|
+
const chineseChars = (cleanText.match(/[\u4e00-\u9fff\u3400-\u4dbf]/g) || []).length;
|
|
63
|
+
const englishChars = cleanText.length - chineseChars;
|
|
64
|
+
|
|
65
|
+
// 中英文分开估算后相加
|
|
66
|
+
// 中文约 2 字符/token,英文约 4 字符/token
|
|
67
|
+
const chineseTokens = chineseChars / 2;
|
|
68
|
+
const englishTokens = englishChars / 4;
|
|
69
|
+
|
|
70
|
+
return Math.ceil(chineseTokens + englishTokens);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 全局 tokenizer 实例
|
|
75
|
+
const _globalTokenizer = new SimpleTokenizer();
|
|
76
|
+
|
|
77
|
+
// 压缩超时时间(毫秒)
|
|
78
|
+
const COMPRESSION_TIMEOUT = 30000;
|
|
79
|
+
|
|
80
|
+
class AgentChatHandler extends EventEmitter {
|
|
81
|
+
/**
|
|
82
|
+
* @param {Agent} agent - Agent 实例
|
|
83
|
+
* @param {Object} config - 配置
|
|
84
|
+
*/
|
|
85
|
+
constructor(agent, config = {}) {
|
|
86
|
+
super();
|
|
87
|
+
|
|
88
|
+
this.agent = agent;
|
|
89
|
+
this.config = config;
|
|
90
|
+
|
|
91
|
+
this.model = config.model || 'deepseek-chat';
|
|
92
|
+
this.provider = config.provider || 'deepseek';
|
|
93
|
+
this.apiKey = config.apiKey;
|
|
94
|
+
this.baseURL = config.baseURL;
|
|
95
|
+
this.providerOptions = config.providerOptions || {};
|
|
96
|
+
|
|
97
|
+
this._systemPrompt = config.systemPrompt || 'You are a helpful assistant.';
|
|
98
|
+
this._messages = [];
|
|
99
|
+
this._tools = new Map();
|
|
100
|
+
this._maxSteps = 5; // 降低默认步骤数,减少上下文消耗
|
|
101
|
+
|
|
102
|
+
// 上下文压缩配置:根据模型自动设置限制
|
|
103
|
+
const modelKey = Object.keys(MODEL_CONTEXT_LIMITS).find((k) =>
|
|
104
|
+
this.model.toLowerCase().includes(k.toLowerCase())
|
|
105
|
+
);
|
|
106
|
+
const defaultLimit = modelKey ? MODEL_CONTEXT_LIMITS[modelKey] : 40000;
|
|
107
|
+
this._maxContextTokens = config.maxContextTokens || defaultLimit;
|
|
108
|
+
this._compressionThreshold = config.compressionThreshold || 0.6; // 60% 就压缩,早触发
|
|
109
|
+
this._keepRecentMessages = config.keepRecentMessages || 20; // 保留最近 20 条
|
|
110
|
+
this._enableSmartCompress = config.enableSmartCompress !== false; // 默认开启智能摘要
|
|
111
|
+
this._encoder = null;
|
|
112
|
+
this._compressionCount = 0; // 压缩次数统计
|
|
113
|
+
this._compressionInProgress = false; // 压缩锁,防止并发压缩
|
|
114
|
+
this._compressionPromise = null; // 正在进行的压缩 Promise
|
|
115
|
+
|
|
116
|
+
// 工具结果压缩配置
|
|
117
|
+
this._maxToolResultSize = config.maxToolResultSize || 4000; // 工具结果超过此大小则压缩(字节)
|
|
118
|
+
|
|
119
|
+
// 初始化编码器
|
|
120
|
+
// 使用纯 JS tokenizer
|
|
121
|
+
this._encoder = _globalTokenizer;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 静态方法:保留接口兼容性(无实际作用)
|
|
126
|
+
*/
|
|
127
|
+
static clearEncoderCache() {
|
|
128
|
+
// 不再需要清理
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 计算文本的 token 数
|
|
133
|
+
* @param {string} text
|
|
134
|
+
* @returns {number}
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
_countTokens(text) {
|
|
138
|
+
if (!text || typeof text !== 'string') return 0;
|
|
139
|
+
// SimpleTokenizer.encode 已经处理了清理逻辑
|
|
140
|
+
try {
|
|
141
|
+
return this._encoder.encode(text);
|
|
142
|
+
} catch (err) {
|
|
143
|
+
// 估算失败时使用字符计数
|
|
144
|
+
return Math.ceil(text.length / 4);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 计算消息列表的总 token 数
|
|
150
|
+
* @param {Array} messages
|
|
151
|
+
* @returns {number}
|
|
152
|
+
* @private
|
|
153
|
+
*/
|
|
154
|
+
_countMessagesTokens(messages) {
|
|
155
|
+
let total = 0;
|
|
156
|
+
for (const msg of messages) {
|
|
157
|
+
if (!msg) continue;
|
|
158
|
+
total += 4; // role 标记
|
|
159
|
+
if (typeof msg.content === 'string') {
|
|
160
|
+
total += this._countTokens(msg.content);
|
|
161
|
+
} else if (Array.isArray(msg.content)) {
|
|
162
|
+
for (const part of msg.content) {
|
|
163
|
+
if (part && typeof part === 'object' && part.text) {
|
|
164
|
+
total += this._countTokens(String(part.text));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
} else if (msg.content && typeof msg.content === 'object') {
|
|
168
|
+
// 处理工具结果等对象类型
|
|
169
|
+
try {
|
|
170
|
+
const str = JSON.stringify(msg.content);
|
|
171
|
+
total += this._countTokens(str);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
// 无法序列化的内容,忽略
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
total += 4; // 结尾标记
|
|
178
|
+
return total;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* 检查是否需要压缩上下文
|
|
183
|
+
* @returns {boolean}
|
|
184
|
+
* @private
|
|
185
|
+
*/
|
|
186
|
+
_shouldCompress() {
|
|
187
|
+
const totalTokens = this._countMessagesTokens(this._messages);
|
|
188
|
+
return totalTokens > this._maxContextTokens * this._compressionThreshold;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 计算工具定义的 token 数(估算)
|
|
193
|
+
* @returns {number}
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_countToolsTokens() {
|
|
197
|
+
let total = 0;
|
|
198
|
+
for (const toolDef of this._tools.values()) {
|
|
199
|
+
// 工具名 + 描述
|
|
200
|
+
total += this._countTokens(String(toolDef.name || ''));
|
|
201
|
+
total += this._countTokens(String(toolDef.description || ''));
|
|
202
|
+
|
|
203
|
+
// 工具参数 schema
|
|
204
|
+
if (toolDef.inputSchema) {
|
|
205
|
+
const schemaStr =
|
|
206
|
+
typeof toolDef.inputSchema === 'string'
|
|
207
|
+
? toolDef.inputSchema
|
|
208
|
+
: JSON.stringify(toolDef.inputSchema);
|
|
209
|
+
total += this._countTokens(schemaStr);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return total;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 检查是否需要压缩(包括工具定义)
|
|
217
|
+
* @returns {boolean}
|
|
218
|
+
* @private
|
|
219
|
+
*/
|
|
220
|
+
_shouldCompressWithTools() {
|
|
221
|
+
const messagesTokens = this._countMessagesTokens(this._messages);
|
|
222
|
+
const toolsTokens = this._countToolsTokens();
|
|
223
|
+
const systemPromptTokens = this._countTokens(this._systemPrompt);
|
|
224
|
+
const total = messagesTokens + toolsTokens + systemPromptTokens;
|
|
225
|
+
|
|
226
|
+
// 如果总token数超过上下文限制的 85%,就压缩
|
|
227
|
+
return total > this._maxContextTokens * 0.85;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* 压缩上下文消息(智能摘要模式,支持超时控制)
|
|
232
|
+
* 策略:
|
|
233
|
+
* 1. 如果启用了智能摘要且有 AI 客户端,对早期消息进行 AI 总结
|
|
234
|
+
* 2. 否则使用简单裁剪 + 标记
|
|
235
|
+
* 3. 支持超时控制,防止压缩阻塞太久
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
async _compressContext() {
|
|
239
|
+
// 如果已经有压缩在进行,返回现有的 Promise
|
|
240
|
+
if (this._compressionInProgress && this._compressionPromise) {
|
|
241
|
+
logger.debug('Compression already in progress, waiting for it...');
|
|
242
|
+
return this._compressionPromise;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (this._messages.length <= this._keepRecentMessages) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this._compressionInProgress = true;
|
|
250
|
+
|
|
251
|
+
// 创建压缩 Promise,带超时控制
|
|
252
|
+
this._compressionPromise = this._executeCompressionWithTimeout().finally(() => {
|
|
253
|
+
this._compressionInProgress = false;
|
|
254
|
+
this._compressionPromise = null;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
return this._compressionPromise;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 执行压缩(带超时)
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
async _executeCompressionWithTimeout() {
|
|
265
|
+
try {
|
|
266
|
+
return await Promise.race([this._doCompress(), this._createTimeoutPromise()]);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
logger.warn('Compression failed:', err.message);
|
|
269
|
+
// 压缩失败时使用简单的截断策略
|
|
270
|
+
this._simpleCompress();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 创建超时 Promise
|
|
276
|
+
* @private
|
|
277
|
+
*/
|
|
278
|
+
_createTimeoutPromise() {
|
|
279
|
+
return new Promise((_, reject) => {
|
|
280
|
+
setTimeout(() => {
|
|
281
|
+
reject(new Error('Compression timeout'));
|
|
282
|
+
}, COMPRESSION_TIMEOUT);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 执行实际压缩逻辑
|
|
288
|
+
* @private
|
|
289
|
+
*/
|
|
290
|
+
async _doCompress() {
|
|
291
|
+
const systemMessages = this._messages.filter((m) => m.role === 'system');
|
|
292
|
+
const otherMessages = this._messages.filter((m) => m.role !== 'system');
|
|
293
|
+
|
|
294
|
+
// 保留最近的 N 条非系统消息
|
|
295
|
+
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
296
|
+
const messagesToSummarize = otherMessages.slice(0, -this._keepRecentMessages);
|
|
297
|
+
|
|
298
|
+
const compressedCount = messagesToSummarize.length;
|
|
299
|
+
let summaryContent = '';
|
|
300
|
+
|
|
301
|
+
// 尝试使用 AI 总结
|
|
302
|
+
if (this._enableSmartCompress && this._aiClient) {
|
|
303
|
+
try {
|
|
304
|
+
const summaryText = await this._summarizeMessages(messagesToSummarize);
|
|
305
|
+
summaryContent = `[早期对话摘要]: ${summaryText}`;
|
|
306
|
+
logger.info(`AI summary generated (${summaryText.length} chars)`);
|
|
307
|
+
} catch (err) {
|
|
308
|
+
logger.warn('AI summary failed, using simple compression:', err.message);
|
|
309
|
+
summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const summary = {
|
|
316
|
+
role: 'system',
|
|
317
|
+
content: summaryContent,
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
this._messages = [...systemMessages, summary, ...recentMessages];
|
|
321
|
+
this._compressionCount++;
|
|
322
|
+
|
|
323
|
+
const totalTokens = this._countMessagesTokens(this._messages);
|
|
324
|
+
logger.info(
|
|
325
|
+
`Context compressed (${this._compressionCount} times). Messages: ${this._messages.length}, Est. tokens: ${totalTokens}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* 简单压缩(当 AI 压缩失败时使用)
|
|
331
|
+
* @private
|
|
332
|
+
*/
|
|
333
|
+
_simpleCompress() {
|
|
334
|
+
const systemMessages = this._messages.filter((m) => m.role === 'system');
|
|
335
|
+
const otherMessages = this._messages.filter((m) => m.role !== 'system');
|
|
336
|
+
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
337
|
+
const compressedCount = otherMessages.length - this._keepRecentMessages;
|
|
338
|
+
|
|
339
|
+
const summaryContent = `[上下文已压缩: 省略了 ${compressedCount} 条早期消息。保留了最近 ${this._keepRecentMessages} 条对话记录。]`;
|
|
340
|
+
|
|
341
|
+
const summary = {
|
|
342
|
+
role: 'system',
|
|
343
|
+
content: summaryContent,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
this._messages = [...systemMessages, summary, ...recentMessages];
|
|
347
|
+
this._compressionCount++;
|
|
348
|
+
|
|
349
|
+
logger.info(
|
|
350
|
+
`Context simple compressed (${this._compressionCount} times). Messages: ${this._messages.length}`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 使用 AI 对消息进行总结
|
|
356
|
+
* @param {Array} messages - 要总结的消息
|
|
357
|
+
* @returns {Promise<string>} 总结文本
|
|
358
|
+
* @private
|
|
359
|
+
*/
|
|
360
|
+
async _summarizeMessages(messages) {
|
|
361
|
+
if (!this._aiClient || messages.length === 0) {
|
|
362
|
+
return '(无早期对话)';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 构建总结提示
|
|
366
|
+
const conversationText = messages
|
|
367
|
+
.map((m) => {
|
|
368
|
+
const role = m.role === 'user' ? '用户' : '助手';
|
|
369
|
+
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
|
|
370
|
+
return `${role}: ${content}`;
|
|
371
|
+
})
|
|
372
|
+
.join('\n');
|
|
373
|
+
|
|
374
|
+
const summarizePrompt = `请简洁地总结以下对话的要点,保留关键信息和用户需求:
|
|
375
|
+
|
|
376
|
+
${conversationText}
|
|
377
|
+
|
|
378
|
+
总结要求:
|
|
379
|
+
1. 提取用户的主要需求和意图
|
|
380
|
+
2. 保留关键的技术细节或决策
|
|
381
|
+
3. 不要超过 1000 字
|
|
382
|
+
4. 用中文回复`;
|
|
383
|
+
|
|
384
|
+
// 使用 AI SDK 6.x 的 generateText
|
|
385
|
+
const { text } = await generateText({
|
|
386
|
+
model: this._aiClient,
|
|
387
|
+
prompt: summarizePrompt,
|
|
388
|
+
...this.providerOptions,
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
return text || '(总结生成失败)';
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* 检查工具结果是否需要压缩
|
|
396
|
+
* @param {any} result - 工具返回结果
|
|
397
|
+
* @returns {boolean}
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
_shouldCompressToolResult(result) {
|
|
401
|
+
if (!result || this._maxToolResultSize <= 0) return false;
|
|
402
|
+
|
|
403
|
+
// 计算结果的大小
|
|
404
|
+
let size = 0;
|
|
405
|
+
if (typeof result === 'string') {
|
|
406
|
+
size = result.length;
|
|
407
|
+
} else if (typeof result === 'object') {
|
|
408
|
+
try {
|
|
409
|
+
size = JSON.stringify(result).length;
|
|
410
|
+
} catch {
|
|
411
|
+
size = String(result).length;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return size > this._maxToolResultSize;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* 压缩工具结果
|
|
420
|
+
* @param {any} result - 工具返回结果
|
|
421
|
+
* @returns {Promise<any>} 压缩后的结果
|
|
422
|
+
* @private
|
|
423
|
+
*/
|
|
424
|
+
async _compressToolResult(result) {
|
|
425
|
+
if (!this._shouldCompressToolResult(result)) {
|
|
426
|
+
return result;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!this._aiClient) {
|
|
430
|
+
logger.warn('Cannot compress tool result: no AI client');
|
|
431
|
+
return this._fallbackCompress(result);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const originalSize =
|
|
436
|
+
typeof result === 'string' ? result.length : JSON.stringify(result).length;
|
|
437
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
438
|
+
|
|
439
|
+
// 对于超大型内容(如网页),采用更好的截断策略
|
|
440
|
+
const maxInputSize = 6000; // 保留给 AI 处理的输入大小
|
|
441
|
+
const shouldTruncate = resultStr.length > maxInputSize;
|
|
442
|
+
const truncatedContent = resultStr.substring(0, maxInputSize);
|
|
443
|
+
const truncatedNote = shouldTruncate ? `\n\n[内容已截断,原始长度 ${originalSize} 字符]` : '';
|
|
444
|
+
|
|
445
|
+
// 检测内容类型
|
|
446
|
+
const isHTML =
|
|
447
|
+
resultStr.startsWith('<') || resultStr.includes('<html') || resultStr.includes('<!DOCTYPE');
|
|
448
|
+
const isJSON = !isHTML && (resultStr.startsWith('{') || resultStr.startsWith('['));
|
|
449
|
+
const contentTypeHint = isHTML ? '(HTML 网页内容)' : isJSON ? '(JSON 数据)' : '';
|
|
450
|
+
|
|
451
|
+
// 构建压缩提示
|
|
452
|
+
const compressPrompt = `以下是一个工具执行结果${contentTypeHint},长度 ${originalSize} 字符。请简洁地总结其核心内容:
|
|
453
|
+
|
|
454
|
+
${truncatedContent}${truncatedNote}
|
|
455
|
+
|
|
456
|
+
请提取并保留:
|
|
457
|
+
1. 主要标题和主题
|
|
458
|
+
2. 关键信息点(不超过 5 个)
|
|
459
|
+
3. 重要数据或结论
|
|
460
|
+
|
|
461
|
+
用简洁的中文总结,不超过 400 字:`;
|
|
462
|
+
|
|
463
|
+
// 使用 AI SDK 6.x 的 generateText
|
|
464
|
+
const { text } = await generateText({
|
|
465
|
+
model: this._aiClient,
|
|
466
|
+
prompt: compressPrompt,
|
|
467
|
+
...this.providerOptions,
|
|
468
|
+
maxTokens: 500,
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
const summary = text || '(总结生成失败)';
|
|
472
|
+
const compressed = `[工具结果已压缩${contentTypeHint}: ${originalSize} -> ${summary.length} 字符]\n\n${summary}`;
|
|
473
|
+
|
|
474
|
+
logger.info(`Tool result compressed: ${originalSize} -> ${summary.length} chars`);
|
|
475
|
+
return compressed;
|
|
476
|
+
} catch (err) {
|
|
477
|
+
logger.warn('Tool result compression failed:', err.message);
|
|
478
|
+
return this._fallbackCompress(result);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* 回退压缩方法(当 AI 客户端不可用时)
|
|
484
|
+
* @param {any} result - 工具返回结果
|
|
485
|
+
* @returns {string} 压缩后的结果
|
|
486
|
+
* @private
|
|
487
|
+
*/
|
|
488
|
+
_fallbackCompress(result) {
|
|
489
|
+
const originalSize = typeof result === 'string' ? result.length : JSON.stringify(result).length;
|
|
490
|
+
const resultStr = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
491
|
+
|
|
492
|
+
// 简单截断策略:保留前 2000 字符
|
|
493
|
+
const maxSize = 2000;
|
|
494
|
+
if (resultStr.length <= maxSize) {
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const compressed = `[工具结果已压缩(简单截断): ${originalSize} -> ${maxSize} 字符]\n\n${resultStr.substring(0, maxSize)}\n\n...[内容已截断,原文 ${originalSize} 字符]`;
|
|
499
|
+
logger.info(`Tool result fallback compressed: ${originalSize} -> ${maxSize} chars`);
|
|
500
|
+
return compressed;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 设置 AI 客户端
|
|
505
|
+
* @param {Object} client - AI 模型客户端
|
|
506
|
+
*/
|
|
507
|
+
setAIClient(client) {
|
|
508
|
+
this._aiClient = client;
|
|
509
|
+
return this;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* 设置系统提示
|
|
514
|
+
* @param {string} prompt
|
|
515
|
+
*/
|
|
516
|
+
setSystemPrompt(prompt) {
|
|
517
|
+
this._systemPrompt = prompt;
|
|
518
|
+
return this;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* 注册工具
|
|
523
|
+
* @param {Object} toolDef - 工具定义
|
|
524
|
+
*/
|
|
525
|
+
registerTool(toolDef) {
|
|
526
|
+
this._tools.set(toolDef.name, toolDef);
|
|
527
|
+
return this;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 清空对话历史
|
|
532
|
+
*/
|
|
533
|
+
clearHistory() {
|
|
534
|
+
this._messages = [];
|
|
535
|
+
this._compressionCount = 0;
|
|
536
|
+
return this;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* 获取已注册的工具
|
|
541
|
+
* @returns {Array}
|
|
542
|
+
*/
|
|
543
|
+
getTools() {
|
|
544
|
+
return Array.from(this._tools.values());
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* 导入 AI SDK(动态导入)
|
|
549
|
+
* @private
|
|
550
|
+
*/
|
|
551
|
+
async _importAI() {
|
|
552
|
+
try {
|
|
553
|
+
const ai = require('ai');
|
|
554
|
+
return {
|
|
555
|
+
tool: ai.tool,
|
|
556
|
+
ToolLoopAgent: ai.ToolLoopAgent,
|
|
557
|
+
};
|
|
558
|
+
} catch (err) {
|
|
559
|
+
throw new Error('AI SDK not found. Please install: npm install ai');
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* 发送消息(非流式)
|
|
565
|
+
* @param {string|Object} message - 消息
|
|
566
|
+
* @param {Object} options - 选项
|
|
567
|
+
*/
|
|
568
|
+
async chat(message, options = {}) {
|
|
569
|
+
const context = { sessionId: options.sessionId || null, isStream: false };
|
|
570
|
+
const framework = this.agent.framework;
|
|
571
|
+
const self = this; // 保存引用用于回调
|
|
572
|
+
|
|
573
|
+
// 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
|
|
574
|
+
// 解决上下文过长时 LLM 不调用工具的问题
|
|
575
|
+
this._systemPrompt = this.agent._buildSystemPrompt();
|
|
576
|
+
// 动态导入 AI SDK
|
|
577
|
+
const { tool, ToolLoopAgent } = await this._importAI();
|
|
578
|
+
|
|
579
|
+
const userMessage = typeof message === 'string' ? { role: 'user', content: message } : message;
|
|
580
|
+
|
|
581
|
+
this._messages.push(userMessage);
|
|
582
|
+
|
|
583
|
+
// 检查是否需要压缩上下文(包括工具定义)
|
|
584
|
+
const messagesTokens = this._countMessagesTokens(this._messages);
|
|
585
|
+
const toolsTokens = this._countToolsTokens();
|
|
586
|
+
const systemPromptTokens = this._countTokens(this._systemPrompt);
|
|
587
|
+
const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
|
|
588
|
+
const limit = this._maxContextTokens * 0.7; // 降低到 70%,更早压缩
|
|
589
|
+
|
|
590
|
+
if (totalTokens > limit) {
|
|
591
|
+
logger.info(
|
|
592
|
+
`Context large (${totalTokens}/${this._maxContextTokens} tokens = msgs:${messagesTokens} + tools:${toolsTokens} + sys:${systemPromptTokens}), compressing...`
|
|
593
|
+
);
|
|
594
|
+
// 使用带超时控制的压缩
|
|
595
|
+
await this._compressContext();
|
|
596
|
+
} else {
|
|
597
|
+
logger.info(`Context OK: ${totalTokens}/${this._maxContextTokens} tokens`);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const maxSteps = options.maxSteps || this._maxSteps;
|
|
601
|
+
const tools = this._getAITools(tool);
|
|
602
|
+
|
|
603
|
+
if (!this._aiClient) {
|
|
604
|
+
throw new Error('AI client not configured.');
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const agent = new ToolLoopAgent({
|
|
608
|
+
model: this._aiClient,
|
|
609
|
+
instructions: this._systemPrompt,
|
|
610
|
+
tools: tools,
|
|
611
|
+
stopWhen: (step) => step.stepCount >= maxSteps,
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
const messages = this._cleanMessages(this._messages);
|
|
615
|
+
const prunedMessages = pruneMessages({
|
|
616
|
+
messages,
|
|
617
|
+
reasoning: 'all', // Remove all reasoning parts
|
|
618
|
+
toolCalls: 'before-last-2-messages', // Remove tool calls except those in the last message
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
// 使用 runWithContext 让工具执行时能获取 sessionId
|
|
623
|
+
const result = await framework.runWithContext(context, async () => {
|
|
624
|
+
return agent.generate({ messages: prunedMessages, ...this.providerOptions });
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// 处理返回的消息历史:只保留 user 和 assistant 消息,让 SDK 自动处理 tool 消息
|
|
628
|
+
if (result.messages && Array.isArray(result.messages)) {
|
|
629
|
+
// 只保留 user 和 assistant 消息,SDK 会自动维护 tool 消息
|
|
630
|
+
this._messages = result.messages.filter((m) => m.role === 'user' || m.role === 'assistant');
|
|
631
|
+
} else if (result.text) {
|
|
632
|
+
this._messages.push({
|
|
633
|
+
role: 'assistant',
|
|
634
|
+
content: result.text,
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// 生成后检查:如果消息太长,下次需要压缩
|
|
639
|
+
const afterTokens = this._countMessagesTokens(this._messages);
|
|
640
|
+
if (afterTokens > this._maxContextTokens * 0.8) {
|
|
641
|
+
logger.info(`After generation: ${afterTokens} tokens, will compress on next turn`);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return {
|
|
645
|
+
success: true,
|
|
646
|
+
message: result.text || '',
|
|
647
|
+
stepCount: result.stepCount || 1,
|
|
648
|
+
};
|
|
649
|
+
} catch (err) {
|
|
650
|
+
this.emit('error', { error: err.message });
|
|
651
|
+
// 抛出错误而不是返回错误响应,让 Agent 能捕获
|
|
652
|
+
throw err;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* 发送消息(流式)
|
|
658
|
+
* @param {string|Object} message - 消息
|
|
659
|
+
* @param {Object} options - 选项
|
|
660
|
+
*/
|
|
661
|
+
async *chatStream(message, options = {}) {
|
|
662
|
+
const context = { sessionId: options.sessionId || null, isStream: true };
|
|
663
|
+
const framework = this.agent.framework;
|
|
664
|
+
|
|
665
|
+
// 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
|
|
666
|
+
// 解决上下文过长时 LLM 不调用工具的问题
|
|
667
|
+
this._systemPrompt = this.agent._buildSystemPrompt();
|
|
668
|
+
// 动态导入 AI SDK
|
|
669
|
+
const { tool, ToolLoopAgent } = await this._importAI();
|
|
670
|
+
|
|
671
|
+
const userMessage = typeof message === 'string' ? { role: 'user', content: message } : message;
|
|
672
|
+
// console.log('System Prompt:', this._systemPrompt);
|
|
673
|
+
this._messages.push(userMessage);
|
|
674
|
+
// 检查是否需要压缩上下文(包括工具定义)
|
|
675
|
+
const messagesTokens = this._countMessagesTokens(this._messages);
|
|
676
|
+
const toolsTokens = this._countToolsTokens();
|
|
677
|
+
const systemPromptTokens = this._countTokens(this._systemPrompt);
|
|
678
|
+
const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
|
|
679
|
+
const limit = this._maxContextTokens * 0.7; // 降低到 70%
|
|
680
|
+
|
|
681
|
+
// 对于流式调用,如果上下文太大,先压缩再开始
|
|
682
|
+
if (totalTokens > limit) {
|
|
683
|
+
logger.info(
|
|
684
|
+
`Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
|
|
685
|
+
);
|
|
686
|
+
// 流式调用时等待压缩完成(使用带超时控制的压缩)
|
|
687
|
+
await this._compressContext();
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const maxSteps = options.maxSteps || this._maxSteps;
|
|
691
|
+
const tools = this._getAITools(tool);
|
|
692
|
+
const self = this; // 保存引用用于回调
|
|
693
|
+
|
|
694
|
+
if (!this._aiClient) {
|
|
695
|
+
throw new Error('AI client not configured.');
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const agent = new ToolLoopAgent({
|
|
699
|
+
model: this._aiClient,
|
|
700
|
+
instructions: this._systemPrompt,
|
|
701
|
+
tools: tools,
|
|
702
|
+
stopWhen: (step) => step.stepCount >= maxSteps,
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
const messages = this._cleanMessages(this._messages);
|
|
706
|
+
const prunedMessages = pruneMessages({
|
|
707
|
+
messages,
|
|
708
|
+
reasoning: 'all', // Remove all reasoning parts
|
|
709
|
+
toolCalls: 'before-last-2-messages', // Remove tool calls except those in the last message
|
|
710
|
+
});
|
|
711
|
+
try {
|
|
712
|
+
// 使用 runWithContext 让工具执行时能获取 sessionId(支持并行)
|
|
713
|
+
const result = await framework.runWithContext(context, async () => {
|
|
714
|
+
return agent.stream({ messages: prunedMessages, ...this.providerOptions });
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
const stream = result.fullStream;
|
|
718
|
+
let fullText = '';
|
|
719
|
+
|
|
720
|
+
// 流式迭代器
|
|
721
|
+
const iterator = stream[Symbol.asyncIterator] ? stream : stream.fullStream;
|
|
722
|
+
const finalMessages = [];
|
|
723
|
+
|
|
724
|
+
for await (const part of iterator || stream) {
|
|
725
|
+
if (part.type === 'text-delta') {
|
|
726
|
+
const text = part.text || part.textDelta || '';
|
|
727
|
+
fullText += text;
|
|
728
|
+
yield { type: 'text', text };
|
|
729
|
+
} else if (part.type === 'reasoning') {
|
|
730
|
+
finalMessages.push(part);
|
|
731
|
+
this.emit('thinking', { content: part.text });
|
|
732
|
+
} else if (part.type === 'tool-call') {
|
|
733
|
+
finalMessages.push(part);
|
|
734
|
+
yield { type: 'tool-call', toolName: part.toolName, args: part.input };
|
|
735
|
+
} else if (part.type === 'tool-result') {
|
|
736
|
+
// AI SDK 6.x 要求 tool 消息格式为:
|
|
737
|
+
// { role: 'tool', content: [{ type: 'tool-result', toolCallId, toolName, output }] }
|
|
738
|
+
finalMessages.push(part); // 先保存到 finalMessages,等生成结束后统一添加到历史
|
|
739
|
+
yield { type: 'tool-result', toolName: part.toolName, result: part.output };
|
|
740
|
+
} else if (part.type === 'error') {
|
|
741
|
+
yield { type: 'error', error: part.error };
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
this._messages.push({ role: 'tool', content: finalMessages });
|
|
746
|
+
// 添加 assistant 消息
|
|
747
|
+
const assistantMsg = { role: 'assistant', content: fullText };
|
|
748
|
+
this._messages.push(assistantMsg);
|
|
749
|
+
} catch (err) {
|
|
750
|
+
this.emit('error', { error: err.message });
|
|
751
|
+
yield { type: 'error', error: err.message };
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* 获取 AI SDK 格式的工具
|
|
757
|
+
* AI SDK 6.x 需要对象形式 { toolName: tool }
|
|
758
|
+
* @param {Function} toolFn - AI SDK 的 tool 函数
|
|
759
|
+
* @private
|
|
760
|
+
*/
|
|
761
|
+
_getAITools(toolFn) {
|
|
762
|
+
const tools = {};
|
|
763
|
+
|
|
764
|
+
for (const [name, toolDef] of this._tools) {
|
|
765
|
+
const toolName = toolDef.name || name;
|
|
766
|
+
|
|
767
|
+
// 使用 AI SDK 的 tool() 格式
|
|
768
|
+
// 支持 inputSchema (zod schema) 或 parameters (旧格式)
|
|
769
|
+
const toolConfig = {
|
|
770
|
+
name: toolName,
|
|
771
|
+
description: toolDef.description || '',
|
|
772
|
+
execute: async (args) => {
|
|
773
|
+
// 清理参数:移除 undefined、function 等无效值
|
|
774
|
+
const cleanedArgs = this._cleanToolArgs(args);
|
|
775
|
+
|
|
776
|
+
// 执行工具
|
|
777
|
+
this.emit('tool-call', { name: toolName, args: cleanedArgs });
|
|
778
|
+
try {
|
|
779
|
+
const result = await toolDef.execute(cleanedArgs, this.agent.framework);
|
|
780
|
+
this.emit('tool-result', { name: toolName, args: cleanedArgs, result });
|
|
781
|
+
return result;
|
|
782
|
+
} catch (err) {
|
|
783
|
+
this.emit('tool-error', { name: toolName, args: cleanedArgs, error: err.message });
|
|
784
|
+
return { error: err.message };
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
// 支持 inputSchema (zod) 或 parameters (旧格式)
|
|
790
|
+
if (toolDef.inputSchema) {
|
|
791
|
+
toolConfig.inputSchema = toolDef.inputSchema;
|
|
792
|
+
} else if (toolDef.parameters) {
|
|
793
|
+
toolConfig.parameters = toolDef.parameters;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// AI SDK 6.x 使用对象形式,键为工具名
|
|
797
|
+
tools[toolName] = toolFn(toolConfig);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return tools;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* 清理工具参数,移除无效值
|
|
805
|
+
* @param {Object} args - 原始参数
|
|
806
|
+
* @returns {Object} 清理后的参数
|
|
807
|
+
* @private
|
|
808
|
+
*/
|
|
809
|
+
_cleanToolArgs(args) {
|
|
810
|
+
if (!args || typeof args !== 'object') {
|
|
811
|
+
return {};
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const cleaned = {};
|
|
815
|
+
for (const [key, value] of Object.entries(args)) {
|
|
816
|
+
// 跳过 undefined、function、symbol 等无效值
|
|
817
|
+
if (value === undefined || value === null) {
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
// 递归清理嵌套对象
|
|
824
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
825
|
+
cleaned[key] = this._cleanToolArgs(value);
|
|
826
|
+
} else if (Array.isArray(value)) {
|
|
827
|
+
cleaned[key] = value
|
|
828
|
+
.map((item) =>
|
|
829
|
+
typeof item === 'object' && item !== null ? this._cleanToolArgs(item) : item
|
|
830
|
+
)
|
|
831
|
+
.filter((item) => item !== undefined && typeof item !== 'function');
|
|
832
|
+
} else {
|
|
833
|
+
cleaned[key] = value;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
return cleaned;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/**
|
|
840
|
+
* 清理消息格式
|
|
841
|
+
* @private
|
|
842
|
+
*/
|
|
843
|
+
_cleanMessages(messages) {
|
|
844
|
+
return messages.map((msg) => {
|
|
845
|
+
if (!msg || typeof msg !== 'object') {
|
|
846
|
+
return { role: 'user', content: '' };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const cleaned = {
|
|
850
|
+
role: msg.role || 'user',
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
if (Array.isArray(msg.content)) {
|
|
854
|
+
cleaned.content = msg.content;
|
|
855
|
+
} else if (typeof msg.content === 'string') {
|
|
856
|
+
cleaned.content = msg.content;
|
|
857
|
+
} else if (typeof msg.content === 'object' && msg.content !== null) {
|
|
858
|
+
// 对象类型的 content(如 tool result),转为字符串
|
|
859
|
+
cleaned.content =
|
|
860
|
+
typeof msg.content === 'object' && msg.content.content !== undefined
|
|
861
|
+
? String(msg.content.content)
|
|
862
|
+
: JSON.stringify(msg.content);
|
|
863
|
+
} else {
|
|
864
|
+
cleaned.content = msg.text || msg.input || '';
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return cleaned;
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* 销毁
|
|
873
|
+
*/
|
|
874
|
+
destroy() {
|
|
875
|
+
this._messages = [];
|
|
876
|
+
this._tools.clear();
|
|
877
|
+
this._encoder = null;
|
|
878
|
+
this.removeAllListeners();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
module.exports = { AgentChatHandler };
|