@livekit/agents 1.0.14 → 1.0.16
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/dist/cli.cjs +12 -12
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +3 -3
- package/dist/cli.d.ts +3 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +13 -13
- package/dist/cli.js.map +1 -1
- package/dist/inference/stt.cjs.map +1 -1
- package/dist/inference/stt.d.ts.map +1 -1
- package/dist/inference/stt.js +1 -1
- package/dist/inference/stt.js.map +1 -1
- package/dist/inference/tts.cjs.map +1 -1
- package/dist/inference/tts.d.cts +2 -1
- package/dist/inference/tts.d.ts +2 -1
- package/dist/inference/tts.d.ts.map +1 -1
- package/dist/inference/tts.js +1 -5
- package/dist/inference/tts.js.map +1 -1
- package/dist/llm/chat_context.cjs +78 -0
- package/dist/llm/chat_context.cjs.map +1 -1
- package/dist/llm/chat_context.d.cts +16 -0
- package/dist/llm/chat_context.d.ts +16 -0
- package/dist/llm/chat_context.d.ts.map +1 -1
- package/dist/llm/chat_context.js +78 -0
- package/dist/llm/chat_context.js.map +1 -1
- package/dist/llm/chat_context.test.cjs +531 -0
- package/dist/llm/chat_context.test.cjs.map +1 -1
- package/dist/llm/chat_context.test.js +531 -0
- package/dist/llm/chat_context.test.js.map +1 -1
- package/dist/llm/tool_context.cjs +43 -2
- package/dist/llm/tool_context.cjs.map +1 -1
- package/dist/llm/tool_context.d.cts +39 -11
- package/dist/llm/tool_context.d.ts +39 -11
- package/dist/llm/tool_context.d.ts.map +1 -1
- package/dist/llm/tool_context.js +42 -3
- package/dist/llm/tool_context.js.map +1 -1
- package/dist/llm/tool_context.test.cjs +197 -0
- package/dist/llm/tool_context.test.cjs.map +1 -1
- package/dist/llm/tool_context.test.js +175 -0
- package/dist/llm/tool_context.test.js.map +1 -1
- package/dist/llm/utils.cjs +17 -11
- package/dist/llm/utils.cjs.map +1 -1
- package/dist/llm/utils.d.cts +1 -2
- package/dist/llm/utils.d.ts +1 -2
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +17 -11
- package/dist/llm/utils.js.map +1 -1
- package/dist/llm/zod-utils.cjs +99 -0
- package/dist/llm/zod-utils.cjs.map +1 -0
- package/dist/llm/zod-utils.d.cts +65 -0
- package/dist/llm/zod-utils.d.ts +65 -0
- package/dist/llm/zod-utils.d.ts.map +1 -0
- package/dist/llm/zod-utils.js +61 -0
- package/dist/llm/zod-utils.js.map +1 -0
- package/dist/llm/zod-utils.test.cjs +389 -0
- package/dist/llm/zod-utils.test.cjs.map +1 -0
- package/dist/llm/zod-utils.test.js +372 -0
- package/dist/llm/zod-utils.test.js.map +1 -0
- package/dist/metrics/base.cjs.map +1 -1
- package/dist/metrics/base.d.cts +7 -0
- package/dist/metrics/base.d.ts +7 -0
- package/dist/metrics/base.d.ts.map +1 -1
- package/dist/stt/stt.cjs +1 -0
- package/dist/stt/stt.cjs.map +1 -1
- package/dist/stt/stt.d.cts +7 -1
- package/dist/stt/stt.d.ts +7 -1
- package/dist/stt/stt.d.ts.map +1 -1
- package/dist/stt/stt.js +1 -0
- package/dist/stt/stt.js.map +1 -1
- package/dist/vad.cjs +16 -0
- package/dist/vad.cjs.map +1 -1
- package/dist/vad.d.cts +6 -0
- package/dist/vad.d.ts +6 -0
- package/dist/vad.d.ts.map +1 -1
- package/dist/vad.js +16 -0
- package/dist/vad.js.map +1 -1
- package/dist/voice/agent_activity.cjs +83 -8
- package/dist/voice/agent_activity.cjs.map +1 -1
- package/dist/voice/agent_activity.d.cts +6 -2
- package/dist/voice/agent_activity.d.ts +6 -2
- package/dist/voice/agent_activity.d.ts.map +1 -1
- package/dist/voice/agent_activity.js +83 -8
- package/dist/voice/agent_activity.js.map +1 -1
- package/dist/voice/agent_session.cjs +3 -2
- package/dist/voice/agent_session.cjs.map +1 -1
- package/dist/voice/agent_session.d.cts +2 -1
- package/dist/voice/agent_session.d.ts +2 -1
- package/dist/voice/agent_session.d.ts.map +1 -1
- package/dist/voice/agent_session.js +3 -2
- package/dist/voice/agent_session.js.map +1 -1
- package/dist/voice/audio_recognition.cjs +138 -16
- package/dist/voice/audio_recognition.cjs.map +1 -1
- package/dist/voice/audio_recognition.d.cts +11 -0
- package/dist/voice/audio_recognition.d.ts +11 -0
- package/dist/voice/audio_recognition.d.ts.map +1 -1
- package/dist/voice/audio_recognition.js +138 -16
- package/dist/voice/audio_recognition.js.map +1 -1
- package/dist/voice/generation.cjs +8 -3
- package/dist/voice/generation.cjs.map +1 -1
- package/dist/voice/generation.d.ts.map +1 -1
- package/dist/voice/generation.js +8 -3
- package/dist/voice/generation.js.map +1 -1
- package/dist/voice/room_io/_input.cjs.map +1 -1
- package/dist/voice/room_io/_input.d.ts.map +1 -1
- package/dist/voice/room_io/_input.js +0 -1
- package/dist/voice/room_io/_input.js.map +1 -1
- package/dist/worker.cjs +17 -11
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.d.cts +16 -9
- package/dist/worker.d.ts +16 -9
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +16 -12
- package/dist/worker.js.map +1 -1
- package/package.json +5 -4
- package/src/cli.ts +17 -17
- package/src/inference/stt.ts +2 -1
- package/src/inference/tts.ts +2 -5
- package/src/llm/__snapshots__/zod-utils.test.ts.snap +341 -0
- package/src/llm/chat_context.test.ts +607 -0
- package/src/llm/chat_context.ts +106 -0
- package/src/llm/tool_context.test.ts +210 -1
- package/src/llm/tool_context.ts +101 -17
- package/src/llm/utils.ts +18 -15
- package/src/llm/zod-utils.test.ts +476 -0
- package/src/llm/zod-utils.ts +144 -0
- package/src/metrics/base.ts +7 -0
- package/src/stt/stt.ts +6 -0
- package/src/vad.ts +18 -0
- package/src/voice/agent_activity.ts +119 -9
- package/src/voice/agent_session.ts +3 -1
- package/src/voice/audio_recognition.ts +235 -57
- package/src/voice/generation.ts +8 -3
- package/src/voice/room_io/_input.ts +1 -1
- package/src/worker.ts +29 -18
|
@@ -448,3 +448,610 @@ describe('ReadonlyChatContext with immutable array', () => {
|
|
|
448
448
|
expect(ids).toEqual(['msg_1', 'msg_2']);
|
|
449
449
|
});
|
|
450
450
|
});
|
|
451
|
+
|
|
452
|
+
describe('ChatContext.isEquivalent', () => {
|
|
453
|
+
it('should return true for same reference', () => {
|
|
454
|
+
const ctx = new ChatContext();
|
|
455
|
+
ctx.addMessage({
|
|
456
|
+
id: 'msg_1',
|
|
457
|
+
role: 'user',
|
|
458
|
+
content: 'Hello',
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
expect(ctx.isEquivalent(ctx)).toBe(true);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('should return true for identical empty contexts', () => {
|
|
465
|
+
const ctx1 = new ChatContext();
|
|
466
|
+
const ctx2 = new ChatContext();
|
|
467
|
+
|
|
468
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it('should return false for contexts with different lengths', () => {
|
|
472
|
+
const ctx1 = new ChatContext();
|
|
473
|
+
ctx1.addMessage({
|
|
474
|
+
id: 'msg_1',
|
|
475
|
+
role: 'user',
|
|
476
|
+
content: 'Hello',
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const ctx2 = new ChatContext();
|
|
480
|
+
ctx2.addMessage({
|
|
481
|
+
id: 'msg_1',
|
|
482
|
+
role: 'user',
|
|
483
|
+
content: 'Hello',
|
|
484
|
+
});
|
|
485
|
+
ctx2.addMessage({
|
|
486
|
+
id: 'msg_2',
|
|
487
|
+
role: 'assistant',
|
|
488
|
+
content: 'Hi',
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should return false for contexts with different item IDs', () => {
|
|
495
|
+
const ctx1 = new ChatContext();
|
|
496
|
+
ctx1.addMessage({
|
|
497
|
+
id: 'msg_1',
|
|
498
|
+
role: 'user',
|
|
499
|
+
content: 'Hello',
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
const ctx2 = new ChatContext();
|
|
503
|
+
ctx2.addMessage({
|
|
504
|
+
id: 'msg_2',
|
|
505
|
+
role: 'user',
|
|
506
|
+
content: 'Hello',
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('should return false for contexts with different item types', () => {
|
|
513
|
+
const ctx1 = new ChatContext();
|
|
514
|
+
ctx1.addMessage({
|
|
515
|
+
id: 'msg_1',
|
|
516
|
+
role: 'user',
|
|
517
|
+
content: 'Hello',
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
const ctx2 = new ChatContext();
|
|
521
|
+
ctx2.insert(
|
|
522
|
+
new FunctionCall({
|
|
523
|
+
id: 'msg_1',
|
|
524
|
+
callId: 'call_1',
|
|
525
|
+
name: 'test',
|
|
526
|
+
args: '{}',
|
|
527
|
+
}),
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
describe('message comparison', () => {
|
|
534
|
+
it('should return true for identical messages', () => {
|
|
535
|
+
const ctx1 = new ChatContext();
|
|
536
|
+
ctx1.addMessage({
|
|
537
|
+
id: 'msg_1',
|
|
538
|
+
role: 'user',
|
|
539
|
+
content: 'Hello',
|
|
540
|
+
interrupted: false,
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const ctx2 = new ChatContext();
|
|
544
|
+
ctx2.addMessage({
|
|
545
|
+
id: 'msg_1',
|
|
546
|
+
role: 'user',
|
|
547
|
+
content: 'Hello',
|
|
548
|
+
interrupted: false,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should return false for messages with different roles', () => {
|
|
555
|
+
const ctx1 = new ChatContext();
|
|
556
|
+
ctx1.addMessage({
|
|
557
|
+
id: 'msg_1',
|
|
558
|
+
role: 'user',
|
|
559
|
+
content: 'Hello',
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
const ctx2 = new ChatContext();
|
|
563
|
+
ctx2.addMessage({
|
|
564
|
+
id: 'msg_1',
|
|
565
|
+
role: 'assistant',
|
|
566
|
+
content: 'Hello',
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('should return false for messages with different interrupted flags', () => {
|
|
573
|
+
const ctx1 = new ChatContext();
|
|
574
|
+
ctx1.addMessage({
|
|
575
|
+
id: 'msg_1',
|
|
576
|
+
role: 'user',
|
|
577
|
+
content: 'Hello',
|
|
578
|
+
interrupted: false,
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
const ctx2 = new ChatContext();
|
|
582
|
+
ctx2.addMessage({
|
|
583
|
+
id: 'msg_1',
|
|
584
|
+
role: 'user',
|
|
585
|
+
content: 'Hello',
|
|
586
|
+
interrupted: true,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it('should return false for messages with different content', () => {
|
|
593
|
+
const ctx1 = new ChatContext();
|
|
594
|
+
ctx1.addMessage({
|
|
595
|
+
id: 'msg_1',
|
|
596
|
+
role: 'user',
|
|
597
|
+
content: 'Hello',
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
const ctx2 = new ChatContext();
|
|
601
|
+
ctx2.addMessage({
|
|
602
|
+
id: 'msg_1',
|
|
603
|
+
role: 'user',
|
|
604
|
+
content: 'World',
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('should return true for messages with identical array content', () => {
|
|
611
|
+
const ctx1 = new ChatContext();
|
|
612
|
+
ctx1.addMessage({
|
|
613
|
+
id: 'msg_1',
|
|
614
|
+
role: 'user',
|
|
615
|
+
content: ['Hello', 'World'],
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
const ctx2 = new ChatContext();
|
|
619
|
+
ctx2.addMessage({
|
|
620
|
+
id: 'msg_1',
|
|
621
|
+
role: 'user',
|
|
622
|
+
content: ['Hello', 'World'],
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
it('should return false for messages with different array content', () => {
|
|
629
|
+
const ctx1 = new ChatContext();
|
|
630
|
+
ctx1.addMessage({
|
|
631
|
+
id: 'msg_1',
|
|
632
|
+
role: 'user',
|
|
633
|
+
content: ['Hello', 'World'],
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
const ctx2 = new ChatContext();
|
|
637
|
+
ctx2.addMessage({
|
|
638
|
+
id: 'msg_1',
|
|
639
|
+
role: 'user',
|
|
640
|
+
content: ['Hello'],
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it('should return true for messages with identical image content', () => {
|
|
647
|
+
const imageContent: ImageContent = {
|
|
648
|
+
id: 'img_1',
|
|
649
|
+
type: 'image_content',
|
|
650
|
+
image: 'https://example.com/image.jpg',
|
|
651
|
+
inferenceDetail: 'high',
|
|
652
|
+
inferenceWidth: 1024,
|
|
653
|
+
inferenceHeight: 768,
|
|
654
|
+
mimeType: 'image/jpeg',
|
|
655
|
+
_cache: {},
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const ctx1 = new ChatContext();
|
|
659
|
+
ctx1.addMessage({
|
|
660
|
+
id: 'msg_1',
|
|
661
|
+
role: 'user',
|
|
662
|
+
content: ['Check this:', imageContent],
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const ctx2 = new ChatContext();
|
|
666
|
+
ctx2.addMessage({
|
|
667
|
+
id: 'msg_1',
|
|
668
|
+
role: 'user',
|
|
669
|
+
content: ['Check this:', { ...imageContent }],
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('should return false for messages with different image content', () => {
|
|
676
|
+
const imageContent1: ImageContent = {
|
|
677
|
+
id: 'img_1',
|
|
678
|
+
type: 'image_content',
|
|
679
|
+
image: 'https://example.com/image1.jpg',
|
|
680
|
+
inferenceDetail: 'high',
|
|
681
|
+
_cache: {},
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
const imageContent2: ImageContent = {
|
|
685
|
+
id: 'img_2',
|
|
686
|
+
type: 'image_content',
|
|
687
|
+
image: 'https://example.com/image2.jpg',
|
|
688
|
+
inferenceDetail: 'high',
|
|
689
|
+
_cache: {},
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const ctx1 = new ChatContext();
|
|
693
|
+
ctx1.addMessage({
|
|
694
|
+
id: 'msg_1',
|
|
695
|
+
role: 'user',
|
|
696
|
+
content: ['Check this:', imageContent1],
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
const ctx2 = new ChatContext();
|
|
700
|
+
ctx2.addMessage({
|
|
701
|
+
id: 'msg_1',
|
|
702
|
+
role: 'user',
|
|
703
|
+
content: ['Check this:', imageContent2],
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
describe('function call comparison', () => {
|
|
711
|
+
it('should return true for identical function calls', () => {
|
|
712
|
+
const ctx1 = new ChatContext();
|
|
713
|
+
ctx1.insert(
|
|
714
|
+
new FunctionCall({
|
|
715
|
+
id: 'func_1',
|
|
716
|
+
callId: 'call_1',
|
|
717
|
+
name: 'get_weather',
|
|
718
|
+
args: '{"location": "Paris"}',
|
|
719
|
+
}),
|
|
720
|
+
);
|
|
721
|
+
|
|
722
|
+
const ctx2 = new ChatContext();
|
|
723
|
+
ctx2.insert(
|
|
724
|
+
new FunctionCall({
|
|
725
|
+
id: 'func_1',
|
|
726
|
+
callId: 'call_1',
|
|
727
|
+
name: 'get_weather',
|
|
728
|
+
args: '{"location": "Paris"}',
|
|
729
|
+
}),
|
|
730
|
+
);
|
|
731
|
+
|
|
732
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('should return false for function calls with different names', () => {
|
|
736
|
+
const ctx1 = new ChatContext();
|
|
737
|
+
ctx1.insert(
|
|
738
|
+
new FunctionCall({
|
|
739
|
+
id: 'func_1',
|
|
740
|
+
callId: 'call_1',
|
|
741
|
+
name: 'get_weather',
|
|
742
|
+
args: '{}',
|
|
743
|
+
}),
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
const ctx2 = new ChatContext();
|
|
747
|
+
ctx2.insert(
|
|
748
|
+
new FunctionCall({
|
|
749
|
+
id: 'func_1',
|
|
750
|
+
callId: 'call_1',
|
|
751
|
+
name: 'get_time',
|
|
752
|
+
args: '{}',
|
|
753
|
+
}),
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
it('should return false for function calls with different call IDs', () => {
|
|
760
|
+
const ctx1 = new ChatContext();
|
|
761
|
+
ctx1.insert(
|
|
762
|
+
new FunctionCall({
|
|
763
|
+
id: 'func_1',
|
|
764
|
+
callId: 'call_1',
|
|
765
|
+
name: 'get_weather',
|
|
766
|
+
args: '{}',
|
|
767
|
+
}),
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
const ctx2 = new ChatContext();
|
|
771
|
+
ctx2.insert(
|
|
772
|
+
new FunctionCall({
|
|
773
|
+
id: 'func_1',
|
|
774
|
+
callId: 'call_2',
|
|
775
|
+
name: 'get_weather',
|
|
776
|
+
args: '{}',
|
|
777
|
+
}),
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
it('should return false for function calls with different arguments', () => {
|
|
784
|
+
const ctx1 = new ChatContext();
|
|
785
|
+
ctx1.insert(
|
|
786
|
+
new FunctionCall({
|
|
787
|
+
id: 'func_1',
|
|
788
|
+
callId: 'call_1',
|
|
789
|
+
name: 'get_weather',
|
|
790
|
+
args: '{"location": "Paris"}',
|
|
791
|
+
}),
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
const ctx2 = new ChatContext();
|
|
795
|
+
ctx2.insert(
|
|
796
|
+
new FunctionCall({
|
|
797
|
+
id: 'func_1',
|
|
798
|
+
callId: 'call_1',
|
|
799
|
+
name: 'get_weather',
|
|
800
|
+
args: '{"location": "London"}',
|
|
801
|
+
}),
|
|
802
|
+
);
|
|
803
|
+
|
|
804
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('should ignore timestamps', () => {
|
|
808
|
+
const ctx1 = new ChatContext();
|
|
809
|
+
ctx1.insert(
|
|
810
|
+
new FunctionCall({
|
|
811
|
+
id: 'func_1',
|
|
812
|
+
callId: 'call_1',
|
|
813
|
+
name: 'get_weather',
|
|
814
|
+
args: '{}',
|
|
815
|
+
createdAt: 1000,
|
|
816
|
+
}),
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
const ctx2 = new ChatContext();
|
|
820
|
+
ctx2.insert(
|
|
821
|
+
new FunctionCall({
|
|
822
|
+
id: 'func_1',
|
|
823
|
+
callId: 'call_1',
|
|
824
|
+
name: 'get_weather',
|
|
825
|
+
args: '{}',
|
|
826
|
+
createdAt: 2000,
|
|
827
|
+
}),
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
describe('function call output comparison', () => {
|
|
835
|
+
it('should return true for identical function call outputs', () => {
|
|
836
|
+
const ctx1 = new ChatContext();
|
|
837
|
+
ctx1.insert(
|
|
838
|
+
new FunctionCallOutput({
|
|
839
|
+
id: 'output_1',
|
|
840
|
+
callId: 'call_1',
|
|
841
|
+
name: 'get_weather',
|
|
842
|
+
output: '{"temperature": 22}',
|
|
843
|
+
isError: false,
|
|
844
|
+
}),
|
|
845
|
+
);
|
|
846
|
+
|
|
847
|
+
const ctx2 = new ChatContext();
|
|
848
|
+
ctx2.insert(
|
|
849
|
+
new FunctionCallOutput({
|
|
850
|
+
id: 'output_1',
|
|
851
|
+
callId: 'call_1',
|
|
852
|
+
name: 'get_weather',
|
|
853
|
+
output: '{"temperature": 22}',
|
|
854
|
+
isError: false,
|
|
855
|
+
}),
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
it('should return false for function call outputs with different names', () => {
|
|
862
|
+
const ctx1 = new ChatContext();
|
|
863
|
+
ctx1.insert(
|
|
864
|
+
new FunctionCallOutput({
|
|
865
|
+
id: 'output_1',
|
|
866
|
+
callId: 'call_1',
|
|
867
|
+
name: 'get_weather',
|
|
868
|
+
output: '{}',
|
|
869
|
+
isError: false,
|
|
870
|
+
}),
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
const ctx2 = new ChatContext();
|
|
874
|
+
ctx2.insert(
|
|
875
|
+
new FunctionCallOutput({
|
|
876
|
+
id: 'output_1',
|
|
877
|
+
callId: 'call_1',
|
|
878
|
+
name: 'get_time',
|
|
879
|
+
output: '{}',
|
|
880
|
+
isError: false,
|
|
881
|
+
}),
|
|
882
|
+
);
|
|
883
|
+
|
|
884
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('should return false for function call outputs with different call IDs', () => {
|
|
888
|
+
const ctx1 = new ChatContext();
|
|
889
|
+
ctx1.insert(
|
|
890
|
+
new FunctionCallOutput({
|
|
891
|
+
id: 'output_1',
|
|
892
|
+
callId: 'call_1',
|
|
893
|
+
name: 'get_weather',
|
|
894
|
+
output: '{}',
|
|
895
|
+
isError: false,
|
|
896
|
+
}),
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
const ctx2 = new ChatContext();
|
|
900
|
+
ctx2.insert(
|
|
901
|
+
new FunctionCallOutput({
|
|
902
|
+
id: 'output_1',
|
|
903
|
+
callId: 'call_2',
|
|
904
|
+
name: 'get_weather',
|
|
905
|
+
output: '{}',
|
|
906
|
+
isError: false,
|
|
907
|
+
}),
|
|
908
|
+
);
|
|
909
|
+
|
|
910
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it('should return false for function call outputs with different output values', () => {
|
|
914
|
+
const ctx1 = new ChatContext();
|
|
915
|
+
ctx1.insert(
|
|
916
|
+
new FunctionCallOutput({
|
|
917
|
+
id: 'output_1',
|
|
918
|
+
callId: 'call_1',
|
|
919
|
+
name: 'get_weather',
|
|
920
|
+
output: '{"temperature": 22}',
|
|
921
|
+
isError: false,
|
|
922
|
+
}),
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
const ctx2 = new ChatContext();
|
|
926
|
+
ctx2.insert(
|
|
927
|
+
new FunctionCallOutput({
|
|
928
|
+
id: 'output_1',
|
|
929
|
+
callId: 'call_1',
|
|
930
|
+
name: 'get_weather',
|
|
931
|
+
output: '{"temperature": 25}',
|
|
932
|
+
isError: false,
|
|
933
|
+
}),
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
it('should return false for function call outputs with different error flags', () => {
|
|
940
|
+
const ctx1 = new ChatContext();
|
|
941
|
+
ctx1.insert(
|
|
942
|
+
new FunctionCallOutput({
|
|
943
|
+
id: 'output_1',
|
|
944
|
+
callId: 'call_1',
|
|
945
|
+
name: 'get_weather',
|
|
946
|
+
output: 'Error occurred',
|
|
947
|
+
isError: false,
|
|
948
|
+
}),
|
|
949
|
+
);
|
|
950
|
+
|
|
951
|
+
const ctx2 = new ChatContext();
|
|
952
|
+
ctx2.insert(
|
|
953
|
+
new FunctionCallOutput({
|
|
954
|
+
id: 'output_1',
|
|
955
|
+
callId: 'call_1',
|
|
956
|
+
name: 'get_weather',
|
|
957
|
+
output: 'Error occurred',
|
|
958
|
+
isError: true,
|
|
959
|
+
}),
|
|
960
|
+
);
|
|
961
|
+
|
|
962
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(false);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
it('should ignore timestamps', () => {
|
|
966
|
+
const ctx1 = new ChatContext();
|
|
967
|
+
ctx1.insert(
|
|
968
|
+
new FunctionCallOutput({
|
|
969
|
+
id: 'output_1',
|
|
970
|
+
callId: 'call_1',
|
|
971
|
+
name: 'get_weather',
|
|
972
|
+
output: '{}',
|
|
973
|
+
isError: false,
|
|
974
|
+
createdAt: 1000,
|
|
975
|
+
}),
|
|
976
|
+
);
|
|
977
|
+
|
|
978
|
+
const ctx2 = new ChatContext();
|
|
979
|
+
ctx2.insert(
|
|
980
|
+
new FunctionCallOutput({
|
|
981
|
+
id: 'output_1',
|
|
982
|
+
callId: 'call_1',
|
|
983
|
+
name: 'get_weather',
|
|
984
|
+
output: '{}',
|
|
985
|
+
isError: false,
|
|
986
|
+
createdAt: 2000,
|
|
987
|
+
}),
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
describe('complex context comparison', () => {
|
|
995
|
+
it('should return true for identical complex contexts', () => {
|
|
996
|
+
const ctx1 = new ChatContext();
|
|
997
|
+
ctx1.addMessage({
|
|
998
|
+
id: 'msg_1',
|
|
999
|
+
role: 'user',
|
|
1000
|
+
content: 'What is the weather?',
|
|
1001
|
+
});
|
|
1002
|
+
ctx1.insert(
|
|
1003
|
+
new FunctionCall({
|
|
1004
|
+
id: 'func_1',
|
|
1005
|
+
callId: 'call_1',
|
|
1006
|
+
name: 'get_weather',
|
|
1007
|
+
args: '{"location": "Paris"}',
|
|
1008
|
+
}),
|
|
1009
|
+
);
|
|
1010
|
+
ctx1.insert(
|
|
1011
|
+
new FunctionCallOutput({
|
|
1012
|
+
id: 'output_1',
|
|
1013
|
+
callId: 'call_1',
|
|
1014
|
+
name: 'get_weather',
|
|
1015
|
+
output: '{"temperature": 22}',
|
|
1016
|
+
isError: false,
|
|
1017
|
+
}),
|
|
1018
|
+
);
|
|
1019
|
+
ctx1.addMessage({
|
|
1020
|
+
id: 'msg_2',
|
|
1021
|
+
role: 'assistant',
|
|
1022
|
+
content: 'The weather is 22°C',
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
const ctx2 = new ChatContext();
|
|
1026
|
+
ctx2.addMessage({
|
|
1027
|
+
id: 'msg_1',
|
|
1028
|
+
role: 'user',
|
|
1029
|
+
content: 'What is the weather?',
|
|
1030
|
+
});
|
|
1031
|
+
ctx2.insert(
|
|
1032
|
+
new FunctionCall({
|
|
1033
|
+
id: 'func_1',
|
|
1034
|
+
callId: 'call_1',
|
|
1035
|
+
name: 'get_weather',
|
|
1036
|
+
args: '{"location": "Paris"}',
|
|
1037
|
+
}),
|
|
1038
|
+
);
|
|
1039
|
+
ctx2.insert(
|
|
1040
|
+
new FunctionCallOutput({
|
|
1041
|
+
id: 'output_1',
|
|
1042
|
+
callId: 'call_1',
|
|
1043
|
+
name: 'get_weather',
|
|
1044
|
+
output: '{"temperature": 22}',
|
|
1045
|
+
isError: false,
|
|
1046
|
+
}),
|
|
1047
|
+
);
|
|
1048
|
+
ctx2.addMessage({
|
|
1049
|
+
id: 'msg_2',
|
|
1050
|
+
role: 'assistant',
|
|
1051
|
+
content: 'The weather is 22°C',
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
expect(ctx1.isEquivalent(ctx2)).toBe(true);
|
|
1055
|
+
});
|
|
1056
|
+
});
|
|
1057
|
+
});
|
package/src/llm/chat_context.ts
CHANGED
|
@@ -513,6 +513,112 @@ export class ChatContext {
|
|
|
513
513
|
return 0;
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
+
/**
|
|
517
|
+
* Return true if `other` has the same sequence of items with matching
|
|
518
|
+
* essential fields (IDs, types, and payload) as this context.
|
|
519
|
+
*
|
|
520
|
+
* Comparison rules:
|
|
521
|
+
* - Messages: compares the full `content` list, `role` and `interrupted`.
|
|
522
|
+
* - Function calls: compares `name`, `callId`, and `args`.
|
|
523
|
+
* - Function call outputs: compares `name`, `callId`, `output`, and `isError`.
|
|
524
|
+
*
|
|
525
|
+
* Does not consider timestamps or other metadata.
|
|
526
|
+
*/
|
|
527
|
+
isEquivalent(other: ChatContext): boolean {
|
|
528
|
+
if (this === other) {
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
if (this.items.length !== other.items.length) {
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
for (let i = 0; i < this.items.length; i++) {
|
|
537
|
+
const a = this.items[i]!;
|
|
538
|
+
const b = other.items[i]!;
|
|
539
|
+
|
|
540
|
+
if (a.id !== b.id || a.type !== b.type) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (a.type === 'message' && b.type === 'message') {
|
|
545
|
+
if (
|
|
546
|
+
a.role !== b.role ||
|
|
547
|
+
a.interrupted !== b.interrupted ||
|
|
548
|
+
!this.compareContent(a.content, b.content)
|
|
549
|
+
) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
} else if (a.type === 'function_call' && b.type === 'function_call') {
|
|
553
|
+
if (a.name !== b.name || a.callId !== b.callId || a.args !== b.args) {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
} else if (a.type === 'function_call_output' && b.type === 'function_call_output') {
|
|
557
|
+
if (
|
|
558
|
+
a.name !== b.name ||
|
|
559
|
+
a.callId !== b.callId ||
|
|
560
|
+
a.output !== b.output ||
|
|
561
|
+
a.isError !== b.isError
|
|
562
|
+
) {
|
|
563
|
+
return false;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Compare two content arrays for equality.
|
|
573
|
+
*/
|
|
574
|
+
private compareContent(a: ChatContent[], b: ChatContent[]): boolean {
|
|
575
|
+
if (a.length !== b.length) {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
for (let i = 0; i < a.length; i++) {
|
|
580
|
+
const contentA = a[i]!;
|
|
581
|
+
const contentB = b[i]!;
|
|
582
|
+
|
|
583
|
+
if (typeof contentA === 'string' && typeof contentB === 'string') {
|
|
584
|
+
if (contentA !== contentB) {
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (typeof contentA !== typeof contentB) {
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
if (typeof contentA === 'object' && typeof contentB === 'object') {
|
|
595
|
+
if (contentA.type === 'image_content' && contentB.type === 'image_content') {
|
|
596
|
+
if (
|
|
597
|
+
contentA.id !== contentB.id ||
|
|
598
|
+
contentA.image !== contentB.image ||
|
|
599
|
+
contentA.inferenceDetail !== contentB.inferenceDetail ||
|
|
600
|
+
contentA.inferenceWidth !== contentB.inferenceWidth ||
|
|
601
|
+
contentA.inferenceHeight !== contentB.inferenceHeight ||
|
|
602
|
+
contentA.mimeType !== contentB.mimeType
|
|
603
|
+
) {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
} else if (contentA.type === 'audio_content' && contentB.type === 'audio_content') {
|
|
607
|
+
if (contentA.frame.length !== contentB.frame.length) {
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
610
|
+
if (contentA.transcript !== contentB.transcript) {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
|
|
516
622
|
/**
|
|
517
623
|
* Indicates whether the context is read-only
|
|
518
624
|
*/
|