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.
Files changed (139) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +112 -14
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +5 -1
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +148 -8
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +277 -11
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/bedrock/index.cjs +128 -61
  10. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +22 -7
  12. package/dist/cjs/main.cjs.map +1 -1
  13. package/dist/cjs/messages/cache.cjs +140 -46
  14. package/dist/cjs/messages/cache.cjs.map +1 -1
  15. package/dist/cjs/messages/core.cjs +1 -1
  16. package/dist/cjs/messages/core.cjs.map +1 -1
  17. package/dist/cjs/messages/tools.cjs +2 -2
  18. package/dist/cjs/messages/tools.cjs.map +1 -1
  19. package/dist/cjs/schemas/validate.cjs +173 -0
  20. package/dist/cjs/schemas/validate.cjs.map +1 -0
  21. package/dist/cjs/stream.cjs +4 -2
  22. package/dist/cjs/stream.cjs.map +1 -1
  23. package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
  24. package/dist/cjs/tools/CodeExecutor.cjs +22 -21
  25. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  26. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +14 -11
  27. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  28. package/dist/cjs/tools/ToolNode.cjs +101 -2
  29. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  30. package/dist/cjs/tools/ToolSearch.cjs +862 -0
  31. package/dist/cjs/tools/ToolSearch.cjs.map +1 -0
  32. package/dist/esm/agents/AgentContext.mjs +112 -14
  33. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  34. package/dist/esm/common/enum.mjs +5 -1
  35. package/dist/esm/common/enum.mjs.map +1 -1
  36. package/dist/esm/graphs/Graph.mjs +149 -9
  37. package/dist/esm/graphs/Graph.mjs.map +1 -1
  38. package/dist/esm/graphs/MultiAgentGraph.mjs +278 -12
  39. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  40. package/dist/esm/llm/bedrock/index.mjs +127 -60
  41. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  42. package/dist/esm/main.mjs +2 -1
  43. package/dist/esm/main.mjs.map +1 -1
  44. package/dist/esm/messages/cache.mjs +140 -46
  45. package/dist/esm/messages/cache.mjs.map +1 -1
  46. package/dist/esm/messages/core.mjs +1 -1
  47. package/dist/esm/messages/core.mjs.map +1 -1
  48. package/dist/esm/messages/tools.mjs +2 -2
  49. package/dist/esm/messages/tools.mjs.map +1 -1
  50. package/dist/esm/schemas/validate.mjs +167 -0
  51. package/dist/esm/schemas/validate.mjs.map +1 -0
  52. package/dist/esm/stream.mjs +4 -2
  53. package/dist/esm/stream.mjs.map +1 -1
  54. package/dist/esm/tools/BrowserTools.mjs.map +1 -1
  55. package/dist/esm/tools/CodeExecutor.mjs +22 -21
  56. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  57. package/dist/esm/tools/ProgrammaticToolCalling.mjs +14 -11
  58. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  59. package/dist/esm/tools/ToolNode.mjs +102 -3
  60. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  61. package/dist/esm/tools/ToolSearch.mjs +827 -0
  62. package/dist/esm/tools/ToolSearch.mjs.map +1 -0
  63. package/dist/types/agents/AgentContext.d.ts +51 -1
  64. package/dist/types/common/enum.d.ts +6 -2
  65. package/dist/types/graphs/Graph.d.ts +12 -0
  66. package/dist/types/graphs/MultiAgentGraph.d.ts +16 -0
  67. package/dist/types/index.d.ts +2 -1
  68. package/dist/types/llm/bedrock/index.d.ts +89 -11
  69. package/dist/types/llm/bedrock/types.d.ts +27 -0
  70. package/dist/types/llm/bedrock/utils/index.d.ts +5 -0
  71. package/dist/types/llm/bedrock/utils/message_inputs.d.ts +31 -0
  72. package/dist/types/llm/bedrock/utils/message_outputs.d.ts +33 -0
  73. package/dist/types/messages/cache.d.ts +4 -1
  74. package/dist/types/schemas/index.d.ts +1 -0
  75. package/dist/types/schemas/validate.d.ts +36 -0
  76. package/dist/types/tools/CodeExecutor.d.ts +0 -3
  77. package/dist/types/tools/ProgrammaticToolCalling.d.ts +0 -3
  78. package/dist/types/tools/ToolNode.d.ts +3 -1
  79. package/dist/types/tools/ToolSearch.d.ts +148 -0
  80. package/dist/types/types/graph.d.ts +71 -0
  81. package/dist/types/types/llm.d.ts +3 -1
  82. package/dist/types/types/tools.d.ts +42 -2
  83. package/package.json +13 -6
  84. package/src/agents/AgentContext.test.ts +312 -0
  85. package/src/agents/AgentContext.ts +144 -16
  86. package/src/common/enum.ts +5 -1
  87. package/src/graphs/Graph.ts +214 -13
  88. package/src/graphs/MultiAgentGraph.ts +350 -13
  89. package/src/index.ts +4 -1
  90. package/src/llm/bedrock/index.ts +221 -99
  91. package/src/llm/bedrock/llm.spec.ts +616 -0
  92. package/src/llm/bedrock/types.ts +51 -0
  93. package/src/llm/bedrock/utils/index.ts +18 -0
  94. package/src/llm/bedrock/utils/message_inputs.ts +563 -0
  95. package/src/llm/bedrock/utils/message_outputs.ts +310 -0
  96. package/src/messages/__tests__/tools.test.ts +21 -21
  97. package/src/messages/cache.test.ts +304 -0
  98. package/src/messages/cache.ts +183 -53
  99. package/src/messages/core.ts +1 -1
  100. package/src/messages/tools.ts +2 -2
  101. package/src/schemas/index.ts +2 -0
  102. package/src/schemas/validate.test.ts +358 -0
  103. package/src/schemas/validate.ts +238 -0
  104. package/src/scripts/caching.ts +27 -19
  105. package/src/scripts/code_exec_files.ts +58 -15
  106. package/src/scripts/code_exec_multi_session.ts +241 -0
  107. package/src/scripts/code_exec_session.ts +282 -0
  108. package/src/scripts/multi-agent-conditional.ts +1 -0
  109. package/src/scripts/multi-agent-supervisor.ts +1 -0
  110. package/src/scripts/programmatic_exec_agent.ts +4 -4
  111. package/src/scripts/test-handoff-preamble.ts +277 -0
  112. package/src/scripts/test-parallel-handoffs.ts +291 -0
  113. package/src/scripts/test-tools-before-handoff.ts +8 -4
  114. package/src/scripts/test_code_api.ts +361 -0
  115. package/src/scripts/thinking-bedrock.ts +159 -0
  116. package/src/scripts/thinking.ts +39 -18
  117. package/src/scripts/{tool_search_regex.ts → tool_search.ts} +5 -5
  118. package/src/scripts/tools.ts +7 -3
  119. package/src/specs/cache.simple.test.ts +396 -0
  120. package/src/stream.ts +4 -2
  121. package/src/tools/BrowserTools.ts +39 -17
  122. package/src/tools/CodeExecutor.ts +26 -23
  123. package/src/tools/ProgrammaticToolCalling.ts +18 -14
  124. package/src/tools/ToolNode.ts +114 -1
  125. package/src/tools/ToolSearch.ts +1041 -0
  126. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +0 -2
  127. package/src/tools/__tests__/{ToolSearchRegex.integration.test.ts → ToolSearch.integration.test.ts} +6 -6
  128. package/src/tools/__tests__/ToolSearch.test.ts +1003 -0
  129. package/src/types/graph.test.ts +183 -0
  130. package/src/types/graph.ts +73 -0
  131. package/src/types/llm.ts +3 -1
  132. package/src/types/tools.ts +51 -2
  133. package/dist/cjs/tools/ToolSearchRegex.cjs +0 -455
  134. package/dist/cjs/tools/ToolSearchRegex.cjs.map +0 -1
  135. package/dist/esm/tools/ToolSearchRegex.mjs +0 -448
  136. package/dist/esm/tools/ToolSearchRegex.mjs.map +0 -1
  137. package/dist/types/tools/ToolSearchRegex.d.ts +0 -80
  138. package/src/tools/ToolSearchRegex.ts +0 -535
  139. 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[] = [
@@ -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 - The updated array of message objects with cache control added.
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 message = updatedMessages[i];
148
+ const originalMessage = updatedMessages[i];
149
+ const content = originalMessage.content;
41
150
  const isUserMessage =
42
- ('getType' in message && message.getType() === 'human') ||
43
- ('role' in message && message.role === 'user');
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
- if (Array.isArray(message.content)) {
46
- message.content = message.content.filter(
47
- (block) => !isCachePoint(block as MessageContentComplex)
48
- ) as typeof message.content;
167
+ let workingContent: MessageContentComplex[];
49
168
 
50
- for (let j = 0; j < message.content.length; j++) {
51
- const block = message.content[j] as Record<string, unknown>;
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
- if (typeof message.content === 'string') {
63
- message.content = [
64
- {
65
- type: 'text',
66
- text: message.content,
67
- cache_control: { type: 'ephemeral' },
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 message = updatedMessages[i];
110
- const content = message.content;
231
+ const originalMessage = updatedMessages[i];
232
+ const content = originalMessage.content;
111
233
 
112
- if (Array.isArray(content)) {
113
- for (let j = 0; j < content.length; j++) {
114
- const block = content[j] as Record<string, unknown>;
115
- if ('cache_control' in block) {
116
- delete block.cache_control;
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 message = updatedMessages[i];
140
- const content = message.content;
266
+ const originalMessage = updatedMessages[i];
267
+ const content = originalMessage.content;
141
268
 
142
- if (Array.isArray(content)) {
143
- message.content = content.filter(
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;
@@ -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
  };
@@ -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.TOOL_SEARCH_REGEX) continue;
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.TOOL_SEARCH_REGEX &&
98
+ (msg as ToolMessage).name === Constants.TOOL_SEARCH &&
99
99
  toolCallIds.has((msg as ToolMessage).tool_call_id)
100
100
  ) {
101
101
  return true;
@@ -0,0 +1,2 @@
1
+ // src/schemas/index.ts
2
+ export * from './validate';