foliko 1.0.74 → 1.0.75

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