@librechat/agents 3.0.19 → 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/main.cjs CHANGED
@@ -63,6 +63,8 @@ exports.labelContentByAgent = format.labelContentByAgent;
63
63
  exports.shiftIndexTokenCountMap = format.shiftIndexTokenCountMap;
64
64
  exports.addBedrockCacheControl = cache.addBedrockCacheControl;
65
65
  exports.addCacheControl = cache.addCacheControl;
66
+ exports.stripAnthropicCacheControl = cache.stripAnthropicCacheControl;
67
+ exports.stripBedrockCacheControl = cache.stripBedrockCacheControl;
66
68
  exports.formatContentStrings = content.formatContentStrings;
67
69
  exports.Graph = Graph.Graph;
68
70
  exports.StandardGraph = Graph.StandardGraph;
@@ -1 +1 @@
1
- {"version":3,"file":"main.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"main.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -4,6 +4,9 @@ var _enum = require('../common/enum.cjs');
4
4
 
5
5
  /**
6
6
  * Anthropic API: Adds cache control to the appropriate user messages in the payload.
7
+ * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
8
+ * then adds fresh cache control to the last 2 user messages in a single backward pass.
9
+ * This ensures we don't accumulate stale cache points across multiple turns.
7
10
  * @param messages - The array of message objects.
8
11
  * @returns - The updated array of message objects with cache control added.
9
12
  */
@@ -13,12 +16,20 @@ function addCacheControl(messages) {
13
16
  }
14
17
  const updatedMessages = [...messages];
15
18
  let userMessagesModified = 0;
16
- for (let i = updatedMessages.length - 1; i >= 0 && userMessagesModified < 2; i--) {
19
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
17
20
  const message = updatedMessages[i];
18
- if ('getType' in message && message.getType() !== 'human') {
19
- continue;
21
+ const isUserMessage = ('getType' in message && message.getType() === 'human') ||
22
+ ('role' in message && message.role === 'user');
23
+ if (Array.isArray(message.content)) {
24
+ message.content = message.content.filter((block) => !isCachePoint(block));
25
+ for (let j = 0; j < message.content.length; j++) {
26
+ const block = message.content[j];
27
+ if ('cache_control' in block) {
28
+ delete block.cache_control;
29
+ }
30
+ }
20
31
  }
21
- else if ('role' in message && message.role !== 'user') {
32
+ if (userMessagesModified >= 2 || !isUserMessage) {
22
33
  continue;
23
34
  }
24
35
  if (typeof message.content === 'string') {
@@ -46,10 +57,60 @@ function addCacheControl(messages) {
46
57
  }
47
58
  return updatedMessages;
48
59
  }
60
+ /**
61
+ * Checks if a content block is a cache point
62
+ */
63
+ function isCachePoint(block) {
64
+ return 'cachePoint' in block && !('type' in block);
65
+ }
66
+ /**
67
+ * Removes all Anthropic cache_control fields from messages
68
+ * Used when switching from Anthropic to Bedrock provider
69
+ */
70
+ function stripAnthropicCacheControl(messages) {
71
+ if (!Array.isArray(messages)) {
72
+ return messages;
73
+ }
74
+ const updatedMessages = [...messages];
75
+ for (let i = 0; i < updatedMessages.length; i++) {
76
+ const message = updatedMessages[i];
77
+ const content = message.content;
78
+ if (Array.isArray(content)) {
79
+ for (let j = 0; j < content.length; j++) {
80
+ const block = content[j];
81
+ if ('cache_control' in block) {
82
+ delete block.cache_control;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ return updatedMessages;
88
+ }
89
+ /**
90
+ * Removes all Bedrock cachePoint blocks from messages
91
+ * Used when switching from Bedrock to Anthropic provider
92
+ */
93
+ function stripBedrockCacheControl(messages) {
94
+ if (!Array.isArray(messages)) {
95
+ return messages;
96
+ }
97
+ const updatedMessages = [...messages];
98
+ for (let i = 0; i < updatedMessages.length; i++) {
99
+ const message = updatedMessages[i];
100
+ const content = message.content;
101
+ if (Array.isArray(content)) {
102
+ message.content = content.filter((block) => !isCachePoint(block));
103
+ }
104
+ }
105
+ return updatedMessages;
106
+ }
49
107
  /**
50
108
  * Adds Bedrock Converse API cache points to the last two messages.
51
109
  * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block
52
110
  * immediately after the last text block in each targeted message.
111
+ * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
112
+ * then adds fresh cache points to the last 2 messages in a single backward pass.
113
+ * This ensures we don't accumulate stale cache points across multiple turns.
53
114
  * @param messages - The array of message objects.
54
115
  * @returns - The updated array of message objects with cache points added.
55
116
  */
@@ -59,14 +120,24 @@ function addBedrockCacheControl(messages) {
59
120
  }
60
121
  const updatedMessages = messages.slice();
61
122
  let messagesModified = 0;
62
- for (let i = updatedMessages.length - 1; i >= 0 && messagesModified < 2; i--) {
123
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
63
124
  const message = updatedMessages[i];
64
- if ('getType' in message &&
125
+ const isToolMessage = 'getType' in message &&
65
126
  typeof message.getType === 'function' &&
66
- message.getType() === 'tool') {
127
+ message.getType() === 'tool';
128
+ const content = message.content;
129
+ if (Array.isArray(content)) {
130
+ message.content = content.filter((block) => !isCachePoint(block));
131
+ for (let j = 0; j < message.content.length; j++) {
132
+ const block = message.content[j];
133
+ if ('cache_control' in block) {
134
+ delete block.cache_control;
135
+ }
136
+ }
137
+ }
138
+ if (messagesModified >= 2 || isToolMessage) {
67
139
  continue;
68
140
  }
69
- const content = message.content;
70
141
  if (typeof content === 'string' && content === '') {
71
142
  continue;
72
143
  }
@@ -78,9 +149,9 @@ function addBedrockCacheControl(messages) {
78
149
  messagesModified++;
79
150
  continue;
80
151
  }
81
- if (Array.isArray(content)) {
152
+ if (Array.isArray(message.content)) {
82
153
  let hasCacheableContent = false;
83
- for (const block of content) {
154
+ for (const block of message.content) {
84
155
  if (block.type === _enum.ContentTypes.TEXT) {
85
156
  if (typeof block.text === 'string' && block.text !== '') {
86
157
  hasCacheableContent = true;
@@ -92,15 +163,15 @@ function addBedrockCacheControl(messages) {
92
163
  continue;
93
164
  }
94
165
  let inserted = false;
95
- for (let j = content.length - 1; j >= 0; j--) {
96
- const block = content[j];
166
+ for (let j = message.content.length - 1; j >= 0; j--) {
167
+ const block = message.content[j];
97
168
  const type = block.type;
98
169
  if (type === _enum.ContentTypes.TEXT || type === 'text') {
99
170
  const text = block.text;
100
171
  if (text === '' || text === undefined) {
101
172
  continue;
102
173
  }
103
- content.splice(j + 1, 0, {
174
+ message.content.splice(j + 1, 0, {
104
175
  cachePoint: { type: 'default' },
105
176
  });
106
177
  inserted = true;
@@ -108,7 +179,7 @@ function addBedrockCacheControl(messages) {
108
179
  }
109
180
  }
110
181
  if (!inserted) {
111
- content.push({
182
+ message.content.push({
112
183
  cachePoint: { type: 'default' },
113
184
  });
114
185
  }
@@ -120,4 +191,6 @@ function addBedrockCacheControl(messages) {
120
191
 
121
192
  exports.addBedrockCacheControl = addBedrockCacheControl;
122
193
  exports.addCacheControl = addCacheControl;
194
+ exports.stripAnthropicCacheControl = stripAnthropicCacheControl;
195
+ exports.stripBedrockCacheControl = stripBedrockCacheControl;
123
196
  //# sourceMappingURL=cache.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.cjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import { BaseMessage, MessageContentComplex } from '@langchain/core/messages';\nimport type { AnthropicMessage } from '@/types/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { ContentTypes } from '@/common/enum';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n let userMessagesModified = 0;\n\n for (\n let i = updatedMessages.length - 1;\n i >= 0 && userMessagesModified < 2;\n i--\n ) {\n const message = updatedMessages[i];\n if ('getType' in message && message.getType() !== 'human') {\n continue;\n } else if ('role' in message && message.role !== 'user') {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n {\n type: 'text',\n text: message.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n userMessagesModified++;\n } else if (Array.isArray(message.content)) {\n for (let j = message.content.length - 1; j >= 0; j--) {\n const contentPart = message.content[j];\n if ('type' in contentPart && contentPart.type === 'text') {\n (contentPart as Anthropic.TextBlockParam).cache_control = {\n type: 'ephemeral',\n };\n userMessagesModified++;\n break;\n }\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the last two messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends Partial<BaseMessage> & MessageWithContent,\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = messages.slice();\n let messagesModified = 0;\n\n for (\n let i = updatedMessages.length - 1;\n i >= 0 && messagesModified < 2;\n i--\n ) {\n const message = updatedMessages[i];\n\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n message.getType() === 'tool'\n ) {\n continue;\n }\n\n const content = message.content;\n\n if (typeof content === 'string' && content === '') {\n continue;\n }\n\n if (typeof content === 'string') {\n message.content = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n messagesModified++;\n continue;\n }\n\n if (Array.isArray(content)) {\n let hasCacheableContent = false;\n for (const block of content) {\n if (block.type === ContentTypes.TEXT) {\n if (typeof block.text === 'string' && block.text !== '') {\n hasCacheableContent = true;\n break;\n }\n }\n }\n\n if (!hasCacheableContent) {\n continue;\n }\n\n let inserted = false;\n for (let j = content.length - 1; j >= 0; j--) {\n const block = content[j] as MessageContentComplex;\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n if (text === '' || text === undefined) {\n continue;\n }\n content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n content.push({\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n }\n messagesModified++;\n }\n }\n\n return updatedMessages;\n}\n"],"names":["ContentTypes"],"mappings":"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"}
1
+ {"version":3,"file":"cache.cjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import { BaseMessage, MessageContentComplex } from '@langchain/core/messages';\nimport type { AnthropicMessage } from '@/types/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { ContentTypes } from '@/common/enum';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const isUserMessage =\n ('getType' in message && message.getType() === 'human') ||\n ('role' in message && message.role === 'user');\n\n if (Array.isArray(message.content)) {\n message.content = message.content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof message.content;\n\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (userMessagesModified >= 2 || !isUserMessage) {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n {\n type: 'text',\n text: message.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n userMessagesModified++;\n } else if (Array.isArray(message.content)) {\n for (let j = message.content.length - 1; j >= 0; j--) {\n const contentPart = message.content[j];\n if ('type' in contentPart && contentPart.type === 'text') {\n (contentPart as Anthropic.TextBlockParam).cache_control = {\n type: 'ephemeral',\n };\n userMessagesModified++;\n break;\n }\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n for (let j = 0; j < content.length; j++) {\n const block = content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof content;\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the last two messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,\n * then adds fresh cache points to the last 2 messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends Partial<BaseMessage> & MessageWithContent,\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = messages.slice();\n let messagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const isToolMessage =\n 'getType' in message &&\n typeof message.getType === 'function' &&\n message.getType() === 'tool';\n\n const content = message.content;\n\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block)\n ) as typeof content;\n\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (messagesModified >= 2 || isToolMessage) {\n continue;\n }\n\n if (typeof content === 'string' && content === '') {\n continue;\n }\n\n if (typeof content === 'string') {\n message.content = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n messagesModified++;\n continue;\n }\n\n if (Array.isArray(message.content)) {\n let hasCacheableContent = false;\n for (const block of message.content) {\n if (block.type === ContentTypes.TEXT) {\n if (typeof block.text === 'string' && block.text !== '') {\n hasCacheableContent = true;\n break;\n }\n }\n }\n\n if (!hasCacheableContent) {\n continue;\n }\n\n let inserted = false;\n for (let j = message.content.length - 1; j >= 0; j--) {\n const block = message.content[j] as MessageContentComplex;\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n if (text === '' || text === undefined) {\n continue;\n }\n message.content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n message.content.push({\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n }\n messagesModified++;\n }\n }\n\n return updatedMessages;\n}\n"],"names":["ContentTypes"],"mappings":"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"}
package/dist/esm/main.mjs CHANGED
@@ -6,7 +6,7 @@ export { convertMessagesToContent, findLastIndex, formatAnthropicArtifactContent
6
6
  export { getMessageId } from './messages/ids.mjs';
7
7
  export { calculateTotalTokens, checkValidNumber, createPruneMessages, getMessagesWithinTokenLimit } from './messages/prune.mjs';
8
8
  export { ensureThinkingBlockInMessages, formatAgentMessages, formatFromLangChain, formatLangChainMessages, formatMediaMessage, formatMessage, labelContentByAgent, shiftIndexTokenCountMap } from './messages/format.mjs';
9
- export { addBedrockCacheControl, addCacheControl } from './messages/cache.mjs';
9
+ export { addBedrockCacheControl, addCacheControl, stripAnthropicCacheControl, stripBedrockCacheControl } from './messages/cache.mjs';
10
10
  export { formatContentStrings } from './messages/content.mjs';
11
11
  export { Graph, StandardGraph } from './graphs/Graph.mjs';
12
12
  export { MultiAgentGraph } from './graphs/MultiAgentGraph.mjs';
@@ -2,6 +2,9 @@ import { ContentTypes } from '../common/enum.mjs';
2
2
 
3
3
  /**
4
4
  * Anthropic API: Adds cache control to the appropriate user messages in the payload.
5
+ * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
6
+ * then adds fresh cache control to the last 2 user messages in a single backward pass.
7
+ * This ensures we don't accumulate stale cache points across multiple turns.
5
8
  * @param messages - The array of message objects.
6
9
  * @returns - The updated array of message objects with cache control added.
7
10
  */
@@ -11,12 +14,20 @@ function addCacheControl(messages) {
11
14
  }
12
15
  const updatedMessages = [...messages];
13
16
  let userMessagesModified = 0;
14
- for (let i = updatedMessages.length - 1; i >= 0 && userMessagesModified < 2; i--) {
17
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
15
18
  const message = updatedMessages[i];
16
- if ('getType' in message && message.getType() !== 'human') {
17
- continue;
19
+ const isUserMessage = ('getType' in message && message.getType() === 'human') ||
20
+ ('role' in message && message.role === 'user');
21
+ if (Array.isArray(message.content)) {
22
+ message.content = message.content.filter((block) => !isCachePoint(block));
23
+ for (let j = 0; j < message.content.length; j++) {
24
+ const block = message.content[j];
25
+ if ('cache_control' in block) {
26
+ delete block.cache_control;
27
+ }
28
+ }
18
29
  }
19
- else if ('role' in message && message.role !== 'user') {
30
+ if (userMessagesModified >= 2 || !isUserMessage) {
20
31
  continue;
21
32
  }
22
33
  if (typeof message.content === 'string') {
@@ -44,10 +55,60 @@ function addCacheControl(messages) {
44
55
  }
45
56
  return updatedMessages;
46
57
  }
58
+ /**
59
+ * Checks if a content block is a cache point
60
+ */
61
+ function isCachePoint(block) {
62
+ return 'cachePoint' in block && !('type' in block);
63
+ }
64
+ /**
65
+ * Removes all Anthropic cache_control fields from messages
66
+ * Used when switching from Anthropic to Bedrock provider
67
+ */
68
+ function stripAnthropicCacheControl(messages) {
69
+ if (!Array.isArray(messages)) {
70
+ return messages;
71
+ }
72
+ const updatedMessages = [...messages];
73
+ for (let i = 0; i < updatedMessages.length; i++) {
74
+ const message = updatedMessages[i];
75
+ const content = message.content;
76
+ if (Array.isArray(content)) {
77
+ for (let j = 0; j < content.length; j++) {
78
+ const block = content[j];
79
+ if ('cache_control' in block) {
80
+ delete block.cache_control;
81
+ }
82
+ }
83
+ }
84
+ }
85
+ return updatedMessages;
86
+ }
87
+ /**
88
+ * Removes all Bedrock cachePoint blocks from messages
89
+ * Used when switching from Bedrock to Anthropic provider
90
+ */
91
+ function stripBedrockCacheControl(messages) {
92
+ if (!Array.isArray(messages)) {
93
+ return messages;
94
+ }
95
+ const updatedMessages = [...messages];
96
+ for (let i = 0; i < updatedMessages.length; i++) {
97
+ const message = updatedMessages[i];
98
+ const content = message.content;
99
+ if (Array.isArray(content)) {
100
+ message.content = content.filter((block) => !isCachePoint(block));
101
+ }
102
+ }
103
+ return updatedMessages;
104
+ }
47
105
  /**
48
106
  * Adds Bedrock Converse API cache points to the last two messages.
49
107
  * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block
50
108
  * immediately after the last text block in each targeted message.
109
+ * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
110
+ * then adds fresh cache points to the last 2 messages in a single backward pass.
111
+ * This ensures we don't accumulate stale cache points across multiple turns.
51
112
  * @param messages - The array of message objects.
52
113
  * @returns - The updated array of message objects with cache points added.
53
114
  */
@@ -57,14 +118,24 @@ function addBedrockCacheControl(messages) {
57
118
  }
58
119
  const updatedMessages = messages.slice();
59
120
  let messagesModified = 0;
60
- for (let i = updatedMessages.length - 1; i >= 0 && messagesModified < 2; i--) {
121
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
61
122
  const message = updatedMessages[i];
62
- if ('getType' in message &&
123
+ const isToolMessage = 'getType' in message &&
63
124
  typeof message.getType === 'function' &&
64
- message.getType() === 'tool') {
125
+ message.getType() === 'tool';
126
+ const content = message.content;
127
+ if (Array.isArray(content)) {
128
+ message.content = content.filter((block) => !isCachePoint(block));
129
+ for (let j = 0; j < message.content.length; j++) {
130
+ const block = message.content[j];
131
+ if ('cache_control' in block) {
132
+ delete block.cache_control;
133
+ }
134
+ }
135
+ }
136
+ if (messagesModified >= 2 || isToolMessage) {
65
137
  continue;
66
138
  }
67
- const content = message.content;
68
139
  if (typeof content === 'string' && content === '') {
69
140
  continue;
70
141
  }
@@ -76,9 +147,9 @@ function addBedrockCacheControl(messages) {
76
147
  messagesModified++;
77
148
  continue;
78
149
  }
79
- if (Array.isArray(content)) {
150
+ if (Array.isArray(message.content)) {
80
151
  let hasCacheableContent = false;
81
- for (const block of content) {
152
+ for (const block of message.content) {
82
153
  if (block.type === ContentTypes.TEXT) {
83
154
  if (typeof block.text === 'string' && block.text !== '') {
84
155
  hasCacheableContent = true;
@@ -90,15 +161,15 @@ function addBedrockCacheControl(messages) {
90
161
  continue;
91
162
  }
92
163
  let inserted = false;
93
- for (let j = content.length - 1; j >= 0; j--) {
94
- const block = content[j];
164
+ for (let j = message.content.length - 1; j >= 0; j--) {
165
+ const block = message.content[j];
95
166
  const type = block.type;
96
167
  if (type === ContentTypes.TEXT || type === 'text') {
97
168
  const text = block.text;
98
169
  if (text === '' || text === undefined) {
99
170
  continue;
100
171
  }
101
- content.splice(j + 1, 0, {
172
+ message.content.splice(j + 1, 0, {
102
173
  cachePoint: { type: 'default' },
103
174
  });
104
175
  inserted = true;
@@ -106,7 +177,7 @@ function addBedrockCacheControl(messages) {
106
177
  }
107
178
  }
108
179
  if (!inserted) {
109
- content.push({
180
+ message.content.push({
110
181
  cachePoint: { type: 'default' },
111
182
  });
112
183
  }
@@ -116,5 +187,5 @@ function addBedrockCacheControl(messages) {
116
187
  return updatedMessages;
117
188
  }
118
189
 
119
- export { addBedrockCacheControl, addCacheControl };
190
+ export { addBedrockCacheControl, addCacheControl, stripAnthropicCacheControl, stripBedrockCacheControl };
120
191
  //# sourceMappingURL=cache.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"cache.mjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import { BaseMessage, MessageContentComplex } from '@langchain/core/messages';\nimport type { AnthropicMessage } from '@/types/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { ContentTypes } from '@/common/enum';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n let userMessagesModified = 0;\n\n for (\n let i = updatedMessages.length - 1;\n i >= 0 && userMessagesModified < 2;\n i--\n ) {\n const message = updatedMessages[i];\n if ('getType' in message && message.getType() !== 'human') {\n continue;\n } else if ('role' in message && message.role !== 'user') {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n {\n type: 'text',\n text: message.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n userMessagesModified++;\n } else if (Array.isArray(message.content)) {\n for (let j = message.content.length - 1; j >= 0; j--) {\n const contentPart = message.content[j];\n if ('type' in contentPart && contentPart.type === 'text') {\n (contentPart as Anthropic.TextBlockParam).cache_control = {\n type: 'ephemeral',\n };\n userMessagesModified++;\n break;\n }\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the last two messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends Partial<BaseMessage> & MessageWithContent,\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = messages.slice();\n let messagesModified = 0;\n\n for (\n let i = updatedMessages.length - 1;\n i >= 0 && messagesModified < 2;\n i--\n ) {\n const message = updatedMessages[i];\n\n if (\n 'getType' in message &&\n typeof message.getType === 'function' &&\n message.getType() === 'tool'\n ) {\n continue;\n }\n\n const content = message.content;\n\n if (typeof content === 'string' && content === '') {\n continue;\n }\n\n if (typeof content === 'string') {\n message.content = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n messagesModified++;\n continue;\n }\n\n if (Array.isArray(content)) {\n let hasCacheableContent = false;\n for (const block of content) {\n if (block.type === ContentTypes.TEXT) {\n if (typeof block.text === 'string' && block.text !== '') {\n hasCacheableContent = true;\n break;\n }\n }\n }\n\n if (!hasCacheableContent) {\n continue;\n }\n\n let inserted = false;\n for (let j = content.length - 1; j >= 0; j--) {\n const block = content[j] as MessageContentComplex;\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n if (text === '' || text === undefined) {\n continue;\n }\n content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n content.push({\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n }\n messagesModified++;\n }\n }\n\n return updatedMessages;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cache.mjs","sources":["../../../src/messages/cache.ts"],"sourcesContent":["import { BaseMessage, MessageContentComplex } from '@langchain/core/messages';\nimport type { AnthropicMessage } from '@/types/messages';\nimport type Anthropic from '@anthropic-ai/sdk';\nimport { ContentTypes } from '@/common/enum';\n\ntype MessageWithContent = {\n content?: string | MessageContentComplex[];\n};\n\n/**\n * Anthropic API: Adds cache control to the appropriate user messages in the payload.\n * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,\n * then adds fresh cache control to the last 2 user messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache control added.\n */\nexport function addCacheControl<T extends AnthropicMessage | BaseMessage>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const isUserMessage =\n ('getType' in message && message.getType() === 'human') ||\n ('role' in message && message.role === 'user');\n\n if (Array.isArray(message.content)) {\n message.content = message.content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof message.content;\n\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (userMessagesModified >= 2 || !isUserMessage) {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n {\n type: 'text',\n text: message.content,\n cache_control: { type: 'ephemeral' },\n },\n ];\n userMessagesModified++;\n } else if (Array.isArray(message.content)) {\n for (let j = message.content.length - 1; j >= 0; j--) {\n const contentPart = message.content[j];\n if ('type' in contentPart && contentPart.type === 'text') {\n (contentPart as Anthropic.TextBlockParam).cache_control = {\n type: 'ephemeral',\n };\n userMessagesModified++;\n break;\n }\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a content block is a cache point\n */\nfunction isCachePoint(block: MessageContentComplex): boolean {\n return 'cachePoint' in block && !('type' in block);\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n */\nexport function stripAnthropicCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n for (let j = 0; j < content.length; j++) {\n const block = content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n */\nexport function stripBedrockCacheControl<T extends MessageWithContent>(\n messages: T[]\n): T[] {\n if (!Array.isArray(messages)) {\n return messages;\n }\n\n const updatedMessages = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const message = updatedMessages[i];\n const content = message.content;\n\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof content;\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Adds Bedrock Converse API cache points to the last two messages.\n * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block\n * immediately after the last text block in each targeted message.\n * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,\n * then adds fresh cache points to the last 2 messages in a single backward pass.\n * This ensures we don't accumulate stale cache points across multiple turns.\n * @param messages - The array of message objects.\n * @returns - The updated array of message objects with cache points added.\n */\nexport function addBedrockCacheControl<\n T extends Partial<BaseMessage> & MessageWithContent,\n>(messages: T[]): T[] {\n if (!Array.isArray(messages) || messages.length < 2) {\n return messages;\n }\n\n const updatedMessages: T[] = messages.slice();\n let messagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const message = updatedMessages[i];\n const isToolMessage =\n 'getType' in message &&\n typeof message.getType === 'function' &&\n message.getType() === 'tool';\n\n const content = message.content;\n\n if (Array.isArray(content)) {\n message.content = content.filter(\n (block) => !isCachePoint(block)\n ) as typeof content;\n\n for (let j = 0; j < message.content.length; j++) {\n const block = message.content[j] as Record<string, unknown>;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (messagesModified >= 2 || isToolMessage) {\n continue;\n }\n\n if (typeof content === 'string' && content === '') {\n continue;\n }\n\n if (typeof content === 'string') {\n message.content = [\n { type: ContentTypes.TEXT, text: content },\n { cachePoint: { type: 'default' } },\n ] as MessageContentComplex[];\n messagesModified++;\n continue;\n }\n\n if (Array.isArray(message.content)) {\n let hasCacheableContent = false;\n for (const block of message.content) {\n if (block.type === ContentTypes.TEXT) {\n if (typeof block.text === 'string' && block.text !== '') {\n hasCacheableContent = true;\n break;\n }\n }\n }\n\n if (!hasCacheableContent) {\n continue;\n }\n\n let inserted = false;\n for (let j = message.content.length - 1; j >= 0; j--) {\n const block = message.content[j] as MessageContentComplex;\n const type = (block as { type?: string }).type;\n if (type === ContentTypes.TEXT || type === 'text') {\n const text = (block as { text?: string }).text;\n if (text === '' || text === undefined) {\n continue;\n }\n message.content.splice(j + 1, 0, {\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n inserted = true;\n break;\n }\n }\n if (!inserted) {\n message.content.push({\n cachePoint: { type: 'default' },\n } as MessageContentComplex);\n }\n messagesModified++;\n }\n }\n\n return updatedMessages;\n}\n"],"names":[],"mappings":"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"}
@@ -5,14 +5,30 @@ type MessageWithContent = {
5
5
  };
6
6
  /**
7
7
  * Anthropic API: Adds cache control to the appropriate user messages in the payload.
8
+ * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
9
+ * then adds fresh cache control to the last 2 user messages in a single backward pass.
10
+ * This ensures we don't accumulate stale cache points across multiple turns.
8
11
  * @param messages - The array of message objects.
9
12
  * @returns - The updated array of message objects with cache control added.
10
13
  */
11
14
  export declare function addCacheControl<T extends AnthropicMessage | BaseMessage>(messages: T[]): T[];
15
+ /**
16
+ * Removes all Anthropic cache_control fields from messages
17
+ * Used when switching from Anthropic to Bedrock provider
18
+ */
19
+ export declare function stripAnthropicCacheControl<T extends MessageWithContent>(messages: T[]): T[];
20
+ /**
21
+ * Removes all Bedrock cachePoint blocks from messages
22
+ * Used when switching from Bedrock to Anthropic provider
23
+ */
24
+ export declare function stripBedrockCacheControl<T extends MessageWithContent>(messages: T[]): T[];
12
25
  /**
13
26
  * Adds Bedrock Converse API cache points to the last two messages.
14
27
  * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block
15
28
  * immediately after the last text block in each targeted message.
29
+ * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
30
+ * then adds fresh cache points to the last 2 messages in a single backward pass.
31
+ * This ensures we don't accumulate stale cache points across multiple turns.
16
32
  * @param messages - The array of message objects.
17
33
  * @returns - The updated array of message objects with cache points added.
18
34
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.0.19",
3
+ "version": "3.0.20",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -1,6 +1,11 @@
1
1
  import type Anthropic from '@anthropic-ai/sdk';
2
2
  import type { AnthropicMessages } from '@/types/messages';
3
- import { addCacheControl, addBedrockCacheControl } from './cache';
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[1]).toEqual({ cachePoint: { type: 'default' } });
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
  });
@@ -9,6 +9,9 @@ type MessageWithContent = {
9
9
 
10
10
  /**
11
11
  * Anthropic API: Adds cache control to the appropriate user messages in the payload.
12
+ * Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
13
+ * then adds fresh cache control to the last 2 user messages in a single backward pass.
14
+ * This ensures we don't accumulate stale cache points across multiple turns.
12
15
  * @param messages - The array of message objects.
13
16
  * @returns - The updated array of message objects with cache control added.
14
17
  */
@@ -22,15 +25,26 @@ export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
22
25
  const updatedMessages = [...messages];
23
26
  let userMessagesModified = 0;
24
27
 
25
- for (
26
- let i = updatedMessages.length - 1;
27
- i >= 0 && userMessagesModified < 2;
28
- i--
29
- ) {
28
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
30
29
  const message = updatedMessages[i];
31
- if ('getType' in message && message.getType() !== 'human') {
32
- continue;
33
- } else if ('role' in message && message.role !== 'user') {
30
+ const isUserMessage =
31
+ ('getType' in message && message.getType() === 'human') ||
32
+ ('role' in message && message.role === 'user');
33
+
34
+ if (Array.isArray(message.content)) {
35
+ message.content = message.content.filter(
36
+ (block) => !isCachePoint(block as MessageContentComplex)
37
+ ) as typeof message.content;
38
+
39
+ for (let j = 0; j < message.content.length; j++) {
40
+ const block = message.content[j] as Record<string, unknown>;
41
+ if ('cache_control' in block) {
42
+ delete block.cache_control;
43
+ }
44
+ }
45
+ }
46
+
47
+ if (userMessagesModified >= 2 || !isUserMessage) {
34
48
  continue;
35
49
  }
36
50
 
@@ -60,10 +74,77 @@ export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
60
74
  return updatedMessages;
61
75
  }
62
76
 
77
+ /**
78
+ * Checks if a content block is a cache point
79
+ */
80
+ function isCachePoint(block: MessageContentComplex): boolean {
81
+ return 'cachePoint' in block && !('type' in block);
82
+ }
83
+
84
+ /**
85
+ * Removes all Anthropic cache_control fields from messages
86
+ * Used when switching from Anthropic to Bedrock provider
87
+ */
88
+ export function stripAnthropicCacheControl<T extends MessageWithContent>(
89
+ messages: T[]
90
+ ): T[] {
91
+ if (!Array.isArray(messages)) {
92
+ return messages;
93
+ }
94
+
95
+ const updatedMessages = [...messages];
96
+
97
+ for (let i = 0; i < updatedMessages.length; i++) {
98
+ const message = updatedMessages[i];
99
+ const content = message.content;
100
+
101
+ if (Array.isArray(content)) {
102
+ for (let j = 0; j < content.length; j++) {
103
+ const block = content[j] as Record<string, unknown>;
104
+ if ('cache_control' in block) {
105
+ delete block.cache_control;
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ return updatedMessages;
112
+ }
113
+
114
+ /**
115
+ * Removes all Bedrock cachePoint blocks from messages
116
+ * Used when switching from Bedrock to Anthropic provider
117
+ */
118
+ export function stripBedrockCacheControl<T extends MessageWithContent>(
119
+ messages: T[]
120
+ ): T[] {
121
+ if (!Array.isArray(messages)) {
122
+ return messages;
123
+ }
124
+
125
+ const updatedMessages = [...messages];
126
+
127
+ for (let i = 0; i < updatedMessages.length; i++) {
128
+ const message = updatedMessages[i];
129
+ const content = message.content;
130
+
131
+ if (Array.isArray(content)) {
132
+ message.content = content.filter(
133
+ (block) => !isCachePoint(block as MessageContentComplex)
134
+ ) as typeof content;
135
+ }
136
+ }
137
+
138
+ return updatedMessages;
139
+ }
140
+
63
141
  /**
64
142
  * Adds Bedrock Converse API cache points to the last two messages.
65
143
  * Inserts `{ cachePoint: { type: 'default' } }` as a separate content block
66
144
  * immediately after the last text block in each targeted message.
145
+ * Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
146
+ * then adds fresh cache points to the last 2 messages in a single backward pass.
147
+ * This ensures we don't accumulate stale cache points across multiple turns.
67
148
  * @param messages - The array of message objects.
68
149
  * @returns - The updated array of message objects with cache points added.
69
150
  */
@@ -77,23 +158,32 @@ export function addBedrockCacheControl<
77
158
  const updatedMessages: T[] = messages.slice();
78
159
  let messagesModified = 0;
79
160
 
80
- for (
81
- let i = updatedMessages.length - 1;
82
- i >= 0 && messagesModified < 2;
83
- i--
84
- ) {
161
+ for (let i = updatedMessages.length - 1; i >= 0; i--) {
85
162
  const message = updatedMessages[i];
86
-
87
- if (
163
+ const isToolMessage =
88
164
  'getType' in message &&
89
165
  typeof message.getType === 'function' &&
90
- message.getType() === 'tool'
91
- ) {
92
- continue;
93
- }
166
+ message.getType() === 'tool';
94
167
 
95
168
  const content = message.content;
96
169
 
170
+ if (Array.isArray(content)) {
171
+ message.content = content.filter(
172
+ (block) => !isCachePoint(block)
173
+ ) as typeof content;
174
+
175
+ for (let j = 0; j < message.content.length; j++) {
176
+ const block = message.content[j] as Record<string, unknown>;
177
+ if ('cache_control' in block) {
178
+ delete block.cache_control;
179
+ }
180
+ }
181
+ }
182
+
183
+ if (messagesModified >= 2 || isToolMessage) {
184
+ continue;
185
+ }
186
+
97
187
  if (typeof content === 'string' && content === '') {
98
188
  continue;
99
189
  }
@@ -107,9 +197,9 @@ export function addBedrockCacheControl<
107
197
  continue;
108
198
  }
109
199
 
110
- if (Array.isArray(content)) {
200
+ if (Array.isArray(message.content)) {
111
201
  let hasCacheableContent = false;
112
- for (const block of content) {
202
+ for (const block of message.content) {
113
203
  if (block.type === ContentTypes.TEXT) {
114
204
  if (typeof block.text === 'string' && block.text !== '') {
115
205
  hasCacheableContent = true;
@@ -123,15 +213,15 @@ export function addBedrockCacheControl<
123
213
  }
124
214
 
125
215
  let inserted = false;
126
- for (let j = content.length - 1; j >= 0; j--) {
127
- const block = content[j] as MessageContentComplex;
216
+ for (let j = message.content.length - 1; j >= 0; j--) {
217
+ const block = message.content[j] as MessageContentComplex;
128
218
  const type = (block as { type?: string }).type;
129
219
  if (type === ContentTypes.TEXT || type === 'text') {
130
220
  const text = (block as { text?: string }).text;
131
221
  if (text === '' || text === undefined) {
132
222
  continue;
133
223
  }
134
- content.splice(j + 1, 0, {
224
+ message.content.splice(j + 1, 0, {
135
225
  cachePoint: { type: 'default' },
136
226
  } as MessageContentComplex);
137
227
  inserted = true;
@@ -139,7 +229,7 @@ export function addBedrockCacheControl<
139
229
  }
140
230
  }
141
231
  if (!inserted) {
142
- content.push({
232
+ message.content.push({
143
233
  cachePoint: { type: 'default' },
144
234
  } as MessageContentComplex);
145
235
  }