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
@@ -0,0 +1,162 @@
1
+ // src/scripts/tool_search_regex.ts
2
+ /**
3
+ * Test script for the Tool Search Regex tool.
4
+ * Run with: npm run tool_search_regex
5
+ *
6
+ * Demonstrates runtime registry injection - the tool registry is passed
7
+ * at invocation time, not at initialization time.
8
+ */
9
+ import { config } from 'dotenv';
10
+ config();
11
+
12
+ import { createToolSearchRegexTool } from '@/tools/ToolSearchRegex';
13
+ import type { LCToolRegistry } from '@/types';
14
+ import { createToolSearchToolRegistry } from '@/test/mockTools';
15
+
16
+ interface RunTestOptions {
17
+ fields?: ('name' | 'description' | 'parameters')[];
18
+ max_results?: number;
19
+ showArtifact?: boolean;
20
+ toolRegistry: LCToolRegistry;
21
+ onlyDeferred?: boolean;
22
+ }
23
+
24
+ async function runTest(
25
+ searchTool: ReturnType<typeof createToolSearchRegexTool>,
26
+ testName: string,
27
+ query: string,
28
+ options: RunTestOptions
29
+ ): Promise<void> {
30
+ console.log(`\n${'='.repeat(60)}`);
31
+ console.log(`TEST: ${testName}`);
32
+ console.log(`Query: "${query}"`);
33
+ if (options.fields) console.log(`Fields: ${options.fields.join(', ')}`);
34
+ if (options.max_results) console.log(`Max Results: ${options.max_results}`);
35
+ console.log('='.repeat(60));
36
+
37
+ try {
38
+ const startTime = Date.now();
39
+
40
+ // Manual testing uses schema params directly
41
+ // (ToolNode uses different param structure when injecting)
42
+ const result = await searchTool.invoke({
43
+ query,
44
+ fields: options.fields,
45
+ max_results: options.max_results,
46
+ });
47
+ const duration = Date.now() - startTime;
48
+
49
+ console.log(`\nResult (${duration}ms):`);
50
+ if (Array.isArray(result)) {
51
+ console.log(result[0]);
52
+ if (options.showArtifact) {
53
+ console.log('\n--- Artifact ---');
54
+ console.dir(result[1], { depth: null });
55
+ }
56
+ } else {
57
+ console.log(result);
58
+ }
59
+ } catch (error) {
60
+ console.error('Error:', error instanceof Error ? error.message : error);
61
+ }
62
+ }
63
+
64
+ async function main(): Promise<void> {
65
+ console.log('Tool Search Regex - Test Script');
66
+ console.log('================================');
67
+ console.log('Demonstrating runtime tool registry injection\n');
68
+
69
+ const apiKey = process.env.LIBRECHAT_CODE_API_KEY;
70
+ if (!apiKey) {
71
+ console.error(
72
+ 'Error: LIBRECHAT_CODE_API_KEY environment variable is not set.'
73
+ );
74
+ console.log('Please set it in your .env file or environment.');
75
+ process.exit(1);
76
+ }
77
+
78
+ console.log('Creating sample tool registry...');
79
+ const toolRegistry = createToolSearchToolRegistry();
80
+ console.log(
81
+ `Registry contains ${toolRegistry.size} tools (${Array.from(toolRegistry.values()).filter((t) => t.defer_loading).length} deferred)`
82
+ );
83
+
84
+ console.log('\nCreating Tool Search Regex tool WITH registry for testing...');
85
+ const searchTool = createToolSearchRegexTool({ apiKey, toolRegistry });
86
+ console.log('Tool created successfully!');
87
+ console.log(
88
+ 'Note: In production, ToolNode injects toolRegistry via params when invoked through the graph.\n'
89
+ );
90
+
91
+ const baseOptions = { toolRegistry, onlyDeferred: true };
92
+
93
+ // Test 1: Simple keyword search (with artifact display)
94
+ await runTest(searchTool, 'Simple keyword search', 'expense', {
95
+ ...baseOptions,
96
+ showArtifact: true,
97
+ });
98
+
99
+ // Test 2: Search for weather-related tools
100
+ await runTest(searchTool, 'Weather tools', 'weather|forecast', baseOptions);
101
+
102
+ // Test 3: Search with case variations
103
+ await runTest(searchTool, 'Case insensitive search', 'EMAIL', baseOptions);
104
+
105
+ // Test 4: Search in description only
106
+ await runTest(searchTool, 'Description-only search', 'database', {
107
+ ...baseOptions,
108
+ fields: ['description'],
109
+ });
110
+
111
+ // Test 5: Search with parameters field
112
+ await runTest(searchTool, 'Parameters search', 'query', {
113
+ ...baseOptions,
114
+ fields: ['parameters'],
115
+ });
116
+
117
+ // Test 6: Limited results
118
+ await runTest(searchTool, 'Limited to 2 results', 'get', {
119
+ ...baseOptions,
120
+ max_results: 2,
121
+ });
122
+
123
+ // Test 7: Pattern that matches nothing
124
+ await runTest(searchTool, 'No matches', 'xyznonexistent123', baseOptions);
125
+
126
+ // Test 8: Regex pattern with character class
127
+ await runTest(
128
+ searchTool,
129
+ 'Regex with character class',
130
+ 'get_[a-z]+',
131
+ baseOptions
132
+ );
133
+
134
+ // Test 9: Dangerous pattern (should be sanitized)
135
+ await runTest(
136
+ searchTool,
137
+ 'Dangerous pattern (sanitized)',
138
+ '(a+)+',
139
+ baseOptions
140
+ );
141
+
142
+ // Test 10: Search all fields
143
+ await runTest(searchTool, 'All fields search', 'text', {
144
+ ...baseOptions,
145
+ fields: ['name', 'description', 'parameters'],
146
+ });
147
+
148
+ // Test 11: Search ALL tools (not just deferred)
149
+ await runTest(searchTool, 'Search ALL tools (incl. non-deferred)', 'calc', {
150
+ toolRegistry,
151
+ onlyDeferred: false, // Include non-deferred tools
152
+ });
153
+
154
+ console.log('\n' + '='.repeat(60));
155
+ console.log('All tests completed!');
156
+ console.log('='.repeat(60) + '\n');
157
+ }
158
+
159
+ main().catch((err) => {
160
+ console.error('Fatal error:', err);
161
+ process.exit(1);
162
+ });
@@ -1,11 +1,5 @@
1
1
  // src/specs/thinking-prune.test.ts
