illuma-agents 1.0.9 → 1.0.11

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 (146) hide show
  1. package/LICENSE +1 -1
  2. package/dist/cjs/agents/AgentContext.cjs +228 -27
  3. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  4. package/dist/cjs/common/enum.cjs +2 -0
  5. package/dist/cjs/common/enum.cjs.map +1 -1
  6. package/dist/cjs/events.cjs +3 -0
  7. package/dist/cjs/events.cjs.map +1 -1
  8. package/dist/cjs/graphs/Graph.cjs +29 -19
  9. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  10. package/dist/cjs/instrumentation.cjs +1 -1
  11. package/dist/cjs/instrumentation.cjs.map +1 -1
  12. package/dist/cjs/llm/anthropic/index.cjs +1 -1
  13. package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
  14. package/dist/cjs/llm/bedrock/index.cjs +122 -7
  15. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  16. package/dist/cjs/llm/google/index.cjs +1 -1
  17. package/dist/cjs/llm/google/index.cjs.map +1 -1
  18. package/dist/cjs/llm/openai/index.cjs +108 -6
  19. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  20. package/dist/cjs/llm/openai/utils/index.cjs +87 -1
  21. package/dist/cjs/llm/openai/utils/index.cjs.map +1 -1
  22. package/dist/cjs/llm/openrouter/index.cjs +176 -2
  23. package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
  24. package/dist/cjs/main.cjs +18 -0
  25. package/dist/cjs/main.cjs.map +1 -1
  26. package/dist/cjs/messages/cache.cjs +149 -54
  27. package/dist/cjs/messages/cache.cjs.map +1 -1
  28. package/dist/cjs/messages/tools.cjs +85 -0
  29. package/dist/cjs/messages/tools.cjs.map +1 -0
  30. package/dist/cjs/stream.cjs +20 -0
  31. package/dist/cjs/stream.cjs.map +1 -1
  32. package/dist/cjs/tools/CodeExecutor.cjs +4 -0
  33. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  34. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +438 -0
  35. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
  36. package/dist/cjs/tools/ToolNode.cjs +54 -6
  37. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  38. package/dist/cjs/tools/ToolSearchRegex.cjs +455 -0
  39. package/dist/cjs/tools/ToolSearchRegex.cjs.map +1 -0
  40. package/dist/cjs/tools/search/tool.cjs +21 -1
  41. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  42. package/dist/cjs/utils/run.cjs +5 -1
  43. package/dist/cjs/utils/run.cjs.map +1 -1
  44. package/dist/esm/agents/AgentContext.mjs +228 -27
  45. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  46. package/dist/esm/common/enum.mjs +2 -0
  47. package/dist/esm/common/enum.mjs.map +1 -1
  48. package/dist/esm/events.mjs +4 -1
  49. package/dist/esm/events.mjs.map +1 -1
  50. package/dist/esm/graphs/Graph.mjs +29 -19
  51. package/dist/esm/graphs/Graph.mjs.map +1 -1
  52. package/dist/esm/instrumentation.mjs +1 -1
  53. package/dist/esm/instrumentation.mjs.map +1 -1
  54. package/dist/esm/llm/anthropic/index.mjs +1 -1
  55. package/dist/esm/llm/anthropic/index.mjs.map +1 -1
  56. package/dist/esm/llm/bedrock/index.mjs +122 -7
  57. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  58. package/dist/esm/llm/google/index.mjs +1 -1
  59. package/dist/esm/llm/google/index.mjs.map +1 -1
  60. package/dist/esm/llm/openai/index.mjs +109 -7
  61. package/dist/esm/llm/openai/index.mjs.map +1 -1
  62. package/dist/esm/llm/openai/utils/index.mjs +88 -2
  63. package/dist/esm/llm/openai/utils/index.mjs.map +1 -1
  64. package/dist/esm/llm/openrouter/index.mjs +176 -2
  65. package/dist/esm/llm/openrouter/index.mjs.map +1 -1
  66. package/dist/esm/main.mjs +3 -0
  67. package/dist/esm/main.mjs.map +1 -1
  68. package/dist/esm/messages/cache.mjs +149 -54
  69. package/dist/esm/messages/cache.mjs.map +1 -1
  70. package/dist/esm/messages/tools.mjs +82 -0
  71. package/dist/esm/messages/tools.mjs.map +1 -0
  72. package/dist/esm/stream.mjs +20 -0
  73. package/dist/esm/stream.mjs.map +1 -1
  74. package/dist/esm/tools/CodeExecutor.mjs +4 -0
  75. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  76. package/dist/esm/tools/ProgrammaticToolCalling.mjs +430 -0
  77. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
  78. package/dist/esm/tools/ToolNode.mjs +54 -6
  79. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  80. package/dist/esm/tools/ToolSearchRegex.mjs +448 -0
  81. package/dist/esm/tools/ToolSearchRegex.mjs.map +1 -0
  82. package/dist/esm/tools/search/tool.mjs +21 -1
  83. package/dist/esm/tools/search/tool.mjs.map +1 -1
  84. package/dist/esm/utils/run.mjs +5 -1
  85. package/dist/esm/utils/run.mjs.map +1 -1
  86. package/dist/types/agents/AgentContext.d.ts +65 -5
  87. package/dist/types/common/enum.d.ts +2 -0
  88. package/dist/types/graphs/Graph.d.ts +3 -2
  89. package/dist/types/index.d.ts +2 -0
  90. package/dist/types/llm/anthropic/index.d.ts +1 -1
  91. package/dist/types/llm/bedrock/index.d.ts +31 -4
  92. package/dist/types/llm/google/index.d.ts +1 -1
  93. package/dist/types/llm/openai/index.d.ts +4 -3
  94. package/dist/types/llm/openai/utils/index.d.ts +10 -1
  95. package/dist/types/llm/openrouter/index.d.ts +5 -2
  96. package/dist/types/messages/cache.d.ts +23 -8
  97. package/dist/types/messages/index.d.ts +1 -0
  98. package/dist/types/messages/tools.d.ts +17 -0
  99. package/dist/types/test/mockTools.d.ts +28 -0
  100. package/dist/types/tools/ProgrammaticToolCalling.d.ts +91 -0
  101. package/dist/types/tools/ToolNode.d.ts +10 -2
  102. package/dist/types/tools/ToolSearchRegex.d.ts +80 -0
  103. package/dist/types/types/graph.d.ts +7 -1
  104. package/dist/types/types/tools.d.ts +138 -0
  105. package/package.json +8 -3
  106. package/src/agents/AgentContext.ts +267 -27
  107. package/src/agents/__tests__/AgentContext.test.ts +805 -0
  108. package/src/common/enum.ts +2 -0
  109. package/src/events.ts +5 -1
  110. package/src/graphs/Graph.ts +35 -20
  111. package/src/index.ts +2 -0
  112. package/src/instrumentation.ts +1 -1
  113. package/src/llm/anthropic/index.ts +2 -2
  114. package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +473 -0
  115. package/src/llm/bedrock/index.ts +150 -13
  116. package/src/llm/google/index.ts +2 -2
  117. package/src/llm/google/llm.spec.ts +3 -1
  118. package/src/llm/openai/index.ts +135 -9
  119. package/src/llm/openai/utils/index.ts +116 -1
  120. package/src/llm/openrouter/index.ts +224 -3
  121. package/src/messages/__tests__/tools.test.ts +473 -0
  122. package/src/messages/cache.ts +163 -61
  123. package/src/messages/index.ts +1 -0
  124. package/src/messages/tools.ts +99 -0
  125. package/src/scripts/code_exec_ptc.ts +334 -0
  126. package/src/scripts/programmatic_exec.ts +396 -0
  127. package/src/scripts/programmatic_exec_agent.ts +231 -0
  128. package/src/scripts/tool_search_regex.ts +162 -0
  129. package/src/specs/thinking-prune.test.ts +52 -118
  130. package/src/stream.ts +26 -0
  131. package/src/test/mockTools.ts +366 -0
  132. package/src/tools/CodeExecutor.ts +4 -0
  133. package/src/tools/ProgrammaticToolCalling.ts +558 -0
  134. package/src/tools/ToolNode.ts +60 -7
  135. package/src/tools/ToolSearchRegex.ts +535 -0
  136. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +318 -0
  137. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +853 -0
  138. package/src/tools/__tests__/ToolSearchRegex.integration.test.ts +161 -0
  139. package/src/tools/__tests__/ToolSearchRegex.test.ts +232 -0
  140. package/src/tools/search/jina-reranker.test.ts +16 -16
  141. package/src/tools/search/tool.ts +23 -1
  142. package/src/types/graph.ts +7 -1
  143. package/src/types/tools.ts +166 -0
  144. package/src/utils/llmConfig.ts +8 -2
  145. package/src/utils/run.ts +5 -1
  146. package/src/tools/search/direct-url.test.ts +0 -530
