foliko 1.1.4 → 1.1.6
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/agents/code-assistant.json +4 -1
- package/.agent/agents/file-assistant.json +4 -1
- package/.agent/agents/orchestrator-demo.md +53 -0
- package/.agent/agents/orchestrator.json +7 -0
- package/.agent/data/plugins-state.json +4 -0
- package/.agent/mcp_config.json +6 -0
- package/.agent/memory/feedback/mnrsiuoc-e1ru74.md +9 -0
- package/.agent/memory/feedback/mnrt2mmz-98az6n.md +9 -0
- package/.agent/memory/feedback/mnrtqrhm-kxsicz.md +9 -0
- package/.agent/memory/feedback/mnrts8vg-i0ngzp.md +15 -0
- package/.agent/memory/feedback/mnrtt7jt-c0trb2.md +9 -0
- package/.agent/memory/feedback/mnruc2f0-5s52la.md +16 -0
- package/.agent/memory/feedback/mnrumbmx-63sa0v.md +9 -0
- package/.agent/memory/project/mnrp7p5n-8enm2a.md +31 -0
- package/.agent/memory/project/mnrp9ifb-yynks0.md +40 -0
- package/.agent/memory/project/mnrpb3b8-f617s4.md +25 -0
- package/.agent/memory/project/mnrrmqgg-focprv.md +9 -0
- package/.agent/memory/project/mnrtykbh-6atsor.md +9 -0
- package/.agent/memory/project/mnru9jiu-kgau16.md +35 -0
- package/.agent/memory/reference/mnrnvpwo-rcqv9m.md +52 -0
- package/.agent/memory/reference/mnrovxvz-zy9xqm.md +25 -0
- package/.agent/memory/reference/mnroxabj-1b3930.md +68 -0
- package/.agent/memory/reference/mnrpjtlp-mnb9od.md +35 -0
- package/.agent/memory/reference/mnrps1x3-6b8xfm.md +28 -0
- package/.agent/memory/reference/mnrpt9ov-15er5w.md +22 -0
- package/.agent/memory/reference/mnrq82dn-y9tv9e.md +50 -0
- package/.agent/memory/reference/mnrqnr5v-v75drf.md +34 -0
- package/.agent/memory/reference/mnrrfzys-urudaf.md +31 -0
- package/.agent/memory/reference/mnrrocha-t0027n.md +21 -0
- package/.agent/memory/reference/mnrukklc-bxndsb.md +35 -0
- package/.agent/memory/user/mnrt39t8-8eosy0.md +9 -0
- package/.agent/plugins/marknative/fonts.zip +0 -0
- package/.agent/sessions/cli_default.json +4493 -1183
- package/.agent/test-agent.js +35 -0
- package/cli/src/commands/chat.js +69 -1
- package/cli/src/index.js +11 -2
- package/foliko-cloud-rising.png +0 -0
- package/foliko-dawn-of-ai.png +0 -0
- package/foliko-mindful-observation.png +0 -0
- package/foliko-stellar-dreams.png +0 -0
- package/foliko-zen-jing.png +0 -0
- package/foliko-zen-kong.png +0 -0
- package/foliko-zen-wu.png +0 -0
- package/package.json +2 -2
- package/plugins/coordinator-plugin.js +282 -0
- package/plugins/default-plugins.js +1 -1
- package/plugins/extension-executor-plugin.js +27 -1
- package/plugins/memory-plugin.js +228 -57
- package/src/core/agent-chat.js +206 -115
- package/src/core/agent.js +258 -71
- package/src/core/coordinator-manager.js +341 -0
- package/src/core/framework.js +50 -0
- package/src/core/plugin-base.js +30 -17
- package/src/core/sub-agent-config.js +8 -1
- package/src/core/worker-agent.js +176 -0
- package/src/executors/mcp-executor.js +4 -4
- package/zen_karesansui.png +0 -0
- package/.agent/plugins/marknative/update-readme.js +0 -134
- package/output/emoji-segoe-test-v2.png +0 -0
- package/output/emoji-segoe-test.png +0 -0
- package/output/emoji-test.png +0 -0
- package/output/emoji-windows-test.png +0 -0
- package/output/foliko-emoji-poster.png +0 -0
- package/output/foliko-muji-poster-final.png +0 -0
- package/output/foliko-muji-poster-v2.png +0 -0
- package/output/foliko-muji-poster.png +0 -0
- package/output/foliko-share.png +0 -0
- package/output/progress-circle-test.png +0 -0
- package/output/vb-agent-poster.png +0 -0
package/src/core/agent-chat.js
CHANGED
|
@@ -31,7 +31,7 @@ const MODEL_CONTEXT_LIMITS = {
|
|
|
31
31
|
'deepseek-chat': 28000,
|
|
32
32
|
'deepseek-coder': 28000,
|
|
33
33
|
// MiniMax
|
|
34
|
-
'MiniMax-M2.7':
|
|
34
|
+
'MiniMax-M2.7': 120000,
|
|
35
35
|
// OpenAI
|
|
36
36
|
'gpt-4': 100000,
|
|
37
37
|
'gpt-4o': 100000,
|
|
@@ -41,6 +41,7 @@ const MODEL_CONTEXT_LIMITS = {
|
|
|
41
41
|
'claude-3-5-sonnet': 150000,
|
|
42
42
|
'claude-3-opus': 150000,
|
|
43
43
|
'claude-3-sonnet': 150000,
|
|
44
|
+
'glm-5.1': 200000,
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
/**
|
|
@@ -374,6 +375,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
374
375
|
/**
|
|
375
376
|
* 计算单个内容块的 token 数
|
|
376
377
|
* 参考 Claude Code 的实现
|
|
378
|
+
* 兼容 AI SDK 6.x 标准格式
|
|
377
379
|
* @param {Object} block - 内容块
|
|
378
380
|
* @returns {number}
|
|
379
381
|
* @private
|
|
@@ -390,8 +392,8 @@ class AgentChatHandler extends EventEmitter {
|
|
|
390
392
|
return tokenizer.encode(block.text || '');
|
|
391
393
|
}
|
|
392
394
|
|
|
393
|
-
// 工具调用块 -
|
|
394
|
-
if (block.type === 'tool-use' || block.type === 'tool_call') {
|
|
395
|
+
// 工具调用块 - 兼容 tool-call 和 tool-use 两种类型
|
|
396
|
+
if (block.type === 'tool-call' || block.type === 'tool-use' || block.type === 'tool_call') {
|
|
395
397
|
let count = 0;
|
|
396
398
|
if (block.name || block.toolName) {
|
|
397
399
|
count += tokenizer.encode(String(block.name || block.toolName || ''));
|
|
@@ -404,7 +406,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
404
406
|
return count;
|
|
405
407
|
}
|
|
406
408
|
|
|
407
|
-
// 工具结果块
|
|
409
|
+
// 工具结果块 - 兼容 tool-result 和 tool_result 两种类型
|
|
408
410
|
if (block.type === 'tool-result' || block.type === 'tool_result') {
|
|
409
411
|
let count = 0;
|
|
410
412
|
if (block.content) {
|
|
@@ -668,13 +670,19 @@ class AgentChatHandler extends EventEmitter {
|
|
|
668
670
|
const assistantToolCalls = new Map(); // toolCallId -> assistant message index
|
|
669
671
|
const toolResults = new Map(); // toolCallId -> tool result indices
|
|
670
672
|
|
|
673
|
+
// 工具类型检查辅助函数(兼容 AI SDK 6.x)
|
|
674
|
+
const isToolCall = (item) =>
|
|
675
|
+
item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
|
|
676
|
+
const isToolResult = (item) =>
|
|
677
|
+
item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
|
|
678
|
+
|
|
671
679
|
// 第一遍:找出所有 tool call 和它们的 results
|
|
672
680
|
for (let i = 0; i < recentMessages.length; i++) {
|
|
673
681
|
const msg = recentMessages[i];
|
|
674
682
|
if (msg.role === 'assistant' && msg.content) {
|
|
675
683
|
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
676
684
|
for (const item of content) {
|
|
677
|
-
if (item
|
|
685
|
+
if (isToolCall(item)) {
|
|
678
686
|
assistantToolCalls.set(item.toolCallId, i);
|
|
679
687
|
}
|
|
680
688
|
}
|
|
@@ -682,7 +690,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
682
690
|
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
683
691
|
// 遍历 tool message 中的所有 tool-results
|
|
684
692
|
for (const item of msg.content) {
|
|
685
|
-
if (item
|
|
693
|
+
if (isToolResult(item)) {
|
|
686
694
|
if (!toolResults.has(item.toolCallId)) {
|
|
687
695
|
toolResults.set(item.toolCallId, []);
|
|
688
696
|
}
|
|
@@ -711,7 +719,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
711
719
|
if (msg.content) {
|
|
712
720
|
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
713
721
|
for (const item of content) {
|
|
714
|
-
if (item
|
|
722
|
+
if (isToolCall(item)) {
|
|
715
723
|
hasToolCall = true;
|
|
716
724
|
break;
|
|
717
725
|
}
|
|
@@ -743,7 +751,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
743
751
|
let hasPairedAssistant = false;
|
|
744
752
|
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
745
753
|
for (const item of content) {
|
|
746
|
-
if (item
|
|
754
|
+
if (isToolResult(item)) {
|
|
747
755
|
// 检查这个 toolCallId 对应的 assistant 消息是否被保留
|
|
748
756
|
const assistantIdx = assistantToolCalls.get(item.toolCallId);
|
|
749
757
|
if (assistantIdx !== undefined && assistantIndicesToKeep.has(assistantIdx)) {
|
|
@@ -815,6 +823,12 @@ class AgentChatHandler extends EventEmitter {
|
|
|
815
823
|
content: summaryContent,
|
|
816
824
|
};
|
|
817
825
|
|
|
826
|
+
// 工具类型检查辅助函数(兼容 AI SDK 6.x)
|
|
827
|
+
const isToolCall = (item) =>
|
|
828
|
+
item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
|
|
829
|
+
const isToolResult = (item) =>
|
|
830
|
+
item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
|
|
831
|
+
|
|
818
832
|
// 构建保留的消息,确保 tool call 和 tool result 配对不被分离
|
|
819
833
|
const assistantToolCalls = new Map(); // toolCallId -> assistant message index
|
|
820
834
|
const toolResults = new Map(); // toolCallId -> tool result indices
|
|
@@ -825,7 +839,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
825
839
|
if (msg.role === 'assistant' && msg.content) {
|
|
826
840
|
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
827
841
|
for (const item of content) {
|
|
828
|
-
if (item
|
|
842
|
+
if (isToolCall(item)) {
|
|
829
843
|
assistantToolCalls.set(item.toolCallId, i);
|
|
830
844
|
}
|
|
831
845
|
}
|
|
@@ -833,7 +847,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
833
847
|
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
834
848
|
// 遍历 tool message 中的所有 tool-results
|
|
835
849
|
for (const item of msg.content) {
|
|
836
|
-
if (item
|
|
850
|
+
if (isToolResult(item)) {
|
|
837
851
|
if (!toolResults.has(item.toolCallId)) {
|
|
838
852
|
toolResults.set(item.toolCallId, []);
|
|
839
853
|
}
|
|
@@ -863,7 +877,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
863
877
|
if (msg.content) {
|
|
864
878
|
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
865
879
|
for (const item of content) {
|
|
866
|
-
if (item
|
|
880
|
+
if (isToolCall(item)) {
|
|
867
881
|
hasToolCall = true;
|
|
868
882
|
break;
|
|
869
883
|
}
|
|
@@ -1216,6 +1230,9 @@ ${truncatedContent}${truncatedNote}
|
|
|
1216
1230
|
messages.push(...validatedMessages);
|
|
1217
1231
|
}
|
|
1218
1232
|
|
|
1233
|
+
// 验证并修复消息中的不完整工具调用(在 API 调用前执行,防止开始聊天的时候就报错)
|
|
1234
|
+
this._validateToolCalls(messages);
|
|
1235
|
+
|
|
1219
1236
|
// 最终检查:在 API 调用前再次验证 token 数
|
|
1220
1237
|
const finalMessagesTokens = this._countMessagesTokens(messages);
|
|
1221
1238
|
const finalToolsTokens = this._countToolsTokens();
|
|
@@ -1223,9 +1240,9 @@ ${truncatedContent}${truncatedNote}
|
|
|
1223
1240
|
const finalTotalTokens = finalMessagesTokens + finalToolsTokens + finalSystemPromptTokens;
|
|
1224
1241
|
const finalLimit = this._maxContextTokens * 0.5;
|
|
1225
1242
|
|
|
1226
|
-
logger.info(
|
|
1227
|
-
|
|
1228
|
-
);
|
|
1243
|
+
// logger.info(
|
|
1244
|
+
// `[API Call Check] messages=${messages.length}, tokens=(${finalMessagesTokens}+${finalToolsTokens}+${finalSystemPromptTokens}=${finalTotalTokens}) vs limit=${finalLimit}`
|
|
1245
|
+
// );
|
|
1229
1246
|
|
|
1230
1247
|
// 如果仍然超过限制,强制压缩
|
|
1231
1248
|
if (finalTotalTokens > finalLimit) {
|
|
@@ -1241,25 +1258,44 @@ ${truncatedContent}${truncatedNote}
|
|
|
1241
1258
|
}
|
|
1242
1259
|
|
|
1243
1260
|
// 准备传给 agent 的消息
|
|
1244
|
-
const prepareStepChainStream = async ({ stepNumber, messages }) => {
|
|
1261
|
+
const prepareStepChainStream = async ({ stepNumber, messages: inputMessages }) => {
|
|
1245
1262
|
try {
|
|
1246
1263
|
// 1. 验证消息格式
|
|
1247
|
-
if (!Array.isArray(
|
|
1264
|
+
if (!Array.isArray(inputMessages)) {
|
|
1248
1265
|
logger.warn('prepareStep received non-array messages');
|
|
1249
|
-
return messages;
|
|
1266
|
+
return { messages: inputMessages };
|
|
1250
1267
|
}
|
|
1251
1268
|
|
|
1252
|
-
// 2.
|
|
1253
|
-
|
|
1254
|
-
|
|
1269
|
+
// 2. 修复不完整的工具调用(input 只有 { 或其他无效值)
|
|
1270
|
+
this._validateToolCalls(inputMessages);
|
|
1271
|
+
|
|
1272
|
+
// 3. 验证消息配对并更新 inputMessages(防止 API 报 orphaned tool result 错误)
|
|
1273
|
+
const pairedMessages = this._validateMessagesPairing(inputMessages);
|
|
1274
|
+
if (pairedMessages.length !== inputMessages.length) {
|
|
1275
|
+
logger.debug(
|
|
1276
|
+
`prepareStep pairing: ${inputMessages.length} -> ${pairedMessages.length}`
|
|
1277
|
+
);
|
|
1255
1278
|
}
|
|
1279
|
+
// 重要:用配对验证后的结果替换 inputMessages
|
|
1280
|
+
inputMessages.length = 0;
|
|
1281
|
+
inputMessages.push(...pairedMessages);
|
|
1282
|
+
|
|
1283
|
+
// 4. Token 数检查:超过限制才修剪
|
|
1284
|
+
const tokenCount = this._countMessagesTokens(inputMessages);
|
|
1285
|
+
const tokenLimit = this._maxContextTokens * 0.75; // 75% 阈值,保留缓冲空间
|
|
1286
|
+
if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
|
|
1287
|
+
return { messages: inputMessages }; // 消息少且 token 未超限,不修剪
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// 5. 保留配对完整的消息(使用 AI SDK 标准格式)
|
|
1291
|
+
const pruned = this._prepareMessagesForAI(inputMessages);
|
|
1292
|
+
logger.debug(`prepareStep pruned: ${inputMessages.length} -> ${pruned.length} messages`);
|
|
1256
1293
|
|
|
1257
|
-
//
|
|
1258
|
-
|
|
1259
|
-
return pruned;
|
|
1294
|
+
// 6. 返回 AI SDK 6.x 要求的格式
|
|
1295
|
+
return { messages: pruned };
|
|
1260
1296
|
} catch (err) {
|
|
1261
1297
|
logger.error('prepareStep error:', err.message);
|
|
1262
|
-
return messages
|
|
1298
|
+
return { messages: inputMessages };
|
|
1263
1299
|
}
|
|
1264
1300
|
};
|
|
1265
1301
|
|
|
@@ -1371,6 +1407,9 @@ ${truncatedContent}${truncatedNote}
|
|
|
1371
1407
|
messages.push(...validatedMessages);
|
|
1372
1408
|
}
|
|
1373
1409
|
|
|
1410
|
+
// 验证并修复消息中的不完整工具调用(在 API 调用前执行,防止开始聊天的时候就报错)
|
|
1411
|
+
this._validateToolCalls(messages);
|
|
1412
|
+
|
|
1374
1413
|
// 最终检查:在 API 调用前再次验证 token 数
|
|
1375
1414
|
const finalMessagesTokens = this._countMessagesTokens(messages);
|
|
1376
1415
|
const finalToolsTokens = this._countToolsTokens();
|
|
@@ -1395,26 +1434,45 @@ ${truncatedContent}${truncatedNote}
|
|
|
1395
1434
|
);
|
|
1396
1435
|
}
|
|
1397
1436
|
|
|
1398
|
-
// 准备传给 agent 的消息
|
|
1399
|
-
const prepareStepChainStream = async ({ stepNumber, messages }) => {
|
|
1437
|
+
// 准备传给 agent 的消息 (流式版本)
|
|
1438
|
+
const prepareStepChainStream = async ({ stepNumber, messages: inputMessages }) => {
|
|
1400
1439
|
try {
|
|
1401
1440
|
// 1. 验证消息格式
|
|
1402
|
-
if (!Array.isArray(
|
|
1441
|
+
if (!Array.isArray(inputMessages)) {
|
|
1403
1442
|
logger.warn('prepareStep received non-array messages');
|
|
1404
|
-
return messages;
|
|
1443
|
+
return { messages: inputMessages };
|
|
1405
1444
|
}
|
|
1406
1445
|
|
|
1407
|
-
// 2.
|
|
1408
|
-
|
|
1409
|
-
|
|
1446
|
+
// 2. 修复不完整的工具调用(input 只有 { 或其他无效值)
|
|
1447
|
+
this._validateToolCalls(inputMessages);
|
|
1448
|
+
|
|
1449
|
+
// 3. 验证消息配对并更新 inputMessages(防止 API 报 orphaned tool result 错误)
|
|
1450
|
+
const pairedMessages = this._validateMessagesPairing(inputMessages);
|
|
1451
|
+
if (pairedMessages.length !== inputMessages.length) {
|
|
1452
|
+
logger.debug(
|
|
1453
|
+
`prepareStep pairing: ${inputMessages.length} -> ${pairedMessages.length}`
|
|
1454
|
+
);
|
|
1410
1455
|
}
|
|
1456
|
+
// 重要:用配对验证后的结果替换 inputMessages
|
|
1457
|
+
inputMessages.length = 0;
|
|
1458
|
+
inputMessages.push(...pairedMessages);
|
|
1459
|
+
|
|
1460
|
+
// 4. Token 数检查:超过限制才修剪
|
|
1461
|
+
const tokenCount = this._countMessagesTokens(inputMessages);
|
|
1462
|
+
const tokenLimit = this._maxContextTokens * 0.75; // 75% 阈值,保留缓冲空间
|
|
1463
|
+
if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
|
|
1464
|
+
return { messages: inputMessages }; // 消息少且 token 未超限,不修剪
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// 5. 保留配对完整的消息(使用 AI SDK 标准格式)
|
|
1468
|
+
const pruned = this._prepareMessagesForAI(inputMessages);
|
|
1469
|
+
logger.debug(`prepareStep pruned: ${inputMessages.length} -> ${pruned.length} messages`);
|
|
1411
1470
|
|
|
1412
|
-
//
|
|
1413
|
-
|
|
1414
|
-
return pruned;
|
|
1471
|
+
// 6. 返回 AI SDK 6.x 要求的格式
|
|
1472
|
+
return { messages: pruned };
|
|
1415
1473
|
} catch (err) {
|
|
1416
1474
|
logger.error('prepareStep error:', err.message);
|
|
1417
|
-
return messages
|
|
1475
|
+
return { messages: inputMessages };
|
|
1418
1476
|
}
|
|
1419
1477
|
};
|
|
1420
1478
|
|
|
@@ -1492,75 +1550,98 @@ ${truncatedContent}${truncatedNote}
|
|
|
1492
1550
|
* @private
|
|
1493
1551
|
*/
|
|
1494
1552
|
_prepareMessagesForAI(messages) {
|
|
1495
|
-
if (messages.length <=
|
|
1553
|
+
if (messages.length <= 15) {
|
|
1496
1554
|
return messages;
|
|
1497
1555
|
}
|
|
1498
1556
|
|
|
1499
|
-
//
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1557
|
+
// 1. 确定要保留的消息范围(保留最近 N 条)
|
|
1558
|
+
const keepRecentCount = Math.min(30, Math.floor(messages.length * 0.4));
|
|
1559
|
+
const minIndexToKeep = Math.max(0, messages.length - keepRecentCount);
|
|
1560
|
+
|
|
1561
|
+
// 2. 只从保留下来的消息(minIndexToKeep 之后)中收集 assistant 的 tool-call IDs
|
|
1562
|
+
// 重要:这样可以确保只收集保留下来的 assistant 消息的 tool-call IDs
|
|
1563
|
+
// 防止 assistant 消息被修剪但其 tool-call ID 仍然在集合中导致孤儿的 tool-result
|
|
1564
|
+
const assistantToolCallIds = new Set();
|
|
1565
|
+
for (let i = minIndexToKeep; i < messages.length; i++) {
|
|
1502
1566
|
const msg = messages[i];
|
|
1503
1567
|
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1504
1568
|
for (const item of msg.content) {
|
|
1505
|
-
if (item.type === 'tool-call' && item.toolCallId) {
|
|
1506
|
-
|
|
1569
|
+
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
|
|
1570
|
+
assistantToolCallIds.add(item.toolCallId);
|
|
1507
1571
|
}
|
|
1508
1572
|
}
|
|
1509
1573
|
}
|
|
1510
1574
|
}
|
|
1511
1575
|
|
|
1512
|
-
//
|
|
1513
|
-
const
|
|
1514
|
-
for (let i =
|
|
1576
|
+
// 3. 收集有配对的 tool result indices(只在保留下来的消息中检查)
|
|
1577
|
+
const pairedToolResultIndices = new Set();
|
|
1578
|
+
for (let i = minIndexToKeep; i < messages.length; i++) {
|
|
1515
1579
|
const msg = messages[i];
|
|
1516
1580
|
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1517
1581
|
for (const item of msg.content) {
|
|
1518
|
-
if (item.type === 'tool-result' && item.toolCallId) {
|
|
1519
|
-
if (
|
|
1520
|
-
|
|
1582
|
+
if ((item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId) {
|
|
1583
|
+
if (assistantToolCallIds.has(item.toolCallId)) {
|
|
1584
|
+
pairedToolResultIndices.add(i);
|
|
1521
1585
|
}
|
|
1522
1586
|
}
|
|
1523
1587
|
}
|
|
1524
1588
|
}
|
|
1525
1589
|
}
|
|
1526
1590
|
|
|
1527
|
-
//
|
|
1528
|
-
const
|
|
1529
|
-
const minIndexToKeep = messages.length - recentCount;
|
|
1591
|
+
// 4. 构建最终保留索引集合
|
|
1592
|
+
const indicesToKeep = new Set();
|
|
1530
1593
|
|
|
1531
|
-
|
|
1594
|
+
// 从 minIndexToKeep 开始迭代,和原始代码保持一致
|
|
1532
1595
|
for (let i = minIndexToKeep; i < messages.length; i++) {
|
|
1533
1596
|
const msg = messages[i];
|
|
1534
|
-
|
|
1535
1597
|
if (msg.role === 'tool') {
|
|
1536
|
-
//
|
|
1537
|
-
if (
|
|
1538
|
-
|
|
1598
|
+
// 工具消息:只有有配对的才能保留
|
|
1599
|
+
if (pairedToolResultIndices.has(i)) {
|
|
1600
|
+
indicesToKeep.add(i);
|
|
1539
1601
|
}
|
|
1540
|
-
} else
|
|
1602
|
+
} else {
|
|
1603
|
+
// 其他角色(user, system, assistant 等)默认保留
|
|
1604
|
+
indicesToKeep.add(i);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// 5. 构建最终结果数组
|
|
1609
|
+
const result = [];
|
|
1610
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1611
|
+
if (!indicesToKeep.has(i)) {
|
|
1612
|
+
continue;
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const msg = messages[i];
|
|
1616
|
+
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1541
1617
|
// 检查是否有未配对的 tool-call
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
if (filteredContent.length > 0) {
|
|
1555
|
-
result.push({ ...msg, content: filteredContent });
|
|
1618
|
+
const hasUnpaired = msg.content.some((item) => {
|
|
1619
|
+
if (item.type === 'tool-call' || item.type === 'tool-use') {
|
|
1620
|
+
return !assistantToolCallIds.has(item.toolCallId);
|
|
1621
|
+
}
|
|
1622
|
+
return false;
|
|
1623
|
+
});
|
|
1624
|
+
|
|
1625
|
+
if (hasUnpaired) {
|
|
1626
|
+
// 过滤掉未配对的 tool-call
|
|
1627
|
+
const filteredContent = msg.content.filter((item) => {
|
|
1628
|
+
if (item.type === 'tool-call' || item.type === 'tool-use') {
|
|
1629
|
+
return assistantToolCallIds.has(item.toolCallId);
|
|
1556
1630
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1631
|
+
return true;
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
if (filteredContent.length > 0) {
|
|
1635
|
+
result.push({ ...msg, content: filteredContent });
|
|
1559
1636
|
}
|
|
1560
1637
|
} else {
|
|
1561
1638
|
result.push(msg);
|
|
1562
1639
|
}
|
|
1640
|
+
} else if (msg.role === 'tool') {
|
|
1641
|
+
// 工具消息已经在第4步检查过了,直接添加
|
|
1642
|
+
result.push(msg);
|
|
1563
1643
|
} else {
|
|
1644
|
+
// 其他角色(user, system等)默认保留
|
|
1564
1645
|
result.push(msg);
|
|
1565
1646
|
}
|
|
1566
1647
|
}
|
|
@@ -1580,6 +1661,12 @@ ${truncatedContent}${truncatedNote}
|
|
|
1580
1661
|
return {};
|
|
1581
1662
|
}
|
|
1582
1663
|
|
|
1664
|
+
// 工具类型检查辅助函数(兼容 AI SDK 6.x)
|
|
1665
|
+
const isToolCall = (item) =>
|
|
1666
|
+
item && (item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId;
|
|
1667
|
+
const isToolResult = (item) =>
|
|
1668
|
+
item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId;
|
|
1669
|
+
|
|
1583
1670
|
// 构建配对索引集合:确保 assistant 的 tool call 和 tool result 不会被单独裁剪
|
|
1584
1671
|
const assistantIndices = new Map(); // toolCallId -> assistant index
|
|
1585
1672
|
const toolResultIndices = new Map(); // toolCallId -> tool result indices
|
|
@@ -1589,7 +1676,7 @@ ${truncatedContent}${truncatedNote}
|
|
|
1589
1676
|
if (msg.role === 'assistant' && msg.content) {
|
|
1590
1677
|
const content = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
1591
1678
|
for (const item of content) {
|
|
1592
|
-
if (item
|
|
1679
|
+
if (isToolCall(item)) {
|
|
1593
1680
|
assistantIndices.set(item.toolCallId, i);
|
|
1594
1681
|
}
|
|
1595
1682
|
}
|
|
@@ -1597,7 +1684,7 @@ ${truncatedContent}${truncatedNote}
|
|
|
1597
1684
|
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1598
1685
|
// 遍历 tool message 中的所有 tool-results
|
|
1599
1686
|
for (const item of msg.content) {
|
|
1600
|
-
if (item
|
|
1687
|
+
if (isToolResult(item)) {
|
|
1601
1688
|
if (!toolResultIndices.has(item.toolCallId)) {
|
|
1602
1689
|
toolResultIndices.set(item.toolCallId, []);
|
|
1603
1690
|
}
|
|
@@ -1639,16 +1726,18 @@ ${truncatedContent}${truncatedNote}
|
|
|
1639
1726
|
/**
|
|
1640
1727
|
* 验证并修复消息中的 tool call/result 配对
|
|
1641
1728
|
* 删除没有对应 assistant tool_call 的 tool result(会导致 API 报错)
|
|
1729
|
+
* 兼容 AI SDK 6.x 标准格式
|
|
1642
1730
|
* @param {Array} messages - 消息列表
|
|
1643
1731
|
* @private
|
|
1644
1732
|
*/
|
|
1645
1733
|
_validateMessagesPairing(messages) {
|
|
1646
|
-
// 收集所有 assistant 的 tool-call IDs
|
|
1734
|
+
// 收集所有 assistant 的 tool-call IDs(兼容多种类型名)
|
|
1647
1735
|
const assistantToolCallIds = new Set();
|
|
1648
1736
|
for (const msg of messages) {
|
|
1649
1737
|
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1650
1738
|
for (const item of msg.content) {
|
|
1651
|
-
|
|
1739
|
+
// 兼容 tool-call 和 tool-use 两种类型
|
|
1740
|
+
if ((item.type === 'tool-call' || item.type === 'tool-use') && item.toolCallId) {
|
|
1652
1741
|
assistantToolCallIds.add(item.toolCallId);
|
|
1653
1742
|
}
|
|
1654
1743
|
}
|
|
@@ -1661,7 +1750,12 @@ ${truncatedContent}${truncatedNote}
|
|
|
1661
1750
|
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1662
1751
|
const originalLength = msg.content.length;
|
|
1663
1752
|
msg.content = msg.content.filter((item) => {
|
|
1664
|
-
|
|
1753
|
+
// 兼容 tool-result 和 tool_result 两种类型
|
|
1754
|
+
if (
|
|
1755
|
+
item &&
|
|
1756
|
+
(item.type === 'tool-result' || item.type === 'tool_result') &&
|
|
1757
|
+
item.toolCallId
|
|
1758
|
+
) {
|
|
1665
1759
|
if (!assistantToolCallIds.has(item.toolCallId)) {
|
|
1666
1760
|
removedCount++;
|
|
1667
1761
|
return false; // 删除没有配对的 tool result
|
|
@@ -1694,17 +1788,21 @@ ${truncatedContent}${truncatedNote}
|
|
|
1694
1788
|
* 校验并修复消息中的工具调用参数
|
|
1695
1789
|
* 移除不完整的 JSON(如只有 "{" )的工具调用
|
|
1696
1790
|
* 同时清理 tool result 中的错误信息
|
|
1791
|
+
* 兼容 AI SDK 6.x 标准格式
|
|
1697
1792
|
* @param {Array} messages - 消息列表
|
|
1698
1793
|
* @private
|
|
1699
1794
|
*/
|
|
1700
1795
|
_validateToolCalls(messages) {
|
|
1701
1796
|
let fixedCount = 0;
|
|
1797
|
+
// 收集被跳过的 toolCallId,用于清理对应的 tool-result
|
|
1798
|
+
const invalidatedToolCallIds = new Set();
|
|
1702
1799
|
|
|
1703
1800
|
for (const msg of messages) {
|
|
1704
1801
|
// 清理 assistant 消息中的不完整 tool-call
|
|
1705
1802
|
if (msg.role === 'assistant' && Array.isArray(msg.content)) {
|
|
1706
1803
|
for (const item of msg.content) {
|
|
1707
|
-
|
|
1804
|
+
// 兼容 tool-call 和 tool-use 两种类型
|
|
1805
|
+
if (item.type !== 'tool-call' && item.type !== 'tool-use') {
|
|
1708
1806
|
continue;
|
|
1709
1807
|
}
|
|
1710
1808
|
|
|
@@ -1717,6 +1815,13 @@ ${truncatedContent}${truncatedNote}
|
|
|
1717
1815
|
const trimmed = input.trim();
|
|
1718
1816
|
if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
|
|
1719
1817
|
// 不完整的 JSON,移除这个 tool-call
|
|
1818
|
+
// 记录 toolCallId,以便后续清理对应的 tool-result
|
|
1819
|
+
if (item.toolCallId) {
|
|
1820
|
+
invalidatedToolCallIds.add(item.toolCallId);
|
|
1821
|
+
}
|
|
1822
|
+
logger.warn(
|
|
1823
|
+
`_validateToolCalls: invalid tool-call input="${input}", toolCallId=${item.toolCallId}, converting to text`
|
|
1824
|
+
);
|
|
1720
1825
|
item.type = 'text';
|
|
1721
1826
|
item.text = `(工具调用 ${item.toolName} 参数不完整,已跳过)`;
|
|
1722
1827
|
delete item.toolCallId;
|
|
@@ -1726,51 +1831,37 @@ ${truncatedContent}${truncatedNote}
|
|
|
1726
1831
|
}
|
|
1727
1832
|
}
|
|
1728
1833
|
}
|
|
1834
|
+
}
|
|
1729
1835
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
errorText = output;
|
|
1743
|
-
} else if (typeof output === 'object' && output !== null) {
|
|
1744
|
-
// 处理 { type: 'error-text', value: '...' } 结构
|
|
1745
|
-
if (output.value && typeof output.value === 'string') {
|
|
1746
|
-
errorText = output.value;
|
|
1836
|
+
// 如果有无效的 tool-call,清理对应的 tool-result
|
|
1837
|
+
if (invalidatedToolCallIds.size > 0) {
|
|
1838
|
+
logger.warn(
|
|
1839
|
+
`_validateToolCalls: removing ${invalidatedToolCallIds.size} tool-results with invalidated toolCallIds`
|
|
1840
|
+
);
|
|
1841
|
+
for (const msg of messages) {
|
|
1842
|
+
if (msg.role === 'tool' && Array.isArray(msg.content)) {
|
|
1843
|
+
// 过滤掉引用了无效 toolCallId 的 tool-result
|
|
1844
|
+
const oldLen = msg.content.length;
|
|
1845
|
+
msg.content = msg.content.filter((item) => {
|
|
1846
|
+
if (item.type !== 'tool-result' && item.type !== 'tool_result') {
|
|
1847
|
+
return true;
|
|
1747
1848
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
if (errorText.includes('JSON parsing failed') || errorText.includes('Invalid input')) {
|
|
1756
|
-
const toolName = item.toolName || 'unknown';
|
|
1757
|
-
// 替换为更简洁的错误信息
|
|
1758
|
-
if (typeof output === 'string') {
|
|
1759
|
-
item.output = `(工具 ${toolName} 参数不完整,已跳过)`;
|
|
1760
|
-
} else {
|
|
1761
|
-
item.output = {
|
|
1762
|
-
type: 'error-text',
|
|
1763
|
-
value: `(工具 ${toolName} 参数不完整,已跳过)`,
|
|
1764
|
-
};
|
|
1849
|
+
// 如果 tool-result 引用的 toolCallId 已被标记为无效,则移除
|
|
1850
|
+
if (item.toolCallId && invalidatedToolCallIds.has(item.toolCallId)) {
|
|
1851
|
+
logger.warn(
|
|
1852
|
+
`_validateToolCalls: removing orphaned tool-result with toolCallId=${item.toolCallId}`
|
|
1853
|
+
);
|
|
1854
|
+
fixedCount++;
|
|
1855
|
+
return false;
|
|
1765
1856
|
}
|
|
1766
|
-
|
|
1767
|
-
}
|
|
1857
|
+
return true;
|
|
1858
|
+
});
|
|
1768
1859
|
}
|
|
1769
1860
|
}
|
|
1770
1861
|
}
|
|
1771
1862
|
|
|
1772
1863
|
if (fixedCount > 0) {
|
|
1773
|
-
logger.info(`Fixed ${fixedCount} incomplete tool calls/results`);
|
|
1864
|
+
logger.info(`_validateToolCalls: Fixed ${fixedCount} incomplete tool calls/results`);
|
|
1774
1865
|
}
|
|
1775
1866
|
}
|
|
1776
1867
|
|