foliko 2.0.11 → 2.0.12
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/.editorconfig +56 -56
- package/.lintstagedrc +7 -7
- package/.prettierignore +29 -29
- package/.prettierrc +11 -11
- package/Dockerfile +63 -63
- package/install.ps1 +129 -129
- package/install.sh +121 -121
- package/package.json +1 -1
- package/plugins/core/workflow/context.js +941 -941
- package/plugins/core/workflow/engine.js +66 -66
- package/plugins/core/workflow/js-runner.js +318 -318
- package/plugins/core/workflow/json-runner.js +323 -323
- package/plugins/core/workflow/stages/choice.js +74 -74
- package/plugins/core/workflow/stages/each.js +123 -123
- package/plugins/core/workflow/stages/parallel.js +69 -69
- package/skills/find-skills/SKILL.md +133 -133
- package/src/agent/chat.js +227 -54
- package/src/utils/message-validator.js +139 -16
- package/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.jsonl +0 -266
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: find-skills
|
|
3
|
-
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Find Skills
|
|
7
|
-
|
|
8
|
-
This skill helps you discover and install skills from the open agent skills ecosystem.
|
|
9
|
-
|
|
10
|
-
## When to Use This Skill
|
|
11
|
-
|
|
12
|
-
Use this skill when the user:
|
|
13
|
-
|
|
14
|
-
- Asks "how do I do X" where X might be a common task with an existing skill
|
|
15
|
-
- Says "find a skill for X" or "is there a skill for X"
|
|
16
|
-
- Asks "can you do X" where X is a specialized capability
|
|
17
|
-
- Expresses interest in extending agent capabilities
|
|
18
|
-
- Wants to search for tools, templates, or workflows
|
|
19
|
-
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
|
|
20
|
-
|
|
21
|
-
## What is the Skills CLI?
|
|
22
|
-
|
|
23
|
-
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
|
|
24
|
-
|
|
25
|
-
**Key commands:**
|
|
26
|
-
|
|
27
|
-
- `npx skills find [query]` - Search for skills interactively or by keyword
|
|
28
|
-
- `npx skills add <package>` - Install a skill from GitHub or other sources
|
|
29
|
-
- `npx skills check` - Check for skill updates
|
|
30
|
-
- `npx skills update` - Update all installed skills
|
|
31
|
-
|
|
32
|
-
**Browse skills at:** https://skills.sh/
|
|
33
|
-
|
|
34
|
-
## How to Help Users Find Skills
|
|
35
|
-
|
|
36
|
-
### Step 1: Understand What They Need
|
|
37
|
-
|
|
38
|
-
When a user asks for help with something, identify:
|
|
39
|
-
|
|
40
|
-
1. The domain (e.g., React, testing, design, deployment)
|
|
41
|
-
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
|
|
42
|
-
3. Whether this is a common enough task that a skill likely exists
|
|
43
|
-
|
|
44
|
-
### Step 2: Search for Skills
|
|
45
|
-
|
|
46
|
-
Run the find command with a relevant query:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
npx skills find [query]
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
For example:
|
|
53
|
-
|
|
54
|
-
- User asks "how do I make my React app faster?" → `npx skills find react performance`
|
|
55
|
-
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
|
|
56
|
-
- User asks "I need to create a changelog" → `npx skills find changelog`
|
|
57
|
-
|
|
58
|
-
The command will return results like:
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
Install with npx skills add <owner/repo@skill>
|
|
62
|
-
|
|
63
|
-
vercel-labs/agent-skills@vercel-react-best-practices
|
|
64
|
-
└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### Step 3: Present Options to the User
|
|
68
|
-
|
|
69
|
-
When you find relevant skills, present them to the user with:
|
|
70
|
-
|
|
71
|
-
1. The skill name and what it does
|
|
72
|
-
2. The install command they can run
|
|
73
|
-
3. A link to learn more at skills.sh
|
|
74
|
-
|
|
75
|
-
Example response:
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
I found a skill that might help! The "vercel-react-best-practices" skill provides
|
|
79
|
-
React and Next.js performance optimization guidelines from Vercel Engineering.
|
|
80
|
-
|
|
81
|
-
To install it:
|
|
82
|
-
npx skills add vercel-labs/agent-skills@vercel-react-best-practices
|
|
83
|
-
|
|
84
|
-
Learn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Step 4: Offer to Install
|
|
88
|
-
|
|
89
|
-
If the user wants to proceed, you can install the skill for them:
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
npx skills add <owner/repo@skill> -g -y
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
|
|
96
|
-
|
|
97
|
-
## Common Skill Categories
|
|
98
|
-
|
|
99
|
-
When searching, consider these common categories:
|
|
100
|
-
|
|
101
|
-
| Category | Example Queries |
|
|
102
|
-
| --------------- | ---------------------------------------- |
|
|
103
|
-
| Web Development | react, nextjs, typescript, css, tailwind |
|
|
104
|
-
| Testing | testing, jest, playwright, e2e |
|
|
105
|
-
| DevOps | deploy, docker, kubernetes, ci-cd |
|
|
106
|
-
| Documentation | docs, readme, changelog, api-docs |
|
|
107
|
-
| Code Quality | review, lint, refactor, best-practices |
|
|
108
|
-
| Design | ui, ux, design-system, accessibility |
|
|
109
|
-
| Productivity | workflow, automation, git |
|
|
110
|
-
|
|
111
|
-
## Tips for Effective Searches
|
|
112
|
-
|
|
113
|
-
1. **Use specific keywords**: "react testing" is better than just "testing"
|
|
114
|
-
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
|
|
115
|
-
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
|
|
116
|
-
|
|
117
|
-
## When No Skills Are Found
|
|
118
|
-
|
|
119
|
-
If no relevant skills exist:
|
|
120
|
-
|
|
121
|
-
1. Acknowledge that no existing skill was found
|
|
122
|
-
2. Offer to help with the task directly using your general capabilities
|
|
123
|
-
3. Suggest the user could create their own skill with `npx skills init`
|
|
124
|
-
|
|
125
|
-
Example:
|
|
126
|
-
|
|
127
|
-
```
|
|
128
|
-
I searched for skills related to "xyz" but didn't find any matches.
|
|
129
|
-
I can still help you with this task directly! Would you like me to proceed?
|
|
130
|
-
|
|
131
|
-
If this is something you do often, you could create your own skill:
|
|
132
|
-
npx skills init my-xyz-skill
|
|
133
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: find-skills
|
|
3
|
+
description: Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Find Skills
|
|
7
|
+
|
|
8
|
+
This skill helps you discover and install skills from the open agent skills ecosystem.
|
|
9
|
+
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
Use this skill when the user:
|
|
13
|
+
|
|
14
|
+
- Asks "how do I do X" where X might be a common task with an existing skill
|
|
15
|
+
- Says "find a skill for X" or "is there a skill for X"
|
|
16
|
+
- Asks "can you do X" where X is a specialized capability
|
|
17
|
+
- Expresses interest in extending agent capabilities
|
|
18
|
+
- Wants to search for tools, templates, or workflows
|
|
19
|
+
- Mentions they wish they had help with a specific domain (design, testing, deployment, etc.)
|
|
20
|
+
|
|
21
|
+
## What is the Skills CLI?
|
|
22
|
+
|
|
23
|
+
The Skills CLI (`npx skills`) is the package manager for the open agent skills ecosystem. Skills are modular packages that extend agent capabilities with specialized knowledge, workflows, and tools.
|
|
24
|
+
|
|
25
|
+
**Key commands:**
|
|
26
|
+
|
|
27
|
+
- `npx skills find [query]` - Search for skills interactively or by keyword
|
|
28
|
+
- `npx skills add <package>` - Install a skill from GitHub or other sources
|
|
29
|
+
- `npx skills check` - Check for skill updates
|
|
30
|
+
- `npx skills update` - Update all installed skills
|
|
31
|
+
|
|
32
|
+
**Browse skills at:** https://skills.sh/
|
|
33
|
+
|
|
34
|
+
## How to Help Users Find Skills
|
|
35
|
+
|
|
36
|
+
### Step 1: Understand What They Need
|
|
37
|
+
|
|
38
|
+
When a user asks for help with something, identify:
|
|
39
|
+
|
|
40
|
+
1. The domain (e.g., React, testing, design, deployment)
|
|
41
|
+
2. The specific task (e.g., writing tests, creating animations, reviewing PRs)
|
|
42
|
+
3. Whether this is a common enough task that a skill likely exists
|
|
43
|
+
|
|
44
|
+
### Step 2: Search for Skills
|
|
45
|
+
|
|
46
|
+
Run the find command with a relevant query:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx skills find [query]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For example:
|
|
53
|
+
|
|
54
|
+
- User asks "how do I make my React app faster?" → `npx skills find react performance`
|
|
55
|
+
- User asks "can you help me with PR reviews?" → `npx skills find pr review`
|
|
56
|
+
- User asks "I need to create a changelog" → `npx skills find changelog`
|
|
57
|
+
|
|
58
|
+
The command will return results like:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Install with npx skills add <owner/repo@skill>
|
|
62
|
+
|
|
63
|
+
vercel-labs/agent-skills@vercel-react-best-practices
|
|
64
|
+
└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Step 3: Present Options to the User
|
|
68
|
+
|
|
69
|
+
When you find relevant skills, present them to the user with:
|
|
70
|
+
|
|
71
|
+
1. The skill name and what it does
|
|
72
|
+
2. The install command they can run
|
|
73
|
+
3. A link to learn more at skills.sh
|
|
74
|
+
|
|
75
|
+
Example response:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
I found a skill that might help! The "vercel-react-best-practices" skill provides
|
|
79
|
+
React and Next.js performance optimization guidelines from Vercel Engineering.
|
|
80
|
+
|
|
81
|
+
To install it:
|
|
82
|
+
npx skills add vercel-labs/agent-skills@vercel-react-best-practices
|
|
83
|
+
|
|
84
|
+
Learn more: https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Step 4: Offer to Install
|
|
88
|
+
|
|
89
|
+
If the user wants to proceed, you can install the skill for them:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx skills add <owner/repo@skill> -g -y
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The `-g` flag installs globally (user-level) and `-y` skips confirmation prompts.
|
|
96
|
+
|
|
97
|
+
## Common Skill Categories
|
|
98
|
+
|
|
99
|
+
When searching, consider these common categories:
|
|
100
|
+
|
|
101
|
+
| Category | Example Queries |
|
|
102
|
+
| --------------- | ---------------------------------------- |
|
|
103
|
+
| Web Development | react, nextjs, typescript, css, tailwind |
|
|
104
|
+
| Testing | testing, jest, playwright, e2e |
|
|
105
|
+
| DevOps | deploy, docker, kubernetes, ci-cd |
|
|
106
|
+
| Documentation | docs, readme, changelog, api-docs |
|
|
107
|
+
| Code Quality | review, lint, refactor, best-practices |
|
|
108
|
+
| Design | ui, ux, design-system, accessibility |
|
|
109
|
+
| Productivity | workflow, automation, git |
|
|
110
|
+
|
|
111
|
+
## Tips for Effective Searches
|
|
112
|
+
|
|
113
|
+
1. **Use specific keywords**: "react testing" is better than just "testing"
|
|
114
|
+
2. **Try alternative terms**: If "deploy" doesn't work, try "deployment" or "ci-cd"
|
|
115
|
+
3. **Check popular sources**: Many skills come from `vercel-labs/agent-skills` or `ComposioHQ/awesome-claude-skills`
|
|
116
|
+
|
|
117
|
+
## When No Skills Are Found
|
|
118
|
+
|
|
119
|
+
If no relevant skills exist:
|
|
120
|
+
|
|
121
|
+
1. Acknowledge that no existing skill was found
|
|
122
|
+
2. Offer to help with the task directly using your general capabilities
|
|
123
|
+
3. Suggest the user could create their own skill with `npx skills init`
|
|
124
|
+
|
|
125
|
+
Example:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
I searched for skills related to "xyz" but didn't find any matches.
|
|
129
|
+
I can still help you with this task directly! Would you like me to proceed?
|
|
130
|
+
|
|
131
|
+
If this is something you do often, you could create your own skill:
|
|
132
|
+
npx skills init my-xyz-skill
|
|
133
|
+
```
|
package/src/agent/chat.js
CHANGED
|
@@ -15,9 +15,11 @@ const {
|
|
|
15
15
|
tool: aiTool,
|
|
16
16
|
ToolLoopAgent,
|
|
17
17
|
isLoopFinished,
|
|
18
|
+
InvalidToolInputError,
|
|
18
19
|
} = require('ai');
|
|
19
20
|
const { z } = require('zod');
|
|
20
21
|
const { autoSplitToolResult } = require('../../plugins/executors/data-splitter');
|
|
22
|
+
const { normalizeToolOutputs, validateAll } = require('../utils/message-validator');
|
|
21
23
|
const { cleanResponse } = require('../utils');
|
|
22
24
|
const { ChatQueueManager } = require('../common/queue');
|
|
23
25
|
const { TokenCounter } = require('../llm/tokens');
|
|
@@ -232,12 +234,70 @@ class AgentChatHandler extends EventEmitter {
|
|
|
232
234
|
|
|
233
235
|
/**
|
|
234
236
|
* 验证工具调用
|
|
235
|
-
* @
|
|
237
|
+
* @deprecated Use validateAll() instead
|
|
236
238
|
*/
|
|
237
239
|
_validateToolCalls(messages) {
|
|
238
240
|
return this._toolExecutor.validateToolCalls(messages);
|
|
239
241
|
}
|
|
240
242
|
|
|
243
|
+
/**
|
|
244
|
+
* 规范化 tool-result output 字段。
|
|
245
|
+
* 委托给 message-validator 版本(避免重复逻辑)。
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
_normalizeToolOutputs(messages) {
|
|
249
|
+
return normalizeToolOutputs(messages);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 最终兜底:暴力清理所有 orphaned tool 消息。
|
|
254
|
+
* @deprecated 由 validateAll 替代
|
|
255
|
+
*/
|
|
256
|
+
_stripOrphanedToolMessages(messages) {
|
|
257
|
+
const validIds = new Set();
|
|
258
|
+
for (const msg of messages) {
|
|
259
|
+
if (msg.role !== 'assistant') continue;
|
|
260
|
+
if (Array.isArray(msg.content)) {
|
|
261
|
+
for (const item of msg.content) {
|
|
262
|
+
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) validIds.add(item.toolCallId);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
266
|
+
for (const tc of msg.tool_calls) { if (tc.id) validIds.add(tc.id); }
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let removed = 0;
|
|
271
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
272
|
+
const msg = messages[i];
|
|
273
|
+
if (msg.role !== 'tool') continue;
|
|
274
|
+
|
|
275
|
+
let isOrphaned = false;
|
|
276
|
+
if (Array.isArray(msg.content)) {
|
|
277
|
+
msg.content = msg.content.filter(item => {
|
|
278
|
+
if (item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId && !validIds.has(item.toolCallId)) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
return true;
|
|
282
|
+
});
|
|
283
|
+
if (msg.content.length === 0) isOrphaned = true;
|
|
284
|
+
} else if (msg.tool_call_id && !validIds.has(msg.tool_call_id)) {
|
|
285
|
+
isOrphaned = true;
|
|
286
|
+
} else if (!msg.tool_call_id && validIds.size === 0) {
|
|
287
|
+
isOrphaned = true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (isOrphaned) {
|
|
291
|
+
messages.splice(i, 1);
|
|
292
|
+
removed++;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (removed > 0) {
|
|
297
|
+
logger.debug(`[_stripOrphanedToolMessages] removed ${removed} orphaned tool message(s)`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
241
301
|
// ==================== AI 调用 ====================
|
|
242
302
|
|
|
243
303
|
setAIClient(client) {
|
|
@@ -268,6 +328,152 @@ class AgentChatHandler extends EventEmitter {
|
|
|
268
328
|
* 创建 ToolLoopAgent
|
|
269
329
|
* @private
|
|
270
330
|
*/
|
|
331
|
+
/**
|
|
332
|
+
* 为历史消息中的 tool-call ID 加上前缀,避免与新生成的 ID 冲突。
|
|
333
|
+
* 历史消息的 tool-call ID 可能与当前请求新生成的 ID 重复,
|
|
334
|
+
* 导致 AI API 返回 "tool result's tool id not found" 错误。
|
|
335
|
+
*/
|
|
336
|
+
/**
|
|
337
|
+
* 单次遍历完成:
|
|
338
|
+
* 1. 修复历史错误转换的 text tool-call("参数不完整,已跳过")
|
|
339
|
+
* 2. 给所有历史 tool-call ID 加上前缀(避免与新 ID 冲突)
|
|
340
|
+
* 3. 重映射对应的 tool-result ID
|
|
341
|
+
* @param {Array} messages - 消息数组(会被修改)
|
|
342
|
+
* @param {Map} [idMap] - 可选,外部传入的 ID 映射(用于跨调用共享)
|
|
343
|
+
*/
|
|
344
|
+
_repairAndPrefixHistoricalIds(messages, idMap = new Map()) {
|
|
345
|
+
const HISTORICAL_PREFIX = 'h_';
|
|
346
|
+
|
|
347
|
+
for (const msg of messages) {
|
|
348
|
+
if (msg.role === 'assistant') {
|
|
349
|
+
if (Array.isArray(msg.content)) {
|
|
350
|
+
for (let i = 0; i < msg.content.length; i++) {
|
|
351
|
+
const item = msg.content[i];
|
|
352
|
+
if (!item) continue;
|
|
353
|
+
|
|
354
|
+
// 1. 修复被错误转成 text 的 tool-call
|
|
355
|
+
if (item.type === 'text') {
|
|
356
|
+
const text = item.text;
|
|
357
|
+
if (typeof text === 'string') {
|
|
358
|
+
const match = text.match(/^(工具调用 (.+?) 参数不完整,已跳过)$/);
|
|
359
|
+
if (match) {
|
|
360
|
+
const newId = `${HISTORICAL_PREFIX}restored_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
361
|
+
idMap.set(`restored_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, newId);
|
|
362
|
+
msg.content[i] = {
|
|
363
|
+
type: 'tool-call',
|
|
364
|
+
toolName: match[1],
|
|
365
|
+
toolCallId: newId,
|
|
366
|
+
input: '{}',
|
|
367
|
+
};
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 2. 重映射有历史 ID 的 tool-call
|
|
374
|
+
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
|
|
375
|
+
if (!item.toolCallId.startsWith(HISTORICAL_PREFIX)) {
|
|
376
|
+
const newId = HISTORICAL_PREFIX + item.toolCallId;
|
|
377
|
+
idMap.set(item.toolCallId, newId);
|
|
378
|
+
item.toolCallId = newId;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// tool_calls 数组格式
|
|
384
|
+
if (Array.isArray(msg.tool_calls)) {
|
|
385
|
+
for (const tc of msg.tool_calls) {
|
|
386
|
+
if (tc.id && !tc.id.startsWith(HISTORICAL_PREFIX)) {
|
|
387
|
+
const newId = HISTORICAL_PREFIX + tc.id;
|
|
388
|
+
idMap.set(tc.id, newId);
|
|
389
|
+
tc.id = newId;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
} else if (msg.role === 'tool') {
|
|
394
|
+
// 3. 重映射 tool-result 的 ID
|
|
395
|
+
if (Array.isArray(msg.content)) {
|
|
396
|
+
for (const item of msg.content) {
|
|
397
|
+
if ((item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId && !item.toolCallId.startsWith(HISTORICAL_PREFIX)) {
|
|
398
|
+
const newId = idMap.get(item.toolCallId);
|
|
399
|
+
if (newId) item.toolCallId = newId;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
if (msg.tool_call_id && !msg.tool_call_id.startsWith(HISTORICAL_PREFIX)) {
|
|
404
|
+
const newId = idMap.get(msg.tool_call_id);
|
|
405
|
+
if (newId) msg.tool_call_id = newId;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return idMap;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 修复消息数组中 AI SDK 标记为 invalid 的 tool-call
|
|
415
|
+
* @param {Array} messages - 消息数组(会被修改)
|
|
416
|
+
* @returns {number} 修复数量
|
|
417
|
+
*/
|
|
418
|
+
_repairInvalidToolCallsInMessages(messages) {
|
|
419
|
+
let count = 0;
|
|
420
|
+
for (const msg of messages) {
|
|
421
|
+
if (msg.role !== 'assistant' || !Array.isArray(msg.content)) continue;
|
|
422
|
+
for (const item of msg.content) {
|
|
423
|
+
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.invalid) {
|
|
424
|
+
const input = item.input;
|
|
425
|
+
if (typeof input !== 'string') continue;
|
|
426
|
+
const trimmed = input.trim();
|
|
427
|
+
// 空输入 → {}
|
|
428
|
+
if (trimmed === '') {
|
|
429
|
+
item.input = '{}';
|
|
430
|
+
delete item.invalid;
|
|
431
|
+
count++;
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
// 尝试补全花括号
|
|
435
|
+
let fixed = null;
|
|
436
|
+
if (!trimmed.startsWith('{')) {
|
|
437
|
+
fixed = '{' + trimmed + '}';
|
|
438
|
+
} else if (!trimmed.endsWith('}')) {
|
|
439
|
+
fixed = trimmed + '}';
|
|
440
|
+
}
|
|
441
|
+
if (fixed) {
|
|
442
|
+
try { JSON.parse(fixed); item.input = fixed; delete item.invalid; count++; } catch {}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return count;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* AI SDK repairToolCall: 修复无效的 tool-call 输入
|
|
452
|
+
* 当模型返回的 JSON 参数无法解析时,尝试修复
|
|
453
|
+
*/
|
|
454
|
+
_repairToolCall({ toolCall, tools, error }) {
|
|
455
|
+
// 只处理 InvalidToolInputError(JSON 解析失败)
|
|
456
|
+
if (!InvalidToolInputError.isInstance(error)) return null;
|
|
457
|
+
const input = toolCall.input;
|
|
458
|
+
if (typeof input !== 'string') return null;
|
|
459
|
+
const trimmed = input.trim();
|
|
460
|
+
// 空输入 → 修复为 {}
|
|
461
|
+
if (trimmed === '') {
|
|
462
|
+
return { ...toolCall, input: '{}' };
|
|
463
|
+
}
|
|
464
|
+
// 尝试补全花括号
|
|
465
|
+
let fixed = null;
|
|
466
|
+
if (!trimmed.startsWith('{')) {
|
|
467
|
+
fixed = '{' + trimmed + '}';
|
|
468
|
+
} else if (!trimmed.endsWith('}')) {
|
|
469
|
+
fixed = trimmed + '}';
|
|
470
|
+
}
|
|
471
|
+
if (fixed) {
|
|
472
|
+
try { JSON.parse(fixed); return { ...toolCall, input: fixed }; } catch {}
|
|
473
|
+
}
|
|
474
|
+
return null;
|
|
475
|
+
}
|
|
476
|
+
|
|
271
477
|
_createToolLoopAgent(model, tools) {
|
|
272
478
|
return new ToolLoopAgent({
|
|
273
479
|
model,
|
|
@@ -275,6 +481,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
275
481
|
tools,
|
|
276
482
|
stopWhen: isLoopFinished(),
|
|
277
483
|
prepareStep: this._createPrepareStep(),
|
|
484
|
+
experimental_repairToolCall: this._repairToolCall.bind(this),
|
|
278
485
|
});
|
|
279
486
|
}
|
|
280
487
|
|
|
@@ -474,7 +681,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
474
681
|
if (!this._aiClient) {
|
|
475
682
|
throw new Error('AI 客户端未配置');
|
|
476
683
|
}
|
|
477
|
-
this._validateToolCalls(messages);
|
|
478
684
|
const systemPrompt = framework.prompts.build();
|
|
479
685
|
//await fs.promises.writeFile(`.${sessionId}_systemPrompt.md`, systemPrompt); // 调试用:保存系统提示词
|
|
480
686
|
const tools = this._getAITools(aiTool);
|
|
@@ -484,6 +690,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
484
690
|
tools,
|
|
485
691
|
stopWhen: isLoopFinished(),
|
|
486
692
|
prepareStep: this._createPrepareStep(),
|
|
693
|
+
experimental_repairToolCall: this._repairToolCall.bind(this),
|
|
487
694
|
});
|
|
488
695
|
|
|
489
696
|
// AI SDK 错误回调:仅记录日志,不在这里处理(统一由 catch/yield 处理)
|
|
@@ -557,7 +764,8 @@ class AgentChatHandler extends EventEmitter {
|
|
|
557
764
|
}
|
|
558
765
|
// 写入端 sanitize:AI SDK v6 响应里允许 error-text/error-json(工具失败标记),
|
|
559
766
|
// 但 ModelMessage 输入 schema 不接受;持久化前必须规范化
|
|
560
|
-
|
|
767
|
+
normalizeToolOutputs(finishMessages);
|
|
768
|
+
|
|
561
769
|
messages.push(...finishMessages);
|
|
562
770
|
|
|
563
771
|
// 获取或估算 token 用量
|
|
@@ -668,7 +876,6 @@ class AgentChatHandler extends EventEmitter {
|
|
|
668
876
|
if (!this._aiClient) {
|
|
669
877
|
throw new Error('AI 客户端未配置');
|
|
670
878
|
}
|
|
671
|
-
this._validateToolCalls(messages);
|
|
672
879
|
const systemPrompt = framework.prompts.build();
|
|
673
880
|
const tools = this._getAITools(aiTool);
|
|
674
881
|
|
|
@@ -684,6 +891,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
684
891
|
tools,
|
|
685
892
|
stopWhen: isLoopFinished(),
|
|
686
893
|
prepareStep: this._createPrepareStep(),
|
|
894
|
+
experimental_repairToolCall: this._repairToolCall.bind(this),
|
|
687
895
|
});
|
|
688
896
|
|
|
689
897
|
const result = await framework.runInSession(sessionId, {}, async () => {
|
|
@@ -787,65 +995,29 @@ class AgentChatHandler extends EventEmitter {
|
|
|
787
995
|
logger.debug(`[_prepareSession] AFTER compress: messages=${messages.length}`);
|
|
788
996
|
}
|
|
789
997
|
|
|
790
|
-
//
|
|
791
|
-
|
|
998
|
+
// 单次遍历完成:修复 invalid tool-call + 修复错误 text + 重映射历史 ID
|
|
999
|
+
this._repairInvalidToolCallsInMessages(messages);
|
|
1000
|
+
this._repairAndPrefixHistoricalIds(messages);
|
|
1001
|
+
|
|
1002
|
+
// 单次遍历完成所有验证 + 规范化(validateAll 内部处理 error-text/error-json)
|
|
1003
|
+
const validated = validateAll(messages);
|
|
792
1004
|
if (validated.length !== messages.length) {
|
|
793
|
-
// 删除了消息,缓存失效
|
|
794
1005
|
this._invalidateMessageTokensCache(messageStore);
|
|
795
1006
|
messages.length = 0;
|
|
796
1007
|
messages.push(...validated);
|
|
797
1008
|
}
|
|
798
1009
|
messages.push(userMessage);
|
|
799
|
-
this._validateToolCalls(messages);
|
|
800
|
-
|
|
801
|
-
// 读取端 sanitize:修复历史 chat log 中可能存在的 error-text/error-json
|
|
802
|
-
this._normalizeToolOutputs(messages);
|
|
803
|
-
|
|
804
|
-
// 最终兜底:暴力清理所有 orphaned tool 消息,确保 AI SDK 不报错
|
|
805
|
-
this._stripOrphanedToolMessages(messages);
|
|
806
1010
|
|
|
807
1011
|
return { messageStore, messages };
|
|
808
1012
|
}
|
|
809
1013
|
|
|
810
1014
|
/**
|
|
811
1015
|
* 规范化 tool-result 的 output 字段。
|
|
812
|
-
*
|
|
813
|
-
* 但 ModelMessage 输入 schema(AI SDK 发送 prompt 时使用)不接受这两类。
|
|
814
|
-
* 必须在写入磁盘和读取发送前都做转换。
|
|
1016
|
+
* 委托给 message-validator 版本(避免重复逻辑)。
|
|
815
1017
|
* @private
|
|
816
1018
|
*/
|
|
817
1019
|
_normalizeToolOutputs(messages) {
|
|
818
|
-
|
|
819
|
-
let fixed = 0;
|
|
820
|
-
for (const msg of messages) {
|
|
821
|
-
if (msg.role !== 'tool' || !Array.isArray(msg.content)) continue;
|
|
822
|
-
for (const part of msg.content) {
|
|
823
|
-
if (!part || part.type !== 'tool-result') continue;
|
|
824
|
-
const out = part.output;
|
|
825
|
-
if (out === undefined || out === null || typeof out !== 'object') {
|
|
826
|
-
const v = out === undefined ? 'undefined' : (typeof out === 'string' ? out : JSON.stringify(out));
|
|
827
|
-
part.output = { type: 'text', value: String(v) };
|
|
828
|
-
fixed++;
|
|
829
|
-
continue;
|
|
830
|
-
}
|
|
831
|
-
if (VALID_OUTPUT_TYPES.has(out.type)) continue;
|
|
832
|
-
if (out.type === 'error-text' || out.type === 'error_text') {
|
|
833
|
-
part.output = { type: 'text', value: typeof out.value === 'string' ? out.value : String(out.value ?? '') };
|
|
834
|
-
fixed++;
|
|
835
|
-
continue;
|
|
836
|
-
}
|
|
837
|
-
if (out.type === 'error-json' || out.type === 'error_json') {
|
|
838
|
-
part.output = { type: 'json', value: out.value ?? null };
|
|
839
|
-
fixed++;
|
|
840
|
-
continue;
|
|
841
|
-
}
|
|
842
|
-
// 未知 type 降级为 text
|
|
843
|
-
try { part.output = { type: 'text', value: JSON.stringify(out) }; }
|
|
844
|
-
catch { part.output = { type: 'text', value: String(out) }; }
|
|
845
|
-
fixed++;
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
return fixed;
|
|
1020
|
+
return normalizeToolOutputs(messages);
|
|
849
1021
|
}
|
|
850
1022
|
|
|
851
1023
|
/**
|
|
@@ -1090,19 +1262,20 @@ class AgentChatHandler extends EventEmitter {
|
|
|
1090
1262
|
inputMessages.push(...trimmed);
|
|
1091
1263
|
}
|
|
1092
1264
|
|
|
1093
|
-
//
|
|
1094
|
-
const
|
|
1265
|
+
// 单次遍历完成:修复 invalid tool-call + 修复错误 text + 重映射历史 ID
|
|
1266
|
+
const repairedCount = this._repairInvalidToolCallsInMessages(inputMessages);
|
|
1267
|
+
this._repairAndPrefixHistoricalIds(inputMessages);
|
|
1268
|
+
|
|
1269
|
+
// 单次遍历完成所有验证 + 规范化
|
|
1270
|
+
const validated = validateAll(inputMessages);
|
|
1095
1271
|
if (validated.length !== inputMessages.length) {
|
|
1096
1272
|
logger.debug(
|
|
1097
|
-
`[PrepareStep] After
|
|
1273
|
+
`[PrepareStep] After validation: ${inputMessages.length} -> ${validated.length}`
|
|
1098
1274
|
);
|
|
1099
1275
|
inputMessages.length = 0;
|
|
1100
1276
|
inputMessages.push(...validated);
|
|
1101
1277
|
}
|
|
1102
1278
|
|
|
1103
|
-
// 验证 tool-call 格式
|
|
1104
|
-
this._validateToolCalls(inputMessages);
|
|
1105
|
-
|
|
1106
1279
|
return { messages: inputMessages };
|
|
1107
1280
|
} catch (err) {
|
|
1108
1281
|
logger.error('prepareStep error:', err.message, err.stack);
|