@librechat/agents 3.0.35 → 3.0.40

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 (68) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +71 -2
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +2 -0
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/events.cjs +3 -0
  6. package/dist/cjs/events.cjs.map +1 -1
  7. package/dist/cjs/graphs/Graph.cjs +7 -2
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/instrumentation.cjs +1 -1
  10. package/dist/cjs/instrumentation.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +12 -0
  12. package/dist/cjs/main.cjs.map +1 -1
  13. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +329 -0
  14. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
  15. package/dist/cjs/tools/ToolNode.cjs +34 -3
  16. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  17. package/dist/cjs/tools/ToolSearchRegex.cjs +455 -0
  18. package/dist/cjs/tools/ToolSearchRegex.cjs.map +1 -0
  19. package/dist/esm/agents/AgentContext.mjs +71 -2
  20. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  21. package/dist/esm/common/enum.mjs +2 -0
  22. package/dist/esm/common/enum.mjs.map +1 -1
  23. package/dist/esm/events.mjs +4 -1
  24. package/dist/esm/events.mjs.map +1 -1
  25. package/dist/esm/graphs/Graph.mjs +7 -2
  26. package/dist/esm/graphs/Graph.mjs.map +1 -1
  27. package/dist/esm/instrumentation.mjs +1 -1
  28. package/dist/esm/instrumentation.mjs.map +1 -1
  29. package/dist/esm/main.mjs +2 -0
  30. package/dist/esm/main.mjs.map +1 -1
  31. package/dist/esm/tools/ProgrammaticToolCalling.mjs +324 -0
  32. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
  33. package/dist/esm/tools/ToolNode.mjs +34 -3
  34. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  35. package/dist/esm/tools/ToolSearchRegex.mjs +448 -0
  36. package/dist/esm/tools/ToolSearchRegex.mjs.map +1 -0
  37. package/dist/types/agents/AgentContext.d.ts +25 -1
  38. package/dist/types/common/enum.d.ts +2 -0
  39. package/dist/types/graphs/Graph.d.ts +2 -1
  40. package/dist/types/index.d.ts +2 -0
  41. package/dist/types/test/mockTools.d.ts +28 -0
  42. package/dist/types/tools/ProgrammaticToolCalling.d.ts +86 -0
  43. package/dist/types/tools/ToolNode.d.ts +7 -1
  44. package/dist/types/tools/ToolSearchRegex.d.ts +80 -0
  45. package/dist/types/types/graph.d.ts +7 -1
  46. package/dist/types/types/tools.d.ts +136 -0
  47. package/package.json +5 -1
  48. package/src/agents/AgentContext.ts +86 -0
  49. package/src/common/enum.ts +2 -0
  50. package/src/events.ts +5 -1
  51. package/src/graphs/Graph.ts +8 -1
  52. package/src/index.ts +2 -0
  53. package/src/instrumentation.ts +1 -1
  54. package/src/llm/google/llm.spec.ts +3 -1
  55. package/src/scripts/code_exec_ptc.ts +277 -0
  56. package/src/scripts/programmatic_exec.ts +396 -0
  57. package/src/scripts/programmatic_exec_agent.ts +231 -0
  58. package/src/scripts/tool_search_regex.ts +162 -0
  59. package/src/test/mockTools.ts +366 -0
  60. package/src/tools/ProgrammaticToolCalling.ts +423 -0
  61. package/src/tools/ToolNode.ts +38 -4
  62. package/src/tools/ToolSearchRegex.ts +535 -0
  63. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +318 -0
  64. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +613 -0
  65. package/src/tools/__tests__/ToolSearchRegex.integration.test.ts +161 -0
  66. package/src/tools/__tests__/ToolSearchRegex.test.ts +232 -0
  67. package/src/types/graph.ts +7 -1
  68. package/src/types/tools.ts +166 -0
