@librechat/agents 3.0.36 → 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 (62) 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 +5 -1
  8. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  9. package/dist/cjs/main.cjs +12 -0
  10. package/dist/cjs/main.cjs.map +1 -1
  11. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +329 -0
  12. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
  13. package/dist/cjs/tools/ToolNode.cjs +34 -3
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/cjs/tools/ToolSearchRegex.cjs +455 -0
  16. package/dist/cjs/tools/ToolSearchRegex.cjs.map +1 -0
  17. package/dist/esm/agents/AgentContext.mjs +71 -2
  18. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  19. package/dist/esm/common/enum.mjs +2 -0
  20. package/dist/esm/common/enum.mjs.map +1 -1
  21. package/dist/esm/events.mjs +4 -1
  22. package/dist/esm/events.mjs.map +1 -1
  23. package/dist/esm/graphs/Graph.mjs +5 -1
  24. package/dist/esm/graphs/Graph.mjs.map +1 -1
  25. package/dist/esm/main.mjs +2 -0
  26. package/dist/esm/main.mjs.map +1 -1
  27. package/dist/esm/tools/ProgrammaticToolCalling.mjs +324 -0
  28. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
  29. package/dist/esm/tools/ToolNode.mjs +34 -3
  30. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  31. package/dist/esm/tools/ToolSearchRegex.mjs +448 -0
  32. package/dist/esm/tools/ToolSearchRegex.mjs.map +1 -0
  33. package/dist/types/agents/AgentContext.d.ts +25 -1
  34. package/dist/types/common/enum.d.ts +2 -0
  35. package/dist/types/graphs/Graph.d.ts +2 -1
  36. package/dist/types/index.d.ts +2 -0
  37. package/dist/types/test/mockTools.d.ts +28 -0
  38. package/dist/types/tools/ProgrammaticToolCalling.d.ts +86 -0
  39. package/dist/types/tools/ToolNode.d.ts +7 -1
  40. package/dist/types/tools/ToolSearchRegex.d.ts +80 -0
  41. package/dist/types/types/graph.d.ts +7 -1
  42. package/dist/types/types/tools.d.ts +136 -0
  43. package/package.json +5 -1
  44. package/src/agents/AgentContext.ts +86 -0
  45. package/src/common/enum.ts +2 -0
  46. package/src/events.ts +5 -1
  47. package/src/graphs/Graph.ts +6 -0
  48. package/src/index.ts +2 -0
  49. package/src/scripts/code_exec_ptc.ts +277 -0
  50. package/src/scripts/programmatic_exec.ts +396 -0
  51. package/src/scripts/programmatic_exec_agent.ts +231 -0
  52. package/src/scripts/tool_search_regex.ts +162 -0
  53. package/src/test/mockTools.ts +366 -0
  54. package/src/tools/ProgrammaticToolCalling.ts +423 -0
  55. package/src/tools/ToolNode.ts +38 -4
  56. package/src/tools/ToolSearchRegex.ts +535 -0
  57. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +318 -0
  58. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +613 -0
  59. package/src/tools/__tests__/ToolSearchRegex.integration.test.ts +161 -0
  60. package/src/tools/__tests__/ToolSearchRegex.test.ts +232 -0
  61. package/src/types/graph.ts +7 -1
  62. package/src/types/tools.ts +166 -0
