@librechat/agents 3.2.39 → 3.2.42
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.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +4 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +4 -3
- package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +9 -1
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +4 -0
- package/dist/cjs/messages/cache.cjs +21 -0
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/tools/ReadFile.cjs +2 -2
- package/dist/cjs/tools/ReadFile.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +13 -13
- package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +87 -7
- package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
- package/dist/cjs/tools/local/LocalCodingTools.cjs +11 -11
- package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +5 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs +4 -3
- package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +10 -2
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -3
- package/dist/esm/messages/cache.mjs +21 -1
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/tools/ReadFile.mjs +2 -2
- package/dist/esm/tools/ReadFile.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +14 -14
- package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +85 -8
- package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
- package/dist/esm/tools/local/LocalCodingTools.mjs +11 -11
- package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
- package/dist/types/llm/bedrock/index.d.ts +7 -0
- package/dist/types/messages/cache.d.ts +17 -0
- package/dist/types/tools/ReadFile.d.ts +4 -4
- package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +44 -0
- package/package.json +1 -1
- package/src/agents/AgentContext.ts +2 -0
- package/src/graphs/Graph.ts +17 -5
- package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +12 -12
- package/src/hooks/createWorkspacePolicyHook.ts +7 -6
- package/src/llm/bedrock/index.ts +18 -2
- package/src/llm/bedrock/llm.spec.ts +97 -0
- package/src/messages/cache.test.ts +31 -0
- package/src/messages/cache.ts +25 -0
- package/src/tools/ReadFile.ts +2 -2
- package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +131 -0
- package/src/tools/__tests__/LocalExecutionTools.test.ts +25 -25
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +5 -5
- package/src/tools/__tests__/ReadFile.test.ts +3 -3
- package/src/tools/__tests__/ToolNode.session.test.ts +2 -2
- package/src/tools/__tests__/workspaceSeam.test.ts +2 -2
- package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +18 -13
- package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +165 -9
- package/src/tools/local/LocalCodingTools.ts +14 -14
|
@@ -47,6 +47,23 @@ type BedrockCachePoint = {
|
|
|
47
47
|
* legacy default), `'1h'` adds the extended-cache `ttl`.
|
|
48
48
|
*/
|
|
49
49
|
export declare function buildBedrockCachePoint(ttl?: PromptCacheTtl): BedrockCachePoint;
|
|
50
|
+
/**
|
|
51
|
+
* A `cachePoint` under `toolConfig.tools` is only accepted on Anthropic Claude
|
|
52
|
+
* models. Amazon Nova rejects it outright — `Malformed input request:
|
|
53
|
+
* #/toolConfig/tools/0: extraneous key [cachePoint] is not permitted` — even
|
|
54
|
+
* though it accepts `cachePoint` in `system` and `messages` (verified live:
|
|
55
|
+
* Nova returns HTTP 200 with real `cacheWriteInputTokens` for both). Per the AWS
|
|
56
|
+
* prompt-caching docs the support table lists `tools` for Claude only.
|
|
57
|
+
*
|
|
58
|
+
* Gate ONLY the tool checkpoint on this, so a `promptCache: true` config on Nova
|
|
59
|
+
* keeps its valid message/system caching instead of either 400-ing (tool point)
|
|
60
|
+
* or losing caching entirely. This is a capability gate (the key is rejected
|
|
61
|
+
* outright), distinct from the TTL value which Bedrock downgrades gracefully.
|
|
62
|
+
*
|
|
63
|
+
* @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
|
|
64
|
+
* @see https://github.com/danny-avila/LibreChat/issues/13838
|
|
65
|
+
*/
|
|
66
|
+
export declare function supportsBedrockToolCache(model: string | undefined | null): boolean;
|
|
50
67
|
/**
|
|
51
68
|
* Clones a message with new content. For LangChain BaseMessage instances,
|
|
52
69
|
* constructs a proper class instance so that `instanceof` checks are preserved
|
|
@@ -4,12 +4,12 @@ export declare const ReadFileToolDescription = "Read the contents of a file. Ret
|
|
|
4
4
|
export declare const ReadFileToolSchema: {
|
|
5
5
|
readonly type: "object";
|
|
6
6
|
readonly properties: {
|
|
7
|
-
readonly
|
|
7
|
+
readonly path: {
|
|
8
8
|
readonly type: "string";
|
|
9
9
|
readonly description: "Path to the file. For skill files: \"{skillName}/{path}\" (e.g. \"pdf-analyzer/src/utils.py\"). For code execution output: the path as returned by the execution tool.";
|
|
10
10
|
};
|
|
11
11
|
};
|
|
12
|
-
readonly required: readonly ["
|
|
12
|
+
readonly required: readonly ["path"];
|
|
13
13
|
};
|
|
14
14
|
export declare const ReadFileToolDefinition: {
|
|
15
15
|
readonly name: Constants.READ_FILE;
|
|
@@ -17,12 +17,12 @@ export declare const ReadFileToolDefinition: {
|
|
|
17
17
|
readonly parameters: {
|
|
18
18
|
readonly type: "object";
|
|
19
19
|
readonly properties: {
|
|
20
|
-
readonly
|
|
20
|
+
readonly path: {
|
|
21
21
|
readonly type: "string";
|
|
22
22
|
readonly description: "Path to the file. For skill files: \"{skillName}/{path}\" (e.g. \"pdf-analyzer/src/utils.py\"). For code execution output: the path as returned by the execution tool.";
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
|
-
readonly required: readonly ["
|
|
25
|
+
readonly required: readonly ["path"];
|
|
26
26
|
};
|
|
27
27
|
readonly responseFormat: "content_and_artifact";
|
|
28
28
|
};
|
|
@@ -8,6 +8,50 @@ type SpawnResult = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare function getCloudflareWorkspaceRoot(config?: t.CloudflareSandboxExecutionConfig): string;
|
|
10
10
|
export declare function resolveCloudflareSandbox(config: t.CloudflareSandboxExecutionConfig): Promise<t.CloudflareSandboxRuntime>;
|
|
11
|
+
/**
|
|
12
|
+
* Client-side backstop timeout for a `sandbox.exec()` await: a few seconds beyond
|
|
13
|
+
* the exec's own `timeout` option, so a stalled exec that never honors `timeout`
|
|
14
|
+
* still can't outlast this.
|
|
15
|
+
*/
|
|
16
|
+
export declare function clientExecTimeoutMs(timeoutMs: number): number;
|
|
17
|
+
/**
|
|
18
|
+
* Bound a `sandbox.exec()` await with a CLIENT-SIDE timeout.
|
|
19
|
+
*
|
|
20
|
+
* The native Cloudflare Sandbox Durable Object `exec()` is effectively
|
|
21
|
+
* uncancellable from the host: `ExecOptions` has no `signal` (so
|
|
22
|
+
* `supportsExecSignal` is false for the native transport), and its `timeout`
|
|
23
|
+
* option is not reliably enforced when the container/RPC itself stalls — while
|
|
24
|
+
* the in-sandbox `timeout(1)` wrapper only bounds a command that is actually
|
|
25
|
+
* running. So a stalled exec (an unresponsive/cold container) otherwise hangs
|
|
26
|
+
* until the host's run-level abort, burning the whole run budget on one tool
|
|
27
|
+
* call. This race guarantees the host await settles within `timeoutMs`
|
|
28
|
+
* regardless of the transport.
|
|
29
|
+
*
|
|
30
|
+
* On timeout the underlying `exec` promise may keep running in the DO (a
|
|
31
|
+
* native-DO exec cannot be truly cancelled), so its late settlement is swallowed
|
|
32
|
+
* to avoid an unhandled rejection.
|
|
33
|
+
*/
|
|
34
|
+
export declare function withClientTimeout<T>(exec: Promise<T>, timeoutMs: number, label: string, options?: {
|
|
35
|
+
/**
|
|
36
|
+
* Detach the backstop timer from the event loop. Use ONLY when something else
|
|
37
|
+
* already settles the caller (e.g. the spawn path, where spawnLocalProcess's
|
|
38
|
+
* own timer resolves the child). The awaited direct-exec paths must leave it
|
|
39
|
+
* REF'd so the timeout is guaranteed to fire even if nothing else is pending.
|
|
40
|
+
*/
|
|
41
|
+
unref?: boolean;
|
|
42
|
+
/** Invoked when the client timeout fires — e.g. abort a signal-aware exec. */
|
|
43
|
+
onTimeout?: () => void;
|
|
44
|
+
}): Promise<T>;
|
|
45
|
+
/**
|
|
46
|
+
* Run `sandbox.exec()` bounded by a client-side timeout, and — for signal-aware
|
|
47
|
+
* transports (e.g. the HTTP bridge, `supportsExecSignal === true`) — abort the
|
|
48
|
+
* underlying exec when the timeout fires instead of merely abandoning it. The
|
|
49
|
+
* native DO transport ignores `signal`, so it only gets the timeout. Leaves the
|
|
50
|
+
* backstop timer ref'd (this is an awaited direct-exec path).
|
|
51
|
+
*/
|
|
52
|
+
export declare function execWithClientTimeout(sandbox: t.CloudflareSandboxRuntime, command: string, options: t.CloudflareSandboxExecOptions, timeoutMs: number, label: string, runOptions?: {
|
|
53
|
+
unref?: boolean;
|
|
54
|
+
}): Promise<t.CloudflareSandboxExecResult>;
|
|
11
55
|
export declare function createCloudflareWorkspaceFS(config: t.CloudflareSandboxExecutionConfig): WorkspaceFS;
|
|
12
56
|
export declare function createCloudflareLocalExecutionConfig(config: t.CloudflareSandboxExecutionConfig): t.LocalExecutionConfig;
|
|
13
57
|
export declare function validateCloudflareBashCommand(command: string, args: readonly string[], config: t.CloudflareSandboxExecutionConfig): Promise<void>;
|
package/package.json
CHANGED
|
@@ -846,6 +846,8 @@ export class AgentContext {
|
|
|
846
846
|
const bedrockOptions = this.clientOptions as
|
|
847
847
|
| t.BedrockAnthropicClientOptions
|
|
848
848
|
| undefined;
|
|
849
|
+
// Nova accepts system/message cachePoints (only the tool checkpoint is
|
|
850
|
+
// Claude-only), so this is gated on promptCache alone.
|
|
849
851
|
return bedrockOptions?.promptCache === true;
|
|
850
852
|
}
|
|
851
853
|
|
package/src/graphs/Graph.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
syncBudgetDerivedFields,
|
|
29
29
|
addTailCacheControl,
|
|
30
30
|
resolvePromptCacheTtl,
|
|
31
|
+
supportsBedrockToolCache,
|
|
31
32
|
getMessageId,
|
|
32
33
|
makeIsDeferred,
|
|
33
34
|
partitionAndMarkAnthropicToolCache,
|
|
@@ -1442,11 +1443,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1442
1443
|
| undefined
|
|
1443
1444
|
)?.promptCache === true
|
|
1444
1445
|
) {
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1446
|
+
const bedrockModel = (
|
|
1447
|
+
agentContext.clientOptions as { model?: string } | undefined
|
|
1448
|
+
)?.model;
|
|
1449
|
+
// An omitted model falls back to LangChain's default Claude model (which
|
|
1450
|
+
// supports tool caching); only an explicit non-Claude model (e.g. Nova)
|
|
1451
|
+
// skips tool marking so its stray marker never leaks into toolConfig.
|
|
1452
|
+
if (bedrockModel == null || supportsBedrockToolCache(bedrockModel)) {
|
|
1453
|
+
toolsForBinding =
|
|
1454
|
+
partitionAndMarkBedrockToolCache(
|
|
1455
|
+
rawToolsForBinding,
|
|
1456
|
+
makeIsDeferred(agentContext.toolDefinitions)
|
|
1457
|
+
) ?? rawToolsForBinding;
|
|
1458
|
+
}
|
|
1450
1459
|
}
|
|
1451
1460
|
|
|
1452
1461
|
let model =
|
|
@@ -1790,6 +1799,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
|
|
|
1790
1799
|
| t.ProviderOptionsMap[Providers.OPENROUTER]
|
|
1791
1800
|
| undefined
|
|
1792
1801
|
)?.promptCache === true;
|
|
1802
|
+
// Message/system cache points work on all cache-capable Bedrock models,
|
|
1803
|
+
// including Nova (verified live: HTTP 200 with cacheWriteInputTokens). Only
|
|
1804
|
+
// the tool checkpoint is Claude-only, so this is gated on promptCache alone.
|
|
1793
1805
|
const bedrockPromptCacheEnabled =
|
|
1794
1806
|
agentContext.provider === Providers.BEDROCK &&
|
|
1795
1807
|
(
|
|
@@ -51,7 +51,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
51
51
|
const hook = createWorkspacePolicyHook({ root: workspace });
|
|
52
52
|
const out = await call(
|
|
53
53
|
hook,
|
|
54
|
-
makeInput('read_file', {
|
|
54
|
+
makeInput('read_file', { path: 'src/x.ts' })
|
|
55
55
|
);
|
|
56
56
|
expect(out.decision).toBe('allow');
|
|
57
57
|
});
|
|
@@ -60,7 +60,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
60
60
|
const hook = createWorkspacePolicyHook({ root: workspace });
|
|
61
61
|
const out = await call(
|
|
62
62
|
hook,
|
|
63
|
-
makeInput('read_file', {
|
|
63
|
+
makeInput('read_file', { path: join(workspace, 'src/x.ts') })
|
|
64
64
|
);
|
|
65
65
|
expect(out.decision).toBe('allow');
|
|
66
66
|
});
|
|
@@ -72,7 +72,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
72
72
|
});
|
|
73
73
|
const out = await call(
|
|
74
74
|
hook,
|
|
75
|
-
makeInput('read_file', {
|
|
75
|
+
makeInput('read_file', { path: join(extra, 'lib/y.ts') })
|
|
76
76
|
);
|
|
77
77
|
expect(out.decision).toBe('allow');
|
|
78
78
|
});
|
|
@@ -81,7 +81,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
81
81
|
const hook = createWorkspacePolicyHook({ root: workspace });
|
|
82
82
|
const out = await call(
|
|
83
83
|
hook,
|
|
84
|
-
makeInput('read_file', {
|
|
84
|
+
makeInput('read_file', { path: '/etc/passwd' })
|
|
85
85
|
);
|
|
86
86
|
expect(out.decision).toBe('ask');
|
|
87
87
|
expect(out.reason).toContain('/etc/passwd');
|
|
@@ -93,7 +93,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
93
93
|
const out = await call(
|
|
94
94
|
hook,
|
|
95
95
|
makeInput('write_file', {
|
|
96
|
-
|
|
96
|
+
path: '/etc/foo',
|
|
97
97
|
content: 'malicious',
|
|
98
98
|
})
|
|
99
99
|
);
|
|
@@ -107,7 +107,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
107
107
|
});
|
|
108
108
|
const out = await call(
|
|
109
109
|
hook,
|
|
110
|
-
makeInput('read_file', {
|
|
110
|
+
makeInput('read_file', { path: '/etc/passwd' })
|
|
111
111
|
);
|
|
112
112
|
expect(out.decision).toBe('deny');
|
|
113
113
|
expect(out.reason).toContain('/etc/passwd');
|
|
@@ -121,7 +121,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
121
121
|
const out = await call(
|
|
122
122
|
hook,
|
|
123
123
|
makeInput('edit_file', {
|
|
124
|
-
|
|
124
|
+
path: '/etc/foo',
|
|
125
125
|
old_text: 'a',
|
|
126
126
|
new_text: 'b',
|
|
127
127
|
})
|
|
@@ -136,7 +136,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
136
136
|
});
|
|
137
137
|
const out = await call(
|
|
138
138
|
hook,
|
|
139
|
-
makeInput('read_file', {
|
|
139
|
+
makeInput('read_file', { path: '/etc/passwd' })
|
|
140
140
|
);
|
|
141
141
|
expect(out.decision).toBe('allow');
|
|
142
142
|
});
|
|
@@ -184,7 +184,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
184
184
|
});
|
|
185
185
|
const out = await call(
|
|
186
186
|
hook,
|
|
187
|
-
makeInput('read_file', {
|
|
187
|
+
makeInput('read_file', { path: '/somewhere/else.ts' })
|
|
188
188
|
);
|
|
189
189
|
expect(out.reason).toBe('read_file blocked from /somewhere/else.ts');
|
|
190
190
|
});
|
|
@@ -212,7 +212,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
212
212
|
const hook = createWorkspacePolicyHook({ root: '.' });
|
|
213
213
|
const out = await call(
|
|
214
214
|
hook,
|
|
215
|
-
makeInput('read_file', {
|
|
215
|
+
makeInput('read_file', { path: resolve('.', 'src/x.ts') })
|
|
216
216
|
);
|
|
217
217
|
expect(out.decision).toBe('allow');
|
|
218
218
|
});
|
|
@@ -228,7 +228,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
228
228
|
});
|
|
229
229
|
const out = await call(
|
|
230
230
|
hook,
|
|
231
|
-
makeInput('read_file', {
|
|
231
|
+
makeInput('read_file', { path: 'escape' })
|
|
232
232
|
);
|
|
233
233
|
expect(out.decision).toBe('deny');
|
|
234
234
|
expect(out.reason).toContain('escape');
|
|
@@ -245,7 +245,7 @@ describe('createWorkspacePolicyHook', () => {
|
|
|
245
245
|
});
|
|
246
246
|
const out = await call(
|
|
247
247
|
hook,
|
|
248
|
-
makeInput('read_file', {
|
|
248
|
+
makeInput('read_file', { path: join(altMount, 'file.ts') })
|
|
249
249
|
);
|
|
250
250
|
expect(out.decision).toBe('allow');
|
|
251
251
|
});
|
|
@@ -166,13 +166,14 @@ function extractCompileCheckPaths(input: Record<string, unknown>): string[] {
|
|
|
166
166
|
return out;
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// All built-in coding tools take their file/dir target in `path`.
|
|
170
|
+
const extractPath: PathExtractor = (i) =>
|
|
171
|
+
typeof i.path === 'string' && i.path !== '' ? [i.path] : [];
|
|
172
|
+
|
|
169
173
|
const DEFAULT_EXTRACTORS: Record<string, PathExtractor> = {
|
|
170
|
-
[Constants.READ_FILE]:
|
|
171
|
-
|
|
172
|
-
[Constants.
|
|
173
|
-
typeof i.file_path === 'string' ? [i.file_path] : [],
|
|
174
|
-
[Constants.EDIT_FILE]: (i) =>
|
|
175
|
-
typeof i.file_path === 'string' ? [i.file_path] : [],
|
|
174
|
+
[Constants.READ_FILE]: extractPath,
|
|
175
|
+
[Constants.WRITE_FILE]: extractPath,
|
|
176
|
+
[Constants.EDIT_FILE]: extractPath,
|
|
176
177
|
[Constants.GREP_SEARCH]: (i) =>
|
|
177
178
|
typeof i.path === 'string' && i.path !== '' ? [i.path] : [],
|
|
178
179
|
[Constants.GLOB_SEARCH]: (i) =>
|
package/src/llm/bedrock/index.ts
CHANGED
|
@@ -39,7 +39,11 @@ import {
|
|
|
39
39
|
handleConverseStreamContentBlockDelta,
|
|
40
40
|
handleConverseStreamMetadata,
|
|
41
41
|
} from './utils';
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
resolvePromptCacheTtl,
|
|
44
|
+
supportsBedrockToolCache,
|
|
45
|
+
type PromptCacheTtl,
|
|
46
|
+
} from '@/messages/cache';
|
|
43
47
|
import { insertBedrockToolCachePoint } from './toolCache';
|
|
44
48
|
|
|
45
49
|
/**
|
|
@@ -134,12 +138,24 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
|
|
|
134
138
|
*/
|
|
135
139
|
serviceTier?: ServiceTierType;
|
|
136
140
|
|
|
141
|
+
/**
|
|
142
|
+
* The configured model id, captured at construction so it survives the
|
|
143
|
+
* temporary `this.model` swap to an application-inference-profile ARN during
|
|
144
|
+
* generation. Used to gate the Bedrock tool cache point to Claude models
|
|
145
|
+
* (see {@link supportsBedrockToolCache}).
|
|
146
|
+
*/
|
|
147
|
+
private readonly cacheModelId: string;
|
|
148
|
+
|
|
137
149
|
constructor(fields?: CustomChatBedrockConverseInput) {
|
|
138
150
|
super(fields);
|
|
139
151
|
this.promptCache = fields?.promptCache;
|
|
140
152
|
this.promptCacheTtl = fields?.promptCacheTtl;
|
|
141
153
|
this.applicationInferenceProfile = fields?.applicationInferenceProfile;
|
|
142
154
|
this.serviceTier = fields?.serviceTier;
|
|
155
|
+
// `super(fields)` initializes `this.model` to LangChain's default Claude
|
|
156
|
+
// model when `fields.model` is omitted, so fall back to it rather than ''
|
|
157
|
+
// (which would treat the default Claude model as tool-cache-unsupported).
|
|
158
|
+
this.cacheModelId = fields?.model ?? this.model;
|
|
143
159
|
}
|
|
144
160
|
|
|
145
161
|
static lc_name(): string {
|
|
@@ -164,7 +180,7 @@ export class CustomChatBedrockConverse extends ChatBedrockConverse {
|
|
|
164
180
|
} {
|
|
165
181
|
const baseParams = super.invocationParams(options);
|
|
166
182
|
const toolConfig =
|
|
167
|
-
this.promptCache === true
|
|
183
|
+
this.promptCache === true && supportsBedrockToolCache(this.cacheModelId)
|
|
168
184
|
? insertBedrockToolCachePoint(
|
|
169
185
|
baseParams.toolConfig,
|
|
170
186
|
true,
|
|
@@ -375,6 +375,7 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
375
375
|
test('adds a Bedrock cache point for directly-bound tools', () => {
|
|
376
376
|
const model = new CustomChatBedrockConverse({
|
|
377
377
|
...baseConstructorArgs,
|
|
378
|
+
model: 'anthropic.claude-3-haiku-20240307-v1:0',
|
|
378
379
|
promptCache: true,
|
|
379
380
|
});
|
|
380
381
|
|
|
@@ -400,6 +401,7 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
400
401
|
test('defaults the tool cache point to the 1h extended TTL', () => {
|
|
401
402
|
const model = new CustomChatBedrockConverse({
|
|
402
403
|
...baseConstructorArgs,
|
|
404
|
+
model: 'anthropic.claude-3-haiku-20240307-v1:0',
|
|
403
405
|
promptCache: true,
|
|
404
406
|
});
|
|
405
407
|
|
|
@@ -430,6 +432,7 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
430
432
|
test('honors an explicit 5m promptCacheTtl on the tool cache point', () => {
|
|
431
433
|
const model = new CustomChatBedrockConverse({
|
|
432
434
|
...baseConstructorArgs,
|
|
435
|
+
model: 'anthropic.claude-3-haiku-20240307-v1:0',
|
|
433
436
|
promptCache: true,
|
|
434
437
|
promptCacheTtl: '5m',
|
|
435
438
|
});
|
|
@@ -461,6 +464,7 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
461
464
|
test('adds the Bedrock cache point before deferred tools', () => {
|
|
462
465
|
const model = new CustomChatBedrockConverse({
|
|
463
466
|
...baseConstructorArgs,
|
|
467
|
+
model: 'anthropic.claude-3-haiku-20240307-v1:0',
|
|
464
468
|
promptCache: true,
|
|
465
469
|
});
|
|
466
470
|
const tools = partitionAndMarkBedrockToolCache(
|
|
@@ -500,6 +504,7 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
500
504
|
test('does not fall back to caching when Graph marks all tools deferred', () => {
|
|
501
505
|
const model = new CustomChatBedrockConverse({
|
|
502
506
|
...baseConstructorArgs,
|
|
507
|
+
model: 'anthropic.claude-3-haiku-20240307-v1:0',
|
|
503
508
|
promptCache: true,
|
|
504
509
|
});
|
|
505
510
|
const tools = partitionAndMarkBedrockToolCache(
|
|
@@ -525,6 +530,98 @@ describe('CustomChatBedrockConverse', () => {
|
|
|
525
530
|
'__lc_bedrock_skip_tool_cache'
|
|
526
531
|
);
|
|
527
532
|
});
|
|
533
|
+
|
|
534
|
+
test('does not add a tool cache point for Amazon Nova models', () => {
|
|
535
|
+
// Regression for danny-avila/LibreChat#13838: Nova rejects a cachePoint in
|
|
536
|
+
// toolConfig.tools ("Malformed input request: #/toolConfig/tools/0:
|
|
537
|
+
// extraneous key [cachePoint] is not permitted"). Only the tool checkpoint
|
|
538
|
+
// is Claude-only — Nova still caches system/messages — so promptCache: true
|
|
539
|
+
// on Nova must omit just the tool cache point.
|
|
540
|
+
const model = new CustomChatBedrockConverse({
|
|
541
|
+
...baseConstructorArgs,
|
|
542
|
+
model: 'us.amazon.nova-pro-v1:0',
|
|
543
|
+
promptCache: true,
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
const params = model.invocationParams({
|
|
547
|
+
tools: [
|
|
548
|
+
{
|
|
549
|
+
type: 'function',
|
|
550
|
+
function: {
|
|
551
|
+
name: 'direct_tool',
|
|
552
|
+
description: 'Direct tool',
|
|
553
|
+
parameters: { type: 'object', properties: {} },
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
expect(getBedrockToolNames(params.toolConfig?.tools)).toEqual([
|
|
560
|
+
'direct_tool',
|
|
561
|
+
]);
|
|
562
|
+
expect(JSON.stringify(params.toolConfig?.tools ?? [])).not.toContain(
|
|
563
|
+
'cachePoint'
|
|
564
|
+
);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
test('still adds a tool cache point for a Claude model behind an inference profile', () => {
|
|
568
|
+
// The configured Claude model id is captured at construction, so the gate
|
|
569
|
+
// survives the application-inference-profile ARN swap during generation.
|
|
570
|
+
const model = new CustomChatBedrockConverse({
|
|
571
|
+
...baseConstructorArgs,
|
|
572
|
+
model: 'anthropic.claude-sonnet-4-5-20250929-v1:0',
|
|
573
|
+
applicationInferenceProfile:
|
|
574
|
+
'arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/test',
|
|
575
|
+
promptCache: true,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const params = model.invocationParams({
|
|
579
|
+
tools: [
|
|
580
|
+
{
|
|
581
|
+
type: 'function',
|
|
582
|
+
function: {
|
|
583
|
+
name: 'direct_tool',
|
|
584
|
+
description: 'Direct tool',
|
|
585
|
+
parameters: { type: 'object', properties: {} },
|
|
586
|
+
},
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
expect(getBedrockToolNames(params.toolConfig?.tools)).toEqual([
|
|
592
|
+
'direct_tool',
|
|
593
|
+
'cachePoint',
|
|
594
|
+
]);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
test('still adds a tool cache point when model is omitted (default is Claude)', () => {
|
|
598
|
+
// When no model is provided, LangChain initializes this.model to a default
|
|
599
|
+
// Claude model, so the tool cache point must still apply.
|
|
600
|
+
const model = new CustomChatBedrockConverse({
|
|
601
|
+
...baseConstructorArgs,
|
|
602
|
+
promptCache: true,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
expect(model.model).toMatch(/claude/i);
|
|
606
|
+
|
|
607
|
+
const params = model.invocationParams({
|
|
608
|
+
tools: [
|
|
609
|
+
{
|
|
610
|
+
type: 'function',
|
|
611
|
+
function: {
|
|
612
|
+
name: 'direct_tool',
|
|
613
|
+
description: 'Direct tool',
|
|
614
|
+
parameters: { type: 'object', properties: {} },
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
expect(getBedrockToolNames(params.toolConfig?.tools)).toEqual([
|
|
621
|
+
'direct_tool',
|
|
622
|
+
'cachePoint',
|
|
623
|
+
]);
|
|
624
|
+
});
|
|
528
625
|
});
|
|
529
626
|
|
|
530
627
|
describe('contentBlockIndex cleanup', () => {
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
buildAnthropicCacheControl,
|
|
20
20
|
buildBedrockCachePoint,
|
|
21
21
|
resolvePromptCacheTtl,
|
|
22
|
+
supportsBedrockToolCache,
|
|
22
23
|
DEFAULT_PROMPT_CACHE_TTL,
|
|
23
24
|
} from './cache';
|
|
24
25
|
import { _convertMessagesToOpenAIParams } from '@/llm/openai/utils';
|
|
@@ -895,6 +896,36 @@ describe('synthetic skill/meta messages are not cache-anchored', () => {
|
|
|
895
896
|
});
|
|
896
897
|
});
|
|
897
898
|
|
|
899
|
+
describe('supportsBedrockToolCache', () => {
|
|
900
|
+
test('returns true for Claude model ids (incl. cross-region + profile)', () => {
|
|
901
|
+
expect(
|
|
902
|
+
supportsBedrockToolCache('anthropic.claude-3-5-sonnet-20241022-v2:0')
|
|
903
|
+
).toBe(true);
|
|
904
|
+
expect(
|
|
905
|
+
supportsBedrockToolCache('us.anthropic.claude-sonnet-4-5-20250929-v1:0')
|
|
906
|
+
).toBe(true);
|
|
907
|
+
expect(supportsBedrockToolCache('anthropic.claude-opus-4-6-v1')).toBe(true);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
test('returns false for Nova and other non-Claude families (tool cache only)', () => {
|
|
911
|
+
// Nova accepts system/message cachePoints but rejects the tool checkpoint.
|
|
912
|
+
expect(supportsBedrockToolCache('us.amazon.nova-pro-v1:0')).toBe(false);
|
|
913
|
+
expect(supportsBedrockToolCache('amazon.nova-lite-v1:0')).toBe(false);
|
|
914
|
+
expect(supportsBedrockToolCache('meta.llama3-1-70b-instruct-v1:0')).toBe(
|
|
915
|
+
false
|
|
916
|
+
);
|
|
917
|
+
expect(supportsBedrockToolCache('mistral.mistral-large-2407-v1:0')).toBe(
|
|
918
|
+
false
|
|
919
|
+
);
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
test('returns false for empty / missing model', () => {
|
|
923
|
+
expect(supportsBedrockToolCache('')).toBe(false);
|
|
924
|
+
expect(supportsBedrockToolCache(undefined)).toBe(false);
|
|
925
|
+
expect(supportsBedrockToolCache(null)).toBe(false);
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
|
|
898
929
|
describe('stripAnthropicCacheControl', () => {
|
|
899
930
|
it('removes cache_control fields from content blocks', () => {
|
|
900
931
|
const messages: TestMsg[] = [
|
package/src/messages/cache.ts
CHANGED
|
@@ -79,6 +79,31 @@ export function buildBedrockCachePoint(
|
|
|
79
79
|
return ttl === '1h' ? { type: 'default', ttl: '1h' } : { type: 'default' };
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* A `cachePoint` under `toolConfig.tools` is only accepted on Anthropic Claude
|
|
84
|
+
* models. Amazon Nova rejects it outright — `Malformed input request:
|
|
85
|
+
* #/toolConfig/tools/0: extraneous key [cachePoint] is not permitted` — even
|
|
86
|
+
* though it accepts `cachePoint` in `system` and `messages` (verified live:
|
|
87
|
+
* Nova returns HTTP 200 with real `cacheWriteInputTokens` for both). Per the AWS
|
|
88
|
+
* prompt-caching docs the support table lists `tools` for Claude only.
|
|
89
|
+
*
|
|
90
|
+
* Gate ONLY the tool checkpoint on this, so a `promptCache: true` config on Nova
|
|
91
|
+
* keeps its valid message/system caching instead of either 400-ing (tool point)
|
|
92
|
+
* or losing caching entirely. This is a capability gate (the key is rejected
|
|
93
|
+
* outright), distinct from the TTL value which Bedrock downgrades gracefully.
|
|
94
|
+
*
|
|
95
|
+
* @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
|
|
96
|
+
* @see https://github.com/danny-avila/LibreChat/issues/13838
|
|
97
|
+
*/
|
|
98
|
+
export function supportsBedrockToolCache(
|
|
99
|
+
model: string | undefined | null
|
|
100
|
+
): boolean {
|
|
101
|
+
if (typeof model !== 'string') {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return /claude|anthropic/i.test(model);
|
|
105
|
+
}
|
|
106
|
+
|
|
82
107
|
/**
|
|
83
108
|
* Deep clones a message's content to prevent mutation of the original.
|
|
84
109
|
*/
|
package/src/tools/ReadFile.ts
CHANGED
|
@@ -22,13 +22,13 @@ CONSTRAINTS:
|
|
|
22
22
|
export const ReadFileToolSchema = {
|
|
23
23
|
type: 'object',
|
|
24
24
|
properties: {
|
|
25
|
-
|
|
25
|
+
path: {
|
|
26
26
|
type: 'string',
|
|
27
27
|
description:
|
|
28
28
|
'Path to the file. For skill files: "{skillName}/{path}" (e.g. "pdf-analyzer/src/utils.py"). For code execution output: the path as returned by the execution tool.',
|
|
29
29
|
},
|
|
30
30
|
},
|
|
31
|
-
required: ['
|
|
31
|
+
required: ['path'],
|
|
32
32
|
} as const;
|
|
33
33
|
|
|
34
34
|
export const ReadFileToolDefinition = {
|