@@ -0,0 +1,231 @@
1
+ // src/scripts/programmatic_exec_agent.ts
2
+ /**
3
+ * Test script for Programmatic Tool Calling (PTC) with agent integration.
4
+ * Run with: npm run programmatic_exec_agent
5
+ *
6
+ * Demonstrates:
7
+ * 1. Tool classification with allowed_callers:
8
+ * - direct: Tool bound to LLM (can be called directly)
9
+ * - code_execution: Tool available for PTC (not bound to LLM)
10
+ * - Both: Tool bound to LLM AND available for PTC
11
+ * 2. Deferred loading with defer_loading: true (for tool search)
12
+ * 3. Agent-level tool configuration via toolRegistry
13
+ * 4. ToolNode runtime injection of programmatic tools
14
+ *
15
+ * This shows the real-world integration pattern with agents.
16
+ */
17
+ import { config } from 'dotenv';
18
+ config();
19
+
20
+ import type { StructuredToolInterface } from '@langchain/core/tools';
21
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
22
+ import type { RunnableConfig } from '@langchain/core/runnables';
23
+ import type * as t from '@/types';
24
+ import { createCodeExecutionTool } from '@/tools/CodeExecutor';
25
+ import { createProgrammaticToolCallingTool } from '@/tools/ProgrammaticToolCalling';
26
+ import { createToolSearchRegexTool } from '@/tools/ToolSearchRegex';
27
+ import { getLLMConfig } from '@/utils/llmConfig';
28
+ import { getArgs } from '@/scripts/args';
29
+ import { Run } from '@/run';
30
+ import {
31
+ createGetTeamMembersTool,
32
+ createGetExpensesTool,
33
+ createGetWeatherTool,
34
+ createProgrammaticToolRegistry,
35
+ } from '@/test/mockTools';
36
+
37
+ // ============================================================================
38
+ // Tool Registry (Metadata)
39
+ // ============================================================================
40
+
41
+ /**
42
+ * Tool registry only needs business logic tools that require filtering.
43
+ * Special tools (execute_code, programmatic_code_execution, tool_search_regex)
44
+ * are always bound directly to the LLM and don't need registry entries.
45
+ */
46
+ function createAgentToolRegistry(): t.LCToolRegistry {
47
+ // Use shared programmatic tool registry (get_team_members, get_expenses, etc.)
48
+ return createProgrammaticToolRegistry();
49
+ }
50
+
51
+ // ============================================================================
52
+ // Main
53
+ // ============================================================================
54
+
55
+ const conversationHistory: BaseMessage[] = [];
56
+
57
+ async function main(): Promise<void> {
58
+ console.log('Programmatic Tool Calling - Agent Integration Test');
59
+ console.log('===================================================\n');
60
+
61
+ const { userName, location, provider } = await getArgs();
62
+ const llmConfig = getLLMConfig(provider);
63
+
64
+ // Create all tool instances
65
+ const mockTools: StructuredToolInterface[] = [
66
+ createGetTeamMembersTool(),
67
+ createGetExpensesTool(),
68
+ createGetWeatherTool(),
69
+ ];
70
+
71
+ const toolMap = new Map(mockTools.map((t) => [t.name, t]));
72
+
73
+ // Create special tools (PTC, code execution, tool search)
74
+ const codeExecTool = createCodeExecutionTool();
75
+ const ptcTool = createProgrammaticToolCallingTool();
76
+ const toolSearchTool = createToolSearchRegexTool();
77
+
78
+ // Build complete tool list and map
79
+ const allTools = [...mockTools, codeExecTool, ptcTool, toolSearchTool];
80
+ const completeToolMap = new Map(allTools.map((t) => [t.name, t]));
81
+
82
+ // Create tool registry with allowed_callers configuration
83
+ // Only includes business logic tools (not special tools like execute_code, PTC, tool_search)
84
+ const toolRegistry = createAgentToolRegistry();
85
+
86
+ console.log('Tool configuration:');
87
+ console.log('- Total tools:', allTools.length);
88
+ console.log(
89
+ '- Programmatic-allowed:',
90
+ Array.from(toolRegistry.values())
91
+ .filter((t) => t.allowed_callers?.includes('code_execution'))
92
+ .map((t) => t.name)
93
+ .join(', ')
94
+ );
95
+ console.log(
96
+ '- Direct-only:',
97
+ Array.from(toolRegistry.values())
98
+ .filter(
99
+ (t) =>
100
+ !t.allowed_callers ||
101
+ (t.allowed_callers.includes('direct') &&
102
+ !t.allowed_callers.includes('code_execution'))
103
+ )
104
+ .map((t) => t.name)
105
+ .join(', ')
106
+ );
107
+ console.log(
108
+ '- Both:',
109
+ Array.from(toolRegistry.values())
110
+ .filter(
111
+ (t) =>
112
+ t.allowed_callers?.includes('direct') &&
113
+ t.allowed_callers?.includes('code_execution')
114
+ )
115
+ .map((t) => t.name)
116
+ .join(', ')
117
+ );
118
+
119
+ // Create run with toolRegistry configuration
120
+ const run = await Run.create<t.IState>({
121
+ runId: 'ptc-agent-test',
122
+ graphConfig: {
123
+ type: 'standard',
124
+ llmConfig,
125
+ agents: [
126
+ {
127
+ agentId: 'default',
128
+ provider: llmConfig.provider,
129
+ clientOptions: llmConfig,
130
+ tools: allTools,
131
+ toolMap: completeToolMap,
132
+ toolRegistry, // Pass tool registry for programmatic/deferred tool config
133
+ instructions:
134
+ 'You are an AI assistant with access to programmatic tool calling. ' +
135
+ 'When you need to process multiple items or perform complex data operations, ' +
136
+ 'use the programmatic_code_execution tool to write Python code that calls tools efficiently.',
137
+ },
138
+ ],
139
+ },
140
+ returnContent: true,
141
+ });
142
+
143
+ const config: Partial<RunnableConfig> & {
144
+ version: 'v1' | 'v2';
145
+ streamMode: string;
146
+ } = {
147
+ configurable: {
148
+ provider,
149
+ thread_id: 'ptc-test-conversation',
150
+ },
151
+ streamMode: 'values',
152
+ version: 'v2',
153
+ };
154
+
155
+ console.log('\n' + '='.repeat(70));
156
+ console.log('Test: Process team expenses using PTC');
157
+ console.log('='.repeat(70) + '\n');
158
+
159
+ const userMessage = new HumanMessage(
160
+ `Hi! I need you to analyze our team's expenses. Please:
161
+ 1. Get the list of team members
162
+ 2. For each member, get their expense records
163
+ 3. Calculate the total expenses per member
164
+ 4. Identify anyone who spent more than $300
165
+ 5. Show me the results in a nice format
166
+
167
+ Use the programmatic_code_execution tool to do this efficiently - don't call each tool separately!`
168
+ );
169
+
170
+ conversationHistory.push(userMessage);
171
+
172
+ const inputs = {
173
+ messages: conversationHistory,
174
+ };
175
+
176
+ console.log('Running agent with PTC capability...\n');
177
+
178
+ const finalContentParts = await run.processStream(inputs, config);
179
+ const finalMessages = run.getRunMessages();
180
+
181
+ if (finalMessages) {
182
+ conversationHistory.push(...finalMessages);
183
+ }
184
+
185
+ console.log('\n' + '='.repeat(70));
186
+ console.log('Agent Response:');
187
+ console.log('='.repeat(70));
188
+
189
+ if (finalContentParts) {
190
+ for (const part of finalContentParts) {
191
+ if (part?.type === 'text' && part.text) {
192
+ console.log(part.text);
193
+ }
194
+ }
195
+ }
196
+
197
+ console.log('\n' + '='.repeat(70));
198
+ console.log('Test completed successfully!');
199
+ console.log('='.repeat(70));
200
+ console.log('\nKey observations:');
201
+ console.log(
202
+ '1. LLM only sees tools with allowed_callers including "direct" (get_weather, execute_code, programmatic_code_execution, tool_search_regex)'
203
+ );
204
+ console.log(
205
+ '2. When PTC is invoked, ToolNode automatically injects programmatic tools (get_team_members, get_expenses, get_weather)'
206
+ );
207
+ console.log(
208
+ '3. No need to manually configure runtime toolMap - handled by agent context'
209
+ );
210
+ console.log(
211
+ '4. Tool filtering based on allowed_callers happens automatically\n'
212
+ );
213
+ }
214
+
215
+ process.on('unhandledRejection', (reason, promise) => {
216
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
217
+ console.log('Conversation history:');
218
+ console.dir(conversationHistory, { depth: null });
219
+ process.exit(1);
220
+ });
221
+
222
+ process.on('uncaughtException', (err) => {
223
+ console.error('Uncaught Exception:', err);
224
+ });
225
+
226
+ main().catch((err) => {
227
+ console.error('Fatal error:', err);
228
+ console.log('Conversation history:');
229
+ console.dir(conversationHistory, { depth: null });
230
+ process.exit(1);
231
+ });
@@ -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
+ });