illuma-agents 1.0.47 → 1.0.50

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 (114) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +44 -14
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +2 -1
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +19 -0
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/MultiAgentGraph.cjs +26 -17
  8. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  9. package/dist/cjs/llm/openai/index.cjs +1 -0
  10. package/dist/cjs/llm/openai/index.cjs.map +1 -1
  11. package/dist/cjs/main.cjs +9 -3
  12. package/dist/cjs/main.cjs.map +1 -1
  13. package/dist/cjs/stream.cjs +0 -14
  14. package/dist/cjs/stream.cjs.map +1 -1
  15. package/dist/cjs/tools/CodeExecutor.cjs +37 -27
  16. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +22 -18
  18. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +100 -5
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/ToolSearch.cjs +38 -31
  22. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  23. package/dist/cjs/tools/schema.cjs +31 -0
  24. package/dist/cjs/tools/schema.cjs.map +1 -0
  25. package/dist/cjs/tools/search/schema.cjs +25 -23
  26. package/dist/cjs/tools/search/schema.cjs.map +1 -1
  27. package/dist/cjs/tools/search/tool.cjs +9 -33
  28. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  29. package/dist/cjs/utils/schema.cjs +27 -0
  30. package/dist/cjs/utils/schema.cjs.map +1 -0
  31. package/dist/cjs/utils/title.cjs +28 -14
  32. package/dist/cjs/utils/title.cjs.map +1 -1
  33. package/dist/esm/agents/AgentContext.mjs +44 -14
  34. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  35. package/dist/esm/common/enum.mjs +2 -1
  36. package/dist/esm/common/enum.mjs.map +1 -1
  37. package/dist/esm/graphs/Graph.mjs +19 -0
  38. package/dist/esm/graphs/Graph.mjs.map +1 -1
  39. package/dist/esm/graphs/MultiAgentGraph.mjs +26 -17
  40. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  41. package/dist/esm/llm/openai/index.mjs +1 -0
  42. package/dist/esm/llm/openai/index.mjs.map +1 -1
  43. package/dist/esm/main.mjs +3 -1
  44. package/dist/esm/main.mjs.map +1 -1
  45. package/dist/esm/stream.mjs +0 -14
  46. package/dist/esm/stream.mjs.map +1 -1
  47. package/dist/esm/tools/CodeExecutor.mjs +37 -27
  48. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  49. package/dist/esm/tools/ProgrammaticToolCalling.mjs +22 -18
  50. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  51. package/dist/esm/tools/ToolNode.mjs +101 -6
  52. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  53. package/dist/esm/tools/ToolSearch.mjs +38 -31
  54. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  55. package/dist/esm/tools/schema.mjs +28 -0
  56. package/dist/esm/tools/schema.mjs.map +1 -0
  57. package/dist/esm/tools/search/schema.mjs +25 -23
  58. package/dist/esm/tools/search/schema.mjs.map +1 -1
  59. package/dist/esm/tools/search/tool.mjs +10 -34
  60. package/dist/esm/tools/search/tool.mjs.map +1 -1
  61. package/dist/esm/utils/schema.mjs +24 -0
  62. package/dist/esm/utils/schema.mjs.map +1 -0
  63. package/dist/esm/utils/title.mjs +28 -14
  64. package/dist/esm/utils/title.mjs.map +1 -1
  65. package/dist/types/agents/AgentContext.d.ts +13 -2
  66. package/dist/types/common/enum.d.ts +2 -1
  67. package/dist/types/index.d.ts +2 -1
  68. package/dist/types/tools/CodeExecutor.d.ts +1 -15
  69. package/dist/types/tools/ProgrammaticToolCalling.d.ts +1 -13
  70. package/dist/types/tools/ToolNode.d.ts +12 -1
  71. package/dist/types/tools/ToolSearch.d.ts +1 -15
  72. package/dist/types/tools/schema.d.ts +12 -0
  73. package/dist/types/tools/search/schema.d.ts +25 -7
  74. package/dist/types/tools/search/tool.d.ts +1 -52
  75. package/dist/types/tools/search/types.d.ts +5 -23
  76. package/dist/types/types/graph.d.ts +6 -0
  77. package/dist/types/types/tools.d.ts +55 -0
  78. package/dist/types/utils/index.d.ts +1 -0
  79. package/dist/types/utils/schema.d.ts +8 -0
  80. package/package.json +2 -3
  81. package/src/agents/AgentContext.ts +55 -19
  82. package/src/common/enum.ts +2 -1
  83. package/src/graphs/Graph.ts +27 -4
  84. package/src/graphs/MultiAgentGraph.ts +26 -17
  85. package/src/index.ts +2 -1
  86. package/src/scripts/test_code_api.ts +4 -4
  87. package/src/specs/agent-handoffs.test.ts +1 -2
  88. package/src/specs/azure.simple.test.ts +214 -175
  89. package/src/specs/thinking-prune.test.ts +6 -6
  90. package/src/specs/tool-error.test.ts +7 -2
  91. package/src/stream.ts +0 -17
  92. package/src/test/mockTools.ts +34 -14
  93. package/src/tools/CodeExecutor.ts +48 -31
  94. package/src/tools/ProgrammaticToolCalling.ts +25 -24
  95. package/src/tools/ToolNode.ts +137 -15
  96. package/src/tools/ToolSearch.ts +55 -44
  97. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +10 -9
  98. package/src/tools/__tests__/ToolSearch.integration.test.ts +10 -9
  99. package/src/tools/schema.ts +37 -0
  100. package/src/tools/search/schema.ts +30 -25
  101. package/src/tools/search/tool.ts +23 -16
  102. package/src/tools/search/types.ts +5 -29
  103. package/src/types/graph.ts +6 -0
  104. package/src/types/tools.ts +58 -0
  105. package/src/utils/index.ts +1 -0
  106. package/src/utils/schema.ts +35 -0
  107. package/src/utils/title.ts +31 -19
  108. package/LICENSE +0 -21
  109. package/dist/cjs/tools/PresentationTool.cjs +0 -291
  110. package/dist/cjs/tools/PresentationTool.cjs.map +0 -1
  111. package/dist/esm/tools/PresentationTool.mjs +0 -288
  112. package/dist/esm/tools/PresentationTool.mjs.map +0 -1
  113. package/dist/types/tools/PresentationTool.d.ts +0 -2358
  114. package/src/tools/PresentationTool.ts +0 -356