2
- import {
3
- HumanMessage,
4
- AIMessage,
5
- SystemMessage,
6
- BaseMessage,
7
- ToolMessage,
8
- } from '@langchain/core/messages';
2
+ import { HumanMessage, AIMessage, SystemMessage, BaseMessage, ToolMessage } from '@langchain/core/messages';
9
3
  import type * as t from '@/types';
10
4
  import { createPruneMessages } from '@/messages/prune';
11
5
 
@@ -13,10 +7,7 @@ import { createPruneMessages } from '@/messages/prune';
13
7
  const createTestTokenCounter = (): t.TokenCounter => {
14
8
  return (message: BaseMessage): number => {
15
9
  // Use type assertion to help TypeScript understand the type
16
- const content = message.content as
17
- | string
18
- | Array<t.MessageContentComplex | string>
19
- | undefined;
10
+ const content = message.content as string | Array<t.MessageContentComplex | string> | undefined;
20
11
 
21
12
  // Handle string content
22
13
  if (typeof content === 'string') {
@@ -61,7 +52,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
61
52
  content: [
62
53
  {
63
54
  type: 'text',
64
- text: '/home/danny/Illuma/gistfile1.txt\n\nread it 200 lines at a time\n\nthere are 5000 lines\n\ndo not stop until done',
55
+ text: '/home/danny/LibreChat/gistfile1.txt\n\nread it 200 lines at a time\n\nthere are 5000 lines\n\ndo not stop until done',
65
56
  },
66
57
  ],
67
58
  });
@@ -70,8 +61,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
70
61
  content: [
71
62
  {
72
63
  type: 'thinking',
73
- thinking:
74
- 'The user is asking me to read a file located at `/home/danny/Illuma/gistfile1.txt` in chunks of 200 lines at a time, mentioning that the file has 5000 lines total. They want me to continue reading through the entire file without stopping.\n\nI\'ll need to use the text editor tool to view the file in chunks of 200 lines each. Since the file has 5000 lines, I\'ll need to view it in 25 chunks (5000 ÷ 200 = 25).\n\nI\'ll need to make multiple calls to the text editor with the `view` command, specifying different line ranges for each call.\n\nLet me plan out the approach:\n1. Start with lines 1-200\n2. Then 201-400\n3. Then 401-600\n4. And so on until I reach 4801-5000\n\nFor each call, I\'ll use the `view` command with the specific line range in the `view_range` parameter. I\'ll continue until I\'ve shown all 5000 lines as requested.',
64
+ thinking: 'The user is asking me to read a file located at `/home/danny/LibreChat/gistfile1.txt` in chunks of 200 lines at a time, mentioning that the file has 5000 lines total. They want me to continue reading through the entire file without stopping.\n\nI\'ll need to use the text editor tool to view the file in chunks of 200 lines each. Since the file has 5000 lines, I\'ll need to view it in 25 chunks (5000 ÷ 200 = 25).\n\nI\'ll need to make multiple calls to the text editor with the `view` command, specifying different line ranges for each call.\n\nLet me plan out the approach:\n1. Start with lines 1-200\n2. Then 201-400\n3. Then 401-600\n4. And so on until I reach 4801-5000\n\nFor each call, I\'ll use the `view` command with the specific line range in the `view_range` parameter. I\'ll continue until I\'ve shown all 5000 lines as requested.',
75
65
  },
76
66
  {
77
67
  type: 'text',
@@ -81,8 +71,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
81
71
  type: 'tool_use',
82
72
  id: 'toolu_01YApWuFsEQCuBFDgYXmcmeZ',
83
73
  name: 'text_editor_mcp_textEditor',
84
- input:
85
- '{"command": "view", "path": "/home/danny/Illuma/gistfile1.txt", "description": "Viewing lines 1-200 of the file", "view_range": [1,200]}',
74
+ input: '{"command": "view", "path": "/home/danny/LibreChat/gistfile1.txt", "description": "Viewing lines 1-200 of the file", "view_range": [1,200]}',
86
75
  },
87
76
  ],
88
77
  });
