@librechat/agents 3.2.41 → 3.2.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +4 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +5 -2
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +1 -1
  6. package/dist/cjs/llm/bedrock/index.cjs +9 -1
  7. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  8. package/dist/cjs/main.cjs +6 -0
  9. package/dist/cjs/messages/cache.cjs +43 -0
  10. package/dist/cjs/messages/cache.cjs.map +1 -1
  11. package/dist/cjs/session/JsonlSessionStore.cjs +1 -1
  12. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs +2 -2
  13. package/dist/cjs/tools/cloudflare/CloudflareProgrammaticToolCalling.cjs.map +1 -1
  14. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +118 -22
  15. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
  16. package/dist/cjs/tools/local/CompileCheckTool.cjs +11 -3
  17. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -1
  18. package/dist/cjs/tools/local/FileCheckpointer.cjs +8 -3
  19. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -1
  20. package/dist/cjs/tools/local/LocalCodingTools.cjs +26 -7
  21. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  22. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +2 -2
  23. package/dist/cjs/tools/local/syntaxCheck.cjs +6 -2
  24. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
  25. package/dist/cjs/tools/local/workspaceFS.cjs +20 -0
  26. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
  27. package/dist/esm/agents/AgentContext.mjs +5 -4
  28. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  29. package/dist/esm/graphs/Graph.mjs +6 -3
  30. package/dist/esm/graphs/Graph.mjs.map +1 -1
  31. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +1 -1
  32. package/dist/esm/llm/bedrock/index.mjs +10 -2
  33. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  34. package/dist/esm/main.mjs +3 -3
  35. package/dist/esm/messages/cache.mjs +42 -1
  36. package/dist/esm/messages/cache.mjs.map +1 -1
  37. package/dist/esm/session/JsonlSessionStore.mjs +1 -1
  38. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs +3 -3
  39. package/dist/esm/tools/cloudflare/CloudflareProgrammaticToolCalling.mjs.map +1 -1
  40. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +115 -23
  41. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
  42. package/dist/esm/tools/local/CompileCheckTool.mjs +11 -3
  43. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -1
  44. package/dist/esm/tools/local/FileCheckpointer.mjs +9 -4
  45. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -1
  46. package/dist/esm/tools/local/LocalCodingTools.mjs +26 -7
  47. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  48. package/dist/esm/tools/local/LocalExecutionEngine.mjs +2 -2
  49. package/dist/esm/tools/local/syntaxCheck.mjs +6 -2
  50. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
  51. package/dist/esm/tools/local/workspaceFS.mjs +19 -1
  52. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
  53. package/dist/types/agents/AgentContext.d.ts +3 -2
  54. package/dist/types/llm/bedrock/index.d.ts +7 -0
  55. package/dist/types/messages/cache.d.ts +27 -0
  56. package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +53 -0
  57. package/dist/types/tools/local/workspaceFS.d.ts +13 -0
  58. package/package.json +1 -1
  59. package/src/agents/AgentContext.ts +10 -3
  60. package/src/graphs/Graph.ts +24 -6
  61. package/src/llm/bedrock/index.ts +19 -3
  62. package/src/llm/bedrock/llm.spec.ts +97 -0
  63. package/src/messages/cache.test.ts +63 -0
  64. package/src/messages/cache.ts +51 -0
  65. package/src/specs/vllm-reasoning-toolcalls.test.ts +340 -0
  66. package/src/tools/__tests__/CloudflareSandboxExecution.test.ts +323 -0
  67. package/src/tools/__tests__/LocalExecutionTools.test.ts +54 -0
  68. package/src/tools/cloudflare/CloudflareProgrammaticToolCalling.ts +7 -2
  69. package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +269 -37
  70. package/src/tools/local/CompileCheckTool.ts +19 -3
  71. package/src/tools/local/FileCheckpointer.ts +20 -4
  72. package/src/tools/local/LocalCodingTools.ts +61 -8
  73. package/src/tools/local/__tests__/FileCheckpointer.test.ts +42 -0
  74. package/src/tools/local/syntaxCheck.ts +14 -2
  75. package/src/tools/local/workspaceFS.ts +27 -0
@@ -26,6 +26,19 @@ export type ReaddirEntry = {
26
26
  isDirectory(): boolean;
27
27
  isSymbolicLink(): boolean;
28
28
  };
