@librechat/agents 3.2.38 → 3.2.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/agents/AgentContext.cjs +25 -8
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +7 -4
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +20 -4
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +7 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/toolCache.cjs +5 -4
- package/dist/cjs/llm/bedrock/toolCache.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +34 -17
- package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +1 -0
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/toolCache.cjs +18 -5
- package/dist/cjs/llm/openrouter/toolCache.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/messages/anthropicToolCache.cjs +75 -13
- package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +91 -35
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/summarization/node.cjs +3 -2
- package/dist/cjs/summarization/node.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +26 -9
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +8 -5
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +20 -4
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +7 -1
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/toolCache.mjs +5 -4
- package/dist/esm/llm/bedrock/toolCache.mjs.map +1 -1
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs +34 -17
- package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +1 -0
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/toolCache.mjs +18 -5
- package/dist/esm/llm/openrouter/toolCache.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -2
- package/dist/esm/messages/anthropicToolCache.mjs +75 -13
- package/dist/esm/messages/anthropicToolCache.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +88 -36
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/summarization/node.mjs +4 -3
- package/dist/esm/summarization/node.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +11 -0
- package/dist/types/agents/__tests__/promptCacheLiveHelpers.d.ts +2 -0
- package/dist/types/llm/bedrock/index.d.ts +13 -0
- package/dist/types/llm/bedrock/toolCache.d.ts +2 -1
- package/dist/types/llm/openrouter/index.d.ts +8 -0
- package/dist/types/llm/openrouter/toolCache.d.ts +2 -1
- package/dist/types/messages/anthropicToolCache.d.ts +2 -1
- package/dist/types/messages/cache.d.ts +49 -5
- package/dist/types/types/llm.d.ts +14 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +64 -17
- package/src/agents/__tests__/AgentContext.anthropic.live.test.ts +6 -2
- package/src/agents/__tests__/AgentContext.bedrock.live.test.ts +7 -5
- package/src/agents/__tests__/AgentContext.openrouter.live.test.ts +1 -1
- package/src/agents/__tests__/AgentContext.test.ts +31 -19
- package/src/agents/__tests__/promptCacheLiveHelpers.ts +6 -2
- package/src/graphs/Graph.ts +40 -4
- package/src/llm/anthropic/utils/message_inputs.ts +33 -6
- package/src/llm/bedrock/index.ts +21 -1
- package/src/llm/bedrock/llm.spec.ts +61 -0
- package/src/llm/bedrock/toolCache.test.ts +24 -0
- package/src/llm/bedrock/toolCache.ts +12 -7
- package/src/llm/bedrock/utils/message_inputs.ts +57 -40
- package/src/llm/openrouter/index.ts +9 -0
- package/src/llm/openrouter/toolCache.test.ts +52 -1
- package/src/llm/openrouter/toolCache.ts +40 -6
- package/src/messages/__tests__/anthropicToolCache.test.ts +168 -0
- package/src/messages/anthropicToolCache.ts +118 -15
- package/src/messages/cache.test.ts +175 -0
- package/src/messages/cache.ts +133 -48
- package/src/summarization/node.ts +21 -2
- package/src/types/llm.ts +14 -0
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import type { GraphTools } from '@/types';
|
|
29
|
+
import {
|
|
30
|
+
buildAnthropicCacheControl,
|
|
31
|
+
type PromptCacheTtl,
|
|
32
|
+
} from '@/messages/cache';
|
|
29
33
|
|
|
30
34
|
const ANTHROPIC_BUILT_IN_TOOL_PREFIXES = [
|
|
31
35
|
'text_editor_',
|
|
@@ -41,8 +45,6 @@ const ANTHROPIC_BUILT_IN_TOOL_PREFIXES = [
|
|
|
41
45
|
'mcp_toolset',
|
|
42
46
|
] as const;
|
|
43
47
|
|
|
44
|
-
const CACHE_CONTROL = { type: 'ephemeral' as const };
|
|
45
|
-
|
|
46
48
|
type AnthropicToolCacheCandidate = {
|
|
47
49
|
name?: unknown;
|
|
48
50
|
type?: unknown;
|
|
@@ -60,29 +62,91 @@ function isAnthropicBuiltInTool(
|
|
|
60
62
|
);
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Whether a tool already carries a cache breakpoint. Built-ins use a direct
|
|
67
|
+
* `cache_control`; custom tools normally carry it under `extras`, but a
|
|
68
|
+
* caller-supplied Anthropic-native tool object can also put it directly on the
|
|
69
|
+
* block — check both so stale markers are never missed.
|
|
70
|
+
*/
|
|
63
71
|
function hasCacheControl(tool: AnthropicToolCacheCandidate): boolean {
|
|
64
72
|
if (isAnthropicBuiltInTool(tool)) {
|
|
65
73
|
return tool.cache_control != null;
|
|
66
74
|
}
|
|
67
|
-
return tool.extras?.cache_control != null;
|
|
75
|
+
return tool.cache_control != null || tool.extras?.cache_control != null;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Return the `cache_control` from the location that actually reaches the
|
|
80
|
+
* Anthropic payload for this tool's shape: directly on the block for
|
|
81
|
+
* provider-shaped tools (built-ins and raw Anthropic tools), or under `extras`
|
|
82
|
+
* for LangChain custom tools (the adapter promotes only that). Returns
|
|
83
|
+
* `undefined` when no marker sits in the effective location — a marker in the
|
|
84
|
+
* wrong place (e.g. a direct marker on a custom tool) does not count.
|
|
85
|
+
*/
|
|
86
|
+
function getEffectiveCacheControl(
|
|
87
|
+
tool: AnthropicToolCacheCandidate
|
|
88
|
+
): { ttl?: unknown } | undefined {
|
|
89
|
+
return (
|
|
90
|
+
isProviderShapedTool(tool) ? tool.cache_control : tool.extras?.cache_control
|
|
91
|
+
) as { ttl?: unknown } | undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Return a clone of `tool` with any `cache_control` removed — both the direct
|
|
96
|
+
* block marker and the `extras` marker — preserving the prototype chain. Used
|
|
97
|
+
* to clear stray markers off tools that must not anchor a competing breakpoint.
|
|
98
|
+
*/
|
|
99
|
+
function stripCacheControl(
|
|
71
100
|
tool: AnthropicToolCacheCandidate
|
|
72
101
|
): AnthropicToolCacheCandidate {
|
|
73
102
|
const prototype = Object.getPrototypeOf(tool) ?? Object.prototype;
|
|
74
|
-
|
|
103
|
+
const wrapped = { ...tool };
|
|
104
|
+
delete wrapped.cache_control;
|
|
105
|
+
if (wrapped.extras != null) {
|
|
106
|
+
wrapped.extras = { ...wrapped.extras };
|
|
107
|
+
delete wrapped.extras.cache_control;
|
|
108
|
+
}
|
|
109
|
+
return Object.assign(Object.create(prototype), wrapped);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Whether `tool` is already in the Anthropic provider payload shape — an
|
|
114
|
+
* Anthropic built-in or a raw Anthropic tool object (has `input_schema`). These
|
|
115
|
+
* carry `cache_control` directly on the block; the LangChain adapter does NOT
|
|
116
|
+
* promote `extras.cache_control` for them. LangChain StructuredTools, by
|
|
117
|
+
* contrast, expose the marker via `extras`.
|
|
118
|
+
*/
|
|
119
|
+
function isProviderShapedTool(tool: AnthropicToolCacheCandidate): boolean {
|
|
120
|
+
return (
|
|
121
|
+
isAnthropicBuiltInTool(tool) ||
|
|
122
|
+
'input_schema' in (tool as Record<string, unknown>)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function markCacheControl(
|
|
127
|
+
tool: AnthropicToolCacheCandidate,
|
|
128
|
+
ttl?: PromptCacheTtl
|
|
129
|
+
): AnthropicToolCacheCandidate {
|
|
130
|
+
const cacheControl = buildAnthropicCacheControl(ttl);
|
|
131
|
+
const prototype = Object.getPrototypeOf(tool) ?? Object.prototype;
|
|
132
|
+
if (isProviderShapedTool(tool)) {
|
|
133
|
+
// Built-ins and raw Anthropic tool objects carry cache_control directly on
|
|
134
|
+
// the block; `extras` is not promoted onto the payload for these shapes.
|
|
75
135
|
const wrapped = { ...tool };
|
|
76
136
|
delete wrapped.extras;
|
|
77
137
|
return Object.assign(Object.create(prototype), wrapped, {
|
|
78
|
-
cache_control:
|
|
138
|
+
cache_control: cacheControl,
|
|
79
139
|
});
|
|
80
140
|
}
|
|
81
141
|
|
|
82
|
-
|
|
142
|
+
// LangChain custom tools: drop any direct marker and expose the breakpoint via
|
|
143
|
+
// `extras`, which the Anthropic adapter promotes onto the payload.
|
|
144
|
+
const wrapped = { ...tool };
|
|
145
|
+
delete wrapped.cache_control;
|
|
146
|
+
return Object.assign(Object.create(prototype), wrapped, {
|
|
83
147
|
extras: {
|
|
84
148
|
...(tool.extras ?? {}),
|
|
85
|
-
cache_control:
|
|
149
|
+
cache_control: cacheControl,
|
|
86
150
|
},
|
|
87
151
|
});
|
|
88
152
|
}
|
|
@@ -124,7 +188,8 @@ export function makeIsDeferred(
|
|
|
124
188
|
*/
|
|
125
189
|
export function partitionAndMarkAnthropicToolCache(
|
|
126
190
|
tools: GraphTools | undefined,
|
|
127
|
-
isDeferred: (toolName: string) => boolean
|
|
191
|
+
isDeferred: (toolName: string) => boolean,
|
|
192
|
+
ttl?: PromptCacheTtl
|
|
128
193
|
): GraphTools | undefined {
|
|
129
194
|
if (tools == null || tools.length === 0) return tools;
|
|
130
195
|
|
|
@@ -143,19 +208,57 @@ export function partitionAndMarkAnthropicToolCache(
|
|
|
143
208
|
}
|
|
144
209
|
}
|
|
145
210
|
|
|
211
|
+
// Anthropic serializes ALL tools before system/messages, so a stray
|
|
212
|
+
// cache_control on any tool — static or deferred — that survives the resolved
|
|
213
|
+
// breakpoint would violate the longer-TTL-first ordering. Strip stale markers
|
|
214
|
+
// off the deferred tools first (they sit after the breakpoint but still before
|
|
215
|
+
// system/messages, and the all-deferred case has no breakpoint of its own).
|
|
216
|
+
let mutated = false;
|
|
217
|
+
for (let i = 0; i < deferredTools.length; i++) {
|
|
218
|
+
const candidate = deferredTools[i] as AnthropicToolCacheCandidate;
|
|
219
|
+
if (hasCacheControl(candidate)) {
|
|
220
|
+
deferredTools[i] = stripCacheControl(candidate);
|
|
221
|
+
mutated = true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
146
225
|
if (staticTools.length === 0) {
|
|
147
|
-
return tools;
|
|
226
|
+
return mutated ? ([...deferredTools] as GraphTools) : tools;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Strip any stray cache_control off the earlier static tools so a leftover
|
|
230
|
+
// 5-minute marker never sits ahead of the resolved breakpoint, then stamp (or
|
|
231
|
+
// re-stamp) only the last static tool with the resolved TTL.
|
|
232
|
+
for (let i = 0; i < staticTools.length - 1; i++) {
|
|
233
|
+
const candidate = staticTools[i] as AnthropicToolCacheCandidate;
|
|
234
|
+
if (hasCacheControl(candidate)) {
|
|
235
|
+
staticTools[i] = stripCacheControl(candidate);
|
|
236
|
+
mutated = true;
|
|
237
|
+
}
|
|
148
238
|
}
|
|
149
239
|
|
|
150
240
|
const last = staticTools[
|
|
151
241
|
staticTools.length - 1
|
|
152
242
|
] as AnthropicToolCacheCandidate;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
243
|
+
const desiredTtl: '1h' | undefined = ttl === '1h' ? '1h' : undefined;
|
|
244
|
+
// "Already correct" requires a marker IN the effective location (one that
|
|
245
|
+
// reaches the payload for this tool's shape) carrying the resolved TTL. A
|
|
246
|
+
// marker in the wrong place — e.g. a direct `cache_control` on a LangChain
|
|
247
|
+
// custom tool — is ineffective and must be re-stamped.
|
|
248
|
+
const effective = getEffectiveCacheControl(last);
|
|
249
|
+
const lastAlreadyCorrect =
|
|
250
|
+
effective != null &&
|
|
251
|
+
(effective.ttl === '1h' ? '1h' : undefined) === desiredTtl;
|
|
252
|
+
if (!lastAlreadyCorrect) {
|
|
253
|
+
staticTools[staticTools.length - 1] = markCacheControl(last, ttl);
|
|
254
|
+
mutated = true;
|
|
157
255
|
}
|
|
158
256
|
|
|
159
|
-
|
|
257
|
+
// Return the original reference only when nothing changed AND partitioning
|
|
258
|
+
// moved nothing. When deferred tools exist they must end up after the cache
|
|
259
|
+
// breakpoint, so the partitioned array is returned even if no marker changed.
|
|
260
|
+
if (!mutated && deferredTools.length === 0) {
|
|
261
|
+
return tools;
|
|
262
|
+
}
|
|
160
263
|
return [...staticTools, ...deferredTools] as GraphTools;
|
|
161
264
|
}
|
|
@@ -12,8 +12,14 @@ import {
|
|
|
12
12
|
stripAnthropicCacheControl,
|
|
13
13
|
stripBedrockCacheControl,
|
|
14
14
|
addBedrockCacheControl,
|
|
15
|
+
addBedrockTailCacheControl,
|
|
15
16
|
addCacheControl,
|
|
17
|
+
addTailCacheControl,
|
|
16
18
|
addCacheControlToStablePrefixMessages,
|
|
19
|
+
buildAnthropicCacheControl,
|
|
20
|
+
buildBedrockCachePoint,
|
|
21
|
+
resolvePromptCacheTtl,
|
|
22
|
+
DEFAULT_PROMPT_CACHE_TTL,
|
|
17
23
|
} from './cache';
|
|
18
24
|
import { _convertMessagesToOpenAIParams } from '@/llm/openai/utils';
|
|
19
25
|
import { toLangChainContent } from './langchain';
|
|
@@ -1813,3 +1819,172 @@ describe('OpenRouter prompt caching (reuses addCacheControl)', () => {
|
|
|
1813
1819
|
expect('cache_control' in lastContent[0]).toBe(true);
|
|
1814
1820
|
});
|
|
1815
1821
|
});
|
|
1822
|
+
|
|
1823
|
+
describe('prompt-cache TTL (1h default, 5m legacy)', () => {
|
|
1824
|
+
describe('resolvePromptCacheTtl', () => {
|
|
1825
|
+
it('defaults to the 1h extended cache when unset', () => {
|
|
1826
|
+
expect(resolvePromptCacheTtl(undefined)).toBe('1h');
|
|
1827
|
+
expect(DEFAULT_PROMPT_CACHE_TTL).toBe('1h');
|
|
1828
|
+
});
|
|
1829
|
+
|
|
1830
|
+
it('passes an explicit value through unchanged', () => {
|
|
1831
|
+
expect(resolvePromptCacheTtl('5m')).toBe('5m');
|
|
1832
|
+
expect(resolvePromptCacheTtl('1h')).toBe('1h');
|
|
1833
|
+
});
|
|
1834
|
+
});
|
|
1835
|
+
|
|
1836
|
+
describe('marker builders', () => {
|
|
1837
|
+
it('buildAnthropicCacheControl adds ttl only for 1h', () => {
|
|
1838
|
+
expect(buildAnthropicCacheControl('1h')).toEqual({
|
|
1839
|
+
type: 'ephemeral',
|
|
1840
|
+
ttl: '1h',
|
|
1841
|
+
});
|
|
1842
|
+
// 5m / undefined stay byte-identical to the legacy marker (no ttl)
|
|
1843
|
+
expect(buildAnthropicCacheControl('5m')).toEqual({ type: 'ephemeral' });
|
|
1844
|
+
expect(buildAnthropicCacheControl()).toEqual({ type: 'ephemeral' });
|
|
1845
|
+
});
|
|
1846
|
+
|
|
1847
|
+
it('buildBedrockCachePoint adds ttl only for 1h', () => {
|
|
1848
|
+
expect(buildBedrockCachePoint('1h')).toEqual({
|
|
1849
|
+
type: 'default',
|
|
1850
|
+
ttl: '1h',
|
|
1851
|
+
});
|
|
1852
|
+
expect(buildBedrockCachePoint('5m')).toEqual({ type: 'default' });
|
|
1853
|
+
expect(buildBedrockCachePoint()).toEqual({ type: 'default' });
|
|
1854
|
+
});
|
|
1855
|
+
});
|
|
1856
|
+
|
|
1857
|
+
describe('addTailCacheControl threads ttl', () => {
|
|
1858
|
+
const baseMessages = (): AnthropicMessages => [
|
|
1859
|
+
{ role: 'user', content: 'Hello' },
|
|
1860
|
+
{ role: 'assistant', content: 'Hi there' },
|
|
1861
|
+
];
|
|
1862
|
+
|
|
1863
|
+
it('stamps a 1h cache_control on the tail block', () => {
|
|
1864
|
+
const result = addTailCacheControl(baseMessages(), '1h');
|
|
1865
|
+
const tail = result[result.length - 1].content as MessageContentComplex[];
|
|
1866
|
+
expect(tail[tail.length - 1]).toEqual({
|
|
1867
|
+
type: 'text',
|
|
1868
|
+
text: 'Hi there',
|
|
1869
|
+
cache_control: { type: 'ephemeral', ttl: '1h' },
|
|
1870
|
+
});
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
it('omits ttl for 5m and for the unspecified (legacy) default', () => {
|
|
1874
|
+
for (const ttl of ['5m', undefined] as const) {
|
|
1875
|
+
const result = addTailCacheControl(baseMessages(), ttl);
|
|
1876
|
+
const tail = result[result.length - 1]
|
|
1877
|
+
.content as MessageContentComplex[];
|
|
1878
|
+
expect(
|
|
1879
|
+
(tail[tail.length - 1] as Anthropic.TextBlockParam).cache_control
|
|
1880
|
+
).toEqual({ type: 'ephemeral' });
|
|
1881
|
+
}
|
|
1882
|
+
});
|
|
1883
|
+
});
|
|
1884
|
+
|
|
1885
|
+
describe('addCacheControl threads ttl', () => {
|
|
1886
|
+
it('stamps a 1h cache_control on the latest user messages', () => {
|
|
1887
|
+
const messages: AnthropicMessages = [
|
|
1888
|
+
{ role: 'user', content: 'first' },
|
|
1889
|
+
{ role: 'assistant', content: 'reply' },
|
|
1890
|
+
{ role: 'user', content: 'second' },
|
|
1891
|
+
];
|
|
1892
|
+
const result = addCacheControl(messages, '1h');
|
|
1893
|
+
const lastUser = result[2].content as MessageContentComplex[];
|
|
1894
|
+
expect((lastUser[0] as Anthropic.TextBlockParam).cache_control).toEqual({
|
|
1895
|
+
type: 'ephemeral',
|
|
1896
|
+
ttl: '1h',
|
|
1897
|
+
});
|
|
1898
|
+
});
|
|
1899
|
+
});
|
|
1900
|
+
|
|
1901
|
+
describe('addCacheControlToStablePrefixMessages threads ttl', () => {
|
|
1902
|
+
it('stamps 1h on every stable-prefix marker', () => {
|
|
1903
|
+
const messages: AnthropicMessages = [
|
|
1904
|
+
{ role: 'user', content: 'turn 1' },
|
|
1905
|
+
{ role: 'assistant', content: 'reply 1' },
|
|
1906
|
+
{ role: 'user', content: 'turn 2' },
|
|
1907
|
+
{ role: 'assistant', content: 'reply 2' },
|
|
1908
|
+
];
|
|
1909
|
+
const result = addCacheControlToStablePrefixMessages(messages, 2, '1h');
|
|
1910
|
+
const marked = result
|
|
1911
|
+
.flatMap((m) =>
|
|
1912
|
+
Array.isArray(m.content) ? (m.content as MessageContentComplex[]) : []
|
|
1913
|
+
)
|
|
1914
|
+
.filter((block) => 'cache_control' in block);
|
|
1915
|
+
expect(marked.length).toBeGreaterThan(0);
|
|
1916
|
+
for (const block of marked) {
|
|
1917
|
+
expect((block as Anthropic.TextBlockParam).cache_control).toEqual({
|
|
1918
|
+
type: 'ephemeral',
|
|
1919
|
+
ttl: '1h',
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
});
|
|
1923
|
+
});
|
|
1924
|
+
|
|
1925
|
+
describe('addBedrockTailCacheControl threads ttl', () => {
|
|
1926
|
+
const messages = (): TestMsg[] => [
|
|
1927
|
+
{ role: 'user', content: 'Hello' },
|
|
1928
|
+
{ role: 'assistant', content: 'Hi' },
|
|
1929
|
+
];
|
|
1930
|
+
|
|
1931
|
+
it('stamps a 1h cachePoint on the tail message', () => {
|
|
1932
|
+
const result = addBedrockTailCacheControl(messages(), '1h');
|
|
1933
|
+
const last = result[result.length - 1].content as MessageContentComplex[];
|
|
1934
|
+
expect(last[last.length - 1]).toEqual({
|
|
1935
|
+
cachePoint: { type: 'default', ttl: '1h' },
|
|
1936
|
+
});
|
|
1937
|
+
});
|
|
1938
|
+
|
|
1939
|
+
it('omits ttl for 5m and the legacy default', () => {
|
|
1940
|
+
for (const ttl of ['5m', undefined] as const) {
|
|
1941
|
+
const result = addBedrockTailCacheControl(messages(), ttl);
|
|
1942
|
+
const last = result[result.length - 1]
|
|
1943
|
+
.content as MessageContentComplex[];
|
|
1944
|
+
expect(last[last.length - 1]).toEqual({
|
|
1945
|
+
cachePoint: { type: 'default' },
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
});
|
|
1949
|
+
|
|
1950
|
+
it('normalizes a stale 5m system cachePoint to the resolved tail ttl', () => {
|
|
1951
|
+
const msgs: TestMsg[] = [
|
|
1952
|
+
{
|
|
1953
|
+
role: 'system',
|
|
1954
|
+
content: [
|
|
1955
|
+
{ type: ContentTypes.TEXT, text: 'System' },
|
|
1956
|
+
{ cachePoint: { type: 'default' } } as MessageContentComplex,
|
|
1957
|
+
],
|
|
1958
|
+
},
|
|
1959
|
+
{ role: 'user', content: 'Hello' },
|
|
1960
|
+
{ role: 'assistant', content: 'Hi' },
|
|
1961
|
+
];
|
|
1962
|
+
const result = addBedrockTailCacheControl(msgs, '1h');
|
|
1963
|
+
// Stale 5m system checkpoint is upgraded to 1h so it never precedes the
|
|
1964
|
+
// 1h message tail (Bedrock requires longer-TTL checkpoints first).
|
|
1965
|
+
const system = result[0].content as MessageContentComplex[];
|
|
1966
|
+
expect(system[system.length - 1]).toEqual({
|
|
1967
|
+
cachePoint: { type: 'default', ttl: '1h' },
|
|
1968
|
+
});
|
|
1969
|
+
const tail = result[result.length - 1].content as MessageContentComplex[];
|
|
1970
|
+
expect(tail[tail.length - 1]).toEqual({
|
|
1971
|
+
cachePoint: { type: 'default', ttl: '1h' },
|
|
1972
|
+
});
|
|
1973
|
+
});
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
describe('addBedrockCacheControl threads ttl', () => {
|
|
1977
|
+
it('stamps a 1h cachePoint when configured', () => {
|
|
1978
|
+
const messages: TestMsg[] = [
|
|
1979
|
+
{ role: 'user', content: 'Hello' },
|
|
1980
|
+
{ role: 'assistant', content: 'Hi' },
|
|
1981
|
+
];
|
|
1982
|
+
const result = addBedrockCacheControl(messages, '1h');
|
|
1983
|
+
// Only one user message present, so the cachePoint anchors on it.
|
|
1984
|
+
const user = result[0].content as MessageContentComplex[];
|
|
1985
|
+
expect(user[user.length - 1]).toEqual({
|
|
1986
|
+
cachePoint: { type: 'default', ttl: '1h' },
|
|
1987
|
+
});
|
|
1988
|
+
});
|
|
1989
|
+
});
|
|
1990
|
+
});
|