@@ -108,8 +97,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
108
97
  type: 'tool_use',
109
98
  id: 'toolu_01VnyMQ4CvEd6zLDxxtTd6d4',
110
99
  name: 'text_editor_mcp_textEditor',
111
- input:
112
- '{"command": "view", "path": "/home/danny/Illuma/gistfile1.txt", "description": "Viewing lines 201-400 of the file", "view_range": [201,400]}',
100
+ input: '{"command": "view", "path": "/home/danny/LibreChat/gistfile1.txt", "description": "Viewing lines 201-400 of the file", "view_range": [201,400]}',
113
101
  },
114
102
  ],
115
103
  });
@@ -135,8 +123,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
135
123
  type: 'tool_use',
136
124
  id: 'toolu_01TZKs4nnBc58BYXKz1Mw4fp',
137
125
  name: 'text_editor_mcp_textEditor',
138
- input:
139
- '{"command": "view", "path": "/home/danny/Illuma/gistfile1.txt", "description": "Viewing lines 401-600 of the file", "view_range": [401,600]}',
126
+ input: '{"command": "view", "path": "/home/danny/LibreChat/gistfile1.txt", "description": "Viewing lines 401-600 of the file", "view_range": [401,600]}',
140
127
  },
141
128
  ],
142
129
  });
@@ -162,8 +149,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
162
149
  type: 'tool_use',
163
150
  id: 'toolu_01TZgBacNxjx1QNUpJg9hca5',
164
151
  name: 'text_editor_mcp_textEditor',
165
- input:
166
- '{"command": "view", "path": "/home/danny/Illuma/gistfile1.txt", "description": "Viewing lines 601-800 of the file", "view_range": [601,800]}',
152
+ input: '{"command": "view", "path": "/home/danny/LibreChat/gistfile1.txt", "description": "Viewing lines 601-800 of the file", "view_range": [601,800]}',
167
153
  },
168
154
  ],
169
155
  });
@@ -194,7 +180,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
194
180
  // Create indexTokenCountMap based on the example provided
