@librechat/agents 3.0.18 → 3.0.20
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/cjs/graphs/Graph.cjs +6 -4
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +3 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +87 -14
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +179 -6
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +6 -4
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/cache.mjs +86 -15
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/format.mjs +179 -7
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/types/messages/cache.d.ts +16 -0
- package/dist/types/messages/format.d.ts +14 -0
- package/package.json +2 -1
- package/src/graphs/Graph.ts +8 -4
- package/src/messages/cache.test.ts +499 -3
- package/src/messages/cache.ts +115 -25
- package/src/messages/format.ts +231 -6
- package/src/messages/labelContentByAgent.test.ts +887 -0
- package/src/scripts/test-multi-agent-list-handoff.ts +116 -10
- package/src/scripts/test-parallel-agent-labeling.ts +325 -0
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
import type { AnthropicMessages } from '@/types/messages';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
stripAnthropicCacheControl,
|
|
5
|
+
stripBedrockCacheControl,
|
|
6
|
+
addBedrockCacheControl,
|
|
7
|
+
addCacheControl,
|
|
8
|
+
} from './cache';
|
|
4
9
|
import { MessageContentComplex } from '@langchain/core/messages';
|
|
5
10
|
import { ContentTypes } from '@/common/enum';
|
|
6
11
|
|
|
@@ -393,7 +398,7 @@ describe('addBedrockCacheControl (Bedrock cache checkpoints)', () => {
|
|
|
393
398
|
expect(first[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
394
399
|
});
|
|
395
400
|
|
|
396
|
-
it('works with the example from the langchain pr', () => {
|
|
401
|
+
it('works with the example from the langchain pr (with multi-turn behavior)', () => {
|
|
397
402
|
const messages: TestMsg[] = [
|
|
398
403
|
{
|
|
399
404
|
role: 'system',
|
|
@@ -445,7 +450,8 @@ describe('addBedrockCacheControl (Bedrock cache checkpoints)', () => {
|
|
|
445
450
|
type: ContentTypes.TEXT,
|
|
446
451
|
text: 'You\'re an advanced AI assistant.',
|
|
447
452
|
});
|
|
448
|
-
expect(system
|
|
453
|
+
expect(system.length).toBe(1);
|
|
454
|
+
|
|
449
455
|
expect(user[0]).toEqual({
|
|
450
456
|
type: ContentTypes.TEXT,
|
|
451
457
|
text: 'What is the capital of France?',
|
|
@@ -458,4 +464,494 @@ describe('addBedrockCacheControl (Bedrock cache checkpoints)', () => {
|
|
|
458
464
|
});
|
|
459
465
|
expect(assistant[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
460
466
|
});
|
|
467
|
+
|
|
468
|
+
it('is idempotent - calling multiple times does not add duplicate cache points', () => {
|
|
469
|
+
const messages: TestMsg[] = [
|
|
470
|
+
{
|
|
471
|
+
role: 'user',
|
|
472
|
+
content: [{ type: ContentTypes.TEXT, text: 'First message' }],
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
role: 'assistant',
|
|
476
|
+
content: [{ type: ContentTypes.TEXT, text: 'First response' }],
|
|
477
|
+
},
|
|
478
|
+
];
|
|
479
|
+
|
|
480
|
+
const result1 = addBedrockCacheControl(messages);
|
|
481
|
+
const firstContent = result1[0].content as MessageContentComplex[];
|
|
482
|
+
const secondContent = result1[1].content as MessageContentComplex[];
|
|
483
|
+
|
|
484
|
+
expect(firstContent.length).toBe(2);
|
|
485
|
+
expect(firstContent[0]).toEqual({
|
|
486
|
+
type: ContentTypes.TEXT,
|
|
487
|
+
text: 'First message',
|
|
488
|
+
});
|
|
489
|
+
expect(firstContent[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
490
|
+
|
|
491
|
+
expect(secondContent.length).toBe(2);
|
|
492
|
+
expect(secondContent[0]).toEqual({
|
|
493
|
+
type: ContentTypes.TEXT,
|
|
494
|
+
text: 'First response',
|
|
495
|
+
});
|
|
496
|
+
expect(secondContent[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
497
|
+
|
|
498
|
+
const result2 = addBedrockCacheControl(result1);
|
|
499
|
+
const firstContentAfter = result2[0].content as MessageContentComplex[];
|
|
500
|
+
const secondContentAfter = result2[1].content as MessageContentComplex[];
|
|
501
|
+
|
|
502
|
+
expect(firstContentAfter.length).toBe(2);
|
|
503
|
+
expect(firstContentAfter[0]).toEqual({
|
|
504
|
+
type: ContentTypes.TEXT,
|
|
505
|
+
text: 'First message',
|
|
506
|
+
});
|
|
507
|
+
expect(firstContentAfter[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
508
|
+
|
|
509
|
+
expect(secondContentAfter.length).toBe(2);
|
|
510
|
+
expect(secondContentAfter[0]).toEqual({
|
|
511
|
+
type: ContentTypes.TEXT,
|
|
512
|
+
text: 'First response',
|
|
513
|
+
});
|
|
514
|
+
expect(secondContentAfter[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('skips messages that already have cache points in multi-agent scenarios', () => {
|
|
518
|
+
const messages: TestMsg[] = [
|
|
519
|
+
{
|
|
520
|
+
role: 'user',
|
|
521
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hello' }],
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
role: 'assistant',
|
|
525
|
+
content: [
|
|
526
|
+
{ type: ContentTypes.TEXT, text: 'Response from agent 1' },
|
|
527
|
+
{ cachePoint: { type: 'default' } },
|
|
528
|
+
],
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
role: 'user',
|
|
532
|
+
content: [{ type: ContentTypes.TEXT, text: 'Follow-up question' }],
|
|
533
|
+
},
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
const result = addBedrockCacheControl(messages);
|
|
537
|
+
const lastContent = result[2].content as MessageContentComplex[];
|
|
538
|
+
const secondLastContent = result[1].content as MessageContentComplex[];
|
|
539
|
+
|
|
540
|
+
expect(lastContent.length).toBe(2);
|
|
541
|
+
expect(lastContent[0]).toEqual({
|
|
542
|
+
type: ContentTypes.TEXT,
|
|
543
|
+
text: 'Follow-up question',
|
|
544
|
+
});
|
|
545
|
+
expect(lastContent[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
546
|
+
|
|
547
|
+
expect(secondLastContent.length).toBe(2);
|
|
548
|
+
expect(secondLastContent[0]).toEqual({
|
|
549
|
+
type: ContentTypes.TEXT,
|
|
550
|
+
text: 'Response from agent 1',
|
|
551
|
+
});
|
|
552
|
+
expect(secondLastContent[1]).toEqual({ cachePoint: { type: 'default' } });
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
describe('stripAnthropicCacheControl', () => {
|
|
557
|
+
it('removes cache_control fields from content blocks', () => {
|
|
558
|
+
const messages: TestMsg[] = [
|
|
559
|
+
{
|
|
560
|
+
role: 'user',
|
|
561
|
+
content: [
|
|
562
|
+
{
|
|
563
|
+
type: ContentTypes.TEXT,
|
|
564
|
+
text: 'Hello',
|
|
565
|
+
cache_control: { type: 'ephemeral' },
|
|
566
|
+
} as MessageContentComplex,
|
|
567
|
+
],
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
role: 'assistant',
|
|
571
|
+
content: [
|
|
572
|
+
{
|
|
573
|
+
type: ContentTypes.TEXT,
|
|
574
|
+
text: 'Hi there',
|
|
575
|
+
cache_control: { type: 'ephemeral' },
|
|
576
|
+
} as MessageContentComplex,
|
|
577
|
+
],
|
|
578
|
+
},
|
|
579
|
+
];
|
|
580
|
+
|
|
581
|
+
const result = stripAnthropicCacheControl(messages);
|
|
582
|
+
|
|
583
|
+
const firstContent = result[0].content as MessageContentComplex[];
|
|
584
|
+
const secondContent = result[1].content as MessageContentComplex[];
|
|
585
|
+
|
|
586
|
+
expect(firstContent[0]).toEqual({
|
|
587
|
+
type: ContentTypes.TEXT,
|
|
588
|
+
text: 'Hello',
|
|
589
|
+
});
|
|
590
|
+
expect('cache_control' in firstContent[0]).toBe(false);
|
|
591
|
+
|
|
592
|
+
expect(secondContent[0]).toEqual({
|
|
593
|
+
type: ContentTypes.TEXT,
|
|
594
|
+
text: 'Hi there',
|
|
595
|
+
});
|
|
596
|
+
expect('cache_control' in secondContent[0]).toBe(false);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it('handles messages without cache_control gracefully', () => {
|
|
600
|
+
const messages: TestMsg[] = [
|
|
601
|
+
{
|
|
602
|
+
role: 'user',
|
|
603
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hello' }],
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
const result = stripAnthropicCacheControl(messages);
|
|
608
|
+
|
|
609
|
+
expect(result[0].content).toEqual([
|
|
610
|
+
{ type: ContentTypes.TEXT, text: 'Hello' },
|
|
611
|
+
]);
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
it('handles string content gracefully', () => {
|
|
615
|
+
const messages: TestMsg[] = [
|
|
616
|
+
{
|
|
617
|
+
role: 'user',
|
|
618
|
+
content: 'Hello',
|
|
619
|
+
},
|
|
620
|
+
];
|
|
621
|
+
|
|
622
|
+
const result = stripAnthropicCacheControl(messages);
|
|
623
|
+
|
|
624
|
+
expect(result[0].content).toBe('Hello');
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('returns non-array input unchanged', () => {
|
|
628
|
+
const notArray = 'not an array';
|
|
629
|
+
/** @ts-expect-error - Testing invalid input */
|
|
630
|
+
const result = stripAnthropicCacheControl(notArray);
|
|
631
|
+
expect(result).toBe('not an array');
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
describe('stripBedrockCacheControl', () => {
|
|
636
|
+
it('removes cachePoint blocks from content arrays', () => {
|
|
637
|
+
const messages: TestMsg[] = [
|
|
638
|
+
{
|
|
639
|
+
role: 'user',
|
|
640
|
+
content: [
|
|
641
|
+
{ type: ContentTypes.TEXT, text: 'Hello' },
|
|
642
|
+
{ cachePoint: { type: 'default' } },
|
|
643
|
+
],
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
role: 'assistant',
|
|
647
|
+
content: [
|
|
648
|
+
{ type: ContentTypes.TEXT, text: 'Hi there' },
|
|
649
|
+
{ cachePoint: { type: 'default' } },
|
|
650
|
+
],
|
|
651
|
+
},
|
|
652
|
+
];
|
|
653
|
+
|
|
654
|
+
const result = stripBedrockCacheControl(messages);
|
|
655
|
+
|
|
656
|
+
const firstContent = result[0].content as MessageContentComplex[];
|
|
657
|
+
const secondContent = result[1].content as MessageContentComplex[];
|
|
658
|
+
|
|
659
|
+
expect(firstContent.length).toBe(1);
|
|
660
|
+
expect(firstContent[0]).toEqual({
|
|
661
|
+
type: ContentTypes.TEXT,
|
|
662
|
+
text: 'Hello',
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
expect(secondContent.length).toBe(1);
|
|
666
|
+
expect(secondContent[0]).toEqual({
|
|
667
|
+
type: ContentTypes.TEXT,
|
|
668
|
+
text: 'Hi there',
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it('handles messages without cachePoint blocks gracefully', () => {
|
|
673
|
+
const messages: TestMsg[] = [
|
|
674
|
+
{
|
|
675
|
+
role: 'user',
|
|
676
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hello' }],
|
|
677
|
+
},
|
|
678
|
+
];
|
|
679
|
+
|
|
680
|
+
const result = stripBedrockCacheControl(messages);
|
|
681
|
+
|
|
682
|
+
expect(result[0].content).toEqual([
|
|
683
|
+
{ type: ContentTypes.TEXT, text: 'Hello' },
|
|
684
|
+
]);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('handles string content gracefully', () => {
|
|
688
|
+
const messages: TestMsg[] = [
|
|
689
|
+
{
|
|
690
|
+
role: 'user',
|
|
691
|
+
content: 'Hello',
|
|
692
|
+
},
|
|
693
|
+
];
|
|
694
|
+
|
|
695
|
+
const result = stripBedrockCacheControl(messages);
|
|
696
|
+
|
|
697
|
+
expect(result[0].content).toBe('Hello');
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
it('preserves content with type field', () => {
|
|
701
|
+
const messages: TestMsg[] = [
|
|
702
|
+
{
|
|
703
|
+
role: 'user',
|
|
704
|
+
content: [
|
|
705
|
+
{ type: ContentTypes.TEXT, text: 'Hello' },
|
|
706
|
+
{
|
|
707
|
+
type: ContentTypes.IMAGE_FILE,
|
|
708
|
+
image_file: { file_id: 'file_123' },
|
|
709
|
+
},
|
|
710
|
+
{ cachePoint: { type: 'default' } },
|
|
711
|
+
],
|
|
712
|
+
},
|
|
713
|
+
];
|
|
714
|
+
|
|
715
|
+
const result = stripBedrockCacheControl(messages);
|
|
716
|
+
|
|
717
|
+
const content = result[0].content as MessageContentComplex[];
|
|
718
|
+
|
|
719
|
+
expect(content.length).toBe(2);
|
|
720
|
+
expect(content[0]).toEqual({ type: ContentTypes.TEXT, text: 'Hello' });
|
|
721
|
+
expect(content[1]).toEqual({
|
|
722
|
+
type: ContentTypes.IMAGE_FILE,
|
|
723
|
+
image_file: { file_id: 'file_123' },
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it('returns non-array input unchanged', () => {
|
|
728
|
+
const notArray = 'not an array';
|
|
729
|
+
/** @ts-expect-error - Testing invalid input */
|
|
730
|
+
const result = stripBedrockCacheControl(notArray);
|
|
731
|
+
expect(result).toBe('not an array');
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
describe('Multi-agent provider interoperability', () => {
|
|
736
|
+
it('strips Bedrock cache before applying Anthropic cache (single pass)', () => {
|
|
737
|
+
const messages: TestMsg[] = [
|
|
738
|
+
{
|
|
739
|
+
role: 'user',
|
|
740
|
+
content: [
|
|
741
|
+
{ type: ContentTypes.TEXT, text: 'First message' },
|
|
742
|
+
{ cachePoint: { type: 'default' } },
|
|
743
|
+
],
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
role: 'assistant',
|
|
747
|
+
content: [
|
|
748
|
+
{ type: ContentTypes.TEXT, text: 'Response' },
|
|
749
|
+
{ cachePoint: { type: 'default' } },
|
|
750
|
+
],
|
|
751
|
+
},
|
|
752
|
+
];
|
|
753
|
+
|
|
754
|
+
/** @ts-expect-error - Testing cross-provider compatibility */
|
|
755
|
+
const result = addCacheControl(messages);
|
|
756
|
+
|
|
757
|
+
const firstContent = result[0].content as MessageContentComplex[];
|
|
758
|
+
|
|
759
|
+
expect(firstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
760
|
+
expect('cache_control' in firstContent[0]).toBe(true);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('strips Anthropic cache before applying Bedrock cache (single pass)', () => {
|
|
764
|
+
const messages: TestMsg[] = [
|
|
765
|
+
{
|
|
766
|
+
role: 'user',
|
|
767
|
+
content: [
|
|
768
|
+
{
|
|
769
|
+
type: ContentTypes.TEXT,
|
|
770
|
+
text: 'First message',
|
|
771
|
+
cache_control: { type: 'ephemeral' },
|
|
772
|
+
} as MessageContentComplex,
|
|
773
|
+
],
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
role: 'assistant',
|
|
777
|
+
content: [
|
|
778
|
+
{
|
|
779
|
+
type: ContentTypes.TEXT,
|
|
780
|
+
text: 'Response',
|
|
781
|
+
cache_control: { type: 'ephemeral' },
|
|
782
|
+
} as MessageContentComplex,
|
|
783
|
+
],
|
|
784
|
+
},
|
|
785
|
+
];
|
|
786
|
+
|
|
787
|
+
const result = addBedrockCacheControl(messages);
|
|
788
|
+
|
|
789
|
+
const firstContent = result[0].content as MessageContentComplex[];
|
|
790
|
+
const secondContent = result[1].content as MessageContentComplex[];
|
|
791
|
+
|
|
792
|
+
expect('cache_control' in firstContent[0]).toBe(false);
|
|
793
|
+
expect('cache_control' in secondContent[0]).toBe(false);
|
|
794
|
+
|
|
795
|
+
expect(firstContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
796
|
+
expect(secondContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
it('strips Bedrock cache using separate function (backwards compat)', () => {
|
|
800
|
+
const messages: TestMsg[] = [
|
|
801
|
+
{
|
|
802
|
+
role: 'user',
|
|
803
|
+
content: [
|
|
804
|
+
{ type: ContentTypes.TEXT, text: 'First message' },
|
|
805
|
+
{ cachePoint: { type: 'default' } },
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
];
|
|
809
|
+
|
|
810
|
+
const stripped = stripBedrockCacheControl(messages);
|
|
811
|
+
const firstContent = stripped[0].content as MessageContentComplex[];
|
|
812
|
+
|
|
813
|
+
expect(firstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
814
|
+
expect(firstContent.length).toBe(1);
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('strips Anthropic cache using separate function (backwards compat)', () => {
|
|
818
|
+
const messages: TestMsg[] = [
|
|
819
|
+
{
|
|
820
|
+
role: 'user',
|
|
821
|
+
content: [
|
|
822
|
+
{
|
|
823
|
+
type: ContentTypes.TEXT,
|
|
824
|
+
text: 'First message',
|
|
825
|
+
cache_control: { type: 'ephemeral' },
|
|
826
|
+
} as MessageContentComplex,
|
|
827
|
+
],
|
|
828
|
+
},
|
|
829
|
+
];
|
|
830
|
+
|
|
831
|
+
const stripped = stripAnthropicCacheControl(messages);
|
|
832
|
+
const firstContent = stripped[0].content as MessageContentComplex[];
|
|
833
|
+
|
|
834
|
+
expect('cache_control' in firstContent[0]).toBe(false);
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
describe('Multi-turn cache cleanup', () => {
|
|
839
|
+
it('strips stale Bedrock cache points from previous turns before applying new ones', () => {
|
|
840
|
+
const messages: TestMsg[] = [
|
|
841
|
+
{
|
|
842
|
+
role: 'user',
|
|
843
|
+
content: [
|
|
844
|
+
{ type: ContentTypes.TEXT, text: 'Turn 1 message 1' },
|
|
845
|
+
{ cachePoint: { type: 'default' } },
|
|
846
|
+
],
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
role: 'assistant',
|
|
850
|
+
content: [
|
|
851
|
+
{ type: ContentTypes.TEXT, text: 'Turn 1 response 1' },
|
|
852
|
+
{ cachePoint: { type: 'default' } },
|
|
853
|
+
],
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
role: 'user',
|
|
857
|
+
content: [{ type: ContentTypes.TEXT, text: 'Turn 2 message 2' }],
|
|
858
|
+
},
|
|
859
|
+
{
|
|
860
|
+
role: 'assistant',
|
|
861
|
+
content: [{ type: ContentTypes.TEXT, text: 'Turn 2 response 2' }],
|
|
862
|
+
},
|
|
863
|
+
];
|
|
864
|
+
|
|
865
|
+
const result = addBedrockCacheControl(messages);
|
|
866
|
+
|
|
867
|
+
const cachePointCount = result.reduce((count, msg) => {
|
|
868
|
+
if (Array.isArray(msg.content)) {
|
|
869
|
+
return (
|
|
870
|
+
count +
|
|
871
|
+
msg.content.filter(
|
|
872
|
+
(block) => 'cachePoint' in block && !('type' in block)
|
|
873
|
+
).length
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
return count;
|
|
877
|
+
}, 0);
|
|
878
|
+
|
|
879
|
+
expect(cachePointCount).toBe(2);
|
|
880
|
+
|
|
881
|
+
const lastContent = result[3].content as MessageContentComplex[];
|
|
882
|
+
const secondLastContent = result[2].content as MessageContentComplex[];
|
|
883
|
+
|
|
884
|
+
expect(lastContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
885
|
+
expect(secondLastContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
886
|
+
|
|
887
|
+
const firstContent = result[0].content as MessageContentComplex[];
|
|
888
|
+
const secondContent = result[1].content as MessageContentComplex[];
|
|
889
|
+
|
|
890
|
+
expect(firstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
891
|
+
expect(secondContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
it('strips stale Anthropic cache_control from previous turns before applying new ones', () => {
|
|
895
|
+
const messages: TestMsg[] = [
|
|
896
|
+
{
|
|
897
|
+
role: 'user',
|
|
898
|
+
content: [
|
|
899
|
+
{
|
|
900
|
+
type: ContentTypes.TEXT,
|
|
901
|
+
text: 'Turn 1 message 1',
|
|
902
|
+
cache_control: { type: 'ephemeral' },
|
|
903
|
+
} as MessageContentComplex,
|
|
904
|
+
],
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
role: 'assistant',
|
|
908
|
+
content: [{ type: ContentTypes.TEXT, text: 'Turn 1 response 1' }],
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
role: 'user',
|
|
912
|
+
content: [
|
|
913
|
+
{
|
|
914
|
+
type: ContentTypes.TEXT,
|
|
915
|
+
text: 'Turn 2 message 2',
|
|
916
|
+
cache_control: { type: 'ephemeral' },
|
|
917
|
+
} as MessageContentComplex,
|
|
918
|
+
],
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
role: 'assistant',
|
|
922
|
+
content: [{ type: ContentTypes.TEXT, text: 'Turn 2 response 2' }],
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
role: 'user',
|
|
926
|
+
content: [{ type: ContentTypes.TEXT, text: 'Turn 3 message 3' }],
|
|
927
|
+
},
|
|
928
|
+
];
|
|
929
|
+
|
|
930
|
+
/** @ts-expect-error - Testing cross-provider compatibility */
|
|
931
|
+
const result = addCacheControl(messages);
|
|
932
|
+
|
|
933
|
+
const cacheControlCount = result.reduce((count, msg) => {
|
|
934
|
+
if (Array.isArray(msg.content)) {
|
|
935
|
+
return (
|
|
936
|
+
count +
|
|
937
|
+
msg.content.filter(
|
|
938
|
+
(block) => 'cache_control' in block && 'type' in block
|
|
939
|
+
).length
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
return count;
|
|
943
|
+
}, 0);
|
|
944
|
+
|
|
945
|
+
expect(cacheControlCount).toBe(2);
|
|
946
|
+
|
|
947
|
+
const lastContent = result[4].content as MessageContentComplex[];
|
|
948
|
+
const thirdContent = result[2].content as MessageContentComplex[];
|
|
949
|
+
|
|
950
|
+
expect('cache_control' in lastContent[0]).toBe(true);
|
|
951
|
+
expect('cache_control' in thirdContent[0]).toBe(true);
|
|
952
|
+
|
|
953
|
+
const firstContent = result[0].content as MessageContentComplex[];
|
|
954
|
+
|
|
955
|
+
expect('cache_control' in firstContent[0]).toBe(false);
|
|
956
|
+
});
|
|
461
957
|
});
|