29
+ /**
30
+ * Thrown when a {@link WorkspaceFS} operation is abandoned by a client-side
31
+ * backstop timeout (a stalled remote/sandbox RPC that never returned). This is
32
+ * DISTINCT from "file not found": callers that catch generic FS failures as
33
+ * absence — e.g. a pre-read before an overwrite, or a `stat` before snapshotting
34
+ * — MUST rethrow this so a stalled read isn't mistaken for a missing file (which
35
+ * would otherwise overwrite an existing file or snapshot it as absent).
36
+ */
37
+ export declare class WorkspaceClientTimeoutError extends Error {
38
+ readonly code = "WORKSPACE_CLIENT_TIMEOUT";
39
+ constructor(message: string);
40
+ }
41
+ export declare function isWorkspaceClientTimeoutError(error: unknown): error is WorkspaceClientTimeoutError;
29
42
  export interface WorkspaceFS {
30
43
  readFile(path: string, encoding: 'utf8'): Promise<string>;
31
44
  readFile(path: string): Promise<Buffer>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@librechat/agents",
3
- "version": "3.2.41",
3
+ "version": "3.2.43",
4
4
  "main": "./dist/cjs/main.cjs",
5
5
  "module": "./dist/esm/main.mjs",
6
6
  "types": "./dist/types/index.d.ts",
@@ -14,6 +14,7 @@ import {
14
14
  buildAnthropicCacheControl,
15
15
  buildBedrockCachePoint,
16
16
  resolvePromptCacheTtl,
17
+ resolveBedrockPromptCacheTtl,
17
18
  cloneMessage,
18
19
  type PromptCacheTtl,
19
20
  } from '@/messages/cache';
@@ -846,6 +847,8 @@ export class AgentContext {
846
847
  const bedrockOptions = this.clientOptions as
847
848
  | t.BedrockAnthropicClientOptions
848
849
  | undefined;
850
+ // Nova accepts system/message cachePoints (only the tool checkpoint is
851
+ // Claude-only), so this is gated on promptCache alone.
849
852
  return bedrockOptions?.promptCache === true;
850
853
  }
851
854
 
@@ -867,14 +870,18 @@ export class AgentContext {
867
870
  }
868
871
 
869
872
  /**
870
- * Resolved TTL for Bedrock prompt-cache checkpoints (default `'1h'`).
871
- * Models that don't support the 1-hour TTL downgrade to 5m server-side.
873
+ * Resolved TTL for Bedrock prompt-cache checkpoints (default `'1h'` on Claude).
874
+ * Claude models downgrade an unsupported 1h to 5m server-side; non-Claude
875
+ * models (Nova) reject the extended TTL, so they are clamped to 5m.
872
876
  */
873
877
  private getBedrockPromptCacheTtl(): PromptCacheTtl {
874
878
  const bedrockOptions = this.clientOptions as
875
879
  | t.BedrockAnthropicClientOptions
876
880
  | undefined;
877
- return resolvePromptCacheTtl(bedrockOptions?.promptCacheTtl);
881
+ return resolveBedrockPromptCacheTtl(
882
+ bedrockOptions?.promptCacheTtl,
883
+ (bedrockOptions as { model?: string } | undefined)?.model
884
+ );
878
885
  }
879
886
 
880
887
  private buildSystemMessage({
@@ -28,6 +28,8 @@ import {
28
28
  syncBudgetDerivedFields,
29
29
  addTailCacheControl,
30
30
  resolvePromptCacheTtl,
31
+ resolveBedrockPromptCacheTtl,
32
+ supportsBedrockToolCache,
31
33
  getMessageId,
32
34
  makeIsDeferred,
33
35
  partitionAndMarkAnthropicToolCache,
@@ -1442,11 +1444,19 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1442
1444
  | undefined
1443
1445
  )?.promptCache === true
1444
1446
  ) {
1445
- toolsForBinding =
1446
- partitionAndMarkBedrockToolCache(
1447
- rawToolsForBinding,
1448
- makeIsDeferred(agentContext.toolDefinitions)
1449
- ) ?? rawToolsForBinding;
1447
+ const bedrockModel = (
1448
+ agentContext.clientOptions as { model?: string } | undefined
1449
+ )?.model;
1450
+ // An omitted model falls back to LangChain's default Claude model (which
1451
+ // supports tool caching); only an explicit non-Claude model (e.g. Nova)
1452
+ // skips tool marking so its stray marker never leaks into toolConfig.
1453
+ if (bedrockModel == null || supportsBedrockToolCache(bedrockModel)) {
1454
+ toolsForBinding =
1455
+ partitionAndMarkBedrockToolCache(
1456
+ rawToolsForBinding,
1457
+ makeIsDeferred(agentContext.toolDefinitions)
1458
+ ) ?? rawToolsForBinding;
1459
+ }
1450
1460
  }
1451
1461
 
1452
1462
  let model =
@@ -1790,6 +1800,9 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1790
1800
  | t.ProviderOptionsMap[Providers.OPENROUTER]
1791
1801
  | undefined
1792
1802
  )?.promptCache === true;
1803
+ // Message/system cache points work on all cache-capable Bedrock models,
1804
+ // including Nova (verified live: HTTP 200 with cacheWriteInputTokens). Only
1805
+ // the tool checkpoint is Claude-only, so this is gated on promptCache alone.
1793
1806
  const bedrockPromptCacheEnabled =
1794
1807
  agentContext.provider === Providers.BEDROCK &&
1795
1808
  (
@@ -1868,9 +1881,14 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
1868
1881
  const bedrockOptions = agentContext.clientOptions as
1869
1882
  | t.BedrockAnthropicClientOptions
1870
1883
  | undefined;
1884
+ // Non-Claude models (Nova) reject the extended 1h TTL, so resolve it
1885
+ // against the model — message/system caching stays on, clamped to 5m.
1871
1886
  finalMessages = addBedrockTailCacheControl<BaseMessage>(
1872
1887
  finalMessages,
1873
- resolvePromptCacheTtl(bedrockOptions?.promptCacheTtl)
1888
+ resolveBedrockPromptCacheTtl(
1889
+ bedrockOptions?.promptCacheTtl,
1890
+ (bedrockOptions as { model?: string } | undefined)?.model
1891
+ )
1874
1892
  );
1875
1893
  }
1876
1894
 
@@ -39,7 +39,11 @@ import {
39
39
  handleConverseStreamContentBlockDelta,
40
40
  handleConverseStreamMetadata,
41
41
  } from './utils';
42
- import { resolvePromptCacheTtl, type PromptCacheTtl } from '@/messages/cache';
42
+ import {
43
+ resolveBedrockPromptCacheTtl,
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,11 +180,11 @@ 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,
171
- resolvePromptCacheTtl(this.promptCacheTtl)
187
+ resolveBedrockPromptCacheTtl(this.promptCacheTtl, this.cacheModelId)
172
188
  )
173
189
  : baseParams.toolConfig;
174
190
 
@@ -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,8 @@ import {
19
19
  buildAnthropicCacheControl,
20
20
  buildBedrockCachePoint,
21
21
  resolvePromptCacheTtl,
22
+ resolveBedrockPromptCacheTtl,
23
+ supportsBedrockToolCache,
22
24
  DEFAULT_PROMPT_CACHE_TTL,
23
25
  } from './cache';
24
26
  import { _convertMessagesToOpenAIParams } from '@/llm/openai/utils';
@@ -895,6 +897,67 @@ describe('synthetic skill/meta messages are not cache-anchored', () => {
895
897
  });
896
898
  });