@@ -1,4 +1,3 @@
1
- import { z } from 'zod';
2
1
  import { config } from 'dotenv';
3
2
  import fetch, { RequestInit } from 'node-fetch';
4
3
  import { HttpsProxyAgent } from 'https-proxy-agent';
@@ -21,25 +20,33 @@ const accessMessage =
21
20
  const emptyOutputMessage =
22
21
  'stdout: Empty. Ensure you\'re writing output explicitly.\n';
23
22
 
24
- const CodeExecutionToolSchema = z.object({
25
- lang: z
26
- .enum([
27
- 'py',
28
- 'js',
29
- 'ts',
30
- 'c',
31
- 'cpp',
32
- 'java',
33
- 'php',
34
- 'rs',
35
- 'go',
36
- 'd',
37
- 'f90',
38
- 'r',
39
- ])
40
- .describe('The programming language or runtime to execute the code in.'),
41
- code: z.string()
42
- .describe(`The complete, self-contained code to execute, without any truncation or minimization.
23
+ const SUPPORTED_LANGUAGES = [
24
+ 'py',
25
+ 'js',
26
+ 'ts',
27
+ 'c',
28
+ 'cpp',
29
+ 'java',
30
+ 'php',
31
+ 'rs',
32
+ 'go',
33
+ 'd',
34
+ 'f90',
35
+ 'r',
36
+ ] as const;
37
+
38
+ const CodeExecutionToolSchema = {
39
+ type: 'object',
40
+ properties: {
41
+ lang: {
42
+ type: 'string',
43
+ enum: SUPPORTED_LANGUAGES,
44
+ description:
45
+ 'The programming language or runtime to execute the code in.',
46
+ },
47
+ code: {
48
+ type: 'string',
49
+ description: `The complete, self-contained code to execute, without any truncation or minimization.
43
50
  - The environment is stateless; variables and imports don't persist between executions.
44
51
  - Generated files from previous executions are automatically available in "/mnt/data/".
45
52
  - Files from previous executions are automatically available and can be modified in place.
@@ -50,21 +57,26 @@ const CodeExecutionToolSchema = z.object({
50
57
  - py: Matplotlib: Use \`plt.savefig()\` to save plots as files.
51
58
  - js: use the \`console\` or \`process\` methods for all outputs.
52
59
  - r: IMPORTANT: No X11 display available. ALL graphics MUST use Cairo library (library(Cairo)).
53
- - Other languages: use appropriate output functions.`),
54
- args: z
55
- .array(z.string())
56
- .optional()
57
- .describe(
58
- 'Additional arguments to execute the code with. This should only be used if the input code requires additional arguments to run.'
59
- ),
60
- });
60
+ - Other languages: use appropriate output functions.`,
61
+ },
62
+ args: {
63
+ type: 'array',
64
+ items: { type: 'string' },
65
+ description:
66
+ 'Additional arguments to execute the code with. This should only be used if the input code requires additional arguments to run.',
67
+ },
68
+ },
69
+ required: ['lang', 'code'],
70
+ } as const;
61
71
 
62
72
  const baseEndpoint = getCodeBaseURL();
63
73
  const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
64
74
 
75
+ type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];
76
+
65
77
  function createCodeExecutionTool(
66
78
  params: t.CodeExecutionToolParams = {}
67
- ): DynamicStructuredTool<typeof CodeExecutionToolSchema> {
79
+ ): DynamicStructuredTool {
68
80
  const apiKey =
69
81
  params[EnvVar.CODE_API_KEY] ??
70
82
  params.apiKey ??
@@ -96,8 +108,13 @@ Rules:
96
108
  - Generated files auto-delivered (no download links needed)
97
109
  `.trim();
98
110
 
99
- return tool<typeof CodeExecutionToolSchema>(
100
- async ({ lang, code, ...rest }, config) => {
111
+ return tool(
112
+ async (rawInput, config) => {
113
+ const { lang, code, ...rest } = rawInput as {
114
+ lang: SupportedLanguage;
115
+ code: string;
116
+ args?: string[];
117
+ };
101
118
  /**
102
119
  * Extract session context from config.toolCall (injected by ToolNode).
103
120
  * - session_id: For API to associate with previous session
@@ -1,5 +1,4 @@
1
1
  // src/tools/ProgrammaticToolCalling.ts
2
- import { z } from 'zod';
3
2
  import { config } from 'dotenv';
4
3
  import fetch, { RequestInit } from 'node-fetch';
5
4
  import { HttpsProxyAgent } from 'https-proxy-agent';
@@ -33,12 +32,13 @@ const DEFAULT_TIMEOUT = 60000;
33
32
  // Schema
34
33
  // ============================================================================
35
34
 
36
- const ProgrammaticToolCallingSchema = z.object({
37
- code: z
38
- .string()
39
- .min(1)
40
- .describe(
41
- `Python code that calls tools programmatically. Tools are available as async functions.
35
+ const ProgrammaticToolCallingSchema = {
36
+ type: 'object',
37
+ properties: {
38
+ code: {
39
+ type: 'string',
40
+ minLength: 1,
41
+ description: `Python code that calls tools programmatically. Tools are available as async functions.
42
42
 
43
43
  CRITICAL - STATELESS EXECUTION:
44
44
  Each call is a fresh Python interpreter. Variables, imports, and data do NOT persist between calls.
@@ -66,19 +66,19 @@ Rules:
66
66
  - Just write code with await—auto-wrapped in async context
67
67
  - DO NOT define async def main() or call asyncio.run()
68
68
  - Tools are pre-defined—DO NOT write function definitions
69
- - Only print() output returns to the model`
70
- ),
71
- timeout: z
72
- .number()
73
- .int()
74
- .min(1000)
75
- .max(300000)
76
- .optional()
77
- .default(DEFAULT_TIMEOUT)
78
- .describe(
79
- 'Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.'
80
- ),
81
- });
69
+ - Only print() output returns to the model`,
70
+ },
71
+ timeout: {
72
+ type: 'integer',
73
+ minimum: 1000,
74
+ maximum: 300000,
75
+ default: DEFAULT_TIMEOUT,
76
+ description:
77
+ 'Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.',
78
+ },
79
+ },
80
+ required: ['code'],
81
+ } as const;
82
82
 
83
83
  // ============================================================================
84
84
  // Helper Functions
@@ -241,7 +241,7 @@ export async function fetchSessionFiles(
241
241
  const fetchOptions: RequestInit = {
242
242
  method: 'GET',
243
243
  headers: {
244
- 'User-Agent': 'LibreChat/1.0',
244
+ 'User-Agent': 'Illuma/1.0',
245
245
  'X-API-Key': apiKey,
246
246
  },
247
247
  };
@@ -576,7 +576,7 @@ export function formatCompletedResponse(
576
576
  */
577
577
  export function createProgrammaticToolCallingTool(
578
578
  initParams: t.ProgrammaticToolCallingParams = {}
579
- ): DynamicStructuredTool<typeof ProgrammaticToolCallingSchema> {
579
+ ): DynamicStructuredTool {
580
580
  const apiKey =
581
581
  (initParams[EnvVar.CODE_API_KEY] as string | undefined) ??
582
582
  initParams.apiKey ??
@@ -616,8 +616,9 @@ Example (complete pipeline):
616
616
  data = await query_db(sql="..."); df = process(data); await save_to_sheet(data=df); print("Done")
617
617
  `.trim();
618
618
 
619
- return tool<typeof ProgrammaticToolCallingSchema>(
620
- async (params, config) => {
619
+ return tool(
620
+ async (rawParams, config) => {
621
+ const params = rawParams as { code: string; timeout?: number };
621
622
  const { code, timeout = DEFAULT_TIMEOUT } = params;
622
623
 
623
624
  // Extra params injected by ToolNode (follows web_search pattern)
@@ -21,7 +21,8 @@ import type { StructuredToolInterface } from '@langchain/core/tools';
21
21
  import type * as t from '@/types';
22
22
  import { RunnableCallable } from '@/utils';
23
23
  import { processToolOutput } from '@/utils/toonFormat';
24
- import { Constants } from '@/common';
24
+ import { safeDispatchCustomEvent } from '@/utils/events';
25
+ import { Constants, GraphEvents } from '@/common';
25
26
 
26
27
  /**
27
28
  * Helper to check if a value is a Send object
@@ -82,6 +83,12 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
82
83
  private programmaticCache?: t.ProgrammaticCache;
83
84
  /** Reference to Graph's sessions map for automatic session injection */
84
85
  private sessions?: t.ToolSessionMap;
86
+ /** When true, dispatches ON_TOOL_EXECUTE events instead of invoking tools directly */
87
+ private eventDrivenMode: boolean = false;
88
+ /** Tool definitions for event-driven mode */
89
+ private toolDefinitions?: Map<string, t.LCTool>;
90
+ /** Agent ID for event-driven mode */
91
+ private agentId?: string;
85
92
 
86
93
  constructor({
87
94
  tools,
@@ -94,6 +101,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
94
101
  loadRuntimeTools,
95
102
  toolRegistry,
96
103
  sessions,
104
+ eventDrivenMode,
105
+ toolDefinitions,
106
+ agentId,
97
107
  }: t.ToolNodeConstructorParams) {
98
108
  super({ name, tags, func: (input, config) => this.run(input, config) });
99
109
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
@@ -104,6 +114,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
104
114
  this.toolUsageCount = new Map<string, number>();
105
115
  this.toolRegistry = toolRegistry;
106
116
  this.sessions = sessions;
117
+ this.eventDrivenMode = eventDrivenMode ?? false;
118
+ this.toolDefinitions = toolDefinitions;
119
+ this.agentId = agentId;
107
120
  }
108
121
 
109
122
  /**
@@ -316,11 +329,115 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
316
329
  }
317
330
  }
318
331
 
332
+ /**
333
+ * Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
334
+ * Used in event-driven mode where the host handles actual tool execution.
335
+ */
336
+ private async executeViaEvent(
337
+ toolCalls: ToolCall[],
338
+ config: RunnableConfig,
339
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
340
+ input: any
341
+ ): Promise<T> {
342
+ const requests: t.ToolCallRequest[] = toolCalls.map((call) => {
343
+ const turn = this.toolUsageCount.get(call.name) ?? 0;
344
+ this.toolUsageCount.set(call.name, turn + 1);
345
+ return {
346
+ id: call.id!,
347
+ name: call.name,
348
+ args: call.args as Record<string, unknown>,
349
+ stepId: this.toolCallStepIds?.get(call.id!),
350
+ turn,
351
+ };
352
+ });
353
+
354
+ const results = await new Promise<t.ToolExecuteResult[]>(
355
+ (resolve, reject) => {
356
+ const request: t.ToolExecuteBatchRequest = {
357
+ toolCalls: requests,
358
+ userId: config.configurable?.user_id as string | undefined,
359
+ agentId: this.agentId,
360
+ configurable: config.configurable as
361
+ | Record<string, unknown>
362
+ | undefined,
363
+ metadata: config.metadata as Record<string, unknown> | undefined,
364
+ resolve,
365
+ reject,
366
+ };
367
+
368
+ safeDispatchCustomEvent(GraphEvents.ON_TOOL_EXECUTE, request, config);
369
+ }
370
+ );
371
+
372
+ const outputs: ToolMessage[] = results.map((result) => {
373
+ const request = requests.find((r) => r.id === result.toolCallId);
374
+ const toolName = request?.name ?? 'unknown';
375
+ const stepId = this.toolCallStepIds?.get(result.toolCallId) ?? '';
376
+
377
+ let toolMessage: ToolMessage;
378
+ let contentString: string;
379
+
380
+ if (result.status === 'error') {
381
+ contentString = `Error: ${result.errorMessage ?? 'Unknown error'}\n Please fix your mistakes.`;
382
+ toolMessage = new ToolMessage({
383
+ status: 'error',
384
+ content: contentString,
385
+ name: toolName,
386
+ tool_call_id: result.toolCallId,
387
+ });
388
+ } else {
389
+ contentString =
390
+ typeof result.content === 'string'
391
+ ? result.content
392
+ : JSON.stringify(result.content);
393
+ toolMessage = new ToolMessage({
394
+ status: 'success',
395
+ content: contentString,
396
+ name: toolName,
397
+ tool_call_id: result.toolCallId,
398
+ });
399
+ }
400
+
401
+ const tool_call: t.ProcessedToolCall = {
402
+ args:
403
+ typeof request?.args === 'string'
404
+ ? request.args
405
+ : JSON.stringify(request?.args ?? {}),
406
+ name: toolName,
407
+ id: result.toolCallId,
408
+ output: contentString,
409
+ progress: 1,
410
+ };
411
+
412
+ const runStepCompletedData = {
413
+ result: {
414
+ id: stepId,
415
+ index: request?.turn ?? 0,
416
+ type: 'tool_call' as const,
417
+ tool_call,
418
+ },
419
+ };
420
+
421
+ safeDispatchCustomEvent(
422
+ GraphEvents.ON_RUN_STEP_COMPLETED,
423
+ runStepCompletedData,
424
+ config
425
+ );
426
+
427
+ return toolMessage;
428
+ });
429
+
430
+ return (Array.isArray(input) ? outputs : { messages: outputs }) as T;
431
+ }
432
+
319
433
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
320
434
  protected async run(input: any, config: RunnableConfig): Promise<T> {
321
435
  let outputs: (BaseMessage | Command)[];
322
436
 
323
437
  if (this.isSendInput(input)) {
438
+ if (this.eventDrivenMode) {
439
+ return this.executeViaEvent([input.lg_tool_call], config, input);
440
+ }
324
441
  outputs = [await this.runTool(input.lg_tool_call, config)];
325
442
  } else {
326
443
  let messages: BaseMessage[];
@@ -362,21 +479,26 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
362
479
  this.programmaticCache = undefined; // Invalidate cache on toolMap change
363
480
  }
364
481
 
482
+ const filteredCalls =
483
+ aiMessage.tool_calls?.filter((call) => {
484
+ /**
485
+ * Filter out:
486
+ * 1. Already processed tool calls (present in toolMessageIds)
487
+ * 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')
488
+ * which are executed by the provider's API and don't require invocation
489
+ */
490
+ return (
491
+ (call.id == null || !toolMessageIds.has(call.id)) &&
492
+ !(call.id?.startsWith('srvtoolu_') ?? false)
493
+ );
494
+ }) ?? [];
495
+
496
+ if (this.eventDrivenMode && filteredCalls.length > 0) {
497
+ return this.executeViaEvent(filteredCalls, config, input);
498
+ }
499
+
365
500
  outputs = await Promise.all(
366
- aiMessage.tool_calls
367
- ?.filter((call) => {
368
- /**
369
- * Filter out:
370
- * 1. Already processed tool calls (present in toolMessageIds)
371
- * 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')
372
- * which are executed by the provider's API and don't require invocation
373
- */
374
- return (
375
- (call.id == null || !toolMessageIds.has(call.id)) &&
376
- !(call.id?.startsWith('srvtoolu_') ?? false)
377
- );
378
- })
379
- .map((call) => this.runTool(call, config)) ?? []
501
+ filteredCalls.map((call) => this.runTool(call, config))
380
502
  );
381
503
  }
382
504
 
@@ -1,5 +1,4 @@
1
1
  // src/tools/ToolSearch.ts
2
- import { z } from 'zod';
3
2
  import * as okapibm25Module from 'okapibm25';
4
3
  import { config } from 'dotenv';
5
4
 
@@ -40,20 +39,25 @@ const MAX_REGEX_COMPLEXITY = 5;
40
39
  /** Default search timeout in milliseconds */
41
40
  const SEARCH_TIMEOUT = 5000;
42
41
 
43
- /** Zod schema type for tool search parameters */
44
- type ToolSearchSchema = z.ZodObject<{
45
- query: z.ZodDefault<z.ZodOptional<z.ZodString>>;
46
- fields: z.ZodDefault<
47
- z.ZodOptional<z.ZodArray<z.ZodEnum<['name', 'description', 'parameters']>>>
48
- >;
49
- max_results: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
50
- mcp_server: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString>]>>;
51
- }>;
42
+ /** JSON schema type for tool search parameters */
43
+ interface ToolSearchSchema {
44
+ type: 'object';
45
+ properties: Record<string, unknown>;
46
+ required: string[];
47
+ }
48
+
49
+ /** Input params type for tool search */
50
+ interface ToolSearchParams {
51
+ query?: string;
52
+ fields?: ('name' | 'description' | 'parameters')[];
53
+ max_results?: number;
54
+ mcp_server?: string | string[];
55
+ }
52
56
 
53
57
  /**
54
- * Creates the Zod schema with dynamic query description based on mode.
58
+ * Creates the JSON schema with dynamic query description based on mode.
55
59
  * @param mode - The search mode determining query interpretation
56
- * @returns Zod schema for tool search parameters
60
+ * @returns JSON schema for tool search parameters
57
61
  */
58
62
  function createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {
59
63
  const queryDescription =
@@ -61,33 +65,39 @@ function createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {
61
65
  ? 'Search term to find in tool names and descriptions. Case-insensitive substring matching. Optional if mcp_server is provided.'
62
66
  : 'Regex pattern to search tool names and descriptions. Optional if mcp_server is provided.';
63
67
 
64
- return z.object({
65
- query: z
66
- .string()
67
- .max(MAX_PATTERN_LENGTH)
68
- .optional()
69
- .default('')
70
- .describe(queryDescription),
71
- fields: z
72
- .array(z.enum(['name', 'description', 'parameters']))
73
- .optional()
74
- .default(['name', 'description'])
75
- .describe('Which fields to search. Default: name and description'),
76
- max_results: z
77
- .number()
78
- .int()
79
- .min(1)
80
- .max(50)
81
- .optional()
82
- .default(10)
83
- .describe('Maximum number of matching tools to return'),
84
- mcp_server: z
85
- .union([z.string(), z.array(z.string())])
86
- .optional()
87
- .describe(
88
- 'Filter to tools from specific MCP server(s). Can be a single server name or array of names. If provided without a query, lists all tools from those servers.'
89
- ),
90
- });
68
+ return {
69
+ type: 'object',
70
+ properties: {
71
+ query: {
72
+ type: 'string',
73
+ maxLength: MAX_PATTERN_LENGTH,
74
+ default: '',
75
+ description: queryDescription,
76
+ },
77
+ fields: {
78
+ type: 'array',
79
+ items: { type: 'string', enum: ['name', 'description', 'parameters'] },
80
+ default: ['name', 'description'],
81
+ description: 'Which fields to search. Default: name and description',
82
+ },
83
+ max_results: {
84
+ type: 'integer',
85
+ minimum: 1,
86
+ maximum: 50,
87
+ default: 10,
88
+ description: 'Maximum number of matching tools to return',
89
+ },
90
+ mcp_server: {
91
+ oneOf: [
92
+ { type: 'string' },
93
+ { type: 'array', items: { type: 'string' } },
94
+ ],
95
+ description:
96
+ 'Filter to tools from specific MCP server(s). Can be a single server name or array of names. If provided without a query, lists all tools from those servers.',
97
+ },
98
+ },
99
+ required: [],
100
+ };
91
101
  }
92
102
 
93
103
  /**
@@ -748,7 +758,7 @@ function formatServerListing(
748
758
  */
749
759
  function createToolSearch(
750
760
  initParams: t.ToolSearchParams = {}
751
- ): DynamicStructuredTool<ReturnType<typeof createToolSearchSchema>> {
761
+ ): DynamicStructuredTool {
752
762
  const mode: t.ToolSearchMode = initParams.mode ?? 'code_interpreter';
753
763
  const defaultOnlyDeferred = initParams.onlyDeferred ?? true;
754
764
  const schema = createToolSearchSchema(mode);
@@ -802,10 +812,11 @@ Searches deferred tools by regex pattern.
802
812
  ${mcpNote}${toolsListSection}
803
813
  `.trim();
804
814
 
805
- return tool<typeof schema>(
806
- async (params, config) => {
815
+ return tool(
816
+ async (rawParams, config) => {
817
+ const params = rawParams as ToolSearchParams;
807
818
  const {
808
- query,
819
+ query = '',
809
820
  fields = ['name', 'description'],
810
821
  max_results = 10,
811
822
  mcp_server,
@@ -946,7 +957,7 @@ ${mcpNote}${toolsListSection}
946
957
  method: 'POST',
947
958
  headers: {
948
959
  'Content-Type': 'application/json',
949
- 'User-Agent': 'LibreChat/1.0',
960
+ 'User-Agent': 'Illuma/1.0',
950
961
  'X-API-Key': apiKey,
951
962
  },
952
963
  body: JSON.stringify(postData),
@@ -4,6 +4,9 @@
4
4
  * These tests hit the LIVE Code API and verify end-to-end functionality.
5
5
  *
6
6
  * Run with: npm test -- ProgrammaticToolCalling.integration.test.ts
7
+ *
8
+ * Requires CODE_EXECUTOR_API_KEY environment variable.
9
+ * Tests are skipped when the API key is not available.
7
10
  */
8
11
  import { config as dotenvConfig } from 'dotenv';
9
12
  dotenvConfig();
@@ -19,19 +22,17 @@ import {
19
22
  createProgrammaticToolRegistry,
20
23
  } from '@/test/mockTools';
21
24
 
22
- describe('ProgrammaticToolCalling - Live API Integration', () => {
25
+ const apiKey = process.env.CODE_EXECUTOR_API_KEY;
26
+ const shouldSkip = apiKey == null || apiKey === '';
27
+
28
+ const describeIfApiKey = shouldSkip ? describe.skip : describe;
29
+
30
+ describeIfApiKey('ProgrammaticToolCalling - Live API Integration', () => {
23
31
  let ptcTool: ReturnType<typeof createProgrammaticToolCallingTool>;
24
32
  let toolMap: t.ToolMap;
25
33
  let toolDefinitions: t.LCTool[];
26
34
 
27
35
  beforeAll(() => {
28
- const apiKey = process.env.CODE_EXECUTOR_API_KEY;
29
- if (apiKey == null || apiKey === '') {
30
- throw new Error(
31
- 'CODE_EXECUTOR_API_KEY not set. Required for integration tests.'
32
- );
33
- }
34
-
35
36
  const tools = [
36
37
  createGetTeamMembersTool(),
37
38
  createGetExpensesTool(),
@@ -42,7 +43,7 @@ describe('ProgrammaticToolCalling - Live API Integration', () => {
42
43
  toolMap = new Map(tools.map((t) => [t.name, t]));
43
44
  toolDefinitions = Array.from(createProgrammaticToolRegistry().values());
44
45
 
45
- ptcTool = createProgrammaticToolCallingTool({ apiKey });
46
+ ptcTool = createProgrammaticToolCallingTool({ apiKey: apiKey! });
46
47
  });
47
48
 
48
49
  it('executes simple single tool call', async () => {
@@ -4,6 +4,9 @@
4
4
  * These tests hit the LIVE Code API and verify end-to-end search functionality.
5
5
  *
6
6
  * Run with: npm test -- ToolSearch.integration.test.ts
7
+ *
8
+ * Requires CODE_EXECUTOR_API_KEY environment variable.
9
+ * Tests are skipped when the API key is not available.
7
10
  */
8
11
  import { config as dotenvConfig } from 'dotenv';
9
12
  dotenvConfig();
@@ -12,19 +15,17 @@ import { describe, it, expect, beforeAll } from '@jest/globals';
12
15
  import { createToolSearch } from '../ToolSearch';
13
16
  import { createToolSearchToolRegistry } from '@/test/mockTools';
14
17
 
15
- describe('ToolSearch - Live API Integration', () => {
18
+ const apiKey = process.env.CODE_EXECUTOR_API_KEY;
19
+ const shouldSkip = apiKey == null || apiKey === '';
20
+
21
+ const describeIfApiKey = shouldSkip ? describe.skip : describe;
22
+
23
+ describeIfApiKey('ToolSearch - Live API Integration', () => {
16
24
  let searchTool: ReturnType<typeof createToolSearch>;
17
25
  const toolRegistry = createToolSearchToolRegistry();
18
26
 
19
27
  beforeAll(() => {
20
- const apiKey = process.env.CODE_EXECUTOR_API_KEY;
21
- if (apiKey == null || apiKey === '') {
22
- throw new Error(
23
- 'CODE_EXECUTOR_API_KEY not set. Required for integration tests.'
24
- );
25
- }
26
-
27
- searchTool = createToolSearch({ apiKey, toolRegistry });
28
+ searchTool = createToolSearch({ apiKey: apiKey!, toolRegistry });
28
29
  });
29
30
 
30
31
  it('searches for expense-related tools', async () => {
@@ -0,0 +1,37 @@
1
+ import { tool, type StructuredToolInterface } from '@langchain/core/tools';
2
+ import type { LCTool } from '@/types';
3
+
4
+ /**
5
+ * Creates a schema-only tool for LLM binding in event-driven mode.
6
+ * These tools have valid schemas for the LLM to understand but should
7
+ * never be invoked directly - ToolNode handles execution via events.
8
+ */
9
+ export function createSchemaOnlyTool(
10
+ definition: LCTool
11
+ ): StructuredToolInterface {
12
+ const { name, description, parameters, responseFormat } = definition;
13
+
14
+ return tool(
15
+ async () => {
16
+ throw new Error(
17
+ `Tool "${name}" should not be invoked directly in event-driven mode. ` +
18
+ 'ToolNode should dispatch ON_TOOL_EXECUTE events instead.'
19
+ );
20
+ },
21
+ {
22
+ name,
23
+ description: description ?? '',
24
+ schema: parameters ?? { type: 'object', properties: {} },
25
+ responseFormat: responseFormat ?? 'content_and_artifact',
26
+ }
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Creates schema-only tools for all definitions in an array.
32
+ */
33
+ export function createSchemaOnlyTools(
34
+ definitions: LCTool[]
35
+ ): StructuredToolInterface[] {
36
+ return definitions.map((def) => createSchemaOnlyTool(def));
37
+ }