@librechat/agents 3.0.80 → 3.0.81
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/messages/cache.cjs +123 -32
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/esm/messages/cache.mjs +123 -32
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/types/messages/cache.d.ts +6 -2
- package/package.json +1 -1
- package/src/messages/cache.test.ts +215 -0
- package/src/messages/cache.ts +172 -43
|
@@ -2,13 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
var _enum = require('../common/enum.cjs');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Deep clones a message's content to prevent mutation of the original.
|
|
7
|
+
* Handles both string and array content types.
|
|
8
|
+
*/
|
|
9
|
+
function deepCloneContent(content) {
|
|
10
|
+
if (typeof content === 'string') {
|
|
11
|
+
return content;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(content)) {
|
|
14
|
+
return content.map((block) => ({ ...block }));
|
|
15
|
+
}
|
|
16
|
+
return content;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a shallow clone of a message with deep-cloned content.
|
|
20
|
+
* This ensures modifications to content don't affect the original message.
|
|
21
|
+
*/
|
|
22
|
+
function cloneMessageWithContent(message) {
|
|
23
|
+
if (message.content === undefined) {
|
|
24
|
+
return { ...message };
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...message,
|
|
28
|
+
content: deepCloneContent(message.content),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Checks if a message's content needs cache control stripping.
|
|
33
|
+
* Returns true if content has cachePoint blocks or cache_control fields.
|
|
34
|
+
*/
|
|
35
|
+
function needsCacheStripping(content) {
|
|
36
|
+
for (let i = 0; i < content.length; i++) {
|
|
37
|
+
const block = content[i];
|
|
38
|
+
if (isCachePoint(block))
|
|
39
|
+
return true;
|
|
40
|
+
if ('cache_control' in block)
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
5
45
|
/**
|
|
6
46
|
* Anthropic API: Adds cache control to the appropriate user messages in the payload.
|
|
7
47
|
* Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
|
|
8
48
|
* then adds fresh cache control to the last 2 user messages in a single backward pass.
|
|
9
49
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
50
|
+
* Returns a new array - only clones messages that require modification.
|
|
10
51
|
* @param messages - The array of message objects.
|
|
11
|
-
* @returns -
|
|
52
|
+
* @returns - A new array of message objects with cache control added.
|
|
12
53
|
*/
|
|
13
54
|
function addCacheControl(messages) {
|
|
14
55
|
if (!Array.isArray(messages) || messages.length < 2) {
|
|
@@ -17,10 +58,21 @@ function addCacheControl(messages) {
|
|
|
17
58
|
const updatedMessages = [...messages];
|
|
18
59
|
let userMessagesModified = 0;
|
|
19
60
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
|
20
|
-
const
|
|
21
|
-
const isUserMessage = ('getType' in
|
|
22
|
-
('role' in
|
|
23
|
-
|
|
61
|
+
const originalMessage = updatedMessages[i];
|
|
62
|
+
const isUserMessage = ('getType' in originalMessage && originalMessage.getType() === 'human') ||
|
|
63
|
+
('role' in originalMessage && originalMessage.role === 'user');
|
|
64
|
+
const hasArrayContent = Array.isArray(originalMessage.content);
|
|
65
|
+
const needsStripping = hasArrayContent &&
|
|
66
|
+
needsCacheStripping(originalMessage.content);
|
|
67
|
+
const needsCacheAdd = userMessagesModified < 2 &&
|
|
68
|
+
isUserMessage &&
|
|
69
|
+
(typeof originalMessage.content === 'string' || hasArrayContent);
|
|
70
|
+
if (!needsStripping && !needsCacheAdd) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
74
|
+
updatedMessages[i] = message;
|
|
75
|
+
if (hasArrayContent) {
|
|
24
76
|
message.content = message.content.filter((block) => !isCachePoint(block));
|
|
25
77
|
for (let j = 0; j < message.content.length; j++) {
|
|
26
78
|
const block = message.content[j];
|
|
@@ -63,9 +115,20 @@ function addCacheControl(messages) {
|
|
|
63
115
|
function isCachePoint(block) {
|
|
64
116
|
return 'cachePoint' in block && !('type' in block);
|
|
65
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Checks if a message's content has Anthropic cache_control fields.
|
|
120
|
+
*/
|
|
121
|
+
function hasAnthropicCacheControl(content) {
|
|
122
|
+
for (let i = 0; i < content.length; i++) {
|
|
123
|
+
if ('cache_control' in content[i])
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
66
128
|
/**
|
|
67
129
|
* Removes all Anthropic cache_control fields from messages
|
|
68
130
|
* Used when switching from Anthropic to Bedrock provider
|
|
131
|
+
* Returns a new array - only clones messages that require modification.
|
|
69
132
|
*/
|
|
70
133
|
function stripAnthropicCacheControl(messages) {
|
|
71
134
|
if (!Array.isArray(messages)) {
|
|
@@ -73,22 +136,36 @@ function stripAnthropicCacheControl(messages) {
|
|
|
73
136
|
}
|
|
74
137
|
const updatedMessages = [...messages];
|
|
75
138
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
76
|
-
const
|
|
77
|
-
const content =
|
|
78
|
-
if (Array.isArray(content)) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
139
|
+
const originalMessage = updatedMessages[i];
|
|
140
|
+
const content = originalMessage.content;
|
|
141
|
+
if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
145
|
+
updatedMessages[i] = message;
|
|
146
|
+
for (let j = 0; j < message.content.length; j++) {
|
|
147
|
+
const block = message.content[j];
|
|
148
|
+
if ('cache_control' in block) {
|
|
149
|
+
delete block.cache_control;
|
|
84
150
|
}
|
|
85
151
|
}
|
|
86
152
|
}
|
|
87
153
|
return updatedMessages;
|
|
88
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Checks if a message's content has Bedrock cachePoint blocks.
|
|
157
|
+
*/
|
|
158
|
+
function hasBedrockCachePoint(content) {
|
|
159
|
+
for (let i = 0; i < content.length; i++) {
|
|
160
|
+
if (isCachePoint(content[i]))
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
89
165
|
/**
|
|
90
166
|
* Removes all Bedrock cachePoint blocks from messages
|
|
91
167
|
* Used when switching from Bedrock to Anthropic provider
|
|
168
|
+
* Returns a new array - only clones messages that require modification.
|
|
92
169
|
*/
|
|
93
170
|
function stripBedrockCacheControl(messages) {
|
|
94
171
|
if (!Array.isArray(messages)) {
|
|
@@ -96,11 +173,14 @@ function stripBedrockCacheControl(messages) {
|
|
|
96
173
|
}
|
|
97
174
|
const updatedMessages = [...messages];
|
|
98
175
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
99
|
-
const
|
|
100
|
-
const content =
|
|
101
|
-
if (Array.isArray(content)) {
|
|
102
|
-
|
|
176
|
+
const originalMessage = updatedMessages[i];
|
|
177
|
+
const content = originalMessage.content;
|
|
178
|
+
if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {
|
|
179
|
+
continue;
|
|
103
180
|
}
|
|
181
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
182
|
+
updatedMessages[i] = message;
|
|
183
|
+
message.content = message.content.filter((block) => !isCachePoint(block));
|
|
104
184
|
}
|
|
105
185
|
return updatedMessages;
|
|
106
186
|
}
|
|
@@ -111,23 +191,37 @@ function stripBedrockCacheControl(messages) {
|
|
|
111
191
|
* Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
|
|
112
192
|
* then adds fresh cache points to the last 2 messages in a single backward pass.
|
|
113
193
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
194
|
+
* Returns a new array - only clones messages that require modification.
|
|
114
195
|
* @param messages - The array of message objects.
|
|
115
|
-
* @returns -
|
|
196
|
+
* @returns - A new array of message objects with cache points added.
|
|
116
197
|
*/
|
|
117
198
|
function addBedrockCacheControl(messages) {
|
|
118
199
|
if (!Array.isArray(messages) || messages.length < 2) {
|
|
119
200
|
return messages;
|
|
120
201
|
}
|
|
121
|
-
const updatedMessages = messages
|
|
202
|
+
const updatedMessages = [...messages];
|
|
122
203
|
let messagesModified = 0;
|
|
123
204
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
|
124
|
-
const
|
|
125
|
-
const isToolMessage = 'getType' in
|
|
126
|
-
typeof
|
|
127
|
-
|
|
128
|
-
const content =
|
|
129
|
-
|
|
130
|
-
|
|
205
|
+
const originalMessage = updatedMessages[i];
|
|
206
|
+
const isToolMessage = 'getType' in originalMessage &&
|
|
207
|
+
typeof originalMessage.getType === 'function' &&
|
|
208
|
+
originalMessage.getType() === 'tool';
|
|
209
|
+
const content = originalMessage.content;
|
|
210
|
+
const hasArrayContent = Array.isArray(content);
|
|
211
|
+
const needsStripping = hasArrayContent &&
|
|
212
|
+
needsCacheStripping(content);
|
|
213
|
+
const isEmptyString = typeof content === 'string' && content === '';
|
|
214
|
+
const needsCacheAdd = messagesModified < 2 &&
|
|
215
|
+
!isToolMessage &&
|
|
216
|
+
!isEmptyString &&
|
|
217
|
+
(typeof content === 'string' || hasArrayContent);
|
|
218
|
+
if (!needsStripping && !needsCacheAdd) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
222
|
+
updatedMessages[i] = message;
|
|
223
|
+
if (hasArrayContent) {
|
|
224
|
+
message.content = message.content.filter((block) => !isCachePoint(block));
|
|
131
225
|
for (let j = 0; j < message.content.length; j++) {
|
|
132
226
|
const block = message.content[j];
|
|
133
227
|
if ('cache_control' in block) {
|
|
@@ -135,15 +229,12 @@ function addBedrockCacheControl(messages) {
|
|
|
135
229
|
}
|
|
136
230
|
}
|
|
137
231
|
}
|
|
138
|
-
if (messagesModified >= 2 || isToolMessage) {
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (typeof content === 'string' && content === '') {
|
|
232
|
+
if (messagesModified >= 2 || isToolMessage || isEmptyString) {
|
|
142
233
|
continue;
|
|
143
234
|
}
|
|
144
|
-
if (typeof content === 'string') {
|
|
235
|
+
if (typeof message.content === 'string') {
|
|
145
236
|
message.content = [
|
|
146
|
-
{ type: _enum.ContentTypes.TEXT, text: content },
|
|
237
|
+
{ type: _enum.ContentTypes.TEXT, text: message.content },
|
|
147
238
|
{ cachePoint: { type: 'default' } },
|
|
148
239
|
];
|
|
149
240
|
messagesModified++;
|
|
@@ -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 * 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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"}
|
|
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 * Deep clones a message's content to prevent mutation of the original.\n * Handles both string and array content types.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Creates a shallow clone of a message with deep-cloned content.\n * This ensures modifications to content don't affect the original message.\n */\nfunction cloneMessageWithContent<T extends MessageWithContent>(message: T): T {\n if (message.content === undefined) {\n return { ...message };\n }\n return {\n ...message,\n content: deepCloneContent(message.content),\n };\n}\n\n/**\n * Checks if a message's content needs cache control stripping.\n * Returns true if content has cachePoint blocks or cache_control fields.\n */\nfunction needsCacheStripping(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n const block = content[i];\n if (isCachePoint(block)) return true;\n if ('cache_control' in block) return true;\n }\n return false;\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 * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new 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: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n\n const hasArrayContent = Array.isArray(originalMessage.content);\n const needsStripping =\n hasArrayContent &&\n needsCacheStripping(originalMessage.content as MessageContentComplex[]);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n (typeof originalMessage.content === 'string' || hasArrayContent);\n\n if (!needsStripping && !needsCacheAdd) {\n continue;\n }\n\n const message = cloneMessageWithContent(\n originalMessage as MessageWithContent\n ) as T;\n updatedMessages[i] = message;\n\n if (hasArrayContent) {\n message.content = (message.content as MessageContentComplex[]).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof message.content;\n\n for (\n let j = 0;\n j < (message.content as MessageContentComplex[]).length;\n j++\n ) {\n const block = (message.content as MessageContentComplex[])[j] as Record<\n string,\n unknown\n >;\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 * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\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: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const message = cloneMessageWithContent(originalMessage);\n updatedMessages[i] = message;\n\n for (\n let j = 0;\n j < (message.content as MessageContentComplex[]).length;\n j++\n ) {\n const block = (message.content as MessageContentComplex[])[j] as Record<\n string,\n unknown\n >;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\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: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const message = cloneMessageWithContent(originalMessage);\n updatedMessages[i] = message;\n\n message.content = (message.content as MessageContentComplex[]).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof content;\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 * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new 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];\n let messagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const isToolMessage =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function' &&\n originalMessage.getType() === 'tool';\n\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const needsStripping =\n hasArrayContent &&\n needsCacheStripping(content as MessageContentComplex[]);\n const isEmptyString = typeof content === 'string' && content === '';\n const needsCacheAdd =\n messagesModified < 2 &&\n !isToolMessage &&\n !isEmptyString &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsStripping && !needsCacheAdd) {\n continue;\n }\n\n const message = cloneMessageWithContent(originalMessage);\n updatedMessages[i] = message;\n\n if (hasArrayContent) {\n message.content = (message.content as MessageContentComplex[]).filter(\n (block) => !isCachePoint(block)\n ) as typeof content;\n\n for (\n let j = 0;\n j < (message.content as MessageContentComplex[]).length;\n j++\n ) {\n const block = (message.content as MessageContentComplex[])[j] as Record<\n string,\n unknown\n >;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (messagesModified >= 2 || isToolMessage || isEmptyString) {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n { type: ContentTypes.TEXT, text: message.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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"}
|
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
import { ContentTypes } from '../common/enum.mjs';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Deep clones a message's content to prevent mutation of the original.
|
|
5
|
+
* Handles both string and array content types.
|
|
6
|
+
*/
|
|
7
|
+
function deepCloneContent(content) {
|
|
8
|
+
if (typeof content === 'string') {
|
|
9
|
+
return content;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(content)) {
|
|
12
|
+
return content.map((block) => ({ ...block }));
|
|
13
|
+
}
|
|
14
|
+
return content;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Creates a shallow clone of a message with deep-cloned content.
|
|
18
|
+
* This ensures modifications to content don't affect the original message.
|
|
19
|
+
*/
|
|
20
|
+
function cloneMessageWithContent(message) {
|
|
21
|
+
if (message.content === undefined) {
|
|
22
|
+
return { ...message };
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
...message,
|
|
26
|
+
content: deepCloneContent(message.content),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Checks if a message's content needs cache control stripping.
|
|
31
|
+
* Returns true if content has cachePoint blocks or cache_control fields.
|
|
32
|
+
*/
|
|
33
|
+
function needsCacheStripping(content) {
|
|
34
|
+
for (let i = 0; i < content.length; i++) {
|
|
35
|
+
const block = content[i];
|
|
36
|
+
if (isCachePoint(block))
|
|
37
|
+
return true;
|
|
38
|
+
if ('cache_control' in block)
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
3
43
|
/**
|
|
4
44
|
* Anthropic API: Adds cache control to the appropriate user messages in the payload.
|
|
5
45
|
* Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
|
|
6
46
|
* then adds fresh cache control to the last 2 user messages in a single backward pass.
|
|
7
47
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
48
|
+
* Returns a new array - only clones messages that require modification.
|
|
8
49
|
* @param messages - The array of message objects.
|
|
9
|
-
* @returns -
|
|
50
|
+
* @returns - A new array of message objects with cache control added.
|
|
10
51
|
*/
|
|
11
52
|
function addCacheControl(messages) {
|
|
12
53
|
if (!Array.isArray(messages) || messages.length < 2) {
|
|
@@ -15,10 +56,21 @@ function addCacheControl(messages) {
|
|
|
15
56
|
const updatedMessages = [...messages];
|
|
16
57
|
let userMessagesModified = 0;
|
|
17
58
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
|
18
|
-
const
|
|
19
|
-
const isUserMessage = ('getType' in
|
|
20
|
-
('role' in
|
|
21
|
-
|
|
59
|
+
const originalMessage = updatedMessages[i];
|
|
60
|
+
const isUserMessage = ('getType' in originalMessage && originalMessage.getType() === 'human') ||
|
|
61
|
+
('role' in originalMessage && originalMessage.role === 'user');
|
|
62
|
+
const hasArrayContent = Array.isArray(originalMessage.content);
|
|
63
|
+
const needsStripping = hasArrayContent &&
|
|
64
|
+
needsCacheStripping(originalMessage.content);
|
|
65
|
+
const needsCacheAdd = userMessagesModified < 2 &&
|
|
66
|
+
isUserMessage &&
|
|
67
|
+
(typeof originalMessage.content === 'string' || hasArrayContent);
|
|
68
|
+
if (!needsStripping && !needsCacheAdd) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
72
|
+
updatedMessages[i] = message;
|
|
73
|
+
if (hasArrayContent) {
|
|
22
74
|
message.content = message.content.filter((block) => !isCachePoint(block));
|
|
23
75
|
for (let j = 0; j < message.content.length; j++) {
|
|
24
76
|
const block = message.content[j];
|
|
@@ -61,9 +113,20 @@ function addCacheControl(messages) {
|
|
|
61
113
|
function isCachePoint(block) {
|
|
62
114
|
return 'cachePoint' in block && !('type' in block);
|
|
63
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Checks if a message's content has Anthropic cache_control fields.
|
|
118
|
+
*/
|
|
119
|
+
function hasAnthropicCacheControl(content) {
|
|
120
|
+
for (let i = 0; i < content.length; i++) {
|
|
121
|
+
if ('cache_control' in content[i])
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
64
126
|
/**
|
|
65
127
|
* Removes all Anthropic cache_control fields from messages
|
|
66
128
|
* Used when switching from Anthropic to Bedrock provider
|
|
129
|
+
* Returns a new array - only clones messages that require modification.
|
|
67
130
|
*/
|
|
68
131
|
function stripAnthropicCacheControl(messages) {
|
|
69
132
|
if (!Array.isArray(messages)) {
|
|
@@ -71,22 +134,36 @@ function stripAnthropicCacheControl(messages) {
|
|
|
71
134
|
}
|
|
72
135
|
const updatedMessages = [...messages];
|
|
73
136
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
74
|
-
const
|
|
75
|
-
const content =
|
|
76
|
-
if (Array.isArray(content)) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
137
|
+
const originalMessage = updatedMessages[i];
|
|
138
|
+
const content = originalMessage.content;
|
|
139
|
+
if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
143
|
+
updatedMessages[i] = message;
|
|
144
|
+
for (let j = 0; j < message.content.length; j++) {
|
|
145
|
+
const block = message.content[j];
|
|
146
|
+
if ('cache_control' in block) {
|
|
147
|
+
delete block.cache_control;
|
|
82
148
|
}
|
|
83
149
|
}
|
|
84
150
|
}
|
|
85
151
|
return updatedMessages;
|
|
86
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Checks if a message's content has Bedrock cachePoint blocks.
|
|
155
|
+
*/
|
|
156
|
+
function hasBedrockCachePoint(content) {
|
|
157
|
+
for (let i = 0; i < content.length; i++) {
|
|
158
|
+
if (isCachePoint(content[i]))
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
87
163
|
/**
|
|
88
164
|
* Removes all Bedrock cachePoint blocks from messages
|
|
89
165
|
* Used when switching from Bedrock to Anthropic provider
|
|
166
|
+
* Returns a new array - only clones messages that require modification.
|
|
90
167
|
*/
|
|
91
168
|
function stripBedrockCacheControl(messages) {
|
|
92
169
|
if (!Array.isArray(messages)) {
|
|
@@ -94,11 +171,14 @@ function stripBedrockCacheControl(messages) {
|
|
|
94
171
|
}
|
|
95
172
|
const updatedMessages = [...messages];
|
|
96
173
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
97
|
-
const
|
|
98
|
-
const content =
|
|
99
|
-
if (Array.isArray(content)) {
|
|
100
|
-
|
|
174
|
+
const originalMessage = updatedMessages[i];
|
|
175
|
+
const content = originalMessage.content;
|
|
176
|
+
if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {
|
|
177
|
+
continue;
|
|
101
178
|
}
|
|
179
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
180
|
+
updatedMessages[i] = message;
|
|
181
|
+
message.content = message.content.filter((block) => !isCachePoint(block));
|
|
102
182
|
}
|
|
103
183
|
return updatedMessages;
|
|
104
184
|
}
|
|
@@ -109,23 +189,37 @@ function stripBedrockCacheControl(messages) {
|
|
|
109
189
|
* Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
|
|
110
190
|
* then adds fresh cache points to the last 2 messages in a single backward pass.
|
|
111
191
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
192
|
+
* Returns a new array - only clones messages that require modification.
|
|
112
193
|
* @param messages - The array of message objects.
|
|
113
|
-
* @returns -
|
|
194
|
+
* @returns - A new array of message objects with cache points added.
|
|
114
195
|
*/
|
|
115
196
|
function addBedrockCacheControl(messages) {
|
|
116
197
|
if (!Array.isArray(messages) || messages.length < 2) {
|
|
117
198
|
return messages;
|
|
118
199
|
}
|
|
119
|
-
const updatedMessages = messages
|
|
200
|
+
const updatedMessages = [...messages];
|
|
120
201
|
let messagesModified = 0;
|
|
121
202
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
|
122
|
-
const
|
|
123
|
-
const isToolMessage = 'getType' in
|
|
124
|
-
typeof
|
|
125
|
-
|
|
126
|
-
const content =
|
|
127
|
-
|
|
128
|
-
|
|
203
|
+
const originalMessage = updatedMessages[i];
|
|
204
|
+
const isToolMessage = 'getType' in originalMessage &&
|
|
205
|
+
typeof originalMessage.getType === 'function' &&
|
|
206
|
+
originalMessage.getType() === 'tool';
|
|
207
|
+
const content = originalMessage.content;
|
|
208
|
+
const hasArrayContent = Array.isArray(content);
|
|
209
|
+
const needsStripping = hasArrayContent &&
|
|
210
|
+
needsCacheStripping(content);
|
|
211
|
+
const isEmptyString = typeof content === 'string' && content === '';
|
|
212
|
+
const needsCacheAdd = messagesModified < 2 &&
|
|
213
|
+
!isToolMessage &&
|
|
214
|
+
!isEmptyString &&
|
|
215
|
+
(typeof content === 'string' || hasArrayContent);
|
|
216
|
+
if (!needsStripping && !needsCacheAdd) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
220
|
+
updatedMessages[i] = message;
|
|
221
|
+
if (hasArrayContent) {
|
|
222
|
+
message.content = message.content.filter((block) => !isCachePoint(block));
|
|
129
223
|
for (let j = 0; j < message.content.length; j++) {
|
|
130
224
|
const block = message.content[j];
|
|
131
225
|
if ('cache_control' in block) {
|
|
@@ -133,15 +227,12 @@ function addBedrockCacheControl(messages) {
|
|
|
133
227
|
}
|
|
134
228
|
}
|
|
135
229
|
}
|
|
136
|
-
if (messagesModified >= 2 || isToolMessage) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
if (typeof content === 'string' && content === '') {
|
|
230
|
+
if (messagesModified >= 2 || isToolMessage || isEmptyString) {
|
|
140
231
|
continue;
|
|
141
232
|
}
|
|
142
|
-
if (typeof content === 'string') {
|
|
233
|
+
if (typeof message.content === 'string') {
|
|
143
234
|
message.content = [
|
|
144
|
-
{ type: ContentTypes.TEXT, text: content },
|
|
235
|
+
{ type: ContentTypes.TEXT, text: message.content },
|
|
145
236
|
{ cachePoint: { type: 'default' } },
|
|
146
237
|
];
|
|
147
238
|
messagesModified++;
|
|
@@ -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 * 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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"}
|
|
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 * Deep clones a message's content to prevent mutation of the original.\n * Handles both string and array content types.\n */\nfunction deepCloneContent<T extends string | MessageContentComplex[]>(\n content: T\n): T {\n if (typeof content === 'string') {\n return content;\n }\n if (Array.isArray(content)) {\n return content.map((block) => ({ ...block })) as T;\n }\n return content;\n}\n\n/**\n * Creates a shallow clone of a message with deep-cloned content.\n * This ensures modifications to content don't affect the original message.\n */\nfunction cloneMessageWithContent<T extends MessageWithContent>(message: T): T {\n if (message.content === undefined) {\n return { ...message };\n }\n return {\n ...message,\n content: deepCloneContent(message.content),\n };\n}\n\n/**\n * Checks if a message's content needs cache control stripping.\n * Returns true if content has cachePoint blocks or cache_control fields.\n */\nfunction needsCacheStripping(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n const block = content[i];\n if (isCachePoint(block)) return true;\n if ('cache_control' in block) return true;\n }\n return false;\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 * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new 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: T[] = [...messages];\n let userMessagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const isUserMessage =\n ('getType' in originalMessage && originalMessage.getType() === 'human') ||\n ('role' in originalMessage && originalMessage.role === 'user');\n\n const hasArrayContent = Array.isArray(originalMessage.content);\n const needsStripping =\n hasArrayContent &&\n needsCacheStripping(originalMessage.content as MessageContentComplex[]);\n const needsCacheAdd =\n userMessagesModified < 2 &&\n isUserMessage &&\n (typeof originalMessage.content === 'string' || hasArrayContent);\n\n if (!needsStripping && !needsCacheAdd) {\n continue;\n }\n\n const message = cloneMessageWithContent(\n originalMessage as MessageWithContent\n ) as T;\n updatedMessages[i] = message;\n\n if (hasArrayContent) {\n message.content = (message.content as MessageContentComplex[]).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof message.content;\n\n for (\n let j = 0;\n j < (message.content as MessageContentComplex[]).length;\n j++\n ) {\n const block = (message.content as MessageContentComplex[])[j] as Record<\n string,\n unknown\n >;\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 * Checks if a message's content has Anthropic cache_control fields.\n */\nfunction hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if ('cache_control' in content[i]) return true;\n }\n return false;\n}\n\n/**\n * Removes all Anthropic cache_control fields from messages\n * Used when switching from Anthropic to Bedrock provider\n * Returns a new array - only clones messages that require modification.\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: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {\n continue;\n }\n\n const message = cloneMessageWithContent(originalMessage);\n updatedMessages[i] = message;\n\n for (\n let j = 0;\n j < (message.content as MessageContentComplex[]).length;\n j++\n ) {\n const block = (message.content as MessageContentComplex[])[j] as Record<\n string,\n unknown\n >;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n return updatedMessages;\n}\n\n/**\n * Checks if a message's content has Bedrock cachePoint blocks.\n */\nfunction hasBedrockCachePoint(content: MessageContentComplex[]): boolean {\n for (let i = 0; i < content.length; i++) {\n if (isCachePoint(content[i])) return true;\n }\n return false;\n}\n\n/**\n * Removes all Bedrock cachePoint blocks from messages\n * Used when switching from Bedrock to Anthropic provider\n * Returns a new array - only clones messages that require modification.\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: T[] = [...messages];\n\n for (let i = 0; i < updatedMessages.length; i++) {\n const originalMessage = updatedMessages[i];\n const content = originalMessage.content;\n\n if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {\n continue;\n }\n\n const message = cloneMessageWithContent(originalMessage);\n updatedMessages[i] = message;\n\n message.content = (message.content as MessageContentComplex[]).filter(\n (block) => !isCachePoint(block as MessageContentComplex)\n ) as typeof content;\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 * Returns a new array - only clones messages that require modification.\n * @param messages - The array of message objects.\n * @returns - A new 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];\n let messagesModified = 0;\n\n for (let i = updatedMessages.length - 1; i >= 0; i--) {\n const originalMessage = updatedMessages[i];\n const isToolMessage =\n 'getType' in originalMessage &&\n typeof originalMessage.getType === 'function' &&\n originalMessage.getType() === 'tool';\n\n const content = originalMessage.content;\n const hasArrayContent = Array.isArray(content);\n const needsStripping =\n hasArrayContent &&\n needsCacheStripping(content as MessageContentComplex[]);\n const isEmptyString = typeof content === 'string' && content === '';\n const needsCacheAdd =\n messagesModified < 2 &&\n !isToolMessage &&\n !isEmptyString &&\n (typeof content === 'string' || hasArrayContent);\n\n if (!needsStripping && !needsCacheAdd) {\n continue;\n }\n\n const message = cloneMessageWithContent(originalMessage);\n updatedMessages[i] = message;\n\n if (hasArrayContent) {\n message.content = (message.content as MessageContentComplex[]).filter(\n (block) => !isCachePoint(block)\n ) as typeof content;\n\n for (\n let j = 0;\n j < (message.content as MessageContentComplex[]).length;\n j++\n ) {\n const block = (message.content as MessageContentComplex[])[j] as Record<\n string,\n unknown\n >;\n if ('cache_control' in block) {\n delete block.cache_control;\n }\n }\n }\n\n if (messagesModified >= 2 || isToolMessage || isEmptyString) {\n continue;\n }\n\n if (typeof message.content === 'string') {\n message.content = [\n { type: ContentTypes.TEXT, text: message.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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"}
|
|
@@ -8,18 +8,21 @@ type MessageWithContent = {
|
|
|
8
8
|
* Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
|
|
9
9
|
* then adds fresh cache control to the last 2 user messages in a single backward pass.
|
|
10
10
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
11
|
+
* Returns a new array - only clones messages that require modification.
|
|
11
12
|
* @param messages - The array of message objects.
|
|
12
|
-
* @returns -
|
|
13
|
+
* @returns - A new array of message objects with cache control added.
|
|
13
14
|
*/
|
|
14
15
|
export declare function addCacheControl<T extends AnthropicMessage | BaseMessage>(messages: T[]): T[];
|
|
15
16
|
/**
|
|
16
17
|
* Removes all Anthropic cache_control fields from messages
|
|
17
18
|
* Used when switching from Anthropic to Bedrock provider
|
|
19
|
+
* Returns a new array - only clones messages that require modification.
|
|
18
20
|
*/
|
|
19
21
|
export declare function stripAnthropicCacheControl<T extends MessageWithContent>(messages: T[]): T[];
|
|
20
22
|
/**
|
|
21
23
|
* Removes all Bedrock cachePoint blocks from messages
|
|
22
24
|
* Used when switching from Bedrock to Anthropic provider
|
|
25
|
+
* Returns a new array - only clones messages that require modification.
|
|
23
26
|
*/
|
|
24
27
|
export declare function stripBedrockCacheControl<T extends MessageWithContent>(messages: T[]): T[];
|
|
25
28
|
/**
|
|
@@ -29,8 +32,9 @@ export declare function stripBedrockCacheControl<T extends MessageWithContent>(m
|
|
|
29
32
|
* Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
|
|
30
33
|
* then adds fresh cache points to the last 2 messages in a single backward pass.
|
|
31
34
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
35
|
+
* Returns a new array - only clones messages that require modification.
|
|
32
36
|
* @param messages - The array of message objects.
|
|
33
|
-
* @returns -
|
|
37
|
+
* @returns - A new array of message objects with cache points added.
|
|
34
38
|
*/
|
|
35
39
|
export declare function addBedrockCacheControl<T extends Partial<BaseMessage> & MessageWithContent>(messages: T[]): T[];
|
|
36
40
|
export {};
|
package/package.json
CHANGED
|
@@ -835,6 +835,221 @@ 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
|
+
|
|
925
|
+
describe('Immutability - addBedrockCacheControl does not mutate original messages', () => {
|
|
926
|
+
it('should not mutate original messages when adding cache points to string content', () => {
|
|
927
|
+
const originalMessages: TestMsg[] = [
|
|
928
|
+
{ role: 'user', content: 'Hello' },
|
|
929
|
+
{ role: 'assistant', content: 'Hi there' },
|
|
930
|
+
];
|
|
931
|
+
|
|
932
|
+
const originalFirstContent = originalMessages[0].content;
|
|
933
|
+
const originalSecondContent = originalMessages[1].content;
|
|
934
|
+
|
|
935
|
+
const result = addBedrockCacheControl(originalMessages);
|
|
936
|
+
|
|
937
|
+
expect(originalMessages[0].content).toBe(originalFirstContent);
|
|
938
|
+
expect(originalMessages[1].content).toBe(originalSecondContent);
|
|
939
|
+
expect(typeof originalMessages[0].content).toBe('string');
|
|
940
|
+
expect(typeof originalMessages[1].content).toBe('string');
|
|
941
|
+
|
|
942
|
+
expect(Array.isArray(result[0].content)).toBe(true);
|
|
943
|
+
expect(Array.isArray(result[1].content)).toBe(true);
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
it('should not mutate original messages when adding cache points to array content', () => {
|
|
947
|
+
const originalMessages: TestMsg[] = [
|
|
948
|
+
{
|
|
949
|
+
role: 'user',
|
|
950
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hello' }],
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
role: 'assistant',
|
|
954
|
+
content: [{ type: ContentTypes.TEXT, text: 'Hi there' }],
|
|
955
|
+
},
|
|
956
|
+
];
|
|
957
|
+
|
|
958
|
+
const originalFirstContentLength = (
|
|
959
|
+
originalMessages[0].content as MessageContentComplex[]
|
|
960
|
+
).length;
|
|
961
|
+
const originalSecondContentLength = (
|
|
962
|
+
originalMessages[1].content as MessageContentComplex[]
|
|
963
|
+
).length;
|
|
964
|
+
|
|
965
|
+
const result = addBedrockCacheControl(originalMessages);
|
|
966
|
+
|
|
967
|
+
const firstContent = originalMessages[0].content as MessageContentComplex[];
|
|
968
|
+
const secondContent = originalMessages[1]
|
|
969
|
+
.content as MessageContentComplex[];
|
|
970
|
+
|
|
971
|
+
expect(firstContent.length).toBe(originalFirstContentLength);
|
|
972
|
+
expect(secondContent.length).toBe(originalSecondContentLength);
|
|
973
|
+
expect(firstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
974
|
+
expect(secondContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
975
|
+
|
|
976
|
+
const resultFirstContent = result[0].content as MessageContentComplex[];
|
|
977
|
+
const resultSecondContent = result[1].content as MessageContentComplex[];
|
|
978
|
+
expect(resultFirstContent.length).toBe(originalFirstContentLength + 1);
|
|
979
|
+
expect(resultSecondContent.length).toBe(originalSecondContentLength + 1);
|
|
980
|
+
expect(resultFirstContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
981
|
+
expect(resultSecondContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
it('should not mutate original messages when stripping existing cache control', () => {
|
|
985
|
+
const originalMessages: TestMsg[] = [
|
|
986
|
+
{
|
|
987
|
+
role: 'user',
|
|
988
|
+
content: [
|
|
989
|
+
{
|
|
990
|
+
type: ContentTypes.TEXT,
|
|
991
|
+
text: 'Hello',
|
|
992
|
+
cache_control: { type: 'ephemeral' },
|
|
993
|
+
} as MessageContentComplex,
|
|
994
|
+
],
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
role: 'assistant',
|
|
998
|
+
content: [
|
|
999
|
+
{ type: ContentTypes.TEXT, text: 'Hi there' },
|
|
1000
|
+
{ cachePoint: { type: 'default' } },
|
|
1001
|
+
],
|
|
1002
|
+
},
|
|
1003
|
+
];
|
|
1004
|
+
|
|
1005
|
+
const originalFirstBlock = (
|
|
1006
|
+
originalMessages[0].content as MessageContentComplex[]
|
|
1007
|
+
)[0];
|
|
1008
|
+
const originalSecondContentLength = (
|
|
1009
|
+
originalMessages[1].content as MessageContentComplex[]
|
|
1010
|
+
).length;
|
|
1011
|
+
|
|
1012
|
+
addBedrockCacheControl(originalMessages);
|
|
1013
|
+
|
|
1014
|
+
expect('cache_control' in originalFirstBlock).toBe(true);
|
|
1015
|
+
expect(
|
|
1016
|
+
(originalMessages[1].content as MessageContentComplex[]).length
|
|
1017
|
+
).toBe(originalSecondContentLength);
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
it('should allow different providers to process same messages without cross-contamination', () => {
|
|
1021
|
+
const sharedMessages: TestMsg[] = [
|
|
1022
|
+
{
|
|
1023
|
+
role: 'user',
|
|
1024
|
+
content: [{ type: ContentTypes.TEXT, text: 'Shared message 1' }],
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
role: 'assistant',
|
|
1028
|
+
content: [{ type: ContentTypes.TEXT, text: 'Shared response 1' }],
|
|
1029
|
+
},
|
|
1030
|
+
];
|
|
1031
|
+
|
|
1032
|
+
const bedrockResult = addBedrockCacheControl(sharedMessages);
|
|
1033
|
+
|
|
1034
|
+
const anthropicResult = addCacheControl(sharedMessages as never);
|
|
1035
|
+
|
|
1036
|
+
const originalFirstContent = sharedMessages[0]
|
|
1037
|
+
.content as MessageContentComplex[];
|
|
1038
|
+
expect(originalFirstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1039
|
+
expect('cache_control' in originalFirstContent[0]).toBe(false);
|
|
1040
|
+
|
|
1041
|
+
const bedrockFirstContent = bedrockResult[0]
|
|
1042
|
+
.content as MessageContentComplex[];
|
|
1043
|
+
expect(bedrockFirstContent.some((b) => 'cachePoint' in b)).toBe(true);
|
|
1044
|
+
expect('cache_control' in bedrockFirstContent[0]).toBe(false);
|
|
1045
|
+
|
|
1046
|
+
const anthropicFirstContent = anthropicResult[0]
|
|
1047
|
+
.content as MessageContentComplex[];
|
|
1048
|
+
expect(anthropicFirstContent.some((b) => 'cachePoint' in b)).toBe(false);
|
|
1049
|
+
expect('cache_control' in anthropicFirstContent[0]).toBe(true);
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
|
|
838
1053
|
describe('Multi-turn cache cleanup', () => {
|
|
839
1054
|
it('strips stale Bedrock cache points from previous turns before applying new ones', () => {
|
|
840
1055
|
const messages: TestMsg[] = [
|
package/src/messages/cache.ts
CHANGED
|
@@ -7,13 +7,57 @@ type MessageWithContent = {
|
|
|
7
7
|
content?: string | MessageContentComplex[];
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Deep clones a message's content to prevent mutation of the original.
|
|
12
|
+
* Handles both string and array content types.
|
|
13
|
+
*/
|
|
14
|
+
function deepCloneContent<T extends string | MessageContentComplex[]>(
|
|
15
|
+
content: T
|
|
16
|
+
): T {
|
|
17
|
+
if (typeof content === 'string') {
|
|
18
|
+
return content;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(content)) {
|
|
21
|
+
return content.map((block) => ({ ...block })) as T;
|
|
22
|
+
}
|
|
23
|
+
return content;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Creates a shallow clone of a message with deep-cloned content.
|
|
28
|
+
* This ensures modifications to content don't affect the original message.
|
|
29
|
+
*/
|
|
30
|
+
function cloneMessageWithContent<T extends MessageWithContent>(message: T): T {
|
|
31
|
+
if (message.content === undefined) {
|
|
32
|
+
return { ...message };
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
...message,
|
|
36
|
+
content: deepCloneContent(message.content),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Checks if a message's content needs cache control stripping.
|
|
42
|
+
* Returns true if content has cachePoint blocks or cache_control fields.
|
|
43
|
+
*/
|
|
44
|
+
function needsCacheStripping(content: MessageContentComplex[]): boolean {
|
|
45
|
+
for (let i = 0; i < content.length; i++) {
|
|
46
|
+
const block = content[i];
|
|
47
|
+
if (isCachePoint(block)) return true;
|
|
48
|
+
if ('cache_control' in block) return true;
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
10
53
|
/**
|
|
11
54
|
* Anthropic API: Adds cache control to the appropriate user messages in the payload.
|
|
12
55
|
* Strips ALL existing cache control (both Anthropic and Bedrock formats) from all messages,
|
|
13
56
|
* then adds fresh cache control to the last 2 user messages in a single backward pass.
|
|
14
57
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
58
|
+
* Returns a new array - only clones messages that require modification.
|
|
15
59
|
* @param messages - The array of message objects.
|
|
16
|
-
* @returns -
|
|
60
|
+
* @returns - A new array of message objects with cache control added.
|
|
17
61
|
*/
|
|
18
62
|
export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
|
|
19
63
|
messages: T[]
|
|
@@ -22,22 +66,47 @@ export function addCacheControl<T extends AnthropicMessage | BaseMessage>(
|
|
|
22
66
|
return messages;
|
|
23
67
|
}
|
|
24
68
|
|
|
25
|
-
const updatedMessages = [...messages];
|
|
69
|
+
const updatedMessages: T[] = [...messages];
|
|
26
70
|
let userMessagesModified = 0;
|
|
27
71
|
|
|
28
72
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
|
29
|
-
const
|
|
73
|
+
const originalMessage = updatedMessages[i];
|
|
30
74
|
const isUserMessage =
|
|
31
|
-
('getType' in
|
|
32
|
-
('role' in
|
|
75
|
+
('getType' in originalMessage && originalMessage.getType() === 'human') ||
|
|
76
|
+
('role' in originalMessage && originalMessage.role === 'user');
|
|
77
|
+
|
|
78
|
+
const hasArrayContent = Array.isArray(originalMessage.content);
|
|
79
|
+
const needsStripping =
|
|
80
|
+
hasArrayContent &&
|
|
81
|
+
needsCacheStripping(originalMessage.content as MessageContentComplex[]);
|
|
82
|
+
const needsCacheAdd =
|
|
83
|
+
userMessagesModified < 2 &&
|
|
84
|
+
isUserMessage &&
|
|
85
|
+
(typeof originalMessage.content === 'string' || hasArrayContent);
|
|
86
|
+
|
|
87
|
+
if (!needsStripping && !needsCacheAdd) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
33
90
|
|
|
34
|
-
|
|
35
|
-
|
|
91
|
+
const message = cloneMessageWithContent(
|
|
92
|
+
originalMessage as MessageWithContent
|
|
93
|
+
) as T;
|
|
94
|
+
updatedMessages[i] = message;
|
|
95
|
+
|
|
96
|
+
if (hasArrayContent) {
|
|
97
|
+
message.content = (message.content as MessageContentComplex[]).filter(
|
|
36
98
|
(block) => !isCachePoint(block as MessageContentComplex)
|
|
37
99
|
) as typeof message.content;
|
|
38
100
|
|
|
39
|
-
for (
|
|
40
|
-
|
|
101
|
+
for (
|
|
102
|
+
let j = 0;
|
|
103
|
+
j < (message.content as MessageContentComplex[]).length;
|
|
104
|
+
j++
|
|
105
|
+
) {
|
|
106
|
+
const block = (message.content as MessageContentComplex[])[j] as Record<
|
|
107
|
+
string,
|
|
108
|
+
unknown
|
|
109
|
+
>;
|
|
41
110
|
if ('cache_control' in block) {
|
|
42
111
|
delete block.cache_control;
|
|
43
112
|
}
|
|
@@ -81,9 +150,20 @@ function isCachePoint(block: MessageContentComplex): boolean {
|
|
|
81
150
|
return 'cachePoint' in block && !('type' in block);
|
|
82
151
|
}
|
|
83
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Checks if a message's content has Anthropic cache_control fields.
|
|
155
|
+
*/
|
|
156
|
+
function hasAnthropicCacheControl(content: MessageContentComplex[]): boolean {
|
|
157
|
+
for (let i = 0; i < content.length; i++) {
|
|
158
|
+
if ('cache_control' in content[i]) return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
84
163
|
/**
|
|
85
164
|
* Removes all Anthropic cache_control fields from messages
|
|
86
165
|
* Used when switching from Anthropic to Bedrock provider
|
|
166
|
+
* Returns a new array - only clones messages that require modification.
|
|
87
167
|
*/
|
|
88
168
|
export function stripAnthropicCacheControl<T extends MessageWithContent>(
|
|
89
169
|
messages: T[]
|
|
@@ -92,18 +172,30 @@ export function stripAnthropicCacheControl<T extends MessageWithContent>(
|
|
|
92
172
|
return messages;
|
|
93
173
|
}
|
|
94
174
|
|
|
95
|
-
const updatedMessages = [...messages];
|
|
175
|
+
const updatedMessages: T[] = [...messages];
|
|
96
176
|
|
|
97
177
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
98
|
-
const
|
|
99
|
-
const content =
|
|
178
|
+
const originalMessage = updatedMessages[i];
|
|
179
|
+
const content = originalMessage.content;
|
|
100
180
|
|
|
101
|
-
if (Array.isArray(content)) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
181
|
+
if (!Array.isArray(content) || !hasAnthropicCacheControl(content)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
186
|
+
updatedMessages[i] = message;
|
|
187
|
+
|
|
188
|
+
for (
|
|
189
|
+
let j = 0;
|
|
190
|
+
j < (message.content as MessageContentComplex[]).length;
|
|
191
|
+
j++
|
|
192
|
+
) {
|
|
193
|
+
const block = (message.content as MessageContentComplex[])[j] as Record<
|
|
194
|
+
string,
|
|
195
|
+
unknown
|
|
196
|
+
>;
|
|
197
|
+
if ('cache_control' in block) {
|
|
198
|
+
delete block.cache_control;
|
|
107
199
|
}
|
|
108
200
|
}
|
|
109
201
|
}
|
|
@@ -111,9 +203,20 @@ export function stripAnthropicCacheControl<T extends MessageWithContent>(
|
|
|
111
203
|
return updatedMessages;
|
|
112
204
|
}
|
|
113
205
|
|
|
206
|
+
/**
|
|
207
|
+
* Checks if a message's content has Bedrock cachePoint blocks.
|
|
208
|
+
*/
|
|
209
|
+
function hasBedrockCachePoint(content: MessageContentComplex[]): boolean {
|
|
210
|
+
for (let i = 0; i < content.length; i++) {
|
|
211
|
+
if (isCachePoint(content[i])) return true;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
114
216
|
/**
|
|
115
217
|
* Removes all Bedrock cachePoint blocks from messages
|
|
116
218
|
* Used when switching from Bedrock to Anthropic provider
|
|
219
|
+
* Returns a new array - only clones messages that require modification.
|
|
117
220
|
*/
|
|
118
221
|
export function stripBedrockCacheControl<T extends MessageWithContent>(
|
|
119
222
|
messages: T[]
|
|
@@ -122,17 +225,22 @@ export function stripBedrockCacheControl<T extends MessageWithContent>(
|
|
|
122
225
|
return messages;
|
|
123
226
|
}
|
|
124
227
|
|
|
125
|
-
const updatedMessages = [...messages];
|
|
228
|
+
const updatedMessages: T[] = [...messages];
|
|
126
229
|
|
|
127
230
|
for (let i = 0; i < updatedMessages.length; i++) {
|
|
128
|
-
const
|
|
129
|
-
const content =
|
|
231
|
+
const originalMessage = updatedMessages[i];
|
|
232
|
+
const content = originalMessage.content;
|
|
130
233
|
|
|
131
|
-
if (Array.isArray(content)) {
|
|
132
|
-
|
|
133
|
-
(block) => !isCachePoint(block as MessageContentComplex)
|
|
134
|
-
) as typeof content;
|
|
234
|
+
if (!Array.isArray(content) || !hasBedrockCachePoint(content)) {
|
|
235
|
+
continue;
|
|
135
236
|
}
|
|
237
|
+
|
|
238
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
239
|
+
updatedMessages[i] = message;
|
|
240
|
+
|
|
241
|
+
message.content = (message.content as MessageContentComplex[]).filter(
|
|
242
|
+
(block) => !isCachePoint(block as MessageContentComplex)
|
|
243
|
+
) as typeof content;
|
|
136
244
|
}
|
|
137
245
|
|
|
138
246
|
return updatedMessages;
|
|
@@ -145,8 +253,9 @@ export function stripBedrockCacheControl<T extends MessageWithContent>(
|
|
|
145
253
|
* Strips ALL existing cache control (both Bedrock and Anthropic formats) from all messages,
|
|
146
254
|
* then adds fresh cache points to the last 2 messages in a single backward pass.
|
|
147
255
|
* This ensures we don't accumulate stale cache points across multiple turns.
|
|
256
|
+
* Returns a new array - only clones messages that require modification.
|
|
148
257
|
* @param messages - The array of message objects.
|
|
149
|
-
* @returns -
|
|
258
|
+
* @returns - A new array of message objects with cache points added.
|
|
150
259
|
*/
|
|
151
260
|
export function addBedrockCacheControl<
|
|
152
261
|
T extends Partial<BaseMessage> & MessageWithContent,
|
|
@@ -155,42 +264,62 @@ export function addBedrockCacheControl<
|
|
|
155
264
|
return messages;
|
|
156
265
|
}
|
|
157
266
|
|
|
158
|
-
const updatedMessages: T[] = messages
|
|
267
|
+
const updatedMessages: T[] = [...messages];
|
|
159
268
|
let messagesModified = 0;
|
|
160
269
|
|
|
161
270
|
for (let i = updatedMessages.length - 1; i >= 0; i--) {
|
|
162
|
-
const
|
|
271
|
+
const originalMessage = updatedMessages[i];
|
|
163
272
|
const isToolMessage =
|
|
164
|
-
'getType' in
|
|
165
|
-
typeof
|
|
166
|
-
|
|
273
|
+
'getType' in originalMessage &&
|
|
274
|
+
typeof originalMessage.getType === 'function' &&
|
|
275
|
+
originalMessage.getType() === 'tool';
|
|
276
|
+
|
|
277
|
+
const content = originalMessage.content;
|
|
278
|
+
const hasArrayContent = Array.isArray(content);
|
|
279
|
+
const needsStripping =
|
|
280
|
+
hasArrayContent &&
|
|
281
|
+
needsCacheStripping(content as MessageContentComplex[]);
|
|
282
|
+
const isEmptyString = typeof content === 'string' && content === '';
|
|
283
|
+
const needsCacheAdd =
|
|
284
|
+
messagesModified < 2 &&
|
|
285
|
+
!isToolMessage &&
|
|
286
|
+
!isEmptyString &&
|
|
287
|
+
(typeof content === 'string' || hasArrayContent);
|
|
288
|
+
|
|
289
|
+
if (!needsStripping && !needsCacheAdd) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
167
292
|
|
|
168
|
-
const
|
|
293
|
+
const message = cloneMessageWithContent(originalMessage);
|
|
294
|
+
updatedMessages[i] = message;
|
|
169
295
|
|
|
170
|
-
if (
|
|
171
|
-
message.content = content.filter(
|
|
296
|
+
if (hasArrayContent) {
|
|
297
|
+
message.content = (message.content as MessageContentComplex[]).filter(
|
|
172
298
|
(block) => !isCachePoint(block)
|
|
173
299
|
) as typeof content;
|
|
174
300
|
|
|
175
|
-
for (
|
|
176
|
-
|
|
301
|
+
for (
|
|
302
|
+
let j = 0;
|
|
303
|
+
j < (message.content as MessageContentComplex[]).length;
|
|
304
|
+
j++
|
|
305
|
+
) {
|
|
306
|
+
const block = (message.content as MessageContentComplex[])[j] as Record<
|
|
307
|
+
string,
|
|
308
|
+
unknown
|
|
309
|
+
>;
|
|
177
310
|
if ('cache_control' in block) {
|
|
178
311
|
delete block.cache_control;
|
|
179
312
|
}
|
|
180
313
|
}
|
|
181
314
|
}
|
|
182
315
|
|
|
183
|
-
if (messagesModified >= 2 || isToolMessage) {
|
|
316
|
+
if (messagesModified >= 2 || isToolMessage || isEmptyString) {
|
|
184
317
|
continue;
|
|
185
318
|
}
|
|
186
319
|
|
|
187
|
-
if (typeof content === 'string'
|
|
188
|
-
continue;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (typeof content === 'string') {
|
|
320
|
+
if (typeof message.content === 'string') {
|
|
192
321
|
message.content = [
|
|
193
|
-
{ type: ContentTypes.TEXT, text: content },
|
|
322
|
+
{ type: ContentTypes.TEXT, text: message.content },
|
|
194
323
|
{ cachePoint: { type: 'default' } },
|
|
195
324
|
] as MessageContentComplex[];
|
|
196
325
|
messagesModified++;
|