897
899
 
900
+ describe('supportsBedrockToolCache', () => {
901
+ test('returns true for Claude model ids (incl. cross-region + profile)', () => {
902
+ expect(
903
+ supportsBedrockToolCache('anthropic.claude-3-5-sonnet-20241022-v2:0')
904
+ ).toBe(true);
905
+ expect(
906
+ supportsBedrockToolCache('us.anthropic.claude-sonnet-4-5-20250929-v1:0')
907
+ ).toBe(true);
908
+ expect(supportsBedrockToolCache('anthropic.claude-opus-4-6-v1')).toBe(true);
909
+ });
910
+
911
+ test('returns false for Nova and other non-Claude families (tool cache only)', () => {
912
+ // Nova accepts system/message cachePoints but rejects the tool checkpoint.
913
+ expect(supportsBedrockToolCache('us.amazon.nova-pro-v1:0')).toBe(false);
914
+ expect(supportsBedrockToolCache('amazon.nova-lite-v1:0')).toBe(false);
915
+ expect(supportsBedrockToolCache('meta.llama3-1-70b-instruct-v1:0')).toBe(
916
+ false
917
+ );
918
+ expect(supportsBedrockToolCache('mistral.mistral-large-2407-v1:0')).toBe(
919
+ false
920
+ );
921
+ });
922
+
923
+ test('returns false for empty / missing model', () => {
924
+ expect(supportsBedrockToolCache('')).toBe(false);
925
+ expect(supportsBedrockToolCache(undefined)).toBe(false);
926
+ expect(supportsBedrockToolCache(null)).toBe(false);
927
+ });
928
+ });
929
+
930
+ describe('resolveBedrockPromptCacheTtl', () => {
931
+ test('Claude: defaults to 1h and honors an explicit ttl', () => {
932
+ const claude = 'us.anthropic.claude-sonnet-4-5-20250929-v1:0';
933
+ expect(resolveBedrockPromptCacheTtl(undefined, claude)).toBe('1h');
934
+ expect(resolveBedrockPromptCacheTtl('1h', claude)).toBe('1h');
935
+ expect(resolveBedrockPromptCacheTtl('5m', claude)).toBe('5m');
936
+ });
937
+
938
+ test('Nova: clamps to 5m even when 1h is configured (extended TTL Anthropic-only)', () => {
939
+ const nova = 'us.amazon.nova-lite-v1:0';
940
+ expect(resolveBedrockPromptCacheTtl(undefined, nova)).toBe('5m');
941
+ expect(resolveBedrockPromptCacheTtl('1h', nova)).toBe('5m');
942
+ expect(resolveBedrockPromptCacheTtl('5m', nova)).toBe('5m');
943
+ });
944
+
945
+ test('other non-Claude families clamp to 5m', () => {
946
+ expect(
947
+ resolveBedrockPromptCacheTtl('1h', 'meta.llama3-1-70b-instruct-v1:0')
948
+ ).toBe('5m');
949
+ expect(
950
+ resolveBedrockPromptCacheTtl('1h', 'mistral.mistral-large-2407-v1:0')
951
+ ).toBe('5m');
952
+ });
953
+
954
+ test('omitted model defaults to Claude behavior (1h)', () => {
955
+ expect(resolveBedrockPromptCacheTtl(undefined, undefined)).toBe('1h');
956
+ expect(resolveBedrockPromptCacheTtl(undefined, null)).toBe('1h');
957
+ expect(resolveBedrockPromptCacheTtl('1h', undefined)).toBe('1h');
958
+ });
959
+ });
960
+
898
961
  describe('stripAnthropicCacheControl', () => {
899
962
  it('removes cache_control fields from content blocks', () => {
900
963
  const messages: TestMsg[] = [
@@ -79,6 +79,57 @@ export function buildBedrockCachePoint(
79
79
  return ttl === '1h' ? { type: 'default', ttl: '1h' } : { type: 'default' };
80
80
  }
81
81
 
82
+ /**
83
+ * Whether a Bedrock model id is an Anthropic Claude model. Claude is the only
84
+ * Bedrock family that accepts the explicit cache features below; Amazon Nova and
85
+ * others reject them. Matches both the `anthropic.` and cross-region
86
+ * (`us.anthropic.`) / bare `claude` forms.
87
+ */
88
+ function isBedrockAnthropicModel(model: string | undefined | null): boolean {
89
+ return typeof model === 'string' && /claude|anthropic/i.test(model);
90
+ }
91
+
92
+ /**
93
+ * A `cachePoint` under `toolConfig.tools` is only accepted on Anthropic Claude
94
+ * models. Amazon Nova rejects it outright — `Malformed input request:
95
+ * #/toolConfig/tools/0: extraneous key [cachePoint] is not permitted` — even
96
+ * though it accepts `cachePoint` in `system` and `messages` (verified live:
97
+ * Nova returns HTTP 200 with real `cacheWriteInputTokens` for both). Per the AWS
98
+ * prompt-caching docs the support table lists `tools` for Claude only.
99
+ *
100
+ * Gate ONLY the tool checkpoint on this, so a `promptCache: true` config on Nova
101
+ * keeps its valid message/system caching instead of either 400-ing (tool point)
102
+ * or losing caching entirely.
103
+ *
104
+ * @see https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
105
+ * @see https://github.com/danny-avila/LibreChat/issues/13838
106
+ */
107
+ export function supportsBedrockToolCache(
108
+ model: string | undefined | null
109
+ ): boolean {
110
+ return isBedrockAnthropicModel(model);
111
+ }
112
+
113
+ /**
114
+ * Resolve the Bedrock `cachePoint` TTL for a model. The extended `1h` TTL is
115
+ * Anthropic-only: non-Claude models (e.g. Nova) hard-reject it with
116
+ * `Extended TTL prompt caching is only supported for Anthropic models`, so they
117
+ * are clamped to `5m` even when `1h` is configured (verified live). An omitted
118
+ * model defaults to a Claude model, and Claude downgrades an unsupported `1h` to
119
+ * `5m` server-side, so the `1h` default is safe for the Claude/undefined paths.
120
+ *
121
+ * @see https://github.com/danny-avila/LibreChat/issues/13838
122
+ */
123
+ export function resolveBedrockPromptCacheTtl(
124
+ ttl: PromptCacheTtl | undefined,
125
+ model: string | undefined | null
126
+ ): PromptCacheTtl {
127
+ if (model != null && !isBedrockAnthropicModel(model)) {
128
+ return '5m';
129
+ }
130
+ return resolvePromptCacheTtl(ttl);
131
+ }
132
+
82
133
  /**
83
134
  * Deep clones a message's content to prevent mutation of the original.
84
135
  */