illuma-agents 1.0.37 → 1.0.39
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/agents/AgentContext.cjs +112 -14
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +5 -1
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +148 -8
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +277 -11
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +128 -61
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +22 -7
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +140 -46
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/core.cjs +1 -1
- package/dist/cjs/messages/core.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs +2 -2
- package/dist/cjs/messages/tools.cjs.map +1 -1
- package/dist/cjs/schemas/validate.cjs +173 -0
- package/dist/cjs/schemas/validate.cjs.map +1 -0
- package/dist/cjs/stream.cjs +4 -2
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +22 -21
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +14 -11
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +101 -2
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearch.cjs +862 -0
- package/dist/cjs/tools/ToolSearch.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +112 -14
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +5 -1
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +149 -9
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +278 -12
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +127 -60
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +140 -46
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/core.mjs +1 -1
- package/dist/esm/messages/core.mjs.map +1 -1
- package/dist/esm/messages/tools.mjs +2 -2
- package/dist/esm/messages/tools.mjs.map +1 -1
- package/dist/esm/schemas/validate.mjs +167 -0
- package/dist/esm/schemas/validate.mjs.map +1 -0
- package/dist/esm/stream.mjs +4 -2
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BrowserTools.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +22 -21
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +14 -11
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +102 -3
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearch.mjs +827 -0
- package/dist/esm/tools/ToolSearch.mjs.map +1 -0
- package/dist/types/agents/AgentContext.d.ts +51 -1
- package/dist/types/common/enum.d.ts +6 -2
- package/dist/types/graphs/Graph.d.ts +12 -0
- package/dist/types/graphs/MultiAgentGraph.d.ts +16 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/llm/bedrock/index.d.ts +89 -11
- package/dist/types/llm/bedrock/types.d.ts +27 -0
- package/dist/types/llm/bedrock/utils/index.d.ts +5 -0
- package/dist/types/llm/bedrock/utils/message_inputs.d.ts +31 -0
- package/dist/types/llm/bedrock/utils/message_outputs.d.ts +33 -0
- package/dist/types/messages/cache.d.ts +4 -1
- package/dist/types/schemas/index.d.ts +1 -0
- package/dist/types/schemas/validate.d.ts +36 -0
- package/dist/types/tools/CodeExecutor.d.ts +0 -3
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +0 -3
- package/dist/types/tools/ToolNode.d.ts +3 -1
- package/dist/types/tools/ToolSearch.d.ts +148 -0
- package/dist/types/types/graph.d.ts +71 -0
- package/dist/types/types/llm.d.ts +3 -1
- package/dist/types/types/tools.d.ts +42 -2
- package/package.json +13 -6
- package/src/agents/AgentContext.test.ts +312 -0
- package/src/agents/AgentContext.ts +144 -16
- package/src/common/enum.ts +5 -1
- package/src/graphs/Graph.ts +214 -13
- package/src/graphs/MultiAgentGraph.ts +350 -13
- package/src/index.ts +4 -1
- package/src/llm/bedrock/index.ts +221 -99
- package/src/llm/bedrock/llm.spec.ts +616 -0
- package/src/llm/bedrock/types.ts +51 -0
- package/src/llm/bedrock/utils/index.ts +18 -0
- package/src/llm/bedrock/utils/message_inputs.ts +563 -0
- package/src/llm/bedrock/utils/message_outputs.ts +310 -0
- package/src/messages/__tests__/tools.test.ts +21 -21
- package/src/messages/cache.test.ts +304 -0
- package/src/messages/cache.ts +183 -53
- package/src/messages/core.ts +1 -1
- package/src/messages/tools.ts +2 -2
- package/src/schemas/index.ts +2 -0
- package/src/schemas/validate.test.ts +358 -0
- package/src/schemas/validate.ts +238 -0
- package/src/scripts/caching.ts +27 -19
- package/src/scripts/code_exec_files.ts +58 -15
- package/src/scripts/code_exec_multi_session.ts +241 -0
- package/src/scripts/code_exec_session.ts +282 -0
- package/src/scripts/multi-agent-conditional.ts +1 -0
- package/src/scripts/multi-agent-supervisor.ts +1 -0
- package/src/scripts/programmatic_exec_agent.ts +4 -4
- package/src/scripts/test-handoff-preamble.ts +277 -0
- package/src/scripts/test-parallel-handoffs.ts +291 -0
- package/src/scripts/test-tools-before-handoff.ts +8 -4
- package/src/scripts/test_code_api.ts +361 -0
- package/src/scripts/thinking-bedrock.ts +159 -0
- package/src/scripts/thinking.ts +39 -18
- package/src/scripts/{tool_search_regex.ts → tool_search.ts} +5 -5
- package/src/scripts/tools.ts +7 -3
- package/src/specs/cache.simple.test.ts +396 -0
- package/src/stream.ts +4 -2
- package/src/tools/BrowserTools.ts +39 -17
- package/src/tools/CodeExecutor.ts +26 -23
- package/src/tools/ProgrammaticToolCalling.ts +18 -14
- package/src/tools/ToolNode.ts +114 -1
- package/src/tools/ToolSearch.ts +1041 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -2
- package/src/tools/__tests__/{ToolSearchRegex.integration.test.ts → ToolSearch.integration.test.ts} +6 -6
- package/src/tools/__tests__/ToolSearch.test.ts +1003 -0
- package/src/types/graph.test.ts +183 -0
- package/src/types/graph.ts +73 -0
- package/src/types/llm.ts +3 -1
- package/src/types/tools.ts +51 -2
- package/dist/cjs/tools/ToolSearchRegex.cjs +0 -455
- package/dist/cjs/tools/ToolSearchRegex.cjs.map +0 -1
- package/dist/esm/tools/ToolSearchRegex.mjs +0 -448
- package/dist/esm/tools/ToolSearchRegex.mjs.map +0 -1
- package/dist/types/tools/ToolSearchRegex.d.ts +0 -80
- package/src/tools/ToolSearchRegex.ts +0 -535
- package/src/tools/__tests__/ToolSearchRegex.test.ts +0 -232
|
@@ -835,6 +835,310 @@ describe('Multi-agent provider interoperability', () => {
|
|
|
835
835
|
});
|
|
836
836
|
});
|
|
837
837
|
|
|
838
|
+
describe('Immutability - addCacheControl does not mutate original messages', () => {
|
|
839
|
+
it('should not mutate original messages when adding cache control to string content', () => {
|
|
840
|
+
const originalMessages: TestMsg[] = [
|
|
841
|
+
{ role: 'user', content: 'Hello' },
|
|
842
|
+
{ role: 'assistant', content: 'Hi there' },
|
|
843
|
+
{ role: 'user', content: 'How are you?' },
|
|
844
|
+
];
|
|
845
|
+
|
|
846
|
+
const originalFirstContent = originalMessages[0].content;
|
|
847
|
+
const originalThirdContent = originalMessages[2].content;
|
|
848
|
+
|
|
849
|
+
const result = addCacheControl(originalMessages as never);
|
|
850
|
+
|
|
851
|
+
expect(originalMessages[0].content).toBe(originalFirstContent);
|
|
852
|
+
expect(originalMessages[2].content).toBe(originalThirdContent);
|
|
853
|
+
expect(typeof originalMessages[0].content).toBe('string');
|
|
854
|
+
expect(typeof originalMessages[2].content).toBe('string');
|
|
855
|
+
|
|
856
|
+
expect(Array.isArray(result[0].content)).toBe(true);
|
|
857
|
+
expect(Array.isArray(result[2].content)).toBe(true);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
it('should not mutate original messages when adding cache control to array content', () => {
|
|
861
|
+
const originalMessages: TestMsg[] = [
|
|
862
|
+
{
|
|
863
|
+
role: 'user',
|
|
864
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hello' }],
|
|
865
|
+
},
|
|
866
|
+
{ role: 'assistant', content: 'Hi there' },
|
|
867
|
+
{
|
|
868
|
+
role: 'user',
|
|
869
|
+
content: [{ type: ContentTypes.TEXT, text: 'How are you?' }],
|
|
870
|
+
},
|
|
871
|
+
];
|
|
872
|
+
|
|
873
|
+
const originalFirstBlock = {
|
|
874
|
+
...(originalMessages[0].content as MessageContentComplex[])[0],
|
|
875
|
+
};
|
|
876
|
+
const originalThirdBlock = {
|
|
877
|
+
...(originalMessages[2].content as MessageContentComplex[])[0],
|
|
878
|
+
};
|
|
879
|
+
|
|
880
|
+
const result = addCacheControl(originalMessages as never);
|
|
881
|
+
|
|
882
|
+
const firstContent = originalMessages[0].content as MessageContentComplex[];
|
|
883
|
+
const thirdContent = originalMessages[2].content as MessageContentComplex[];
|
|
884
|
+
|
|
885
|
+
expect('cache_control' in firstContent[0]).toBe(false);
|
|
886
|
+
expect('cache_control' in thirdContent[0]).toBe(false);
|
|
887
|
+
expect(firstContent[0]).toEqual(originalFirstBlock);
|
|
888
|
+
expect(thirdContent[0]).toEqual(originalThirdBlock);
|
|
889
|
+
|
|
890
|
+
const resultFirstContent = result[0].content as MessageContentComplex[];
|
|
891
|
+
const resultThirdContent = result[2].content as MessageContentComplex[];
|
|
892
|
+
expect('cache_control' in resultFirstContent[0]).toBe(true);
|
|
893
|
+
expect('cache_control' in resultThirdContent[0]).toBe(true);
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
it('should not mutate original messages when stripping existing cache control', () => {
|
|
897
|
+
const originalMessages: TestMsg[] = [
|
|
898
|
+
{
|
|
899
|
+
role: 'user',
|
|
900
|
+
content: [
|
|
901
|
+
{
|
|
902
|
+
type: ContentTypes.TEXT,
|
|
903
|
+
text: 'Hello',
|
|
904
|
+
cache_control: { type: 'ephemeral' },
|
|
905
|
+
} as MessageContentComplex,
|
|
906
|
+
],
|
|
907
|
+
},
|
|
908
|
+
{ role: 'assistant', content: 'Hi there' },
|
|
909
|
+
{
|
|
910
|
+
role: 'user',
|
|
911
|
+
content: [{ type: ContentTypes.TEXT, text: 'How are you?' }],
|
|
912
|
+
},
|
|
913
|
+
];
|
|
914
|
+
|
|
915
|
+
const originalFirstBlock = (
|
|
916
|
+
originalMessages[0].content as MessageContentComplex[]
|
|
917
|
+
)[0];
|
|
918
|
+
|
|
919
|
+
addCacheControl(originalMessages as never);
|
|
920
|
+
|
|
921
|
+
expect('cache_control' in originalFirstBlock).toBe(true);
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
it('should remove lc_kwargs to prevent serialization mismatch for LangChain messages', () => {
|
|
925
|
+
type LangChainLikeMsg = TestMsg & {
|
|
926
|
+
lc_kwargs?: { content?: MessageContentComplex[] };
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
const messagesWithLcKwargs: LangChainLikeMsg[] = [
|
|
930
|
+
{
|
|
931
|
+
role: 'user',
|
|
932
|
+
content: [{ type: ContentTypes.TEXT, text: 'First user message' }],
|
|
933
|
+
lc_kwargs: {
|
|
934
|
+
content: [{ type: ContentTypes.TEXT, text: 'First user message' }],
|
|
935
|
+
},
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
role: 'assistant',
|
|
939
|
+
content: [{ type: ContentTypes.TEXT, text: 'Assistant response' }],
|
|
940
|
+
lc_kwargs: {
|
|
941
|
+
content: [{ type: ContentTypes.TEXT, text: 'Assistant response' }],
|
|
942
|
+
},
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
role: 'user',
|
|
946
|
+
content: [{ type: ContentTypes.TEXT, text: 'Second user message' }],
|
|
947
|
+
lc_kwargs: {
|
|
948
|
+
content: [{ type: ContentTypes.TEXT, text: 'Second user message' }],
|
|
949
|
+
},
|
|
950
|
+
},
|
|
951
|
+
];
|
|
952
|
+
|
|
953
|
+
const result = addCacheControl(messagesWithLcKwargs as never);
|
|
954
|
+
|
|
955
|
+
const resultFirst = result[0] as LangChainLikeMsg;
|
|
956
|
+
const resultThird = result[2] as LangChainLikeMsg;
|
|
957
|
+
|
|
958
|
+
expect(resultFirst.lc_kwargs).toBeUndefined();
|
|
959
|
+
expect(resultThird.lc_kwargs).toBeUndefined();
|
|
960
|
+
|
|
961
|
+
const firstContent = resultFirst.content as MessageContentComplex[];
|
|
962
|
+
expect('cache_control' in firstContent[0]).toBe(true);
|
|
963
|
+
|
|
964
|
+
const originalFirst = messagesWithLcKwargs[0];
|
|
965
|
+
const originalContent = originalFirst.content as MessageContentComplex[];
|
|
966
|
+
const originalLcContent = originalFirst.lc_kwargs
|
|
967
|
+
?.content as MessageContentComplex[];
|
|
968
|
+
expect('cache_control' in originalContent[0]).toBe(false);
|
|
969
|
+
expect('cache_control' in originalLcContent[0]).toBe(false);
|
|
970
|
+
});
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
describe('Immutability - addBedrockCacheControl does not mutate original messages', () => {
|
|
974
|
+
it('should not mutate original messages when adding cache points to string content', () => {
|
|
975
|
+
const originalMessages: TestMsg[] = [
|
|
976
|
+
{ role: 'user', content: 'Hello' },
|
|
977
|
+
{ role: 'assistant', content: 'Hi there' },
|
|
978
|
+
];
|
|
979
|
+
|
|
980
|
+
const originalFirstContent = originalMessages[0].content;
|
|
981
|
+
const originalSecondContent = originalMessages[1].content;
|
|
982
|
+
|
|
983
|
+
const result = addBedrockCacheControl(originalMessages);
|
|
984
|
+
|
|
985
|
+
expect(originalMessages[0].content).toBe(originalFirstContent);
|
|
986
|
+
expect(originalMessages[1].content).toBe(originalSecondContent);
|
|
987
|
+
expect(typeof originalMessages[0].content).toBe('string');
|
|
988
|
+
expect(typeof originalMessages[1].content).toBe('string');
|
|
989
|
+
|
|
990
|
+
expect(Array.isArray(result[0].content)).toBe(true);
|
|
991
|
+
expect(Array.isArray(result[1].content)).toBe(true);
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
it('should not mutate original messages when adding cache points to array content', () => {
|
|
995
|
+
const originalMessages: TestMsg[] = [
|
|
996
|
+
{
|
|
997
|
+
role: 'user',
|
|
998
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hello' }],
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
role: 'assistant',
|
|
1002
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hi there' }],
|
|
1003
|
+
},
|
|
1004
|
+
];
|
|
1005
|
+
|
|
1006
|
+
const originalFirstContentLength = (
|
|
1007
|
+
originalMessages[0].content as MessageContentComplex[]
|
|
1008
|
+
).length;
|
|
1009
|
+
const originalSecondContentLength = (
|
|
1010
|
+
originalMessages[1].content as MessageContentComplex[]
|
|
1011
|
+
).length;
|
|
1012
|
+
|
|
1013
|
+
const result = addBedrockCacheControl(originalMessages);
|
|
1014
|
+
|
|
1015
|
+
const firstContent = originalMessages[0].content as MessageContentComplex[];
|
|
1016
|
+
const secondContent = originalMessages[1]
|
|
1017
|
+
.content as MessageContentComplex[];
|
|
1018
|
+
|
|
1019
|
+
expect(firstContent.length).toBe(originalFirstContentLength);
|
|
1020
|
+
expect(secondContent.length).toBe(originalSecondContentLength);
|
|
1021
|
+
expect(firstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1022
|
+
expect(secondContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1023
|
+
|
|
1024
|
+
const resultFirstContent = result[0].content as MessageContentComplex[];
|
|
1025
|
+
const resultSecondContent = result[1].content as MessageContentComplex[];
|
|
1026
|
+
expect(resultFirstContent.length).toBe(originalFirstContentLength + 1);
|
|
1027
|
+
expect(resultSecondContent.length).toBe(originalSecondContentLength + 1);
|
|
1028
|
+
expect(resultFirstContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
1029
|
+
expect(resultSecondContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
it('should not mutate original messages when stripping existing cache control', () => {
|
|
1033
|
+
const originalMessages: TestMsg[] = [
|
|
1034
|
+
{
|
|
1035
|
+
role: 'user',
|
|
1036
|
+
content: [
|
|
1037
|
+
{
|
|
1038
|
+
type: ContentTypes.TEXT,
|
|
1039
|
+
text: 'Hello',
|
|
1040
|
+
cache_control: { type: 'ephemeral' },
|
|
1041
|
+
} as MessageContentComplex,
|
|
1042
|
+
],
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
role: 'assistant',
|
|
1046
|
+
content: [
|
|
1047
|
+
{ type: ContentTypes.TEXT, text: 'Hi there' },
|
|
1048
|
+
{ cachePoint: { type: 'default' } },
|
|
1049
|
+
],
|
|
1050
|
+
},
|
|
1051
|
+
];
|
|
1052
|
+
|
|
1053
|
+
const originalFirstBlock = (
|
|
1054
|
+
originalMessages[0].content as MessageContentComplex[]
|
|
1055
|
+
)[0];
|
|
1056
|
+
const originalSecondContentLength = (
|
|
1057
|
+
originalMessages[1].content as MessageContentComplex[]
|
|
1058
|
+
).length;
|
|
1059
|
+
|
|
1060
|
+
addBedrockCacheControl(originalMessages);
|
|
1061
|
+
|
|
1062
|
+
expect('cache_control' in originalFirstBlock).toBe(true);
|
|
1063
|
+
expect(
|
|
1064
|
+
(originalMessages[1].content as MessageContentComplex[]).length
|
|
1065
|
+
).toBe(originalSecondContentLength);
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
it('should allow different providers to process same messages without cross-contamination', () => {
|
|
1069
|
+
const sharedMessages: TestMsg[] = [
|
|
1070
|
+
{
|
|
1071
|
+
role: 'user',
|
|
1072
|
+
content: [{ type: ContentTypes.TEXT, text: 'Shared message 1' }],
|
|
1073
|
+
},
|
|
1074
|
+
{
|
|
1075
|
+
role: 'assistant',
|
|
1076
|
+
content: [{ type: ContentTypes.TEXT, text: 'Shared response 1' }],
|
|
1077
|
+
},
|
|
1078
|
+
];
|
|
1079
|
+
|
|
1080
|
+
const bedrockResult = addBedrockCacheControl(sharedMessages);
|
|
1081
|
+
|
|
1082
|
+
const anthropicResult = addCacheControl(sharedMessages as never);
|
|
1083
|
+
|
|
1084
|
+
const originalFirstContent = sharedMessages[0]
|
|
1085
|
+
.content as MessageContentComplex[];
|
|
1086
|
+
expect(originalFirstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1087
|
+
expect('cache_control' in originalFirstContent[0]).toBe(false);
|
|
1088
|
+
|
|
1089
|
+
const bedrockFirstContent = bedrockResult[0]
|
|
1090
|
+
.content as MessageContentComplex[];
|
|
1091
|
+
expect(bedrockFirstContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
1092
|
+
expect('cache_control' in bedrockFirstContent[0]).toBe(false);
|
|
1093
|
+
|
|
1094
|
+
const anthropicFirstContent = anthropicResult[0]
|
|
1095
|
+
.content as MessageContentComplex[];
|
|
1096
|
+
expect(anthropicFirstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1097
|
+
expect('cache_control' in anthropicFirstContent[0]).toBe(true);
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
it('should remove lc_kwargs to prevent serialization mismatch for LangChain messages', () => {
|
|
1101
|
+
type LangChainLikeMsg = TestMsg & {
|
|
1102
|
+
lc_kwargs?: { content?: MessageContentComplex[] };
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
const messagesWithLcKwargs: LangChainLikeMsg[] = [
|
|
1106
|
+
{
|
|
1107
|
+
role: 'user',
|
|
1108
|
+
content: [{ type: ContentTypes.TEXT, text: 'User message' }],
|
|
1109
|
+
lc_kwargs: {
|
|
1110
|
+
content: [{ type: ContentTypes.TEXT, text: 'User message' }],
|
|
1111
|
+
},
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
role: 'assistant',
|
|
1115
|
+
content: [{ type: ContentTypes.TEXT, text: 'Assistant response' }],
|
|
1116
|
+
lc_kwargs: {
|
|
1117
|
+
content: [{ type: ContentTypes.TEXT, text: 'Assistant response' }],
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
];
|
|
1121
|
+
|
|
1122
|
+
const bedrockResult = addBedrockCacheControl(messagesWithLcKwargs);
|
|
1123
|
+
|
|
1124
|
+
const resultFirst = bedrockResult[0] as LangChainLikeMsg;
|
|
1125
|
+
const resultSecond = bedrockResult[1] as LangChainLikeMsg;
|
|
1126
|
+
|
|
1127
|
+
expect(resultFirst.lc_kwargs).toBeUndefined();
|
|
1128
|
+
expect(resultSecond.lc_kwargs).toBeUndefined();
|
|
1129
|
+
|
|
1130
|
+
const firstContent = resultFirst.content as MessageContentComplex[];
|
|
1131
|
+
expect(firstContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
1132
|
+
|
|
1133
|
+
const originalFirst = messagesWithLcKwargs[0];
|
|
1134
|
+
const originalContent = originalFirst.content as MessageContentComplex[];
|
|
1135
|
+
const originalLcContent = originalFirst.lc_kwargs
|
|
1136
|
+
?.content as MessageContentComplex[];
|
|
1137
|
+
expect(originalContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1138
|
+
expect(originalLcContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
|
|
838
1142
|
describe('Multi-turn cache cleanup', () => {
|
|
839
1143
|
it('strips stale Bedrock cache points from previous turns before applying new ones', () => {
|
|
840
1144
|
const messages: TestMsg[] = [
|
package/src/messages/cache.ts
CHANGED
|
@@ -18,13 +18,121 @@ const debugCache = (message: string, data?: unknown): void => {
|
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Deep clones a message's content to prevent mutation of the original.
|
|
23
|
+
*/
|
|
24
|
+
function deepCloneContent<T extends string | MessageContentComplex[]>(
|
|
25
|
+
content: T
|
|
26
|
+
): T {
|
|
27
|
+
if (typeof content === 'string') {
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(content)) {
|
|
31
|
+
return content.map((block) => ({ ...block })) as T;
|
|
32
|
+
}
|
|
33
|
+
return content;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Clones a message with deep-cloned content, explicitly excluding LangChain
|
|
38
|
+
* serialization metadata to prevent coercion issues.
|
|
39
|
+
* Keeps lc_kwargs in sync with content to prevent LangChain serialization issues.
|
|
40
|
+
*/
|
|
41
|
+
function cloneMessage<T extends MessageWithContent>(
|
|
42
|
+
message: T,
|
|
43
|
+
content: string | MessageContentComplex[]
|
|
44
|
+
): T {
|
|
45
|
+
const {
|
|
46
|
+
lc_kwargs: _lc_kwargs,
|
|
47
|
+
lc_serializable: _lc_serializable,
|
|
48
|
+
lc_namespace: _lc_namespace,
|
|
49
|
+
...rest
|
|
50
|
+
} = message as T & {
|
|
51
|
+
lc_kwargs?: unknown;
|
|
52
|
+
lc_serializable?: unknown;
|
|
53
|
+
lc_namespace?: unknown;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const cloned = { ...rest, content } as T;
|
|
57
|
+
|
|
58
|
+
// Sync lc_kwargs.content with the new content to prevent LangChain coercion issues
|
|
59
|
+
const lcKwargs = (message as Record<string, unknown>).lc_kwargs as
|
|
60
|
+
| Record<string, unknown>
|
|
61
|
+
| undefined;
|
|
62
|
+
if (lcKwargs != null) {
|
|
63
|
+
(cloned as Record<string, unknown>).lc_kwargs = {
|
|
64
|
+
...lcKwargs,
|
|
65
|
+
content: content,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// LangChain messages don't have a direct 'role' property - derive it from getType()
|
|
70
|
+
if (
|
|
71
|
+
'getType' in message &&
|
|
72
|
+
typeof message.getType === 'function' &&
|
|
73
|
+
!('role' in cloned)
|
|
74
|
+
) {
|
|
75
|
+
const msgType = (message as unknown as BaseMessage).getType();
|
|
76
|
+
const roleMap: Record<string, string> = {
|
|
77
|
+
human: 'user',
|
|
78
|
+
ai: 'assistant',
|
|
79
|
+
system: 'system',
|
|
80
|
+
tool: 'tool',
|
|
81
|
+
};
|
|
82
|
+
(cloned as Record<string, unknown>).role = roleMap[msgType] || msgType;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return cloned;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Checks if a content block is a cache point
|
|
90
|
+
*/
|
|
91
|
+
function isCachePoint(block: MessageContentComplex): boolean {
|
|
92
|
+
return 'cachePoint' in block && !('type' in block);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Checks if a message's content needs cache control stripping.
|
|
97
|
+
* Returns true if content has cachePoint blocks or cache_control fields.
|
|
98
|
+
*/
|
|
99
|
+
function needsCacheStripping(content: MessageContentComplex[]): boolean {
|
|
100
|
+
for (let i = 0; i < content.length; i++) {
|
|
101
|
+
const block = content[i];
|
|
102
|
+
if (isCachePoint(block)) return true;
|
|
103
|
+
if ('cache_control' in block) return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Checks if a message's content has Anthropic cache_control fields.
|
|
110
|
+
*/
|
|
111
|
+
function hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {
|
|
112
|
+
for (let i = 0; i < content.length; i++) {
|
|
113
|
+
if ('cache_control' in content[i]) return true;
|
|
114
|
+
}
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Checks if a message's content has Bedrock cachePoint blocks.
|
|
120
|
+
*/
|
|
121
|
+
function hasBedrockCachePoint(content: MessageContentComplex[]): boolean {
|
|
122
|
+
for (let i = 0; i < content.length; i++) {
|
|
123
|
+
if (isCachePoint(content[i])) return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
21
128
|
/**
|
|
22
129
|
* Anthropic API: Adds cache control to the appropriate user messages in the payload.
|
|
23
130
|
* Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
|
|
24
131
|
* then adds fresh cache control to the last 2 user messages in a single backward pass.
|
|
25
132
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
133
|
+
* Returns a new array - only clones messages that require modification.
|
|
26
134
|
* @param messages - The array of message objects.
|
|
27
|
-
* @returns -
|
|
135
|
+
* @returns - A new array of message objects with cache control added.
|
|
28
136
|
*/
|
|
29
137
|
export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
|
|
30
138
|
messages: T[]
|
|
@@ -33,68 +141,82 @@ export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
|
|
|
33
141
|
return messages;
|
|
34
142
|
}
|
|
35
143
|
|
|
36
|
-
const updatedMessages = [...messages];
|
|
144
|
+
const updatedMessages: T[] = [...messages];
|
|
37
145
|
let userMessagesModified = 0;
|
|
38
146
|
|
|
39
147
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
|
40
|
-
const
|
|
148
|
+
const originalMessage = updatedMessages[i];
|
|
149
|
+
const content = originalMessage.content;
|
|
41
150
|
const isUserMessage =
|
|
42
|
-
('getType' in
|
|
43
|
-
('role' in
|
|
151
|
+
('getType' in originalMessage && originalMessage.getType() === 'human') ||
|
|
152
|
+
('role' in originalMessage && originalMessage.role === 'user');
|
|
153
|
+
|
|
154
|
+
const hasArrayContent = Array.isArray(content);
|
|
155
|
+
const needsStripping =
|
|
156
|
+
hasArrayContent &&
|
|
157
|
+
needsCacheStripping(content as MessageContentComplex[]);
|
|
158
|
+
const needsCacheAdd =
|
|
159
|
+
userMessagesModified < 2 &&
|
|
160
|
+
isUserMessage &&
|
|
161
|
+
(typeof content === 'string' || hasArrayContent);
|
|
162
|
+
|
|
163
|
+
if (!needsStripping && !needsCacheAdd) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
44
166
|
|
|
45
|
-
|
|
46
|
-
message.content = message.content.filter(
|
|
47
|
-
(block) => !isCachePoint(block as MessageContentComplex)
|
|
48
|
-
) as typeof message.content;
|
|
167
|
+
let workingContent: MessageContentComplex[];
|
|
49
168
|
|
|
50
|
-
|
|
51
|
-
|
|
169
|
+
if (hasArrayContent) {
|
|
170
|
+
workingContent = deepCloneContent(
|
|
171
|
+
content as MessageContentComplex[]
|
|
172
|
+
).filter((block) => !isCachePoint(block as MessageContentComplex));
|
|
173
|
+
|
|
174
|
+
for (let j = 0; j < workingContent.length; j++) {
|
|
175
|
+
const block = workingContent[j] as Record<string, unknown>;
|
|
52
176
|
if ('cache_control' in block) {
|
|
53
177
|
delete block.cache_control;
|
|
54
178
|
}
|
|
55
179
|
}
|
|
180
|
+
} else if (typeof content === 'string') {
|
|
181
|
+
workingContent = [
|
|
182
|
+
{ type: 'text', text: content },
|
|
183
|
+
] as MessageContentComplex[];
|
|
184
|
+
} else {
|
|
185
|
+
workingContent = [];
|
|
56
186
|
}
|
|
57
187
|
|
|
58
188
|
if (userMessagesModified >= 2 || !isUserMessage) {
|
|
189
|
+
updatedMessages[i] = cloneMessage(
|
|
190
|
+
originalMessage as MessageWithContent,
|
|
191
|
+
workingContent
|
|
192
|
+
) as T;
|
|
59
193
|
continue;
|
|
60
194
|
}
|
|
61
195
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
userMessagesModified++;
|
|
71
|
-
} else if (Array.isArray(message.content)) {
|
|
72
|
-
for (let j = message.content.length - 1; j >= 0; j--) {
|
|
73
|
-
const contentPart = message.content[j];
|
|
74
|
-
if ('type' in contentPart && contentPart.type === 'text') {
|
|
75
|
-
(contentPart as Anthropic.TextBlockParam).cache_control = {
|
|
76
|
-
type: 'ephemeral',
|
|
77
|
-
};
|
|
78
|
-
userMessagesModified++;
|
|
79
|
-
break;
|
|
80
|
-
}
|
|
196
|
+
for (let j = workingContent.length - 1; j >= 0; j--) {
|
|
197
|
+
const contentPart = workingContent[j];
|
|
198
|
+
if ('type' in contentPart && contentPart.type === 'text') {
|
|
199
|
+
(contentPart as Anthropic.TextBlockParam).cache_control = {
|
|
200
|
+
type: 'ephemeral',
|
|
201
|
+
};
|
|
202
|
+
userMessagesModified++;
|
|
203
|
+
break;
|
|
81
204
|
}
|
|
82
205
|
}
|
|
206
|
+
|
|
207
|
+
updatedMessages[i] = cloneMessage(
|
|
208
|
+
originalMessage as MessageWithContent,
|
|
209
|
+
workingContent
|
|
210
|
+
) as T;
|
|
83
211
|
}
|
|
84
212
|
|
|
85
213
|
return updatedMessages;
|
|
86
214
|
}
|
|
87
215
|
|
|
88
|
-
/**
|
|
89
|
-
* Checks if a content block is a cache point
|
|
90
|
-
*/
|
|
91
|
-
function isCachePoint(block: MessageContentComplex): boolean {
|
|
92
|
-
return 'cachePoint' in block && !('type' in block);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
216
|
/**
|
|
96
217
|
* Removes all Anthropic cache_control fields from messages
|
|
97
218
|
* Used when switching from Anthropic to Bedrock provider
|
|
219
|
+
* Returns a new array - only clones messages that require modification.
|
|
98
220
|
*/
|
|
99
221
|
export function stripAnthropicCacheControl<T extends MessageWithContent>(
|
|
100
222
|
messages: T[]
|
|
@@ -103,20 +225,24 @@ export function stripAnthropicCacheControl<T extends MessageWithContent>(
|
|
|
103
225
|
return messages;
|
|
104
226
|
}
|
|
105
227
|
|
|
106
|
-
const updatedMessages = [...messages];
|
|
228
|
+
const updatedMessages: T[] = [...messages];
|
|
107
229
|
|
|
108
230
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
109
|
-
const
|
|
110
|
-
const content =
|
|
231
|
+
const originalMessage = updatedMessages[i];
|
|
232
|
+
const content = originalMessage.content;
|
|
111
233
|
|
|
112
|
-
if (Array.isArray(content)) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
234
|
+
if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const clonedContent = deepCloneContent(content);
|
|
239
|
+
for (let j = 0; j < clonedContent.length; j++) {
|
|
240
|
+
const block = clonedContent[j] as Record<string, unknown>;
|
|
241
|
+
if ('cache_control' in block) {
|
|
242
|
+
delete block.cache_control;
|
|
118
243
|
}
|
|
119
244
|
}
|
|
245
|
+
updatedMessages[i] = cloneMessage(originalMessage, clonedContent) as T;
|
|
120
246
|
}
|
|
121
247
|
|
|
122
248
|
return updatedMessages;
|
|
@@ -125,6 +251,7 @@ export function stripAnthropicCacheControl<T extends MessageWithContent>(
|
|
|
125
251
|
/**
|
|
126
252
|
* Removes all Bedrock cachePoint blocks from messages
|
|
127
253
|
* Used when switching from Bedrock to Anthropic provider
|
|
254
|
+
* Returns a new array - only clones messages that require modification.
|
|
128
255
|
*/
|
|
129
256
|
export function stripBedrockCacheControl<T extends MessageWithContent>(
|
|
130
257
|
messages: T[]
|
|
@@ -133,17 +260,20 @@ export function stripBedrockCacheControl<T extends MessageWithContent>(
|
|
|
133
260
|
return messages;
|
|
134
261
|
}
|
|
135
262
|
|
|
136
|
-
const updatedMessages = [...messages];
|
|
263
|
+
const updatedMessages: T[] = [...messages];
|
|
137
264
|
|
|
138
265
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
139
|
-
const
|
|
140
|
-
const content =
|
|
266
|
+
const originalMessage = updatedMessages[i];
|
|
267
|
+
const content = originalMessage.content;
|
|
141
268
|
|
|
142
|
-
if (Array.isArray(content)) {
|
|
143
|
-
|
|
144
|
-
(block) => !isCachePoint(block as MessageContentComplex)
|
|
145
|
-
) as typeof content;
|
|
269
|
+
if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {
|
|
270
|
+
continue;
|
|
146
271
|
}
|
|
272
|
+
|
|
273
|
+
const clonedContent = deepCloneContent(content).filter(
|
|
274
|
+
(block) => !isCachePoint(block as MessageContentComplex)
|
|
275
|
+
);
|
|
276
|
+
updatedMessages[i] = cloneMessage(originalMessage, clonedContent) as T;
|
|
147
277
|
}
|
|
148
278
|
|
|
149
279
|
return updatedMessages;
|
package/src/messages/core.ts
CHANGED
|
@@ -41,7 +41,7 @@ User: ${userMessage[1]}
|
|
|
41
41
|
const _allowedTypes = ['image_url', 'text', 'tool_use', 'tool_result'];
|
|
42
42
|
const allowedTypesByProvider: Record<string, string[]> = {
|
|
43
43
|
default: _allowedTypes,
|
|
44
|
-
[Providers.ANTHROPIC]: [..._allowedTypes, 'thinking'],
|
|
44
|
+
[Providers.ANTHROPIC]: [..._allowedTypes, 'thinking', 'redacted_thinking'],
|
|
45
45
|
[Providers.BEDROCK]: [..._allowedTypes, 'reasoning_content'],
|
|
46
46
|
[Providers.OPENAI]: _allowedTypes,
|
|
47
47
|
};
|
package/src/messages/tools.ts
CHANGED
|
@@ -47,7 +47,7 @@ export function extractToolDiscoveries(messages: BaseMessage[]): string[] {
|
|
|
47
47
|
// Use getType() instead of instanceof to avoid module mismatch issues
|
|
48
48
|
if (msg.getType() !== MessageTypes.TOOL) continue;
|
|
49
49
|
const toolMsg = msg as ToolMessage;
|
|
50
|
-
if (toolMsg.name !== Constants.
|
|
50
|
+
if (toolMsg.name !== Constants.TOOL_SEARCH) continue;
|
|
51
51
|
if (!toolCallIds.has(toolMsg.tool_call_id)) continue;
|
|
52
52
|
|
|
53
53
|
// This is a tool search result from the current turn
|
|
@@ -95,7 +95,7 @@ export function hasToolSearchInCurrentTurn(messages: BaseMessage[]): boolean {
|
|
|
95
95
|
const msg = messages[i];
|
|
96
96
|
if (
|
|
97
97
|
msg.getType() === MessageTypes.TOOL &&
|
|
98
|
-
(msg as ToolMessage).name === Constants.
|
|
98
|
+
(msg as ToolMessage).name === Constants.TOOL_SEARCH &&
|
|
99
99
|
toolCallIds.has((msg as ToolMessage).tool_call_id)
|
|
100
100
|
) {
|
|
101
101
|
return true;
|