@@ -159,6 +159,8 @@ export enum Callback {
159
159
  export enum Constants {
160
160
  OFFICIAL_CODE_BASEURL = 'https://api.illuma.ai/v1',
161
161
  EXECUTE_CODE = 'execute_code',
162
+ TOOL_SEARCH_REGEX = 'tool_search_regex',
163
+ PROGRAMMATIC_TOOL_CALLING = 'run_tools_with_code',
162
164
  WEB_SEARCH = 'web_search',
163
165
  CONTENT_AND_ARTIFACT = 'content_and_artifact',
164
166
  LC_TRANSFER_TO_ = 'lc_transfer_to_',
package/src/events.ts CHANGED
@@ -9,7 +9,7 @@ import type { MultiAgentGraph, StandardGraph } from '@/graphs';
9
9
  import type { Logger } from 'winston';
10
10
  import type * as t from '@/types';
11
11
  import { handleToolCalls } from '@/tools/handlers';
12
- import { Providers } from '@/common';
12
+ import { Constants, Providers } from '@/common';
13
13
 
14
14
  export class HandlerRegistry {
15
15
  private handlers: Map<string, t.EventHandler> = new Map();
@@ -112,6 +112,10 @@ export class ToolEndHandler implements t.EventHandler {
112
112
  return;
113
113
  }
114
114
 
115
+ if (metadata[Constants.PROGRAMMATIC_TOOL_CALLING] === true) {
116
+ return;
117
+ }
118
+
115
119
  if (this.callback) {
116
120
  await this.callback(toolEndData, metadata);
117
121
  }
@@ -46,6 +46,7 @@ import {
46
46
  formatContentStrings,
47
47
  createPruneMessages,
48
48
  addCacheControl,
49
+ extractToolDiscoveries,
49
50
  } from '@/messages';
50
51
  import {
51
52
  resetIfNotEmpty,
@@ -443,9 +444,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
443
444
  initializeTools({
444
445
  currentTools,
445
446
  currentToolMap,
447
+ agentContext,
446
448
  }: {
447
449
  currentTools?: t.GraphTools;
448
450
  currentToolMap?: t.ToolMap;
451
+ agentContext?: AgentContext;
449
452
  }): CustomToolNode<t.BaseGraphState> | ToolNode<t.BaseGraphState> {
450
453
  return new CustomToolNode<t.BaseGraphState>({
451
454
  tools: (currentTools as t.GenericTool[] | undefined) ?? [],
@@ -453,6 +456,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
453
456
  toolCallStepIds: this.toolCallStepIds,
454
457
  errorHandler: (data, metadata) =>
455
458
  StandardGraph.handleToolCallErrorStatic(this, data, metadata),
459
+ toolRegistry: agentContext?.toolRegistry,
456
460
  });
457
461
  }
458
462
 
@@ -603,7 +607,7 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
603
607
  client.abortHandler = undefined;
604
608
  }
605
609
 
606
- createCallModel(agentId = 'default', currentModel?: t.ChatModel) {
610
+ createCallModel(agentId = 'default') {
607
611
  return async (
608
612
  state: t.BaseGraphState,
609
613
  config?: RunnableConfig
@@ -616,15 +620,31 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
616
620
  throw new Error(`Agent context not found for agentId: ${agentId}`);
617
621
  }
618
622
 
619
- const model = this.overrideModel ?? currentModel;
620
- if (!model) {
621
- throw new Error('No Graph model found');
622
- }
623
623
  if (!config) {
624
624
  throw new Error('No config provided');
625
625
  }
626
626
 
627
- // Ensure token calculations are complete before proceeding
627
+ const { messages } = state;
628
+
629
+ // Extract tool discoveries from current turn only (similar to formatArtifactPayload pattern)
630
+ const discoveredNames = extractToolDiscoveries(messages);
631
+ if (discoveredNames.length > 0) {
632
+ agentContext.markToolsAsDiscovered(discoveredNames);
633
+ }
634
+
635
+ const toolsForBinding = agentContext.getToolsForBinding();
636
+ let model =
637
+ this.overrideModel ??
638
+ this.initializeModel({
639
+ tools: toolsForBinding,
640
+ provider: agentContext.provider,
641
+ clientOptions: agentContext.clientOptions,
642
+ });
643
+
644
+ if (agentContext.systemRunnable) {
645
+ model = agentContext.systemRunnable.pipe(model as Runnable);
646
+ }
647
+
628
648
  if (agentContext.tokenCalculationPromise) {
629
649
  await agentContext.tokenCalculationPromise;
630
650
  }
@@ -632,7 +652,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
632
652
  config.signal = this.signal;
633
653
  }
634
654
  this.config = config;
635
- const { messages } = state;
636
655
 
637
656
  let messagesToUse = messages;
638
657
  if (
@@ -705,7 +724,8 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
705
724
  formatAnthropicArtifactContent(finalMessages);
706
725
  } else if (
707
726
  isLatestToolMessage &&
708
- (isOpenAILike(agentContext.provider) ||
727
+ ((isOpenAILike(agentContext.provider) &&
728
+ agentContext.provider !== Providers.DEEPSEEK) ||
709
729
  isGoogleLike(agentContext.provider))
710
730
  ) {
711
731
  formatArtifactPayload(finalMessages);
@@ -729,7 +749,11 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
729
749
  const bedrockOptions = agentContext.clientOptions as
730
750
  | t.BedrockAnthropicClientOptions
731
751
  | undefined;
732
- if (bedrockOptions?.promptCache === true) {
752
+ // Both Claude and Nova models support cachePoint in system and messages
753
+ // (Llama, Titan, and other models do NOT support cachePoint)
754
+ const modelId = bedrockOptions?.model?.toLowerCase() ?? '';
755
+ const supportsCaching = modelId.includes('claude') || modelId.includes('anthropic') || modelId.includes('nova');
756
+ if (bedrockOptions?.promptCache === true && supportsCaching) {
733
757
  finalMessages = addBedrockCacheControl<BaseMessage>(finalMessages);
734
758
  }
735
759
  }
@@ -843,16 +867,6 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
843
867
  throw new Error(`Agent context not found for agentId: ${agentId}`);
844
868
  }
845
869
 
846
- let currentModel = this.initializeModel({
847
- tools: agentContext.tools,
848
- provider: agentContext.provider,
849
- clientOptions: agentContext.clientOptions,
850
- });
851
-
852
- if (agentContext.systemRunnable) {
853
- currentModel = agentContext.systemRunnable.pipe(currentModel);
854
- }
855
-
856
870
  const agentNode = `${AGENT}${agentId}` as const;
857
871
  const toolNode = `${TOOLS}${agentId}` as const;
858
872
 
@@ -872,12 +886,13 @@ export class StandardGraph extends Graph<t.BaseGraphState, t.GraphNode> {
872
886
  });
873
887
 
874
888
  const workflow = new StateGraph(StateAnnotation)
875
- .addNode(agentNode, this.createCallModel(agentId, currentModel))
889
+ .addNode(agentNode, this.createCallModel(agentId))
876
890
  .addNode(
877
891
  toolNode,
878
892
  this.initializeTools({
879
893
  currentTools: agentContext.tools,
880
894
  currentToolMap: agentContext.toolMap,
895
+ agentContext,
881
896
  })
882
897
  )
883
898
  .addEdge(START, agentNode)
package/src/index.ts CHANGED
@@ -11,6 +11,8 @@ export * from './graphs';
11
11
  /* Tools */
12
12
  export * from './tools/Calculator';
13
13
  export * from './tools/CodeExecutor';
14
+ export * from './tools/ProgrammaticToolCalling';
15
+ export * from './tools/ToolSearchRegex';
14
16
  export * from './tools/handlers';
15
17
  export * from './tools/search';
16
18
 
@@ -11,7 +11,7 @@ if (
11
11
  publicKey: process.env.LANGFUSE_PUBLIC_KEY,
12
12
  secretKey: process.env.LANGFUSE_SECRET_KEY,
13
13
  baseUrl: process.env.LANGFUSE_BASE_URL,
14
- environment: process.env.NODE_ENV ?? 'development',
14
+ environment: process.env.LANGFUSE_TRACING_ENVIRONMENT ?? process.env.NODE_ENV ?? 'development',
15
15
  });
16
16
 
17
17
  const sdk = new NodeSDK({
@@ -143,8 +143,8 @@ export class CustomAnthropic extends ChatAnthropicMessages {
143
143
  this._lc_stream_delay = fields?._lc_stream_delay ?? 25;
144
144
  }
145
145
 
146
- static lc_name(): 'IllumaAnthropic' {
147
- return 'IllumaAnthropic';
146
+ static lc_name(): 'LibreChatAnthropic' {
147
+ return 'LibreChatAnthropic';
148
148
  }
149
149
 
150
150
  /**
@@ -0,0 +1,473 @@
1
+ /**
2
+ * Unit tests for Bedrock Prompt Caching functionality
3
+ *
4
+ * Tests cover:
5
+ * 1. CustomChatBedrockConverse - Tool caching with cachePoint
6
+ * 2. AgentContext - System message caching with cachePoint for Bedrock
7
+ */
8
+
9
+ import { CustomChatBedrockConverse } from '../index';
10
+ import { AgentContext } from '@/agents/AgentContext';
11
+ import { Providers } from '@/common';
12
+ import type * as t from '@/types';
13
+
14
+ describe('Bedrock Prompt Caching', () => {
15
+ describe('CustomChatBedrockConverse - Tool Caching', () => {
16
+ describe('invocationParams with promptCache enabled', () => {
17
+ it('should add cachePoint to tools array when promptCache is true', () => {
18
+ const model = new CustomChatBedrockConverse({
19
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
20
+ region: 'us-east-1',
21
+ promptCache: true,
22
+ });
23
+
24
+ const mockTools = [
25
+ {
26
+ toolSpec: {
27
+ name: 'get_weather',
28
+ description: 'Get weather for a location',
29
+ inputSchema: { json: { type: 'object', properties: {} } },
30
+ },
31
+ },
32
+ {
33
+ toolSpec: {
34
+ name: 'search_web',
35
+ description: 'Search the web',
36
+ inputSchema: { json: { type: 'object', properties: {} } },
37
+ },
38
+ },
39
+ ];
40
+
41
+ const params = model.invocationParams({
42
+ tools: mockTools,
43
+ } as any);
44
+
45
+ // Should have tools + cachePoint
46
+ expect(params.toolConfig?.tools).toHaveLength(3);
47
+ expect(params.toolConfig?.tools?.[2]).toEqual({
48
+ cachePoint: { type: 'default' },
49
+ });
50
+ });
51
+
52
+ it('should NOT add cachePoint when promptCache is false', () => {
53
+ const model = new CustomChatBedrockConverse({
54
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
55
+ region: 'us-east-1',
56
+ promptCache: false,
57
+ });
58
+
59
+ const mockTools = [
60
+ {
61
+ toolSpec: {
62
+ name: 'get_weather',
63
+ description: 'Get weather',
64
+ inputSchema: { json: { type: 'object', properties: {} } },
65
+ },
66
+ },
67
+ ];
68
+
69
+ const params = model.invocationParams({
70
+ tools: mockTools,
71
+ } as any);
72
+
73
+ // Should only have original tools, no cachePoint
74
+ expect(params.toolConfig?.tools).toHaveLength(1);
75
+ expect(params.toolConfig?.tools?.[0]).toEqual(mockTools[0]);
76
+ });
77
+
78
+ it('should NOT add cachePoint when promptCache is undefined (default)', () => {
79
+ const model = new CustomChatBedrockConverse({
80
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
81
+ region: 'us-east-1',
82
+ });
83
+
84
+ const mockTools = [
85
+ {
86
+ toolSpec: {
87
+ name: 'get_weather',
88
+ description: 'Get weather',
89
+ inputSchema: { json: { type: 'object', properties: {} } },
90
+ },
91
+ },
92
+ ];
93
+
94
+ const params = model.invocationParams({
95
+ tools: mockTools,
96
+ } as any);
97
+
98
+ // Should only have original tools
99
+ expect(params.toolConfig?.tools).toHaveLength(1);
100
+ });
101
+
102
+ it('should NOT add cachePoint when no tools are provided', () => {
103
+ const model = new CustomChatBedrockConverse({
104
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
105
+ region: 'us-east-1',
106
+ promptCache: true,
107
+ });
108
+
109
+ const params = model.invocationParams({} as any);
110
+
111
+ // toolConfig should be undefined or have no tools
112
+ expect(params.toolConfig?.tools).toBeUndefined();
113
+ });
114
+
115
+ it('should NOT add cachePoint when tools array is empty', () => {
116
+ const model = new CustomChatBedrockConverse({
117
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
118
+ region: 'us-east-1',
119
+ promptCache: true,
120
+ });
121
+
122
+ const params = model.invocationParams({
123
+ tools: [],
124
+ } as any);
125
+
126
+ // Empty tools array results in undefined toolConfig
127
+ expect(params.toolConfig).toBeUndefined();
128
+ });
129
+
130
+ it('should preserve other invocationParams properties', () => {
131
+ const model = new CustomChatBedrockConverse({
132
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
133
+ region: 'us-east-1',
134
+ promptCache: true,
135
+ temperature: 0.7,
136
+ maxTokens: 1000,
137
+ });
138
+
139
+ const mockTools = [
140
+ {
141
+ toolSpec: {
142
+ name: 'test_tool',
143
+ description: 'Test',
144
+ inputSchema: { json: { type: 'object', properties: {} } },
145
+ },
146
+ },
147
+ ];
148
+
149
+ const params = model.invocationParams({
150
+ tools: mockTools,
151
+ } as any);
152
+
153
+ // Check that other params are preserved
154
+ expect(params.inferenceConfig?.temperature).toBe(0.7);
155
+ expect(params.inferenceConfig?.maxTokens).toBe(1000);
156
+ // And cachePoint is still added
157
+ expect(params.toolConfig?.tools).toHaveLength(2);
158
+ });
159
+ });
160
+
161
+ describe('promptCache property', () => {
162
+ it('should store promptCache value from constructor', () => {
163
+ const modelWithCache = new CustomChatBedrockConverse({
164
+ model: 'test-model',
165
+ region: 'us-east-1',
166
+ promptCache: true,
167
+ });
168
+
169
+ const modelWithoutCache = new CustomChatBedrockConverse({
170
+ model: 'test-model',
171
+ region: 'us-east-1',
172
+ promptCache: false,
173
+ });
174
+
175
+ expect(modelWithCache.promptCache).toBe(true);
176
+ expect(modelWithoutCache.promptCache).toBe(false);
177
+ });
178
+
179
+ it('should default promptCache to false when not provided', () => {
180
+ const model = new CustomChatBedrockConverse({
181
+ model: 'test-model',
182
+ region: 'us-east-1',
183
+ });
184
+
185
+ expect(model.promptCache).toBe(false);
186
+ });
187
+ });
188
+ });
189
+
190
+ describe('AgentContext - System Message Caching', () => {
191
+ const createBedrockContext = (options: {
192
+ instructions?: string;
193
+ promptCache?: boolean;
194
+ additionalInstructions?: string;
195
+ }): AgentContext => {
196
+ const clientOptions: t.BedrockAnthropicInput = {
197
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
198
+ region: 'us-east-1',
199
+ promptCache: options.promptCache,
200
+ };
201
+
202
+ return AgentContext.fromConfig({
203
+ agentId: 'test-bedrock-agent',
204
+ provider: Providers.BEDROCK,
205
+ instructions: options.instructions,
206
+ additional_instructions: options.additionalInstructions,
207
+ clientOptions,
208
+ });
209
+ };
210
+
211
+ const createAnthropicContext = (options: {
212
+ instructions?: string;
213
+ promptCachingEnabled?: boolean;
214
+ }): AgentContext => {
215
+ const clientOptions: t.AnthropicClientOptions = {
216
+ clientOptions: {
217
+ defaultHeaders: options.promptCachingEnabled
218
+ ? { 'anthropic-beta': 'prompt-caching-2024-07-31' }
219
+ : undefined,
220
+ },
221
+ };
222
+
223
+ return AgentContext.fromConfig({
224
+ agentId: 'test-anthropic-agent',
225
+ provider: Providers.ANTHROPIC,
226
+ instructions: options.instructions,
227
+ clientOptions,
228
+ });
229
+ };
230
+
231
+ describe('Bedrock system message with promptCache: true', () => {
232
+ it('should add cachePoint to system message content', async () => {
233
+ const ctx = createBedrockContext({
234
+ instructions: 'You are a helpful assistant.',
235
+ promptCache: true,
236
+ });
237
+
238
+ const systemRunnable = ctx.systemRunnable;
239
+ expect(systemRunnable).toBeDefined();
240
+
241
+ // Invoke the runnable to get the messages
242
+ const result = await systemRunnable!.invoke([]);
243
+ const systemMessage = result[0];
244
+
245
+ // Check content structure has cachePoint
246
+ expect(systemMessage.content).toBeInstanceOf(Array);
247
+ const content = systemMessage.content as Array<Record<string, unknown>>;
248
+
249
+ expect(content).toHaveLength(2);
250
+ expect(content[0]).toEqual({
251
+ type: 'text',
252
+ text: 'You are a helpful assistant.',
253
+ });
254
+ expect(content[1]).toEqual({
255
+ cachePoint: { type: 'default' },
256
+ });
257
+ });
258
+
259
+ it('should include combined instructions and additional_instructions with cachePoint', async () => {
260
+ const ctx = createBedrockContext({
261
+ instructions: 'Base instructions.',
262
+ additionalInstructions: 'Additional context.',
263
+ promptCache: true,
264
+ });
265
+
266
+ const result = await ctx.systemRunnable!.invoke([]);
267
+ const systemMessage = result[0];
268
+ const content = systemMessage.content as Array<Record<string, unknown>>;
269
+
270
+ expect(content).toHaveLength(2);
271
+ expect((content[0] as { text: string }).text).toContain('Base instructions.');
272
+ expect((content[0] as { text: string }).text).toContain('Additional context.');
273
+ expect(content[1]).toEqual({
274
+ cachePoint: { type: 'default' },
275
+ });
276
+ });
277
+ });
278
+
279
+ describe('Bedrock system message with promptCache: false', () => {
280
+ it('should NOT add cachePoint when promptCache is false', async () => {
281
+ const ctx = createBedrockContext({
282
+ instructions: 'You are a helpful assistant.',
283
+ promptCache: false,
284
+ });
285
+
286
+ const result = await ctx.systemRunnable!.invoke([]);
287
+ const systemMessage = result[0];
288
+
289
+ // Content should be plain string, not array with cachePoint
290
+ expect(typeof systemMessage.content).toBe('string');
291
+ expect(systemMessage.content).toBe('You are a helpful assistant.');
292
+ });
293
+
294
+ it('should NOT add cachePoint when promptCache is undefined', async () => {
295
+ const ctx = createBedrockContext({
296
+ instructions: 'You are a helpful assistant.',
297
+ promptCache: undefined,
298
+ });
299
+
300
+ const result = await ctx.systemRunnable!.invoke([]);
301
+ const systemMessage = result[0];
302
+
303
+ expect(typeof systemMessage.content).toBe('string');
304
+ });
305
+ });
306
+
307
+ describe('Anthropic system message caching (for comparison)', () => {
308
+ it('should add cache_control for Anthropic with prompt-caching beta', async () => {
309
+ const ctx = createAnthropicContext({
310
+ instructions: 'You are a helpful assistant.',
311
+ promptCachingEnabled: true,
312
+ });
313
+
314
+ const result = await ctx.systemRunnable!.invoke([]);
315
+ const systemMessage = result[0];
316
+ const content = systemMessage.content as Array<Record<string, unknown>>;
317
+
318
+ expect(content).toHaveLength(1);
319
+ expect(content[0]).toEqual({
320
+ type: 'text',
321
+ text: 'You are a helpful assistant.',
322
+ cache_control: { type: 'ephemeral' },
323
+ });
324
+ });
325
+
326
+ it('should NOT add cache_control for Anthropic without beta header', async () => {
327
+ const ctx = createAnthropicContext({
328
+ instructions: 'You are a helpful assistant.',
329
+ promptCachingEnabled: false,
330
+ });
331
+
332
+ const result = await ctx.systemRunnable!.invoke([]);
333
+ const systemMessage = result[0];
334
+
335
+ expect(typeof systemMessage.content).toBe('string');
336
+ });
337
+ });
338
+
339
+ describe('Provider-specific caching behavior', () => {
340
+ it('should use cachePoint format for Bedrock, not cache_control', async () => {
341
+ const bedrockCtx = createBedrockContext({
342
+ instructions: 'Test',
343
+ promptCache: true,
344
+ });
345
+
346
+ const result = await bedrockCtx.systemRunnable!.invoke([]);
347
+ const content = result[0].content as Array<Record<string, unknown>>;
348
+
349
+ // Bedrock uses cachePoint, NOT cache_control
350
+ expect(content.some((c) => 'cachePoint' in c)).toBe(true);
351
+ expect(content.some((c) => 'cache_control' in c)).toBe(false);
352
+ });
353
+
354
+ it('should use cache_control format for Anthropic, not cachePoint', async () => {
355
+ const anthropicCtx = createAnthropicContext({
356
+ instructions: 'Test',
357
+ promptCachingEnabled: true,
358
+ });
359
+
360
+ const result = await anthropicCtx.systemRunnable!.invoke([]);
361
+ const content = result[0].content as Array<Record<string, unknown>>;
362
+
363
+ // Anthropic uses cache_control, NOT cachePoint
364
+ expect(content.some((c) => 'cache_control' in c)).toBe(true);
365
+ expect(content.some((c) => 'cachePoint' in c)).toBe(false);
366
+ });
367
+
368
+ it('should not add any caching for OpenAI provider', async () => {
369
+ const openaiCtx = AgentContext.fromConfig({
370
+ agentId: 'test-openai-agent',
371
+ provider: Providers.OPENAI,
372
+ instructions: 'Test instructions',
373
+ });
374
+
375
+ const result = await openaiCtx.systemRunnable!.invoke([]);
376
+ const systemMessage = result[0];
377
+
378
+ // OpenAI should have plain string content
379
+ expect(typeof systemMessage.content).toBe('string');
380
+ expect(systemMessage.content).toBe('Test instructions');
381
+ });
382
+ });
383
+ });
384
+
385
+ describe('Integration scenarios', () => {
386
+ it('should handle multi-turn caching scenario', async () => {
387
+ // Simulate: Query 1 with tools → Query 2 with same tools
388
+ // Both should have cachePoint, enabling cache reuse
389
+
390
+ const model = new CustomChatBedrockConverse({
391
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
392
+ region: 'us-east-1',
393
+ promptCache: true,
394
+ });
395
+
396
+ const tools = [
397
+ {
398
+ toolSpec: {
399
+ name: 'execute_code',
400
+ description: 'Execute Python code',
401
+ inputSchema: { json: { type: 'object', properties: {} } },
402
+ },
403
+ },
404
+ {
405
+ toolSpec: {
406
+ name: 'file_search',
407
+ description: 'Search files',
408
+ inputSchema: { json: { type: 'object', properties: {} } },
409
+ },
410
+ },
411
+ ];
412
+
413
+ // Query 1
414
+ const params1 = model.invocationParams({ tools } as any);
415
+
416
+ // Query 2 (same tools)
417
+ const params2 = model.invocationParams({ tools } as any);
418
+
419
+ // Both should have identical tool configs with cachePoint
420
+ expect(params1.toolConfig?.tools).toEqual(params2.toolConfig?.tools);
421
+ expect(params1.toolConfig?.tools).toHaveLength(3); // 2 tools + cachePoint
422
+ });
423
+
424
+ it('should handle dynamic tool selection scenario', async () => {
425
+ // Simulate: Query 1 with [A, B] → Query 2 with [A, B, C]
426
+ // Tool set changed, so cache won't match (expected behavior)
427
+
428
+ const model = new CustomChatBedrockConverse({
429
+ model: 'anthropic.claude-3-haiku-20240307-v1:0',
430
+ region: 'us-east-1',
431
+ promptCache: true,
432
+ });
433
+
434
+ const toolA = {
435
+ toolSpec: {
436
+ name: 'tool_a',
437
+ description: 'Tool A',
438
+ inputSchema: { json: { type: 'object', properties: {} } },
439
+ },
440
+ };
441
+
442
+ const toolB = {
443
+ toolSpec: {
444
+ name: 'tool_b',
445
+ description: 'Tool B',
446
+ inputSchema: { json: { type: 'object', properties: {} } },
447
+ },
448
+ };
449
+
450
+ const toolC = {
451
+ toolSpec: {
452
+ name: 'tool_c',
453
+ description: 'Tool C',
454
+ inputSchema: { json: { type: 'object', properties: {} } },
455
+ },
456
+ };
457
+
458
+ // Query 1: [A, B]
459
+ const params1 = model.invocationParams({ tools: [toolA, toolB] } as any);
460
+
461
+ // Query 2: [A, B, C] - different tool set
462
+ const params2 = model.invocationParams({ tools: [toolA, toolB, toolC] } as any);
463
+
464
+ // Different lengths (cache prefix won't match beyond common prefix)
465
+ expect(params1.toolConfig?.tools).toHaveLength(3); // 2 tools + cachePoint
466
+ expect(params2.toolConfig?.tools).toHaveLength(4); // 3 tools + cachePoint
467
+
468
+ // But both have cachePoint at the end
469
+ expect(params1.toolConfig?.tools?.[2]).toEqual({ cachePoint: { type: 'default' } });
470
+ expect(params2.toolConfig?.tools?.[3]).toEqual({ cachePoint: { type: 'default' } });
471
+ });
472
+ });
473
+ });