195
181
  const indexTokenCountMap = {
196
182
  '0': 617, // userMessage
197
- '1': 52, // assistantMessageWithThinking
183
+ '1': 52, // assistantMessageWithThinking
198
184
  '2': 4995, // toolResponseMessage1
199
185
  '3': 307, // assistantMessage2
200
186
  '4': 9359, // toolResponseMessage2
@@ -232,34 +218,22 @@ describe('Prune Messages with Thinking Mode Tests', () => {
232
218
  expect(result.context.length).toBeGreaterThan(0);
233
219
 
234
220
  // Find the first assistant message in the pruned context
235
- const firstAssistantIndex = result.context.findIndex(
236
- (msg) => msg.getType() === 'ai'
237
- );
221
+ const firstAssistantIndex = result.context.findIndex(msg => msg.getType() === 'ai');
238
222
  expect(firstAssistantIndex).toBe(0);
239
223
 
240
224
  const firstAssistantMsg = result.context[firstAssistantIndex];
241
225
  expect(Array.isArray(firstAssistantMsg.content)).toBe(true);
242
226
 
243
227
  // Verify that the first assistant message has a thinking block
244
- const hasThinkingBlock = (
245
- firstAssistantMsg.content as t.MessageContentComplex[]
246
- ).some(
247
- (item: t.MessageContentComplex) =>
248
- typeof item === 'object' && item.type === 'thinking'
249
- );
228
+ const hasThinkingBlock = (firstAssistantMsg.content as t.MessageContentComplex[]).some((item: t.MessageContentComplex) =>
229
+ typeof item === 'object' && item.type === 'thinking');
250
230
  expect(hasThinkingBlock).toBe(true);
251
231
 
252
232
  // Verify that the thinking block is from the original assistant message
253
- const thinkingBlock = (
254
- firstAssistantMsg.content as t.MessageContentComplex[]
255
- ).find(
256
- (item: t.MessageContentComplex) =>
257
- typeof item === 'object' && item.type === 'thinking'
258
- );
233
+ const thinkingBlock = (firstAssistantMsg.content as t.MessageContentComplex[]).find((item: t.MessageContentComplex) =>
234
+ typeof item === 'object' && item.type === 'thinking');
259
235
  expect(thinkingBlock).toBeDefined();
260
- expect((thinkingBlock as t.ThinkingContentText).thinking).toContain(
261
- 'The user is asking me to read a file'
262
- );
236
+ expect((thinkingBlock as t.ThinkingContentText).thinking).toContain('The user is asking me to read a file');
263
237
  });
264
238
 
265
239
  it('should handle token recalculation when inserting thinking blocks', () => {
@@ -477,49 +451,35 @@ describe('Prune Messages with Thinking Mode Tests', () => {
477
451
  const result = pruneMessages({ messages });
478
452
 
479
453
  // Find assistant message with tool call and its corresponding tool message in the pruned context
480
- const assistantIndex = result.context.findIndex(
481
- (msg) =>
482
- msg.getType() === 'ai' &&
483
- Array.isArray(msg.content) &&
484
- msg.content.some(
485
- (item) =>
486
- typeof item === 'object' &&
487
- item.type === 'tool_use' &&
488
- item.id === 'tool123'
489
- )
454
+ const assistantIndex = result.context.findIndex(msg =>
455
+ msg.getType() === 'ai' &&
456
+ Array.isArray(msg.content) &&
457
+ msg.content.some(item => typeof item === 'object' && item.type === 'tool_use' && item.id === 'tool123')
490
458
  );
491
459
 
492
460
  // If the assistant message with tool call is in the context, its corresponding tool message should also be there
493
461
  if (assistantIndex !== -1) {
494
- const toolIndex = result.context.findIndex(
495
- (msg) =>
496
- msg.getType() === 'tool' &&
497
- 'tool_call_id' in msg &&
498
- msg.tool_call_id === 'tool123'
462
+ const toolIndex = result.context.findIndex(msg =>
463
+ msg.getType() === 'tool' &&
464
+ 'tool_call_id' in msg &&
465
+ msg.tool_call_id === 'tool123'
499
466
  );
500
467
 
501
468
  expect(toolIndex).not.toBe(-1);
502
469
  }
503
470
 
504
471
  // If the tool message is in the context, its corresponding assistant message should also be there
505
- const toolIndex = result.context.findIndex(
506
- (msg) =>
507
- msg.getType() === 'tool' &&
508
- 'tool_call_id' in msg &&
509
- msg.tool_call_id === 'tool123'
472
+ const toolIndex = result.context.findIndex(msg =>
473
+ msg.getType() === 'tool' &&
474
+ 'tool_call_id' in msg &&
475
+ msg.tool_call_id === 'tool123'
510
476
  );
511
477
 
512
478
  if (toolIndex !== -1) {
513
- const assistantWithToolIndex = result.context.findIndex(
514
- (msg) =>
515
- msg.getType() === 'ai' &&
516
- Array.isArray(msg.content) &&
517
- msg.content.some(
518
- (item) =>
519
- typeof item === 'object' &&
520
- item.type === 'tool_use' &&
521
- item.id === 'tool123'
522
- )
479
+ const assistantWithToolIndex = result.context.findIndex(msg =>
480
+ msg.getType() === 'ai' &&
481
+ Array.isArray(msg.content) &&
482
+ msg.content.some(item => typeof item === 'object' && item.type === 'tool_use' && item.id === 'tool123')
523
483
  );
524
484
 
525
485
  expect(assistantWithToolIndex).not.toBe(-1);
@@ -584,9 +544,7 @@ describe('Prune Messages with Thinking Mode Tests', () => {
584
544
  // Calculate token counts for each message
585
545
  const indexTokenCountMapWithoutSystem: Record<string, number> = {};
586
546
  for (let i = 0; i < messagesWithoutSystem.length; i++) {
587
- indexTokenCountMapWithoutSystem[i] = tokenCounter(
588
- messagesWithoutSystem[i]
589
- );
547
+ indexTokenCountMapWithoutSystem[i] = tokenCounter(messagesWithoutSystem[i]);
590
548
  }
591
549
 
592
550
  // Create pruneMessages function with thinking mode enabled
@@ -599,33 +557,24 @@ describe('Prune Messages with Thinking Mode Tests', () => {
599
557
  });
600
558
 
601
559
  // Prune messages
602
- const resultWithoutSystem = pruneMessagesWithoutSystem({
603
- messages: messagesWithoutSystem,
604
- });
560
+ const resultWithoutSystem = pruneMessagesWithoutSystem({ messages: messagesWithoutSystem });
605
561
 
606
562
  // Verify that the pruned context contains at least one message
607
563
  expect(resultWithoutSystem.context.length).toBeGreaterThan(0);
608
564
 
609
565
  // Find all assistant messages in the latest sequence (after the last human message)
610
- const lastHumanIndex = resultWithoutSystem.context
611
- .map((msg) => msg.getType())
612
- .lastIndexOf('human');
613
- const assistantMessagesAfterLastHuman = resultWithoutSystem.context
614
- .slice(lastHumanIndex + 1)
615
- .filter((msg) => msg.getType() === 'ai');
566
+ const lastHumanIndex = resultWithoutSystem.context.map(msg => msg.getType()).lastIndexOf('human');
567
+ const assistantMessagesAfterLastHuman = resultWithoutSystem.context.slice(lastHumanIndex + 1)
568
+ .filter(msg => msg.getType() === 'ai');
616
569
 
617
570
  // Verify that at least one assistant message exists in the latest sequence
618
571
  expect(assistantMessagesAfterLastHuman.length).toBeGreaterThan(0);
619
572
 
620
573
  // Verify that at least one of these assistant messages has a thinking block
621
- const hasThinkingBlock = assistantMessagesAfterLastHuman.some((msg) => {
574
+ const hasThinkingBlock = assistantMessagesAfterLastHuman.some(msg => {
622
575
  const content = msg.content as t.MessageContentComplex[];
623
- return (
624
- Array.isArray(content) &&
625
- content.some(
626
- (item) => typeof item === 'object' && item.type === 'thinking'
627
- )
628
- );
576
+ return Array.isArray(content) && content.some(item =>
577
+ typeof item === 'object' && item.type === 'thinking');
629
578
  });
630
579
  expect(hasThinkingBlock).toBe(true);
631
580
 
@@ -655,36 +604,26 @@ describe('Prune Messages with Thinking Mode Tests', () => {
655
604
  });
656
605
 
657
606
  // Prune messages
658
- const resultWithSystem = pruneMessagesWithSystem({
659
- messages: messagesWithSystem,
660
- });
607
+ const resultWithSystem = pruneMessagesWithSystem({ messages: messagesWithSystem });
661
608
 
662
609
  // Verify that the system message remains first
663
610
  expect(resultWithSystem.context.length).toBeGreaterThan(1);
664
611
  expect(resultWithSystem.context[0].getType()).toBe('system');
665
612
 
666
613
  // Find all assistant messages in the latest sequence (after the last human message)
667
- const lastHumanIndexWithSystem = resultWithSystem.context
668
- .map((msg) => msg.getType())
669
- .lastIndexOf('human');
670
- const assistantMessagesAfterLastHumanWithSystem = resultWithSystem.context
671
- .slice(lastHumanIndexWithSystem + 1)
672
- .filter((msg) => msg.getType() === 'ai');
614
+ const lastHumanIndexWithSystem = resultWithSystem.context.map(msg => msg.getType()).lastIndexOf('human');
615
+ const assistantMessagesAfterLastHumanWithSystem = resultWithSystem.context.slice(lastHumanIndexWithSystem + 1)
616
+ .filter(msg => msg.getType() === 'ai');
673
617
 
674
618
  // Verify that at least one assistant message exists in the latest sequence
675
619
  expect(assistantMessagesAfterLastHumanWithSystem.length).toBeGreaterThan(0);
676
620
 
677
621
  // Verify that at least one of these assistant messages has a thinking block
678
- const hasThinkingBlockWithSystem =
679
- assistantMessagesAfterLastHumanWithSystem.some((msg) => {
680
- const content = msg.content as t.MessageContentComplex[];
681
- return (
682
- Array.isArray(content) &&
683
- content.some(
684
- (item) => typeof item === 'object' && item.type === 'thinking'
685
- )
686
- );
687
- });
622
+ const hasThinkingBlockWithSystem = assistantMessagesAfterLastHumanWithSystem.some(msg => {
623
+ const content = msg.content as t.MessageContentComplex[];
624
+ return Array.isArray(content) && content.some(item =>
625
+ typeof item === 'object' && item.type === 'thinking');
626
+ });
688
627
  expect(hasThinkingBlockWithSystem).toBe(true);
689
628
  });
690
629
 
@@ -747,23 +686,18 @@ describe('Prune Messages with Thinking Mode Tests', () => {
747
686
  const result = pruneMessages({ messages });
748
687
 
749
688
  // Find the first assistant message in the pruned context
750
- const firstAssistantIndex = result.context.findIndex(
751
- (msg) => msg.getType() === 'ai'
752
- );
689
+ const firstAssistantIndex = result.context.findIndex(msg => msg.getType() === 'ai');
753
690
  expect(firstAssistantIndex).not.toBe(-1);
754
691
 
755
692
  const firstAssistantMsg = result.context[firstAssistantIndex];
756
693
  expect(Array.isArray(firstAssistantMsg.content)).toBe(true);
757
694
 
758
695
  // Verify that the first assistant message has a thinking block
759
- const thinkingBlock = (
760
- firstAssistantMsg.content as t.MessageContentComplex[]
761
- ).find((item) => typeof item === 'object' && item.type === 'thinking');
696
+ const thinkingBlock = (firstAssistantMsg.content as t.MessageContentComplex[]).find(item =>
697
+ typeof item === 'object' && item.type === 'thinking');
762
698
  expect(thinkingBlock).toBeDefined();
763
699
 
764
700
  // Verify that it's the newer thinking block
765
- expect((thinkingBlock as t.ThinkingContentText).thinking).toContain(
766
- 'newer thinking block'
767
- );
701
+ expect((thinkingBlock as t.ThinkingContentText).thinking).toContain('newer thinking block');
768
702
  });
769
703
  });
package/src/stream.ts CHANGED
@@ -107,6 +107,25 @@ export function getChunkContent({
107
107
  | undefined
108
108
  )?.summary?.[0]?.text;
109
109
  }
110
+ if (
111
+ provider === Providers.OPENROUTER &&
112
+ chunk?.additional_kwargs?.reasoning_details != null &&
113
+ Array.isArray(chunk.additional_kwargs.reasoning_details)
114
+ ) {
115
+ // Extract text from reasoning_details array (for Gemini, DeepSeek, etc.)
116
+ const textEntries = chunk.additional_kwargs.reasoning_details
117
+ .filter(
118
+ (detail) =>
119
+ detail.type === 'reasoning.text' &&
120
+ detail.text != null &&
121
+ detail.text !== ''
122
+ )
123
+ .map((detail) => detail.text)
124
+ .join('');
125
+ if (textEntries) {
126
+ return textEntries;
127
+ }
128
+ }
110
129
  return (
111
130
  ((chunk?.additional_kwargs?.[reasoningKey] as string | undefined) ?? '') ||
112
131
  chunk?.content
@@ -355,6 +374,13 @@ hasToolCallChunks: ${hasToolCallChunks}
355
374
  reasoning_content.summary[0].text
356
375
  ) {
357
376
  reasoning_content = 'valid';
377
+ } else if (
378
+ agentContext.provider === Providers.OPENROUTER &&
379
+ chunk.additional_kwargs?.reasoning_details != null &&
380
+ Array.isArray(chunk.additional_kwargs.reasoning_details) &&
381
+ chunk.additional_kwargs.reasoning_details.length > 0
382
+ ) {
383
+ reasoning_content = 'valid';
358
384
  }
359
385
  if (
360
386
  reasoning_content != null &&