foliko 1.1.1 → 1.1.2

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 (57) hide show
  1. package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
  2. package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
  3. package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
  4. package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
  5. package/.agent/plugins/poster-plugin/package.json +2 -1
  6. package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
  7. package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
  8. package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
  9. package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
  10. package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
  11. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
  12. package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
  13. package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
  14. package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
  15. package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
  16. package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
  17. package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
  18. package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
  19. package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
  20. package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
  21. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
  22. package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
  23. package/.agent/plugins/poster-plugin/src/composer.js +392 -150
  24. package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
  25. package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
  26. package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
  27. package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
  28. package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
  29. package/.agent/plugins/poster-plugin/src/index.js +430 -7
  30. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
  31. package/.agent/plugins/poster-plugin/test-background.svg +1 -0
  32. package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
  33. package/.agent/plugins/poster-plugin/test-image.png +0 -0
  34. package/.agent/sessions/cli_default.json +1089 -145
  35. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +8902 -0
  36. package/.claude/settings.local.json +6 -1
  37. package/output/beef-love-poster.png +0 -0
  38. package/output/international-news-daily.png +0 -0
  39. package/package.json +2 -1
  40. package/plugins/extension-executor-plugin.js +33 -32
  41. package/plugins/file-system-plugin.js +4 -19
  42. package/plugins/subagent-plugin.js +37 -14
  43. package/plugins/weixin-plugin.js +167 -47
  44. package/poster-test-2.png +0 -0
  45. package/skills/poster-guide/SKILL.md +497 -5
  46. package/src/core/agent-chat.js +141 -8
  47. package/src/core/agent.js +6 -3
  48. package/calc_tokens_weixin.js +0 -81
  49. package/foliko-creative-3.png +0 -0
  50. package/foliko-creative-4.png +0 -0
  51. package/foliko-creative-5.png +0 -0
  52. package/story-cover-book-v2.png +0 -0
  53. package/story-cover-japanese-1.png +0 -0
  54. package/story-cover-japanese-2.png +0 -0
  55. package/story-cover-japanese-3.png +0 -0
  56. package/story-cover-moran.png +0 -0
  57. package/undefined.png +0 -0
