kiku-agent 1.0.0 → 1.0.3
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/README.md +293 -122
- package/package.json +60 -61
- package/server/agent/agentCore.js +892 -782
- package/server/agent/appPaths.js +21 -0
- package/server/agent/audioPlayer.js +739 -673
- package/server/agent/cli.js +1414 -1111
- package/server/agent/commandRegistry.js +74 -58
- package/server/agent/commands.js +1694 -788
- package/server/agent/config.js +264 -90
- package/server/agent/contextCompactor.js +79 -0
- package/server/agent/doctor.js +69 -0
- package/server/agent/eventBus.js +107 -103
- package/server/agent/gpuMetrics.js +189 -0
- package/server/agent/llmClient.js +369 -200
- package/server/agent/mcpClient.js +364 -0
- package/server/agent/memory.js +670 -566
- package/server/agent/modelManager.js +77 -0
- package/server/agent/research.js +202 -0
- package/server/agent/session.js +244 -239
- package/server/agent/subagent.js +116 -0
- package/server/agent/theme.js +125 -124
- package/server/agent/toolRouter.js +201 -0
- package/server/agent/tools.js +1679 -1528
- package/server/agent/ui.js +760 -469
- package/server/mcp/claude-desktop-config.example.json +1 -1
- package/server/mcp/data/discography.js +36 -36
- package/server/mcp/dataAdapter.js +102 -0
- package/server/mcp/examples/claude-code.mcp.example.json +19 -0
- package/server/mcp/examples/claude-desktop.example.json +11 -0
- package/server/mcp/examples/cline.example.json +13 -0
- package/server/mcp/examples/cursor.example.json +14 -0
- package/server/mcp/examples/gemini-cli.example.json +17 -0
- package/server/mcp/examples/opencode.example.json +21 -0
- package/server/mcp/examples/vscode.example.json +19 -0
- package/server/mcp/examples/windsurf.example.json +14 -0
- package/server/mcp/handler.js +85 -159
- package/server/mcp/security.js +140 -138
- package/server/mcp/server.js +129 -100
- package/server/mcp/stdio.js +4 -2
|
@@ -1,782 +1,892 @@
|
|
|
1
|
-
import { config } from './config.js';
|
|
2
|
-
import { toolsSchema, executeTool, prepareToolArguments } from './tools.js';
|
|
3
|
-
import { LMStudioClient } from './llmClient.js';
|
|
4
|
-
import { AgentSession } from './session.js';
|
|
5
|
-
import { AgentMemory } from './memory.js';
|
|
6
|
-
import { AgentStateMachine, AgentStates } from './agentState.js';
|
|
7
|
-
import { eventBus, Events } from './eventBus.js';
|
|
8
|
-
import { sanitizeQuery, isLikelySafe } from './sanitizer.js';
|
|
9
|
-
import { createAbortError, isAbortError } from './errors.js';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
this.
|
|
60
|
-
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
this.
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
'
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
}
|
|
1
|
+
import { config } from './config.js';
|
|
2
|
+
import { toolsSchema, executeTool, prepareToolArguments } from './tools.js';
|
|
3
|
+
import { LLMClient, LMStudioClient } from './llmClient.js';
|
|
4
|
+
import { AgentSession } from './session.js';
|
|
5
|
+
import { AgentMemory } from './memory.js';
|
|
6
|
+
import { AgentStateMachine, AgentStates } from './agentState.js';
|
|
7
|
+
import { eventBus, Events } from './eventBus.js';
|
|
8
|
+
import { sanitizeQuery, isLikelySafe } from './sanitizer.js';
|
|
9
|
+
import { createAbortError, isAbortError } from './errors.js';
|
|
10
|
+
import { ResearchSubagent } from './subagent.js';
|
|
11
|
+
import { ResearchOrchestrator } from './research.js';
|
|
12
|
+
import { McpClientManager } from './mcpClient.js';
|
|
13
|
+
import { DynamicToolRouter } from './toolRouter.js';
|
|
14
|
+
import { contextCompactor } from './contextCompactor.js';
|
|
15
|
+
|
|
16
|
+
export function detectDominantLanguage(text) {
|
|
17
|
+
if (!text || typeof text !== 'string') return 'th';
|
|
18
|
+
|
|
19
|
+
const thaiMatches = text.match(/[\u0E00-\u0E7F]/g) || [];
|
|
20
|
+
const thaiCount = thaiMatches.length;
|
|
21
|
+
|
|
22
|
+
const chineseMatches = text.match(/[\u4E00-\u9FA5]/g) || [];
|
|
23
|
+
const chineseCount = chineseMatches.length;
|
|
24
|
+
|
|
25
|
+
const englishMatches = text.match(/[a-zA-Z]+/g) || [];
|
|
26
|
+
const englishCount = englishMatches.join('').length;
|
|
27
|
+
|
|
28
|
+
if (thaiCount >= chineseCount && thaiCount >= englishCount && thaiCount > 0) {
|
|
29
|
+
return 'th';
|
|
30
|
+
} else if (chineseCount > thaiCount && chineseCount >= englishCount && chineseCount > 0) {
|
|
31
|
+
return 'zh';
|
|
32
|
+
} else if (englishCount > 0) {
|
|
33
|
+
return 'en';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return 'th';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function stableToolSignature(toolName, args) {
|
|
40
|
+
const sortValue = (value) => {
|
|
41
|
+
if (Array.isArray(value)) return value.map(sortValue);
|
|
42
|
+
if (value && typeof value === 'object') {
|
|
43
|
+
return Object.fromEntries(Object.keys(value).sort().map(key => [key, sortValue(value[key])]));
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
};
|
|
47
|
+
return `${toolName}:${JSON.stringify(sortValue(args || {}))}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class KikuAgent {
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
this.client = options.client || new LLMClient({
|
|
53
|
+
provider: options.provider || config.activeProvider,
|
|
54
|
+
baseUrl: options.baseUrl,
|
|
55
|
+
apiKey: options.apiKey,
|
|
56
|
+
model: options.model
|
|
57
|
+
});
|
|
58
|
+
this.maxIterations = options.maxIterations || config.agent.maxIterations || 5;
|
|
59
|
+
this.slidingWindowSize = options.slidingWindowSize || config.agent.slidingWindowSize || 8;
|
|
60
|
+
this.session = new AgentSession({ thinkingMode: options.thinkingMode || 'hide' });
|
|
61
|
+
this.memory = new AgentMemory();
|
|
62
|
+
this.mcpManager = options.mcpManager || new McpClientManager();
|
|
63
|
+
this.router = new DynamicToolRouter(this.getAllToolsSchema());
|
|
64
|
+
this.research = new ResearchOrchestrator({ client: this.client });
|
|
65
|
+
this.systemPrompt = this.buildSystemPrompt();
|
|
66
|
+
this.initHistory();
|
|
67
|
+
this.restoredMessageCount = 0;
|
|
68
|
+
|
|
69
|
+
this.stateMachine = new AgentStateMachine();
|
|
70
|
+
|
|
71
|
+
this.stateMachine.on('transition', ({ from, to }) => {
|
|
72
|
+
eventBus.emit(Events.AGENT_STATE_CHANGE, {
|
|
73
|
+
state: to,
|
|
74
|
+
label: this.stateMachine.label,
|
|
75
|
+
elapsed: this.stateMachine.elapsedMs,
|
|
76
|
+
queryId: this.stateMachine.queryId,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
this._ready = this._init();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async _init() {
|
|
84
|
+
await this.memory.load();
|
|
85
|
+
await this.loadSession();
|
|
86
|
+
if (this.mcpManager && typeof this.mcpManager.loadPersistedServers === 'function') {
|
|
87
|
+
try {
|
|
88
|
+
await this.mcpManager.loadPersistedServers();
|
|
89
|
+
this.router.setTools(this.getAllToolsSchema());
|
|
90
|
+
this.systemPrompt = this.buildSystemPrompt();
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
if (!this.conversationHistory || this.conversationHistory.length === 0) {
|
|
94
|
+
this.initHistory();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async ready() {
|
|
99
|
+
return this._ready;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async runSubagent(task, options = {}) {
|
|
103
|
+
await this._ready;
|
|
104
|
+
const subagent = new ResearchSubagent({
|
|
105
|
+
client: this.client,
|
|
106
|
+
maxIterations: options.maxIterations || 3,
|
|
107
|
+
});
|
|
108
|
+
return subagent.run(task, {
|
|
109
|
+
signal: options.signal,
|
|
110
|
+
onProgress: options.onProgress,
|
|
111
|
+
skillContext: this.memory.buildUserSkillContext(),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async runResearch(question, options = {}) {
|
|
116
|
+
await this._ready;
|
|
117
|
+
return this.research.run(question, {
|
|
118
|
+
...options,
|
|
119
|
+
skillContext: this.memory.buildUserSkillContext(),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getResearchStatus() {
|
|
124
|
+
return this.research.getStatus();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
cancelResearch() {
|
|
128
|
+
return this.research.cancel();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
initHistory() {
|
|
132
|
+
this.conversationHistory = [
|
|
133
|
+
{
|
|
134
|
+
role: 'system',
|
|
135
|
+
content: this.systemPrompt
|
|
136
|
+
}
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
clearMemory() {
|
|
141
|
+
if (this.session.messages.length > 0) {
|
|
142
|
+
this.memory.archiveSession(this.session.toSnapshot()).catch(() => {});
|
|
143
|
+
this.memory.consolidateSession(this.session.toSnapshot()).catch(() => {});
|
|
144
|
+
}
|
|
145
|
+
this.initHistory();
|
|
146
|
+
this.session.reset();
|
|
147
|
+
this.session.clearDisk().catch(() => {});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async loadSession() {
|
|
151
|
+
const loaded = await this.session.loadFromDisk();
|
|
152
|
+
if (loaded && this.session.messages.length > 0) {
|
|
153
|
+
this.conversationHistory = [
|
|
154
|
+
{
|
|
155
|
+
role: 'system',
|
|
156
|
+
content: this.systemPrompt
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
for (const msg of this.session.messages) {
|
|
161
|
+
if (msg.role === 'user') {
|
|
162
|
+
const textPart = msg.parts.find(p => p.type === 'text');
|
|
163
|
+
if (textPart) {
|
|
164
|
+
this.conversationHistory.push({
|
|
165
|
+
role: 'user',
|
|
166
|
+
content: textPart.content
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
} else if (msg.role === 'assistant') {
|
|
170
|
+
const textPart = msg.parts.find(p => p.type === 'text');
|
|
171
|
+
if (textPart) {
|
|
172
|
+
this.conversationHistory.push({
|
|
173
|
+
role: 'assistant',
|
|
174
|
+
content: textPart.content
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this.restoredMessageCount = this.session.messages.length;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getEnrichedSystemPrompt() {
|
|
184
|
+
const memoryContext = this.memory.buildMemoryContext();
|
|
185
|
+
return this.systemPrompt + memoryContext;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async saveSession() {
|
|
189
|
+
await this.session.saveToDisk();
|
|
190
|
+
eventBus.emit(Events.SESSION_SAVED, { sessionId: this.session.id });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
applySlidingWindow() {
|
|
194
|
+
if (this.conversationHistory.length > this.slidingWindowSize + 1) {
|
|
195
|
+
this.conversationHistory = contextCompactor.compact(this.conversationHistory, {
|
|
196
|
+
maxTokens: 6000,
|
|
197
|
+
keepRecentMessages: this.slidingWindowSize,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getAllToolsSchema() {
|
|
203
|
+
const mcpTools = this.mcpManager ? this.mcpManager.getToolsSchema() : [];
|
|
204
|
+
return [...toolsSchema, ...mcpTools];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async connectMcp(name, serverConfig) {
|
|
208
|
+
const result = await this.mcpManager.connect(name, serverConfig);
|
|
209
|
+
this.router.setTools(this.getAllToolsSchema());
|
|
210
|
+
this.systemPrompt = this.buildSystemPrompt();
|
|
211
|
+
if (this.conversationHistory && this.conversationHistory[0]?.role === 'system') {
|
|
212
|
+
this.conversationHistory[0].content = this.getEnrichedSystemPrompt();
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async disconnectMcp(name) {
|
|
218
|
+
const result = await this.mcpManager.disconnect(name);
|
|
219
|
+
this.router.setTools(this.getAllToolsSchema());
|
|
220
|
+
this.systemPrompt = this.buildSystemPrompt();
|
|
221
|
+
if (this.conversationHistory && this.conversationHistory[0]?.role === 'system') {
|
|
222
|
+
this.conversationHistory[0].content = this.getEnrichedSystemPrompt();
|
|
223
|
+
}
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
buildSystemPrompt() {
|
|
228
|
+
const allTools = this.getAllToolsSchema();
|
|
229
|
+
const toolsSummary = allTools.map(t => {
|
|
230
|
+
const f = t.function;
|
|
231
|
+
const paramKeys = Object.keys(f.parameters?.properties || {}).join(', ');
|
|
232
|
+
return `- ${f.name}(${paramKeys}): ${f.description}`;
|
|
233
|
+
}).join('\n');
|
|
234
|
+
|
|
235
|
+
return `${config.agent.systemPersona}
|
|
236
|
+
|
|
237
|
+
=== AVAILABLE KNOWLEDGE & LIVE TOOLS ===
|
|
238
|
+
${toolsSummary}
|
|
239
|
+
|
|
240
|
+
=== DYNAMIC MULTILINGUAL & RESPONSE RULES (CRITICAL) ===
|
|
241
|
+
|
|
242
|
+
0. NO SYSTEM LEAK (ABSOLUTE RULE):
|
|
243
|
+
- NEVER include "Thinking Process", "Tool Calling", "Action:", "Action Input:", function names, parameter schemas, JSON tool call blocks, or any internal reasoning in your response text.
|
|
244
|
+
- NEVER include advertisements, sponsor mentions, or promotional content.
|
|
245
|
+
- Your response must be CLEAN user-facing content ONLY.
|
|
246
|
+
- Think silently using tools, then present ONLY the final polished answer.
|
|
247
|
+
|
|
248
|
+
1. LANGUAGE MIRRORING:
|
|
249
|
+
- THAI ➔ polite Thai (ใช้ ค่ะ/นะคะ เสมอ)
|
|
250
|
+
- CHINESE ➔ fluent, polite Chinese (简体/繁体中文)
|
|
251
|
+
- ENGLISH ➔ polished, professional English
|
|
252
|
+
- MIXED ➔ Match dominant language, keep bilingual titles
|
|
253
|
+
|
|
254
|
+
2. REAL YOUTUBE & MEDIA LINKS:
|
|
255
|
+
- Direct Watch: https://www.youtube.com/watch?v=VIDEO_ID
|
|
256
|
+
- Search: https://www.youtube.com/results?search_query=QUERY
|
|
257
|
+
- Also Bilibili, QQ Music, Youku when applicable
|
|
258
|
+
- NEVER use fake placeholder IDs
|
|
259
|
+
|
|
260
|
+
3. FACT ACCURACY & NO DUPLICATION:
|
|
261
|
+
- Always use tools to verify facts before answering. Do NOT hallucinate.
|
|
262
|
+
- Current year is 2026. Prioritize 2026/2025 projects.
|
|
263
|
+
- Never repeat the same statement. Sort newest first.
|
|
264
|
+
|
|
265
|
+
4. RESPONSE STYLE:
|
|
266
|
+
- NEVER start with template greetings — dive DIRECTLY into the answer.
|
|
267
|
+
- Format with Markdown headers (###), bold (**text**), bullet points (•).
|
|
268
|
+
- Emoji are optional: use at most one contextually relevant emoji per response and never repeat them across headings or bullets.
|
|
269
|
+
- Match answer depth to what the user asked.
|
|
270
|
+
|
|
271
|
+
5. SMART TOOL SELECTION:
|
|
272
|
+
- Videos/stages ➔ get_music_videos or get_media_links
|
|
273
|
+
- Songs/OSTs ➔ search_discography or get_media_links
|
|
274
|
+
- Dramas ➔ search_works or get_work_detail
|
|
275
|
+
- News/external facts ➔ web_search or web_extract
|
|
276
|
+
- Deep multi-source research ➔ web_extract (search + auto-fetch)
|
|
277
|
+
- Read webpage content ➔ fetch_webpage
|
|
278
|
+
- Fetch API/JSON ➔ fetch_json
|
|
279
|
+
- Local files ➔ read_local_file
|
|
280
|
+
- Directory listing ➔ list_directory
|
|
281
|
+
- Screenshot ➔ screenshot_webpage
|
|
282
|
+
|
|
283
|
+
6. AGGRESSIVE WEB SEARCH FALLBACK:
|
|
284
|
+
- If local tools return insufficient results, IMMEDIATELY call web_search or web_extract.
|
|
285
|
+
- Combine local data WITH web search for complete, accurate answers.
|
|
286
|
+
- Use web_extract for comprehensive research on any topic.
|
|
287
|
+
|
|
288
|
+
7. LEGAL & CAREER KNOWLEDGE:
|
|
289
|
+
- When discussing controversies, be specific and accurate:
|
|
290
|
+
• Contract dispute: Star48/Siba Media supplementary agreement. JJY claims main contract ended June 2024, alleges forged signatures. Star48 claims until 2033.
|
|
291
|
+
• Tax allegation: Tip-off report filed by Star48 side; JJY denies and confirms tax compliance.
|
|
292
|
+
• Portrait/copyright cases: JJY has sued beauty clinics and brands for unauthorized image use (肖像权纠纷).
|
|
293
|
+
- Use web_search or web_extract for latest verified information on legal matters.
|
|
294
|
+
|
|
295
|
+
8. DELIBERATE REASONING & AUTONOMOUS TOOL SELECTION (CRITICAL):
|
|
296
|
+
- Think carefully about the complexity, goal, and nature of the query before deciding which tool to use:
|
|
297
|
+
• Direct Specific Query ➔ Use direct catalog tools (search_works, search_discography, get_profile_and_fandom, get_awards, get_brand_endorsements, etc.).
|
|
298
|
+
• Isolated In-Depth Subtask ➔ Call run_subagent with a specific task parameter to delegate an isolated iterative investigation with evidence and tool call tracing.
|
|
299
|
+
• Complex Multi-Faceted Research ➔ Call run_deep_research with question when the user asks an open-ended, complex query that requires parallel multi-worker investigation across catalog, live web sources, and independent verifier with cited sources.
|
|
300
|
+
• Rumors, Cast Leaks & Fact Verification ➔ Call verify_claim with claim when the user asks whether a rumor, statement, or unconfirmed news is true (returns VERIFIED, UNVERIFIED, CONFLICTING, or FALSE).
|
|
301
|
+
• Teaching / Updating Response Preferences ➔ Call manage_skill with action 'learn' (with instruction), 'list', 'remove' (with skillId), 'clear', 'enable', or 'disable' when the user instructs how they want Kiku to answer in future conversations.`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
parseTextToolCalls(content) {
|
|
305
|
+
if (!content) return [];
|
|
306
|
+
const calls = [];
|
|
307
|
+
const allTools = this.getAllToolsSchema();
|
|
308
|
+
|
|
309
|
+
const jsonBlockRegex = /```(?:json)?\s*(\{[\s\S]*?\})\s*```/g;
|
|
310
|
+
let match;
|
|
311
|
+
while ((match = jsonBlockRegex.exec(content)) !== null) {
|
|
312
|
+
try {
|
|
313
|
+
const parsed = JSON.parse(match[1]);
|
|
314
|
+
const toolName = parsed.action || parsed.tool || parsed.name || parsed.function;
|
|
315
|
+
const toolArgs = parsed.action_input || parsed.parameters || parsed.arguments || parsed.args || {};
|
|
316
|
+
if (toolName && allTools.some(t => t.function.name === toolName)) {
|
|
317
|
+
calls.push({
|
|
318
|
+
id: `call_txt_${Date.now()}_${calls.length}`,
|
|
319
|
+
function: {
|
|
320
|
+
name: toolName,
|
|
321
|
+
arguments: typeof toolArgs === 'string' ? toolArgs : JSON.stringify(toolArgs)
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const actionRegex = /Action:\s*([a-zA-Z0-9_]+)\s*\nAction Input:\s*(\{[\s\S]*?\}|[^\n]+)/g;
|
|
330
|
+
while ((match = actionRegex.exec(content)) !== null) {
|
|
331
|
+
const toolName = match[1].trim();
|
|
332
|
+
let rawInput = match[2].trim();
|
|
333
|
+
if (allTools.some(t => t.function.name === toolName)) {
|
|
334
|
+
let args = {};
|
|
335
|
+
try {
|
|
336
|
+
args = JSON.parse(rawInput);
|
|
337
|
+
} catch {
|
|
338
|
+
args = { query: rawInput };
|
|
339
|
+
}
|
|
340
|
+
calls.push({
|
|
341
|
+
id: `call_act_${Date.now()}_${calls.length}`,
|
|
342
|
+
function: {
|
|
343
|
+
name: toolName,
|
|
344
|
+
arguments: JSON.stringify(args)
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return calls;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async ask(userPrompt, callbacks = {}) {
|
|
354
|
+
await this._ready;
|
|
355
|
+
|
|
356
|
+
const { sanitized: cleanPrompt, warnings: sanitizationWarnings } = sanitizeQuery(userPrompt);
|
|
357
|
+
if (sanitizationWarnings.length > 0) {
|
|
358
|
+
eventBus.emit(Events.AGENT_QUERY_ERROR, {
|
|
359
|
+
type: 'sanitization',
|
|
360
|
+
warnings: sanitizationWarnings,
|
|
361
|
+
original: userPrompt,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const promptForLLM = cleanPrompt || userPrompt;
|
|
365
|
+
|
|
366
|
+
const topics = this._extractQuickTopics(promptForLLM);
|
|
367
|
+
for (const topic of topics) {
|
|
368
|
+
this.memory.trackTopicInterest(topic);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const {
|
|
372
|
+
onStepStart = () => {},
|
|
373
|
+
onReasoningStart = () => {},
|
|
374
|
+
onReasoningDelta = () => {},
|
|
375
|
+
onReasoningEnd = () => {},
|
|
376
|
+
onToolStart = () => {},
|
|
377
|
+
onToolResult = () => {},
|
|
378
|
+
onTokenStart = () => {},
|
|
379
|
+
onTokenDelta = () => {},
|
|
380
|
+
onFinalAnswer = () => {},
|
|
381
|
+
onError = () => {},
|
|
382
|
+
signal = null
|
|
383
|
+
} = callbacks;
|
|
384
|
+
|
|
385
|
+
const queryStartTime = Date.now();
|
|
386
|
+
const queryId = `q_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
387
|
+
const dominantLang = detectDominantLanguage(promptForLLM);
|
|
388
|
+
|
|
389
|
+
this.stateMachine.startQuery(queryId);
|
|
390
|
+
eventBus.emit(Events.AGENT_QUERY_START, { queryId, prompt: promptForLLM });
|
|
391
|
+
|
|
392
|
+
if (!this.conversationHistory || this.conversationHistory.length === 0 || this.conversationHistory[0]?.role !== 'system') {
|
|
393
|
+
this.conversationHistory = [
|
|
394
|
+
{ role: 'system', content: this.systemPrompt },
|
|
395
|
+
...(this.conversationHistory?.filter(m => m.role !== 'system') || [])
|
|
396
|
+
];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
this.session.createUserMessage(promptForLLM);
|
|
400
|
+
const userMessage = { role: 'user', content: promptForLLM };
|
|
401
|
+
this.conversationHistory.push(userMessage);
|
|
402
|
+
|
|
403
|
+
const memoryContext = this.memory.buildMemoryContext(promptForLLM);
|
|
404
|
+
this.conversationHistory[0] = {
|
|
405
|
+
role: 'system',
|
|
406
|
+
content: this.systemPrompt + memoryContext
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
let langInstruction;
|
|
410
|
+
if (dominantLang === 'th') {
|
|
411
|
+
langInstruction = '[SYSTEM NOTE: User language is Thai. Reply in Thai only.]\n';
|
|
412
|
+
} else if (dominantLang === 'zh') {
|
|
413
|
+
langInstruction = '[SYSTEM NOTE: User language is Chinese. Reply in Chinese only.]\n';
|
|
414
|
+
} else {
|
|
415
|
+
langInstruction = '[SYSTEM NOTE: User language is English. Reply in English only.]\n';
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const directMentions = this.router.extractAtMentions(promptForLLM);
|
|
419
|
+
if (directMentions.length > 0) {
|
|
420
|
+
langInstruction += `[SYSTEM DIRECTIVE: User explicitly invoked @${directMentions.join(', @')}. You MUST prioritize calling the referenced tool(s) to fetch accurate data.]\n`;
|
|
421
|
+
}
|
|
422
|
+
this.conversationHistory.push({ role: 'system', content: langInstruction });
|
|
423
|
+
|
|
424
|
+
this.applySlidingWindow();
|
|
425
|
+
|
|
426
|
+
const currentAssistantSessionMsg = this.session.createAssistantMessage();
|
|
427
|
+
|
|
428
|
+
let currentIteration = 0;
|
|
429
|
+
const toolResultCache = new Map();
|
|
430
|
+
const activeTools = this.router.routeToolsForQuery(promptForLLM, {
|
|
431
|
+
showAll: callbacks.showAllTools || false,
|
|
432
|
+
includeMcp: true,
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
while (currentIteration < this.maxIterations) {
|
|
436
|
+
if (signal?.aborted) {
|
|
437
|
+
this.stateMachine.reset();
|
|
438
|
+
throw createAbortError();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
currentIteration++;
|
|
442
|
+
onStepStart(currentIteration, this.maxIterations);
|
|
443
|
+
|
|
444
|
+
let assistantMessage = null;
|
|
445
|
+
let isReasoning = false;
|
|
446
|
+
let isTokenEmitting = false;
|
|
447
|
+
|
|
448
|
+
try {
|
|
449
|
+
assistantMessage = await this.client.chat({
|
|
450
|
+
messages: this.conversationHistory,
|
|
451
|
+
tools: activeTools,
|
|
452
|
+
temperature: 0.3,
|
|
453
|
+
stream: true,
|
|
454
|
+
signal,
|
|
455
|
+
onThought: (thoughtChunk) => {
|
|
456
|
+
if (!isReasoning) {
|
|
457
|
+
isReasoning = true;
|
|
458
|
+
this.stateMachine.transition(AgentStates.STREAMING_THOUGHTS);
|
|
459
|
+
onReasoningStart();
|
|
460
|
+
eventBus.emit(Events.AGENT_REASONING_START, { queryId });
|
|
461
|
+
}
|
|
462
|
+
this.session.addReasoningPart(currentAssistantSessionMsg, thoughtChunk);
|
|
463
|
+
onReasoningDelta(thoughtChunk);
|
|
464
|
+
eventBus.emit(Events.AGENT_REASONING_DELTA, { queryId, chunk: thoughtChunk });
|
|
465
|
+
},
|
|
466
|
+
onToken: (tokenChunk) => {
|
|
467
|
+
if (isReasoning) {
|
|
468
|
+
isReasoning = false;
|
|
469
|
+
this.session.finalizeReasoningPart(currentAssistantSessionMsg);
|
|
470
|
+
this.stateMachine.transition(AgentStates.STREAMING_ANSWER);
|
|
471
|
+
onReasoningEnd();
|
|
472
|
+
eventBus.emit(Events.AGENT_REASONING_END, { queryId });
|
|
473
|
+
}
|
|
474
|
+
if (!isTokenEmitting) {
|
|
475
|
+
isTokenEmitting = true;
|
|
476
|
+
if (this.stateMachine.state !== AgentStates.STREAMING_ANSWER) {
|
|
477
|
+
this.stateMachine.transition(AgentStates.STREAMING_ANSWER);
|
|
478
|
+
}
|
|
479
|
+
onTokenStart();
|
|
480
|
+
eventBus.emit(Events.AGENT_TOKEN_START, { queryId });
|
|
481
|
+
}
|
|
482
|
+
this.session.addTextPart(currentAssistantSessionMsg, tokenChunk);
|
|
483
|
+
onTokenDelta(tokenChunk);
|
|
484
|
+
eventBus.emit(Events.AGENT_TOKEN_DELTA, { queryId, chunk: tokenChunk });
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
if (isReasoning) {
|
|
489
|
+
this.session.finalizeReasoningPart(currentAssistantSessionMsg);
|
|
490
|
+
if (this.stateMachine.state === AgentStates.STREAMING_THOUGHTS) {
|
|
491
|
+
this.stateMachine.transition(AgentStates.STREAMING_ANSWER);
|
|
492
|
+
}
|
|
493
|
+
onReasoningEnd();
|
|
494
|
+
}
|
|
495
|
+
} catch (err) {
|
|
496
|
+
this.stateMachine.reset();
|
|
497
|
+
if (!isAbortError(err)) {
|
|
498
|
+
onError(err);
|
|
499
|
+
eventBus.emit(Events.AGENT_QUERY_ERROR, { queryId, error: err.message });
|
|
500
|
+
}
|
|
501
|
+
throw err;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (!assistantMessage) {
|
|
505
|
+
this.stateMachine.reset();
|
|
506
|
+
throw new Error(`No response received from ${this.client?.providerName || 'AI provider'}.`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
let toolCalls = assistantMessage.tool_calls || [];
|
|
510
|
+
if (toolCalls.length === 0 && assistantMessage.content) {
|
|
511
|
+
toolCalls = this.parseTextToolCalls(assistantMessage.content);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
this.conversationHistory.push(assistantMessage);
|
|
515
|
+
|
|
516
|
+
if (!toolCalls || toolCalls.length === 0) {
|
|
517
|
+
const totalDurationMs = Date.now() - queryStartTime;
|
|
518
|
+
this.session.stats.totalDurationMs += totalDurationMs;
|
|
519
|
+
|
|
520
|
+
const finalAnswer = assistantMessage.content || (
|
|
521
|
+
dominantLang === 'zh' ? "抱歉,无法处理您的回答。" :
|
|
522
|
+
dominantLang === 'en' ? "Sorry, unable to process a response." :
|
|
523
|
+
"ขออภัยค่ะ ไม่สามารถประมวลผลคำตอบได้"
|
|
524
|
+
);
|
|
525
|
+
onFinalAnswer(finalAnswer, { durationMs: totalDurationMs, language: dominantLang });
|
|
526
|
+
this._extractFactsFromFinalAnswer(finalAnswer);
|
|
527
|
+
|
|
528
|
+
this.stateMachine.transition(AgentStates.COMPLETE);
|
|
529
|
+
eventBus.emit(Events.AGENT_FINAL_ANSWER, { queryId, answer: finalAnswer, durationMs: totalDurationMs });
|
|
530
|
+
this.stateMachine.reset();
|
|
531
|
+
|
|
532
|
+
return finalAnswer;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
for (const [toolCallIndex, tc] of toolCalls.entries()) {
|
|
536
|
+
if (signal?.aborted) {
|
|
537
|
+
this.stateMachine.reset();
|
|
538
|
+
throw createAbortError();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const toolName = tc?.function?.name || '';
|
|
542
|
+
const toolCallId = tc?.id || `call_${queryId}_${currentIteration}_${toolCallIndex}`;
|
|
543
|
+
const isMcp = Boolean(this.mcpManager?.isMcpTool(toolName));
|
|
544
|
+
|
|
545
|
+
let prepared;
|
|
546
|
+
if (isMcp) {
|
|
547
|
+
let parsedArgs = tc?.function?.arguments || {};
|
|
548
|
+
if (typeof parsedArgs === 'string') {
|
|
549
|
+
try { parsedArgs = JSON.parse(parsedArgs); } catch { parsedArgs = {}; }
|
|
550
|
+
}
|
|
551
|
+
prepared = { safe: true, args: parsedArgs, warnings: [] };
|
|
552
|
+
} else {
|
|
553
|
+
prepared = prepareToolArguments(toolName, tc?.function?.arguments || {});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (!prepared.safe) {
|
|
557
|
+
const rejectedResult = {
|
|
558
|
+
status: 'error',
|
|
559
|
+
message: `Input rejected: ${prepared.error}`,
|
|
560
|
+
validationWarnings: prepared.warnings,
|
|
561
|
+
};
|
|
562
|
+
eventBus.emit(Events.AGENT_TOOL_ERROR, {
|
|
563
|
+
queryId, toolName, args: tc.function.arguments,
|
|
564
|
+
error: prepared.error,
|
|
565
|
+
});
|
|
566
|
+
onToolStart(toolName, {});
|
|
567
|
+
onToolResult(toolName, rejectedResult, 0);
|
|
568
|
+
const toolMessage = {
|
|
569
|
+
role: 'tool',
|
|
570
|
+
tool_call_id: toolCallId,
|
|
571
|
+
name: toolName,
|
|
572
|
+
content: JSON.stringify(rejectedResult)
|
|
573
|
+
};
|
|
574
|
+
this.conversationHistory.push(toolMessage);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (prepared.warnings.length > 0) {
|
|
578
|
+
eventBus.emit(Events.AGENT_TOOL_ERROR, {
|
|
579
|
+
queryId, toolName, warnings: prepared.warnings,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const finalArgs = prepared.args;
|
|
584
|
+
|
|
585
|
+
this.stateMachine.transition(AgentStates.FETCHING_TOOL, { toolName });
|
|
586
|
+
|
|
587
|
+
const toolPart = this.session.addToolPart(currentAssistantSessionMsg, toolName, finalArgs);
|
|
588
|
+
onToolStart(toolName, finalArgs);
|
|
589
|
+
eventBus.emit(Events.AGENT_TOOL_START, { queryId, toolName, args: finalArgs });
|
|
590
|
+
|
|
591
|
+
const toolStartTime = Date.now();
|
|
592
|
+
const signature = stableToolSignature(toolName, finalArgs);
|
|
593
|
+
const cachedResult = toolResultCache.get(signature);
|
|
594
|
+
|
|
595
|
+
let result;
|
|
596
|
+
if (cachedResult) {
|
|
597
|
+
result = { ...cachedResult, cache: { hit: true, reason: 'duplicate_call_in_query' } };
|
|
598
|
+
} else if (isMcp) {
|
|
599
|
+
result = await this.mcpManager.callTool(toolName, finalArgs);
|
|
600
|
+
} else {
|
|
601
|
+
result = await executeTool(toolName, finalArgs, { agent: this, memory: this.memory, client: this.client, signal });
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (!cachedResult) toolResultCache.set(signature, result);
|
|
605
|
+
const toolDurationMs = Date.now() - toolStartTime;
|
|
606
|
+
|
|
607
|
+
this.session.finalizeToolPart(currentAssistantSessionMsg, toolPart.id, result);
|
|
608
|
+
onToolResult(toolName, result, toolDurationMs);
|
|
609
|
+
eventBus.emit(Events.AGENT_TOOL_RESULT, { queryId, toolName, result, durationMs: toolDurationMs });
|
|
610
|
+
|
|
611
|
+
this._extractFactsFromToolResult(toolName, finalArgs, result);
|
|
612
|
+
|
|
613
|
+
const toolMessage = {
|
|
614
|
+
role: 'tool',
|
|
615
|
+
tool_call_id: toolCallId,
|
|
616
|
+
name: toolName,
|
|
617
|
+
content: JSON.stringify({
|
|
618
|
+
authoritative: true,
|
|
619
|
+
result,
|
|
620
|
+
instruction: result.status === 'not_found'
|
|
621
|
+
? 'No matching data was found. Do not invent records; say that the catalog has no match or use an appropriate fallback tool.'
|
|
622
|
+
: result.status === 'error'
|
|
623
|
+
? 'The tool failed. Do not claim that the requested facts were verified.'
|
|
624
|
+
: 'Use only facts present in this result when answering.'
|
|
625
|
+
})
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
this.conversationHistory.push(toolMessage);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const langPrompt = dominantLang === 'zh'
|
|
633
|
+
? "请根据上述工具查询结果,直接回答(不要开头问候语),用流畅优雅的中文,先1-2句概述,再列要点。包含具体的 YouTube 真实链接(如 Direct Watch: https://www.youtube.com/watch?v=... หรือ Search: https://www.youtube.com/results?search_query=...)及事实数据。"
|
|
634
|
+
: dominantLang === 'en'
|
|
635
|
+
? "Based on the tool results above, answer directly (no opening greeting or template phrase). Give a 1-2 sentence summary, then concise bullet points in polished English. Include verified YouTube direct and search links (https://www.youtube.com/results?search_query=...)."
|
|
636
|
+
: "สรุปคำตอบจากผลลัพธ์เครื่องมือข้างต้น โดยไม่ต้องทักทาย ตอบตรงประเด็น 1-2 ประโยค แล้วตามด้วย bullet points สั้นๆ เป็นภาษาไทย พร้อมแสดงลิงก์ YouTube จริง (Direct Watch Link และ https://www.youtube.com/results?search_query=...) และข้อมูลผลงานที่ถูกต้องแม่นยำ";
|
|
637
|
+
|
|
638
|
+
this.conversationHistory.push({ role: 'user', content: langPrompt });
|
|
639
|
+
|
|
640
|
+
if (this.stateMachine.state !== AgentStates.STREAMING_ANSWER) {
|
|
641
|
+
this.stateMachine.transition(AgentStates.STREAMING_ANSWER);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
let isReasoning = false;
|
|
645
|
+
let synthTokenStarted = false;
|
|
646
|
+
const finalRes = await this.client.chat({
|
|
647
|
+
messages: this.conversationHistory,
|
|
648
|
+
temperature: 0.3,
|
|
649
|
+
stream: true,
|
|
650
|
+
signal,
|
|
651
|
+
onThought: (thoughtChunk) => {
|
|
652
|
+
if (!isReasoning) {
|
|
653
|
+
isReasoning = true;
|
|
654
|
+
onReasoningStart();
|
|
655
|
+
}
|
|
656
|
+
this.session.addReasoningPart(currentAssistantSessionMsg, thoughtChunk);
|
|
657
|
+
onReasoningDelta(thoughtChunk);
|
|
658
|
+
},
|
|
659
|
+
onToken: (tokenChunk) => {
|
|
660
|
+
if (isReasoning) {
|
|
661
|
+
isReasoning = false;
|
|
662
|
+
this.session.finalizeReasoningPart(currentAssistantSessionMsg);
|
|
663
|
+
onReasoningEnd();
|
|
664
|
+
}
|
|
665
|
+
if (!synthTokenStarted) {
|
|
666
|
+
synthTokenStarted = true;
|
|
667
|
+
onTokenStart();
|
|
668
|
+
}
|
|
669
|
+
this.session.addTextPart(currentAssistantSessionMsg, tokenChunk);
|
|
670
|
+
onTokenDelta(tokenChunk);
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
const totalDurationMs = Date.now() - queryStartTime;
|
|
675
|
+
this.session.stats.totalDurationMs += totalDurationMs;
|
|
676
|
+
|
|
677
|
+
const defaultDone = dominantLang === 'zh' ? "查询完毕" : dominantLang === 'en' ? "Search complete." : "เสร็จสิ้นการค้นหาข้อมูล";
|
|
678
|
+
const output = finalRes?.content || defaultDone;
|
|
679
|
+
onFinalAnswer(output, { durationMs: totalDurationMs, language: dominantLang });
|
|
680
|
+
|
|
681
|
+
this._extractFactsFromFinalAnswer(output);
|
|
682
|
+
|
|
683
|
+
this.stateMachine.transition(AgentStates.COMPLETE);
|
|
684
|
+
eventBus.emit(Events.AGENT_FINAL_ANSWER, { queryId, answer: output, durationMs: totalDurationMs });
|
|
685
|
+
eventBus.emit(Events.AGENT_QUERY_END, { queryId, durationMs: totalDurationMs });
|
|
686
|
+
this.stateMachine.reset();
|
|
687
|
+
|
|
688
|
+
this.saveSession().catch(err => console.error('Session save failed:', err));
|
|
689
|
+
|
|
690
|
+
return output;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
_extractFactsFromToolResult(toolName, toolArgs, result) {
|
|
694
|
+
if (!result || result.status === 'error' || result.status === 'not_found') return;
|
|
695
|
+
|
|
696
|
+
const facts = [];
|
|
697
|
+
|
|
698
|
+
switch (toolName) {
|
|
699
|
+
case 'search_works': {
|
|
700
|
+
const works = result.works || [];
|
|
701
|
+
for (const w of works.slice(0, 3)) {
|
|
702
|
+
if (w.titleCn && w.role) {
|
|
703
|
+
facts.push({
|
|
704
|
+
content: `${w.titleCn} (${w.titleEn || w.titleTh || ''}) — Role: ${w.role} (${w.year || '?'})`,
|
|
705
|
+
category: 'drama',
|
|
706
|
+
tags: [w.titleEn, w.titleCn, String(w.year)].filter(Boolean)
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
case 'get_work_detail': {
|
|
714
|
+
const w = result.work;
|
|
715
|
+
if (w && w.titleCn) {
|
|
716
|
+
facts.push({
|
|
717
|
+
content: `${w.titleCn} (${w.titleEn || w.titleTh || ''}) — ${w.role || 'Actor'} | ${w.platform || ''} | ${w.year || ''}`,
|
|
718
|
+
category: 'drama',
|
|
719
|
+
tags: [w.titleEn, w.titleCn, w.platform, String(w.year)].filter(Boolean)
|
|
720
|
+
});
|
|
721
|
+
if (w.synopsis) {
|
|
722
|
+
facts.push({
|
|
723
|
+
content: `Synopsis of ${w.titleCn}: ${w.synopsis.slice(0, 150)}`,
|
|
724
|
+
category: 'drama',
|
|
725
|
+
tags: [w.titleCn, 'synopsis']
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
case 'search_discography': {
|
|
733
|
+
const songs = result.songs || [];
|
|
734
|
+
for (const s of songs.slice(0, 3)) {
|
|
735
|
+
if (s.title && s.year) {
|
|
736
|
+
facts.push({
|
|
737
|
+
content: `Song: ${s.title} (${s.year}) — Type: ${s.type || 'Single'}`,
|
|
738
|
+
category: 'music',
|
|
739
|
+
tags: [s.titleEn, s.titleCn, String(s.year)].filter(Boolean)
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
case 'get_music_videos': {
|
|
747
|
+
const vids = result.videos || [];
|
|
748
|
+
for (const v of vids.slice(0, 3)) {
|
|
749
|
+
if (v.title && v.year) {
|
|
750
|
+
facts.push({
|
|
751
|
+
content: `MV/Stage: ${v.title} (${v.year}) — ${v.category || 'MV'}`,
|
|
752
|
+
category: 'video',
|
|
753
|
+
tags: [v.title, String(v.year), v.category].filter(Boolean)
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
case 'get_brand_endorsements': {
|
|
761
|
+
const brands = result.brands || [];
|
|
762
|
+
for (const b of brands.slice(0, 3)) {
|
|
763
|
+
if (b.brand) {
|
|
764
|
+
facts.push({
|
|
765
|
+
content: `Brand: ${b.brand} — ${b.title || 'Ambassador'} (${b.year || ''})${b.isGlobal ? ' [Global]' : ''}`,
|
|
766
|
+
category: 'brand',
|
|
767
|
+
tags: [b.brand, String(b.year)].filter(Boolean)
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
break;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
case 'get_awards': {
|
|
775
|
+
const awards = result.awards || [];
|
|
776
|
+
for (const a of awards.slice(0, 3)) {
|
|
777
|
+
if (a.title && a.year) {
|
|
778
|
+
facts.push({
|
|
779
|
+
content: `Award: ${a.title} (${a.year}) — ${a.ceremony || ''}`,
|
|
780
|
+
category: 'awards',
|
|
781
|
+
tags: [a.title, String(a.year), a.ceremony].filter(Boolean)
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
break;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
case 'get_profile_and_fandom': {
|
|
789
|
+
const p = result.profile;
|
|
790
|
+
if (p) {
|
|
791
|
+
if (p.birthDate) facts.push({ content: `Birth date: ${p.birthDate}`, category: 'profile', tags: ['birthday'] });
|
|
792
|
+
if (p.fandomName) facts.push({ content: `Fandom: ${p.fandomName}`, category: 'profile', tags: ['fandom'] });
|
|
793
|
+
if (p.slogan) facts.push({ content: `Slogan: ${p.slogan}`, category: 'profile', tags: ['slogan'] });
|
|
794
|
+
}
|
|
795
|
+
break;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
case 'get_career_timeline': {
|
|
799
|
+
const milestones = result.milestones || [];
|
|
800
|
+
for (const m of milestones.slice(0, 2)) {
|
|
801
|
+
if (m.title && m.period) {
|
|
802
|
+
facts.push({
|
|
803
|
+
content: `Career milestone: ${m.title} (${m.period})`,
|
|
804
|
+
category: 'timeline',
|
|
805
|
+
tags: [m.title, m.period].filter(Boolean)
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
case 'web_search': {
|
|
813
|
+
const items = result.results || [];
|
|
814
|
+
for (const item of items.slice(0, 2)) {
|
|
815
|
+
if (item.title && item.snippet) {
|
|
816
|
+
facts.push({
|
|
817
|
+
content: `Web: ${item.title.slice(0, 100)} — ${item.snippet.slice(0, 150)}`,
|
|
818
|
+
category: 'news',
|
|
819
|
+
tags: ['web-search']
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
for (const fact of facts) {
|
|
828
|
+
const isDuplicate = this.memory.facts.some(
|
|
829
|
+
existing => existing.content === fact.content
|
|
830
|
+
);
|
|
831
|
+
if (!isDuplicate) {
|
|
832
|
+
this.memory.addFact(fact.content, {
|
|
833
|
+
category: fact.category,
|
|
834
|
+
source: `tool:${toolName}`,
|
|
835
|
+
tags: fact.tags
|
|
836
|
+
});
|
|
837
|
+
eventBus.emit(Events.MEMORY_FACT_ADDED, { fact: fact.content, category: fact.category });
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
_extractFactsFromFinalAnswer(answer) {
|
|
843
|
+
if (!answer || answer.length < 20) return;
|
|
844
|
+
|
|
845
|
+
const ytRegex = /https?:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)/g;
|
|
846
|
+
let match;
|
|
847
|
+
const ytIds = new Set();
|
|
848
|
+
while ((match = ytRegex.exec(answer)) !== null) {
|
|
849
|
+
ytIds.add(match[1]);
|
|
850
|
+
}
|
|
851
|
+
if (ytIds.size > 0) {
|
|
852
|
+
this.memory.addFact(
|
|
853
|
+
`YouTube video IDs discussed: ${[...ytIds].join(', ')}`,
|
|
854
|
+
{ category: 'links', source: 'final-answer', tags: ['youtube'] }
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
_extractQuickTopics(text) {
|
|
860
|
+
const topicMap = {
|
|
861
|
+
'drama': /drama|series|ละคร|剧|电视|tv|film|movie/i,
|
|
862
|
+
'music': /song|music|mv|single|ost|เพลง|歌|album/i,
|
|
863
|
+
'brand': /brand|ambassador|endorsement|แบรนด์|品牌/i,
|
|
864
|
+
'awards': /award|honor|ceremony|รางวัล|奖/i,
|
|
865
|
+
'profile': /profile|bio|birthday|ประวัติ|资料/i,
|
|
866
|
+
'news': /news|latest|recent|update|ข่าว|最新/i,
|
|
867
|
+
'video': /video|stage|performance|live|คลิป|视频|舞台/i,
|
|
868
|
+
'social': /weibo|douyin|instagram|social|微博/i,
|
|
869
|
+
'gallery': /photo|picture|gallery|fashion|ภาพ|照片/i
|
|
870
|
+
};
|
|
871
|
+
const found = [];
|
|
872
|
+
for (const [topic, regex] of Object.entries(topicMap)) {
|
|
873
|
+
if (regex.test(text)) found.push(topic);
|
|
874
|
+
}
|
|
875
|
+
return found;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
async endSession() {
|
|
879
|
+
await this._ready;
|
|
880
|
+
const snapshot = this.session.toSnapshot();
|
|
881
|
+
if (snapshot.messages.length > 0) {
|
|
882
|
+
const archived = await this.memory.archiveSession(snapshot);
|
|
883
|
+
if (archived) {
|
|
884
|
+
await this.memory.consolidateSession(snapshot);
|
|
885
|
+
eventBus.emit(Events.SESSION_ARCHIVED, { sessionId: snapshot.id });
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
await this.session.clearDisk();
|
|
889
|
+
this.session.reset();
|
|
890
|
+
this.initHistory();
|
|
891
|
+
}
|
|
892
|
+
}
|