@@ -0,0 +1,318 @@
1
+ // src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts
2
+ /**
3
+ * Integration tests for Programmatic Tool Calling.
4
+ * These tests hit the LIVE Code API and verify end-to-end functionality.
5
+ *
6
+ * Run with: npm test -- ProgrammaticToolCalling.integration.test.ts
7
+ */
8
+ import { config as dotenvConfig } from 'dotenv';
9
+ dotenvConfig();
10
+
11
+ import { describe, it, expect, beforeAll } from '@jest/globals';
12
+ import type * as t from '@/types';
13
+ import { createProgrammaticToolCallingTool } from '../ProgrammaticToolCalling';
14
+ import {
15
+ createGetTeamMembersTool,
16
+ createGetExpensesTool,
17
+ createGetWeatherTool,
18
+ createCalculatorTool,
19
+ createProgrammaticToolRegistry,
20
+ } from '@/test/mockTools';
21
+
22
+ describe('ProgrammaticToolCalling - Live API Integration', () => {
23
+ let ptcTool: ReturnType<typeof createProgrammaticToolCallingTool>;
24
+ let toolMap: t.ToolMap;
25
+ let toolDefinitions: t.LCTool[];
26
+
27
+ beforeAll(() => {
28
+ const apiKey = process.env.LIBRECHAT_CODE_API_KEY;
29
+ if (apiKey == null || apiKey === '') {
30
+ throw new Error(
31
+ 'LIBRECHAT_CODE_API_KEY not set. Required for integration tests.'
32
+ );
33
+ }
34
+
35
+ const tools = [
36
+ createGetTeamMembersTool(),
37
+ createGetExpensesTool(),
38
+ createGetWeatherTool(),
39
+ createCalculatorTool(),
40
+ ];
41
+
42
+ toolMap = new Map(tools.map((t) => [t.name, t]));
43
+ toolDefinitions = Array.from(createProgrammaticToolRegistry().values());
44
+
45
+ ptcTool = createProgrammaticToolCallingTool({ apiKey });
46
+ });
47
+
48
+ it('executes simple single tool call', async () => {
49
+ const args = {
50
+ code: `
51
+ result = await get_weather(city="San Francisco")
52
+ print(f"Temperature: {result['temperature']}°F")
53
+ print(f"Condition: {result['condition']}")
54
+ `,
55
+ tools: toolDefinitions,
56
+ };
57
+ const toolCall = {
58
+ name: 'programmatic_code_execution',
59
+ args,
60
+ toolMap,
61
+ };
62
+
63
+ const output = await ptcTool.invoke(args, { toolCall });
64
+
65
+ // When responseFormat is 'content_and_artifact', invoke() returns just the content
66
+ // The artifact is available via other methods or when used in a graph
67
+ expect(typeof output).toBe('string');
68
+ expect(output).toContain('stdout:');
69
+ expect(output).toContain('Temperature: 65°F');
70
+ expect(output).toContain('Condition: Foggy');
71
+ }, 10000);
72
+
73
+ it('executes sequential tool calls in a loop', async () => {
74
+ const args = {
75
+ code: `
76
+ team = await get_team_members()
77
+ print(f"Team size: {len(team)}")
78
+
79
+ total = 0
80
+ for member in team:
81
+ expenses = await get_expenses(user_id=member['id'])
82
+ member_total = sum(e['amount'] for e in expenses)
83
+ total += member_total
84
+ print(f"{member['name']}: \${member_total:.2f}")
85
+
86
+ print(f"Grand total: \${total:.2f}")
87
+ `,
88
+ tools: toolDefinitions,
89
+ };
90
+ const toolCall = {
91
+ name: 'programmatic_code_execution',
92
+ args,
93
+ toolMap,
94
+ };
95
+
96
+ const output = await ptcTool.invoke(args, { toolCall });
97
+
98
+ expect(output).toContain('Team size: 3');
99
+ expect(output).toContain('Alice:');
100
+ expect(output).toContain('Bob:');
101
+ expect(output).toContain('Charlie:');
102
+ expect(output).toContain('Grand total:');
103
+ }, 15000);
104
+
105
+ it('executes parallel tool calls with asyncio.gather', async () => {
106
+ const args = {
107
+ code: `
108
+ import asyncio
109
+
110
+ cities = ["San Francisco", "New York", "London"]
111
+ results = await asyncio.gather(*[
112
+ get_weather(city=city)
113
+ for city in cities
114
+ ])
115
+
116
+ for city, weather in zip(cities, results):
117
+ print(f"{city}: {weather['temperature']}°F, {weather['condition']}")
118
+ `,
119
+ tools: toolDefinitions,
120
+ };
121
+ const toolCall = {
122
+ name: 'programmatic_code_execution',
123
+ args,
124
+ toolMap,
125
+ };
126
+
127
+ const output = await ptcTool.invoke(args, { toolCall });
128
+
129
+ expect(output).toContain('San Francisco: 65°F, Foggy');
130
+ expect(output).toContain('New York: 75°F, Sunny');
131
+ expect(output).toContain('London: 55°F, Rainy');
132
+ }, 10000);
133
+
134
+ it('handles conditional logic', async () => {
135
+ const args = {
136
+ code: `
137
+ team = await get_team_members()
138
+ high_spenders = []
139
+
140
+ for member in team:
141
+ expenses = await get_expenses(user_id=member['id'])
142
+ total = sum(e['amount'] for e in expenses)
143
+ if total > 300:
144
+ high_spenders.append((member['name'], total))
145
+
146
+ if high_spenders:
147
+ print("High spenders (over $300):")
148
+ for name, amount in high_spenders:
149
+ print(f" {name}: \${amount:.2f}")
150
+ else:
151
+ print("No high spenders found.")
152
+ `,
153
+ tools: toolDefinitions,
154
+ };
155
+ const toolCall = {
156
+ name: 'programmatic_code_execution',
157
+ args,
158
+ toolMap,
159
+ };
160
+
161
+ const output = await ptcTool.invoke(args, { toolCall });
162
+
163
+ expect(output).toContain('High spenders');
164
+ expect(output).toContain('Bob:');
165
+ expect(output).toContain('Charlie:');
166
+ }, 15000);
167
+
168
+ it('handles early termination with break', async () => {
169
+ const args = {
170
+ code: `
171
+ team = await get_team_members()
172
+
173
+ for member in team:
174
+ expenses = await get_expenses(user_id=member['id'])
175
+ if any(e['category'] == 'equipment' for e in expenses):
176
+ print(f"First member with equipment: {member['name']}")
177
+ equipment_total = sum(e['amount'] for e in expenses if e['category'] == 'equipment')
178
+ print(f"Equipment total: \${equipment_total:.2f}")
179
+ break
180
+ else:
181
+ print("No equipment expenses found")
182
+ `,
183
+ tools: toolDefinitions,
184
+ };
185
+ const toolCall = {
186
+ name: 'programmatic_code_execution',
187
+ args,
188
+ toolMap,
189
+ };
190
+
191
+ const output = await ptcTool.invoke(args, { toolCall });
192
+
193
+ expect(output).toContain('First member with equipment: Charlie');
194
+ expect(output).toContain('Equipment total: $500.00');
195
+ }, 15000);
196
+
197
+ it('handles tool execution errors gracefully', async () => {
198
+ const args = {
199
+ code: `
200
+ cities = ["San Francisco", "InvalidCity", "New York"]
201
+
202
+ for city in cities:
203
+ try:
204
+ weather = await get_weather(city=city)
205
+ print(f"{city}: {weather['temperature']}°F")
206
+ except Exception as e:
207
+ print(f"{city}: Error - {e}")
208
+ `,
209
+ tools: toolDefinitions,
210
+ };
211
+ const toolCall = {
212
+ name: 'programmatic_code_execution',
213
+ args,
214
+ toolMap,
215
+ };
216
+
217
+ const output = await ptcTool.invoke(args, { toolCall });
218
+
219
+ expect(output).toContain('San Francisco: 65°F');
220
+ expect(output).toContain('InvalidCity: Error');
221
+ expect(output).toContain('New York: 75°F');
222
+ }, 15000);
223
+
224
+ it('uses calculator tool', async () => {
225
+ const args = {
226
+ code: `
227
+ result1 = await calculator(expression="2 + 2 * 3")
228
+ result2 = await calculator(expression="(10 + 5) / 3")
229
+
230
+ print(f"2 + 2 * 3 = {result1['result']}")
231
+ print(f"(10 + 5) / 3 = {result2['result']:.2f}")
232
+ `,
233
+ tools: toolDefinitions,
234
+ };
235
+ const toolCall = {
236
+ name: 'programmatic_code_execution',
237
+ args,
238
+ toolMap,
239
+ };
240
+
241
+ const output = await ptcTool.invoke(args, { toolCall });
242
+
243
+ expect(output).toContain('2 + 2 * 3 = 8');
244
+ expect(output).toContain('(10 + 5) / 3 = 5.00');
245
+ }, 10000);
246
+
247
+ it('mixes parallel and sequential execution', async () => {
248
+ const args = {
249
+ code: `
250
+ import asyncio
251
+
252
+ # Step 1: Get team (sequential)
253
+ team = await get_team_members()
254
+ print(f"Fetched {len(team)} team members")
255
+
256
+ # Step 2: Get all expenses in parallel
257
+ all_expenses = await asyncio.gather(*[
258
+ get_expenses(user_id=member['id'])
259
+ for member in team
260
+ ])
261
+
262
+ # Step 3: Process
263
+ print("\\nExpense summary:")
264
+ for member, expenses in zip(team, all_expenses):
265
+ total = sum(e['amount'] for e in expenses)
266
+ print(f" {member['name']}: \${total:.2f} ({len(expenses)} items)")
267
+ `,
268
+ tools: toolDefinitions,
269
+ };
270
+ const toolCall = {
271
+ name: 'programmatic_code_execution',
272
+ args,
273
+ toolMap,
274
+ };
275
+
276
+ const output = await ptcTool.invoke(args, { toolCall });
277
+
278
+ expect(output).toContain('Fetched 3 team members');
279
+ expect(output).toContain('Expense summary:');
280
+ expect(output).toContain('Alice:');
281
+ expect(output).toContain('Bob:');
282
+ expect(output).toContain('Charlie:');
283
+ expect(output).toContain('(2 items)');
284
+ expect(output).toContain('(3 items)');
285
+ }, 15000);
286
+
287
+ it('uses only provided tool definitions (subset)', async () => {
288
+ const weatherToolDef = toolDefinitions.find(
289
+ (t) => t.name === 'get_weather'
290
+ );
291
+
292
+ const args = {
293
+ code: `
294
+ import asyncio
295
+
296
+ sf, nyc = await asyncio.gather(
297
+ get_weather(city="San Francisco"),
298
+ get_weather(city="New York")
299
+ )
300
+
301
+ print(f"SF: {sf['temperature']}°F vs NYC: {nyc['temperature']}°F")
302
+ difference = abs(sf['temperature'] - nyc['temperature'])
303
+ print(f"Temperature difference: {difference}°F")
304
+ `,
305
+ tools: [weatherToolDef!],
306
+ };
307
+ const toolCall = {
308
+ name: 'programmatic_code_execution',
309
+ args,
310
+ toolMap,
311
+ };
312
+
313
+ const output = await ptcTool.invoke(args, { toolCall });
314
+
315
+ expect(output).toContain('SF: 65°F vs NYC: 75°F');
316
+ expect(output).toContain('Temperature difference: 10°F');
317
+ }, 10000);
318
+ });