@@ -637,13 +637,263 @@ ext_call({
637
637
  | `add_poster_list_item` | 列表项 |
638
638
  | `add_poster_notification` | 通知 |
639
639
 
640
+ ### 设计组件(新增)
641
+
642
+ | 工具 | 说明 |
643
+ | --------------------------- | --------- |
644
+ | `add_poster_button` | 按钮 |
645
+ | `add_poster_icon` | 图标 |
646
+ | `add_poster_qrcode` | 二维码 |
647
+ | `add_poster_frame` | 装饰边框 |
648
+ | `add_poster_bubble` | 对话气泡 |
649
+ | `add_poster_ribbon` | 丝带/飘带 |
650
+ | `add_poster_seal` | 印章 |
651
+ | `add_poster_highlight_text` | 高亮文字 |
652
+ | `add_poster_barcode` | 条形码 |
653
+
640
654
  ### 模板
641
655
 
642
- | 工具 | 说明 |
643
- | ------------------------ | -------- |
644
- | `generate_poster` | 一键生成 |
645
- | `list_poster_templates` | 列出模板 |
646
- | `list_poster_components` | 列出组件 |
656
+ | 工具 | 说明 |
657
+ | ------------------------ | ------------ |
658
+ | `generate_poster` | 一键生成 |
659
+ | `list_poster_templates` | 列出模板 |
660
+ | `list_poster_components` | 列出组件 |
661
+ | `list_poster_presets` | 列出预设尺寸 |
662
+
663
+ ---
664
+
665
+ ## 🆕 新增设计组件详解
666
+
667
+ ### 按钮 (button)
668
+
669
+ 支持图标、渐变、阴影的现代化按钮。
670
+
671
+ ```javascript
672
+ ext_call({
673
+ plugin: 'poster',
674
+ tool: 'add_poster_button',
675
+ args: {
676
+ x: 960,
677
+ y: 800,
678
+ width: 280,
679
+ height: 70,
680
+ text: '立即购买',
681
+ fontSize: 28,
682
+ color: '#ffffff',
683
+ backgroundColor: '#3b82f6',
684
+ borderColor: '#2563eb',
685
+ borderWidth: 2,
686
+ radius: 35,
687
+ shadow: { color: '#3b82f6', blur: 20, offsetX: 0, offsetY: 8 },
688
+ gradient: { colors: ['#3b82f6', '#8b5cf6'] },
689
+ icon: '🛒',
690
+ iconPosition: 'left',
691
+ },
692
+ });
693
+ ```
694
+
695
+ ### 图标 (icon)
696
+
697
+ 支持 emoji 和图片的图标组件。
698
+
699
+ ```javascript
700
+ ext_call({
701
+ plugin: 'poster',
702
+ tool: 'add_poster_icon',
703
+ args: {
704
+ x: 100,
705
+ y: 100,
706
+ size: 80,
707
+ icon: '⭐',
708
+ color: '#fbbf24',
709
+ backgroundColor: '#1e293b',
710
+ borderColor: '#fbbf24',
711
+ borderWidth: 2,
712
+ radius: 16,
713
+ shadow: { color: '#000000', blur: 10, offsetX: 2, offsetY: 4 },
714
+ },
715
+ });
716
+ ```
717
+
718
+ ### 二维码 (qrcode)
719
+
720
+ 本地生成二维码,支持自定义颜色和 Logo。
721
+
722
+ ```javascript
723
+ ext_call({
724
+ plugin: 'poster',
725
+ tool: 'add_poster_qrcode',
726
+ args: {
727
+ x: 1600,
728
+ y: 700,
729
+ size: 200,
730
+ content: 'https://example.com',
731
+ color: '#0f172a',
732
+ backgroundColor: '#ffffff',
733
+ logo: 'https://example.com/logo.png',
734
+ logoSize: 40,
735
+ },
736
+ });
737
+ ```
738
+
739
+ ### 装饰边框 (frame)
740
+
741
+ 多种边框样式:simple、double、dashed、dotted、corner、vintage、modern、floral。
742
+
743
+ ```javascript
744
+ ext_call({
745
+ plugin: 'poster',
746
+ tool: 'add_poster_frame',
747
+ args: {
748
+ x: 100,
749
+ y: 100,
750
+ width: 500,
751
+ height: 400,
752
+ style: 'vintage', // 边框样式
753
+ color: '#c9a227',
754
+ borderWidth: 3,
755
+ radius: 8,
756
+ padding: 20,
757
+ },
758
+ });
759
+ ```
760
+
761
+ **style 可选值:**
762
+
763
+ - `simple` - 简单矩形
764
+ - `double` - 双线边框
765
+ - `dashed` - 虚线边框
766
+ - `dotted` - 点线边框
767
+ - `corner` - 四角装饰
768
+ - `vintage` - 复古边框(双线+角装饰)
769
+ - `modern` - 现代边框(粗细交替)
770
+ - `floral` - 花纹边框
771
+
772
+ ### 对话气泡 (bubble)
773
+
774
+ 带尾巴的对话气泡组件。
775
+
776
+ ```javascript
777
+ ext_call({
778
+ plugin: 'poster',
779
+ tool: 'add_poster_bubble',
780
+ args: {
781
+ x: 200,
782
+ y: 500,
783
+ width: 400,
784
+ height: 120,
785
+ text: '你好!有什么可以帮助你的吗?',
786
+ fontSize: 24,
787
+ color: '#1e293b',
788
+ backgroundColor: '#ffffff',
789
+ borderColor: '#e2e8f0',
790
+ borderWidth: 1,
791
+ radius: 20,
792
+ tailDirection: 'bottom', // 尾巴方向: bottom, top, left, right
793
+ tailPosition: 'left', // 尾巴位置: left, center, right
794
+ shadow: { color: '#000000', blur: 10, offsetX: 3, offsetY: 3 },
795
+ },
796
+ });
797
+ ```
798
+
799
+ ### 丝带/飘带 (ribbon)
800
+
801
+ 三种样式:fold(折叠)、diagonal(对角)、corner(角落)。
802
+
803
+ ```javascript
804
+ ext_call({
805
+ plugin: 'poster',
806
+ tool: 'add_poster_ribbon',
807
+ args: {
808
+ x: 100,
809
+ y: 50,
810
+ width: 200,
811
+ text: '限时特惠',
812
+ fontSize: 24,
813
+ color: '#ffffff',
814
+ backgroundColor: '#e74c3c',
815
+ borderColor: '#c0392b',
816
+ borderWidth: 2,
817
+ style: 'fold', // fold, diagonal, corner
818
+ position: 'top',
819
+ shadow: { color: '#000000', blur: 8, offsetX: 3, offsetY: 3 },
820
+ },
821
+ });
822
+ ```
823
+
824
+ ### 印章 (seal)
825
+
826
+ 印章效果组件,支持多种形状。
827
+
828
+ ```javascript
829
+ ext_call({
830
+ plugin: 'poster',
831
+ tool: 'add_poster_seal',
832
+ args: {
833
+ x: 1700,
834
+ y: 900,
835
+ size: 120,
836
+ text: '正品',
837
+ fontSize: 20,
838
+ color: '#e74c3c',
839
+ style: 'circle', // circle, square, star, hexagon
840
+ borderWidth: 4,
841
+ },
842
+ });
843
+ ```
844
+
845
+ ### 高亮文字 (highlightText)
846
+
847
+ 多种高亮效果:marker、underline、background、stroke、neon。
848
+
849
+ ```javascript
850
+ ext_call({
851
+ plugin: 'poster',
852
+ tool: 'add_poster_highlight_text',
853
+ args: {
854
+ x: 300,
855
+ y: 400,
856
+ text: '重要通知',
857
+ fontSize: 60,
858
+ fontFamily: 'sans-serif',
859
+ color: '#1e293b',
860
+ highlightColor: '#fbbf24',
861
+ highlightStyle: 'marker', // marker, underline, background, stroke, neon
862
+ highlightWidth: 20,
863
+ strokeWidth: 3,
864
+ shadow: { color: '#fbbf24', blur: 15, offsetX: 2, offsetY: 2 },
865
+ },
866
+ });
867
+ ```
868
+
869
+ **highlightStyle 可选值:**
870
+
871
+ - `marker` - 荧光笔效果(倾斜半透明矩形)
872
+ - `underline` - 下划线
873
+ - `background` - 背景色块
874
+ - `stroke` - 文字描边
875
+ - `neon` - 霓虹发光效果
876
+
877
+ ### 条形码 (barcode)
878
+
879
+ 生成条形码,支持简单条形码和 API 条形码。
880
+
881
+ ```javascript
882
+ ext_call({
883
+ plugin: 'poster',
884
+ tool: 'add_poster_barcode',
885
+ args: {
886
+ x: 1500,
887
+ y: 900,
888
+ width: 300,
889
+ height: 100,
890
+ content: '123456789012',
891
+ showText: true,
892
+ textColor: '#1e293b',
893
+ fontSize: 16,
894
+ },
895
+ });
896
+ ```
647
897
 
648
898
  ---
649
899
 
@@ -926,6 +1176,248 @@ ext_call({
926
1176
  });
927
1177
  ```
928
1178
 
1179
+ ### 示例 4:产品宣传海报(使用新增组件)
1180
+
1181
+ 展示 button、icon、qrcode、frame、bubble、ribbon、seal、highlightText、barcode 等新组件。
1182
+
1183
+ ```javascript
1184
+ // 1. 创建画布
1185
+ ext_call({
1186
+ plugin: 'poster',
1187
+ tool: 'create_poster_canvas',
1188
+ args: { width: 1200, height: 1600, background: '#f8fafc' },
1189
+ });
1190
+
1191
+ // 2. 装饰边框
1192
+ ext_call({
1193
+ plugin: 'poster',
1194
+ tool: 'add_poster_frame',
1195
+ args: {
1196
+ x: 40,
1197
+ y: 40,
1198
+ width: 1120,
1199
+ height: 1520,
1200
+ style: 'vintage',
1201
+ color: '#c9a227',
1202
+ borderWidth: 4,
1203
+ padding: 30,
1204
+ },
1205
+ });
1206
+
1207
+ // 3. 丝带标签
1208
+ ext_call({
1209
+ plugin: 'poster',
1210
+ tool: 'add_poster_ribbon',
1211
+ args: {
1212
+ x: 800,
1213
+ y: 80,
1214
+ width: 200,
1215
+ text: '新品上市',
1216
+ fontSize: 22,
1217
+ backgroundColor: '#e74c3c',
1218
+ style: 'fold',
1219
+ },
1220
+ });
1221
+
1222
+ // 4. 主标题(高亮文字)
1223
+ ext_call({
1224
+ plugin: 'poster',
1225
+ tool: 'add_poster_highlight_text',
1226
+ args: {
1227
+ x: 100,
1228
+ y: 200,
1229
+ text: '智能手表 Pro',
1230
+ fontSize: 72,
1231
+ fontFamily: 'sans-serif',
1232
+ color: '#1e293b',
1233
+ highlightColor: '#fbbf24',
1234
+ highlightStyle: 'marker',
1235
+ },
1236
+ });
1237
+
1238
+ // 5. 图标装饰
1239
+ ext_call({
1240
+ plugin: 'poster',
1241
+ tool: 'add_poster_icon',
1242
+ args: { x: 100, y: 280, size: 60, icon: '⌚', backgroundColor: '#1e293b', radius: 12 },
1243
+ });
1244
+ ext_call({
1245
+ plugin: 'poster',
1246
+ tool: 'add_poster_icon',
1247
+ args: { x: 180, y: 280, size: 60, icon: '🔋', backgroundColor: '#10b981', radius: 12 },
1248
+ });
1249
+ ext_call({
1250
+ plugin: 'poster',
1251
+ tool: 'add_poster_icon',
1252
+ args: { x: 260, y: 280, size: 60, icon: '💧', backgroundColor: '#3b82f6', radius: 12 },
1253
+ });
1254
+
1255
+ // 6. 对话气泡
1256
+ ext_call({
1257
+ plugin: 'poster',
1258
+ tool: 'add_poster_bubble',
1259
+ args: {
1260
+ x: 100,
1261
+ y: 400,
1262
+ width: 500,
1263
+ height: 100,
1264
+ text: '限时特惠,仅剩 3 天!',
1265
+ fontSize: 24,
1266
+ backgroundColor: '#fef3c7',
1267
+ borderColor: '#fbbf24',
1268
+ tailDirection: 'bottom',
1269
+ tailPosition: 'left',
1270
+ },
1271
+ });
1272
+
1273
+ // 7. 产品介绍文字
1274
+ ext_call({
1275
+ plugin: 'poster',
1276
+ tool: 'add_poster_text',
1277
+ args: {
1278
+ x: 100,
1279
+ y: 580,
1280
+ text: '全新智能手表,支持心率监测、GPS定位、防水50米。',
1281
+ fontSize: 28,
1282
+ color: '#64748b',
1283
+ },
1284
+ });
1285
+
1286
+ // 8. 印章认证
1287
+ ext_call({
1288
+ plugin: 'poster',
1289
+ tool: 'add_poster_seal',
1290
+ args: {
1291
+ x: 900,
1292
+ y: 500,
1293
+ size: 100,
1294
+ text: '正品',
1295
+ fontSize: 18,
1296
+ color: '#e74c3c',
1297
+ style: 'circle',
1298
+ },
1299
+ });
1300
+
1301
+ // 9. CTA 按钮
1302
+ ext_call({
1303
+ plugin: 'poster',
1304
+ tool: 'add_poster_button',
1305
+ args: {
1306
+ x: 350,
1307
+ y: 700,
1308
+ width: 500,
1309
+ height: 80,
1310
+ text: '立即购买 →',
1311
+ fontSize: 32,
1312
+ backgroundColor: '#1e293b',
1313
+ gradient: { colors: ['#1e293b', '#334155'] },
1314
+ radius: 40,
1315
+ shadow: { color: '#1e293b', blur: 20, offsetY: 8 },
1316
+ },
1317
+ });
1318
+
1319
+ // 10. 统计卡片
1320
+ ext_call({
1321
+ plugin: 'poster',
1322
+ tool: 'add_poster_stat_card',
1323
+ args: {
1324
+ x: 100,
1325
+ y: 850,
1326
+ title: '1999',
1327
+ subtitle: '原价',
1328
+ icon: '💰',
1329
+ color: '#94a3b8',
1330
+ background: '#f1f5f9',
1331
+ borderColor: '#e2e8f0',
1332
+ },
1333
+ });
1334
+ ext_call({
1335
+ plugin: 'poster',
1336
+ tool: 'add_poster_stat_card',
1337
+ args: {
1338
+ x: 350,
1339
+ y: 850,
1340
+ title: '1499',
1341
+ subtitle: '限时价',
1342
+ icon: '🔥',
1343
+ color: '#e74c3c',
1344
+ background: '#fef2f2',
1345
+ borderColor: '#fecaca',
1346
+ },
1347
+ });
1348
+ ext_call({
1349
+ plugin: 'poster',
1350
+ tool: 'add_poster_stat_card',
1351
+ args: {
1352
+ x: 600,
1353
+ y: 850,
1354
+ title: '4.9',
1355
+ subtitle: '评分',
1356
+ icon: '⭐',
1357
+ color: '#f59e0b',
1358
+ background: '#fffbeb',
1359
+ borderColor: '#fde68a',
1360
+ },
1361
+ });
1362
+
1363
+ // 11. 分隔线
1364
+ ext_call({
1365
+ plugin: 'poster',
1366
+ tool: 'add_poster_divider',
1367
+ args: { x: 100, y: 1050, width: 1000, color: '#e2e8f0', style: 'dashed' },
1368
+ });
1369
+
1370
+ // 12. 二维码
1371
+ ext_call({
1372
+ plugin: 'poster',
1373
+ tool: 'add_poster_qrcode',
1374
+ args: {
1375
+ x: 150,
1376
+ y: 1150,
1377
+ size: 180,
1378
+ content: 'https://example.com/shop/watch-pro',
1379
+ color: '#1e293b',
1380
+ backgroundColor: '#ffffff',
1381
+ },
1382
+ });
1383
+
1384
+ // 13. 条形码
1385
+ ext_call({
1386
+ plugin: 'poster',
1387
+ tool: 'add_poster_barcode',
1388
+ args: {
1389
+ x: 700,
1390
+ y: 1200,
1391
+ width: 300,
1392
+ height: 80,
1393
+ content: '6923456789012',
1394
+ showText: true,
1395
+ textColor: '#1e293b',
1396
+ },
1397
+ });
1398
+
1399
+ // 14. 页脚文字
1400
+ ext_call({
1401
+ plugin: 'poster',
1402
+ tool: 'add_poster_text',
1403
+ args: {
1404
+ x: 600,
1405
+ y: 1450,
1406
+ text: '扫描二维码访问官网',
1407
+ fontSize: 20,
1408
+ color: '#94a3b8',
1409
+ align: 'center',
1410
+ },
1411
+ });
1412
+
1413
+ // 15. 导出
1414
+ ext_call({
1415
+ plugin: 'poster',
1416
+ tool: 'export_poster_canvas',
1417
+ args: { filename: 'product-poster', format: 'png', outputDir: 'output' },
1418
+ });
1419
+ ```
1420
+
929
1421
  ---
930
1422
 
931
1423
  ## 🔗 参考资源
@@ -1096,12 +1096,18 @@ ${truncatedContent}${truncatedNote}
1096
1096
  return messages;
1097
1097
  }
1098
1098
 
1099
- // 2. 消息数量超过阈值才修剪
1099
+ // 2. 检查并移除不完整的 tool-call 和对应的 tool result
1100
+ const validated = this._validateToolCallsForPrepare(messages);
1101
+ if (validated !== messages) {
1102
+ return validated;
1103
+ }
1104
+
1105
+ // 3. 消息数量超过阈值才修剪
1100
1106
  if (messages.length <= 50) {
1101
1107
  return messages;
1102
1108
  }
1103
1109
 
1104
- // 3. 保留配对完整的消息
1110
+ // 4. 保留配对完整的消息
1105
1111
  const pruned = this._prepareMessagesForAI(messages);
1106
1112
  return pruned;
1107
1113
  } catch (err) {
@@ -1146,8 +1152,6 @@ ${truncatedContent}${truncatedNote}
1146
1152
  stepCount: result.stepCount || 1,
1147
1153
  };
1148
1154
  } finally {
1149
- // 校验并修复消息中的不完整工具调用
1150
- this._validateToolCalls(messages);
1151
1155
  // 确保保存聊天历史到 session(无论成功还是失败)
1152
1156
  messageStore.save();
1153
1157
  }
@@ -1207,12 +1211,18 @@ ${truncatedContent}${truncatedNote}
1207
1211
  return messages;
1208
1212
  }
1209
1213
 
1210
- // 2. 消息数量超过阈值才修剪
1214
+ // 2. 检查并移除不完整的 tool-call 和对应的 tool result
1215
+ const validated = this._validateToolCallsForPrepare(messages);
1216
+ if (validated !== messages) {
1217
+ return validated;
1218
+ }
1219
+
1220
+ // 3. 消息数量超过阈值才修剪
1211
1221
  if (messages.length <= 50) {
1212
1222
  return messages;
1213
1223
  }
1214
1224
 
1215
- // 3. 保留配对完整的消息
1225
+ // 4. 保留配对完整的消息
1216
1226
  const pruned = this._prepareMessagesForAI(messages);
1217
1227
  return pruned;
1218
1228
  } catch (err) {
@@ -1269,8 +1279,6 @@ ${truncatedContent}${truncatedNote}
1269
1279
  this.emit('error', { error: err.message });
1270
1280
  yield { type: 'error', error: err.message };
1271
1281
  } finally {
1272
- // 校验并修复消息中的不完整工具调用
1273
- this._validateToolCalls(messages);
1274
1282
  // 确保保存聊天历史到 session(无论成功还是失败)
1275
1283
  messageStore.save();
1276
1284
  }
@@ -1462,6 +1470,19 @@ ${truncatedContent}${truncatedNote}
1462
1470
  delete item.toolName;
1463
1471
  delete item.input;
1464
1472
  fixedCount++;
1473
+ } else {
1474
+ // 检查 JSON 是否能完整解析
1475
+ try {
1476
+ JSON.parse(trimmed);
1477
+ } catch (e) {
1478
+ // JSON 解析失败,说明是不完整的 JSON
1479
+ item.type = 'text';
1480
+ item.text = `(工具调用 ${item.toolName} 参数不完整(${e.message}),已跳过)`;
1481
+ delete item.toolCallId;
1482
+ delete item.toolName;
1483
+ delete item.input;
1484
+ fixedCount++;
1485
+ }
1465
1486
  }
1466
1487
  }
1467
1488
  }
@@ -1471,6 +1492,118 @@ ${truncatedContent}${truncatedNote}
1471
1492
  }
1472
1493
  }
1473
1494
 
1495
+ /**
1496
+ * 在 prepareStep 中检查并移除不完整的 tool-call 和对应的 tool result
1497
+ * 返回修改后的消息数组(如果没修改则返回原数组)
1498
+ * @param {Array} messages - 消息列表
1499
+ * @returns {Array} 修改后的消息数组或原数组
1500
+ * @private
1501
+ */
1502
+ _validateToolCallsForPrepare(messages) {
1503
+ // 收集不完整的 toolCallId
1504
+ const invalidToolCallIds = new Set();
1505
+
1506
+ for (const msg of messages) {
1507
+ if (msg.role !== 'assistant' || !Array.isArray(msg.content)) {
1508
+ continue;
1509
+ }
1510
+
1511
+ for (const item of msg.content) {
1512
+ if (item.type !== 'tool-call' || !item.toolCallId) {
1513
+ continue;
1514
+ }
1515
+
1516
+ const input = item.input;
1517
+ if (typeof input !== 'string') {
1518
+ continue;
1519
+ }
1520
+
1521
+ // 检查 input 是否是有效的 JSON
1522
+ const trimmed = input.trim();
1523
+ let isInvalid = false;
1524
+
1525
+ if (trimmed === '{' || trimmed === '' || !trimmed.startsWith('{')) {
1526
+ isInvalid = true;
1527
+ } else {
1528
+ try {
1529
+ JSON.parse(trimmed);
1530
+ } catch (e) {
1531
+ isInvalid = true;
1532
+ }
1533
+ }
1534
+
1535
+ if (isInvalid) {
1536
+ invalidToolCallIds.add(item.toolCallId);
1537
+ logger.warn(`Found incomplete tool call: ${item.toolName} (${item.toolCallId})`);
1538
+ }
1539
+ }
1540
+ }
1541
+
1542
+ // 如果没有不完整的 tool-call,返回原数组
1543
+ if (invalidToolCallIds.size === 0) {
1544
+ return messages;
1545
+ }
1546
+
1547
+ // 构建新的消息数组,移除不完整的 tool-call 和对应的 tool result
1548
+ const result = [];
1549
+
1550
+ for (const msg of messages) {
1551
+ if (msg.role === 'tool') {
1552
+ // 检查 tool result 是否对应不完整的 tool-call
1553
+ if (Array.isArray(msg.content)) {
1554
+ const newContent = [];
1555
+ for (const item of msg.content) {
1556
+ if (
1557
+ item.type === 'tool-result' &&
1558
+ item.toolCallId &&
1559
+ invalidToolCallIds.has(item.toolCallId)
1560
+ ) {
1561
+ // 跳过这个 tool result
1562
+ continue;
1563
+ }
1564
+ newContent.push(item);
1565
+ }
1566
+ // 如果所有 content 都被移除了,跳过这条消息
1567
+ if (newContent.length === 0) {
1568
+ continue;
1569
+ }
1570
+ // 浅拷贝消息对象
1571
+ result.push({ ...msg, content: newContent });
1572
+ } else {
1573
+ result.push(msg);
1574
+ }
1575
+ } else if (msg.role === 'assistant') {
1576
+ // 检查并移除 assistant 消息中的不完整 tool-call
1577
+ if (Array.isArray(msg.content)) {
1578
+ const newContent = [];
1579
+ for (const item of msg.content) {
1580
+ if (
1581
+ item.type === 'tool-call' &&
1582
+ item.toolCallId &&
1583
+ invalidToolCallIds.has(item.toolCallId)
1584
+ ) {
1585
+ // 跳过不完整的 tool-call
1586
+ continue;
1587
+ }
1588
+ newContent.push(item);
1589
+ }
1590
+ // 如果所有 content 都被移除了,跳过这条消息
1591
+ if (newContent.length === 0) {
1592
+ continue;
1593
+ }
1594
+ result.push({ ...msg, content: newContent });
1595
+ } else {
1596
+ result.push(msg);
1597
+ }
1598
+ } else {
1599
+ result.push(msg);
1600
+ }
1601
+ }
1602
+
1603
+ logger.info(`Removed ${invalidToolCallIds.size} incomplete tool calls and their results`);
1604
+ return result;
1605
+ }
1606
+
1474
1607
  /**
1475
1608
  * 获取 AI SDK 格式的工具
1476
1609
  * AI SDK 6.x 需要对象形式 { toolName: tool }