@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 @@
1
+ {"version":3,"file":"ProgrammaticToolCalling.mjs","sources":["../../../src/tools/ProgrammaticToolCalling.ts"],"sourcesContent":["// src/tools/ProgrammaticToolCalling.ts\nimport { z } from 'zod';\nimport { config } from 'dotenv';\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { getEnvironmentVariable } from '@langchain/core/utils/env';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { imageExtRegex, getCodeBaseURL } from './CodeExecutor';\nimport { EnvVar, Constants } from '@/common';\n\nconfig();\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst imageMessage = 'Image is already displayed to the user';\nconst otherMessage = 'File is already downloaded by the user';\nconst accessMessage =\n 'Note: Files are READ-ONLY. Save changes to NEW filenames. To access these files in future executions, provide the `session_id` as a parameter (not in your code).';\nconst emptyOutputMessage =\n 'stdout: Empty. Ensure you\\'re writing output explicitly.\\n';\n\n/** Default max round-trips to prevent infinite loops */\nconst DEFAULT_MAX_ROUND_TRIPS = 20;\n\n/** Default execution timeout in milliseconds */\nconst DEFAULT_TIMEOUT = 60000;\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst ProgrammaticToolCallingSchema = z.object({\n code: z\n .string()\n .min(1)\n .describe(\n `Python code that calls tools programmatically. Tools are automatically available as async Python functions - DO NOT define them yourself.\n\nThe Code API generates async function stubs from the tool definitions. Just call them directly:\n\nExample (Simple call):\n result = await get_weather(city=\"San Francisco\")\n print(result)\n\nExample (Parallel - Fastest):\n results = await asyncio.gather(\n get_weather(city=\"SF\"),\n get_weather(city=\"NYC\"),\n get_weather(city=\"London\")\n )\n for city, weather in zip([\"SF\", \"NYC\", \"London\"], results):\n print(f\"{city}: {weather['temperature']}°F\")\n\nExample (Loop with processing):\n team = await get_team_members()\n for member in team:\n expenses = await get_expenses(user_id=member['id'])\n total = sum(e['amount'] for e in expenses)\n print(f\"{member['name']}: \\${total:.2f}\")\n\nExample (Conditional logic):\n data = await fetch_data(source=\"primary\")\n if not data:\n data = await fetch_data(source=\"backup\")\n print(f\"Got {len(data)} records\")\n\nRequirements:\n- Tools are pre-defined as async functions - DO NOT write function definitions\n- Use await for all tool calls\n- Use asyncio.gather() for parallel execution of independent calls\n- Only print() output flows back to the context window\n- Tool results from programmatic calls do NOT consume context tokens`\n ),\n tools: z\n .array(\n z.object({\n name: z.string(),\n description: z.string().optional(),\n parameters: z.any(), // JsonSchemaType\n })\n )\n .optional()\n .describe(\n 'Optional array of tool definitions that can be called from the code. If not provided, uses programmatic tools configured in the agent context. Tool names must match tools available in the toolMap.'\n ),\n session_id: z\n .string()\n .optional()\n .describe(\n 'Session ID for file access (same as regular code execution). Files load into /mnt/data/ and are READ-ONLY.'\n ),\n timeout: z\n .number()\n .int()\n .min(1000)\n .max(300000)\n .optional()\n .default(DEFAULT_TIMEOUT)\n .describe(\n 'Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.'\n ),\n});\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Makes an HTTP request to the Code API.\n * @param endpoint - The API endpoint URL\n * @param apiKey - The API key for authentication\n * @param body - The request body\n * @param proxy - Optional HTTP proxy URL\n * @returns The parsed API response\n */\nexport async function makeRequest(\n endpoint: string,\n apiKey: string,\n body: Record<string, unknown>,\n proxy?: string\n): Promise<t.ProgrammaticExecutionResponse> {\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'LibreChat/1.0',\n 'X-API-Key': apiKey,\n },\n body: JSON.stringify(body),\n };\n\n if (proxy != null && proxy !== '') {\n fetchOptions.agent = new HttpsProxyAgent(proxy);\n }\n\n const response = await fetch(endpoint, fetchOptions);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `HTTP error! status: ${response.status}, body: ${errorText}`\n );\n }\n\n return (await response.json()) as t.ProgrammaticExecutionResponse;\n}\n\n/**\n * Executes tools in parallel when requested by the API.\n * Uses Promise.all for parallel execution, catching individual errors.\n * @param toolCalls - Array of tool calls from the API\n * @param toolMap - Map of tool names to executable tools\n * @returns Array of tool results\n */\nexport async function executeTools(\n toolCalls: t.PTCToolCall[],\n toolMap: t.ToolMap\n): Promise<t.PTCToolResult[]> {\n const executions = toolCalls.map(async (call): Promise<t.PTCToolResult> => {\n const tool = toolMap.get(call.name);\n\n if (!tool) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: `Tool '${call.name}' not found. Available tools: ${Array.from(toolMap.keys()).join(', ')}`,\n };\n }\n\n try {\n const result = await tool.invoke(call.input, {\n metadata: { [Constants.PROGRAMMATIC_TOOL_CALLING]: true },\n });\n return {\n call_id: call.id,\n result,\n is_error: false,\n };\n } catch (error) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: (error as Error).message || 'Tool execution failed',\n };\n }\n });\n\n return await Promise.all(executions);\n}\n\n/**\n * Formats the completed response for the agent.\n * @param response - The completed API response\n * @returns Tuple of [formatted string, artifact]\n */\nexport function formatCompletedResponse(\n response: t.ProgrammaticExecutionResponse\n): [string, t.ProgrammaticExecutionArtifact] {\n let formatted = '';\n\n if (response.stdout != null && response.stdout !== '') {\n formatted += `stdout:\\n${response.stdout}\\n`;\n } else {\n formatted += emptyOutputMessage;\n }\n\n if (response.stderr != null && response.stderr !== '') {\n formatted += `stderr:\\n${response.stderr}\\n`;\n }\n\n if (response.files && response.files.length > 0) {\n formatted += 'Generated files:\\n';\n\n const fileCount = response.files.length;\n for (let i = 0; i < fileCount; i++) {\n const file = response.files[i];\n const isImage = imageExtRegex.test(file.name);\n formatted += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;\n\n if (i < fileCount - 1) {\n formatted += fileCount <= 3 ? ', ' : ',\\n';\n }\n }\n\n formatted += `\\nsession_id: ${response.session_id}\\n\\n${accessMessage}`;\n }\n\n return [\n formatted.trim(),\n {\n session_id: response.session_id,\n files: response.files,\n },\n ];\n}\n\n// ============================================================================\n// Tool Factory\n// ============================================================================\n\n/**\n * Creates a Programmatic Tool Calling tool for complex multi-tool workflows.\n *\n * This tool enables AI agents to write Python code that orchestrates multiple\n * tool calls programmatically, reducing LLM round-trips and token usage.\n *\n * The tool map must be provided at runtime via config.configurable.toolMap.\n *\n * @param params - Configuration parameters (apiKey, baseUrl, maxRoundTrips, proxy)\n * @returns A LangChain DynamicStructuredTool for programmatic tool calling\n *\n * @example\n * const ptcTool = createProgrammaticToolCallingTool({\n * apiKey: process.env.CODE_API_KEY,\n * maxRoundTrips: 20\n * });\n *\n * const [output, artifact] = await ptcTool.invoke(\n * { code, tools },\n * { configurable: { toolMap } }\n * );\n */\nexport function createProgrammaticToolCallingTool(\n initParams: t.ProgrammaticToolCallingParams = {}\n): DynamicStructuredTool<typeof ProgrammaticToolCallingSchema> {\n const apiKey =\n (initParams[EnvVar.CODE_API_KEY] as string | undefined) ??\n initParams.apiKey ??\n getEnvironmentVariable(EnvVar.CODE_API_KEY) ??\n '';\n\n if (!apiKey) {\n throw new Error(\n 'No API key provided for programmatic tool calling. ' +\n 'Set CODE_API_KEY environment variable or pass apiKey in initParams.'\n );\n }\n\n const baseUrl = initParams.baseUrl ?? getCodeBaseURL();\n const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;\n const proxy = initParams.proxy ?? process.env.PROXY;\n const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;\n\n const description = `\nExecutes Python code with programmatic tool calling. Tools are automatically generated as async Python functions from the tool definitions - DO NOT define them in your code.\n\nUsage:\n- Write Python code that calls tools using await: result = await get_data()\n- Tools are pre-defined as async functions - just call them\n- Use asyncio.gather() for parallel execution (single round-trip!)\n- Only print() output flows through the context window\n- Tool results from programmatic calls do NOT consume context tokens\n\nWhen to use:\n- Processing multiple records with tool calls (10+ items)\n- Loops, conditionals, or aggregation based on tool results\n- Any workflow requiring 3+ sequential tool calls\n- Parallel execution of independent tool calls\n- Filtering/summarizing large data before returning to context\n\nPatterns:\n- Simple: result = await get_data()\n- Loop: for item in items: data = await fetch(item)\n- Parallel: results = await asyncio.gather(t1(), t2(), t3())\n- Conditional: if x: await tool_a() else: await tool_b()\n`.trim();\n\n return tool<typeof ProgrammaticToolCallingSchema>(\n async (params, config) => {\n const { code, tools, session_id, timeout = DEFAULT_TIMEOUT } = params;\n\n // Extra params injected by ToolNode (follows web_search pattern)\n const { toolMap, programmaticToolDefs } = config.toolCall ?? {};\n\n if (toolMap == null || toolMap.size === 0) {\n throw new Error(\n 'No toolMap provided. ' +\n 'ToolNode should inject this from AgentContext when invoked through the graph.'\n );\n }\n\n // Use provided tools or fall back to programmaticToolDefs from ToolNode\n const effectiveTools = tools ?? programmaticToolDefs;\n\n if (effectiveTools == null || effectiveTools.length === 0) {\n throw new Error(\n 'No tool definitions provided. ' +\n 'Either pass tools in the input or ensure ToolNode injects programmaticToolDefs.'\n );\n }\n\n let roundTrip = 0;\n\n try {\n // ====================================================================\n // Phase 1: Initial request\n // ====================================================================\n\n let response = await makeRequest(\n EXEC_ENDPOINT,\n apiKey,\n {\n code,\n tools: effectiveTools,\n session_id,\n timeout,\n },\n proxy\n );\n\n // ====================================================================\n // Phase 2: Handle response loop\n // ====================================================================\n\n while (response.status === 'tool_call_required') {\n roundTrip++;\n\n if (roundTrip > maxRoundTrips) {\n throw new Error(\n `Exceeded maximum round trips (${maxRoundTrips}). ` +\n 'This may indicate an infinite loop, excessive tool calls, ' +\n 'or a logic error in your code.'\n );\n }\n\n // eslint-disable-next-line no-console\n console.log(\n `[PTC] Round trip ${roundTrip}: ${response.tool_calls?.length ?? 0} tool(s) to execute`\n );\n\n const toolResults = await executeTools(\n response.tool_calls ?? [],\n toolMap\n );\n\n response = await makeRequest(\n EXEC_ENDPOINT,\n apiKey,\n {\n continuation_token: response.continuation_token,\n tool_results: toolResults,\n },\n proxy\n );\n }\n\n // ====================================================================\n // Phase 3: Handle final state\n // ====================================================================\n\n if (response.status === 'completed') {\n return formatCompletedResponse(response);\n }\n\n if (response.status === 'error') {\n throw new Error(\n `Execution error: ${response.error}` +\n (response.stderr != null && response.stderr !== ''\n ? `\\n\\nStderr:\\n${response.stderr}`\n : '')\n );\n }\n\n throw new Error(`Unexpected response status: ${response.status}`);\n } catch (error) {\n throw new Error(\n `Programmatic execution failed: ${(error as Error).message}`\n );\n }\n },\n {\n name: Constants.PROGRAMMATIC_TOOL_CALLING,\n description,\n schema: ProgrammaticToolCallingSchema,\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { isBaseMessage, ToolMessage, isAIMessage } from '@langchain/core/messages';
2
2
  import { isCommand, isGraphInterrupt, Command, END, Send } from '@langchain/langgraph';
3
- import '../common/enum.mjs';
3
+ import { Constants } from '../common/enum.mjs';
4
4
  import 'nanoid';
5
5
  import '../messages/core.mjs';
6
6
  import { RunnableCallable } from '../utils/run.mjs';
@@ -22,7 +22,13 @@ class ToolNode extends RunnableCallable {
22
22
  toolCallStepIds;
23
23
  errorHandler;
24
24
  toolUsageCount;
25
- constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, }) {
25
+ /** Tools available for programmatic code execution */
26
+ programmaticToolMap;
27
+ /** Tool definitions for programmatic code execution (sent to Code API) */
28
+ programmaticToolDefs;
29
+ /** Tool registry for tool search (deferred tools) */
30
+ toolRegistry;
31
+ constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, programmaticToolMap, programmaticToolDefs, toolRegistry, }) {
26
32
  super({ name, tags, func: (input, config) => this.run(input, config) });
27
33
  this.tools = tools;
28
34
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
@@ -31,6 +37,9 @@ class ToolNode extends RunnableCallable {
31
37
  this.loadRuntimeTools = loadRuntimeTools;
32
38
  this.errorHandler = errorHandler;
33
39
  this.toolUsageCount = new Map();
40
+ this.programmaticToolMap = programmaticToolMap;
41
+ this.programmaticToolDefs = programmaticToolDefs;
42
+ this.toolRegistry = toolRegistry;
34
43
  }
35
44
  /**
36
45
  * Returns a snapshot of the current tool usage counts.
@@ -52,7 +61,29 @@ class ToolNode extends RunnableCallable {
52
61
  this.toolUsageCount.set(call.name, turn + 1);
53
62
  const args = call.args;
54
63
  const stepId = this.toolCallStepIds?.get(call.id);
55
- const output = await tool.invoke({ ...call, args, type: 'tool_call', stepId, turn }, config);
64
+ // Build invoke params - LangChain extracts non-schema fields to config.toolCall
65
+ let invokeParams = {
66
+ ...call,
67
+ args,
68
+ type: 'tool_call',
69
+ stepId,
70
+ turn,
71
+ };
72
+ // Inject runtime data for special tools (becomes available at config.toolCall)
73
+ if (call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {
74
+ invokeParams = {
75
+ ...invokeParams,
76
+ toolMap: this.programmaticToolMap,
77
+ programmaticToolDefs: this.programmaticToolDefs,
78
+ };
79
+ }
80
+ else if (call.name === Constants.TOOL_SEARCH_REGEX) {
81
+ invokeParams = {
82
+ ...invokeParams,
83
+ toolRegistry: this.toolRegistry,
84
+ };
85
+ }
86
+ const output = await tool.invoke(invokeParams, config);
56
87
  if ((isBaseMessage(output) && output._getType() === 'tool') ||
57
88
  isCommand(output)) {
58
89
  return output;
@@ -1 +1 @@
1
- {"version":3,"file":"ToolNode.mjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import { ToolCall } from '@langchain/core/messages/tool';\nimport {\n ToolMessage,\n isAIMessage,\n isBaseMessage,\n} from '@langchain/core/messages';\nimport {\n END,\n Send,\n Command,\n isCommand,\n isGraphInterrupt,\n MessagesAnnotation,\n} from '@langchain/langgraph';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\n\n/**\n * Helper to check if a value is a Send object\n */\nfunction isSend(value: unknown): value is Send {\n return value instanceof Send;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n trace = false;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n /**\n * Runs a single tool call with error handling\n */\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig\n ): Promise<BaseMessage | Command> {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n const output = await tool.invoke(\n { ...call, args, type: 'tool_call', stepId, turn },\n config\n );\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n status: 'success',\n name: tool.name,\n content: typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n if (this.errorHandler) {\n try {\n await this.errorHandler(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n } catch (handlerError) {\n // eslint-disable-next-line no-console\n console.error('Error in errorHandler:', {\n toolName: call.name,\n toolCallId: call.id,\n toolArgs: call.args,\n stepId: this.toolCallStepIds?.get(call.id!),\n turn: this.toolUsageCount.get(call.name),\n originalError: {\n message: e.message,\n stack: e.stack ?? undefined,\n },\n handlerError:\n handlerError instanceof Error\n ? {\n message: handlerError.message,\n stack: handlerError.stack ?? undefined,\n }\n : {\n message: String(handlerError),\n stack: undefined,\n },\n });\n }\n }\n return new ToolMessage({\n status: 'error',\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? '',\n });\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n let outputs: (BaseMessage | Command)[];\n\n if (this.isSendInput(input)) {\n outputs = [await this.runTool(input.lg_tool_call, config)];\n } else {\n let messages: BaseMessage[];\n if (Array.isArray(input)) {\n messages = input;\n } else if (this.isMessagesState(input)) {\n messages = input.messages;\n } else {\n throw new Error(\n 'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'\n );\n }\n\n const toolMessageIds: Set<string> = new Set(\n messages\n .filter((msg) => msg._getType() === 'tool')\n .map((msg) => (msg as ToolMessage).tool_call_id)\n );\n\n let aiMessage: AIMessage | undefined;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (isAIMessage(message)) {\n aiMessage = message;\n break;\n }\n }\n\n if (aiMessage == null || !isAIMessage(aiMessage)) {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n aiMessage.tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap =\n toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n\n outputs = await Promise.all(\n aiMessage.tool_calls\n ?.filter((call) => {\n /**\n * Filter out:\n * 1. Already processed tool calls (present in toolMessageIds)\n * 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')\n * which are executed by the provider's API and don't require invocation\n */\n return (\n (call.id == null || !toolMessageIds.has(call.id)) &&\n !(call.id?.startsWith('srvtoolu_') ?? false)\n );\n })\n .map((call) => this.runTool(call, config)) ?? []\n );\n }\n\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[] = [];\n let parentCommand: Command | null = null;\n\n for (const output of outputs) {\n if (isCommand(output)) {\n if (\n output.graph === Command.PARENT &&\n Array.isArray(output.goto) &&\n output.goto.every((send): send is Send => isSend(send))\n ) {\n if (parentCommand) {\n (parentCommand.goto as Send[]).push(...(output.goto as Send[]));\n } else {\n parentCommand = new Command({\n graph: Command.PARENT,\n goto: output.goto,\n });\n }\n } else {\n combinedOutputs.push(output);\n }\n } else {\n combinedOutputs.push(\n Array.isArray(input) ? [output] : { messages: [output] }\n );\n }\n }\n\n if (parentCommand) {\n combinedOutputs.push(parentCommand);\n }\n\n return combinedOutputs as T;\n }\n\n private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {\n return (\n typeof input === 'object' && input != null && 'lg_tool_call' in input\n );\n }\n\n private isMessagesState(\n input: unknown\n ): input is { messages: BaseMessage[] } {\n return (\n typeof input === 'object' &&\n input != null &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown }).messages) &&\n (input as { messages: unknown[] }).messages.every(isBaseMessage)\n );\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ToolNode.mjs","sources":["../../../src/tools/ToolNode.ts"],"sourcesContent":["import { ToolCall } from '@langchain/core/messages/tool';\nimport {\n ToolMessage,\n isAIMessage,\n isBaseMessage,\n} from '@langchain/core/messages';\nimport {\n END,\n Send,\n Command,\n isCommand,\n isGraphInterrupt,\n MessagesAnnotation,\n} from '@langchain/langgraph';\nimport type {\n RunnableConfig,\n RunnableToolLike,\n} from '@langchain/core/runnables';\nimport type { BaseMessage, AIMessage } from '@langchain/core/messages';\nimport type { StructuredToolInterface } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { RunnableCallable } from '@/utils';\nimport { Constants } from '@/common';\n\n/**\n * Helper to check if a value is a Send object\n */\nfunction isSend(value: unknown): value is Send {\n return value instanceof Send;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport class ToolNode<T = any> extends RunnableCallable<T, T> {\n tools: t.GenericTool[];\n private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;\n private loadRuntimeTools?: t.ToolRefGenerator;\n handleToolErrors = true;\n trace = false;\n toolCallStepIds?: Map<string, string>;\n errorHandler?: t.ToolNodeConstructorParams['errorHandler'];\n private toolUsageCount: Map<string, number>;\n /** Tools available for programmatic code execution */\n private programmaticToolMap?: t.ToolMap;\n /** Tool definitions for programmatic code execution (sent to Code API) */\n private programmaticToolDefs?: t.LCTool[];\n /** Tool registry for tool search (deferred tools) */\n private toolRegistry?: t.LCToolRegistry;\n\n constructor({\n tools,\n toolMap,\n name,\n tags,\n errorHandler,\n toolCallStepIds,\n handleToolErrors,\n loadRuntimeTools,\n programmaticToolMap,\n programmaticToolDefs,\n toolRegistry,\n }: t.ToolNodeConstructorParams) {\n super({ name, tags, func: (input, config) => this.run(input, config) });\n this.tools = tools;\n this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n this.toolCallStepIds = toolCallStepIds;\n this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;\n this.loadRuntimeTools = loadRuntimeTools;\n this.errorHandler = errorHandler;\n this.toolUsageCount = new Map<string, number>();\n this.programmaticToolMap = programmaticToolMap;\n this.programmaticToolDefs = programmaticToolDefs;\n this.toolRegistry = toolRegistry;\n }\n\n /**\n * Returns a snapshot of the current tool usage counts.\n * @returns A ReadonlyMap where keys are tool names and values are their usage counts.\n */\n public getToolUsageCounts(): ReadonlyMap<string, number> {\n return new Map(this.toolUsageCount); // Return a copy\n }\n\n /**\n * Runs a single tool call with error handling\n */\n protected async runTool(\n call: ToolCall,\n config: RunnableConfig\n ): Promise<BaseMessage | Command> {\n const tool = this.toolMap.get(call.name);\n try {\n if (tool === undefined) {\n throw new Error(`Tool \"${call.name}\" not found.`);\n }\n const turn = this.toolUsageCount.get(call.name) ?? 0;\n this.toolUsageCount.set(call.name, turn + 1);\n const args = call.args;\n const stepId = this.toolCallStepIds?.get(call.id!);\n\n // Build invoke params - LangChain extracts non-schema fields to config.toolCall\n let invokeParams: Record<string, unknown> = {\n ...call,\n args,\n type: 'tool_call',\n stepId,\n turn,\n };\n\n // Inject runtime data for special tools (becomes available at config.toolCall)\n if (call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {\n invokeParams = {\n ...invokeParams,\n toolMap: this.programmaticToolMap,\n programmaticToolDefs: this.programmaticToolDefs,\n };\n } else if (call.name === Constants.TOOL_SEARCH_REGEX) {\n invokeParams = {\n ...invokeParams,\n toolRegistry: this.toolRegistry,\n };\n }\n\n const output = await tool.invoke(invokeParams, config);\n if (\n (isBaseMessage(output) && output._getType() === 'tool') ||\n isCommand(output)\n ) {\n return output;\n } else {\n return new ToolMessage({\n status: 'success',\n name: tool.name,\n content: typeof output === 'string' ? output : JSON.stringify(output),\n tool_call_id: call.id!,\n });\n }\n } catch (_e: unknown) {\n const e = _e as Error;\n if (!this.handleToolErrors) {\n throw e;\n }\n if (isGraphInterrupt(e)) {\n throw e;\n }\n if (this.errorHandler) {\n try {\n await this.errorHandler(\n {\n error: e,\n id: call.id!,\n name: call.name,\n input: call.args,\n },\n config.metadata\n );\n } catch (handlerError) {\n // eslint-disable-next-line no-console\n console.error('Error in errorHandler:', {\n toolName: call.name,\n toolCallId: call.id,\n toolArgs: call.args,\n stepId: this.toolCallStepIds?.get(call.id!),\n turn: this.toolUsageCount.get(call.name),\n originalError: {\n message: e.message,\n stack: e.stack ?? undefined,\n },\n handlerError:\n handlerError instanceof Error\n ? {\n message: handlerError.message,\n stack: handlerError.stack ?? undefined,\n }\n : {\n message: String(handlerError),\n stack: undefined,\n },\n });\n }\n }\n return new ToolMessage({\n status: 'error',\n content: `Error: ${e.message}\\n Please fix your mistakes.`,\n name: call.name,\n tool_call_id: call.id ?? '',\n });\n }\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n protected async run(input: any, config: RunnableConfig): Promise<T> {\n let outputs: (BaseMessage | Command)[];\n\n if (this.isSendInput(input)) {\n outputs = [await this.runTool(input.lg_tool_call, config)];\n } else {\n let messages: BaseMessage[];\n if (Array.isArray(input)) {\n messages = input;\n } else if (this.isMessagesState(input)) {\n messages = input.messages;\n } else {\n throw new Error(\n 'ToolNode only accepts BaseMessage[] or { messages: BaseMessage[] } as input.'\n );\n }\n\n const toolMessageIds: Set<string> = new Set(\n messages\n .filter((msg) => msg._getType() === 'tool')\n .map((msg) => (msg as ToolMessage).tool_call_id)\n );\n\n let aiMessage: AIMessage | undefined;\n for (let i = messages.length - 1; i >= 0; i--) {\n const message = messages[i];\n if (isAIMessage(message)) {\n aiMessage = message;\n break;\n }\n }\n\n if (aiMessage == null || !isAIMessage(aiMessage)) {\n throw new Error('ToolNode only accepts AIMessages as input.');\n }\n\n if (this.loadRuntimeTools) {\n const { tools, toolMap } = this.loadRuntimeTools(\n aiMessage.tool_calls ?? []\n );\n this.tools = tools;\n this.toolMap =\n toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));\n }\n\n outputs = await Promise.all(\n aiMessage.tool_calls\n ?.filter((call) => {\n /**\n * Filter out:\n * 1. Already processed tool calls (present in toolMessageIds)\n * 2. Server tool calls (e.g., web_search with IDs starting with 'srvtoolu_')\n * which are executed by the provider's API and don't require invocation\n */\n return (\n (call.id == null || !toolMessageIds.has(call.id)) &&\n !(call.id?.startsWith('srvtoolu_') ?? false)\n );\n })\n .map((call) => this.runTool(call, config)) ?? []\n );\n }\n\n if (!outputs.some(isCommand)) {\n return (Array.isArray(input) ? outputs : { messages: outputs }) as T;\n }\n\n const combinedOutputs: (\n | { messages: BaseMessage[] }\n | BaseMessage[]\n | Command\n )[] = [];\n let parentCommand: Command | null = null;\n\n for (const output of outputs) {\n if (isCommand(output)) {\n if (\n output.graph === Command.PARENT &&\n Array.isArray(output.goto) &&\n output.goto.every((send): send is Send => isSend(send))\n ) {\n if (parentCommand) {\n (parentCommand.goto as Send[]).push(...(output.goto as Send[]));\n } else {\n parentCommand = new Command({\n graph: Command.PARENT,\n goto: output.goto,\n });\n }\n } else {\n combinedOutputs.push(output);\n }\n } else {\n combinedOutputs.push(\n Array.isArray(input) ? [output] : { messages: [output] }\n );\n }\n }\n\n if (parentCommand) {\n combinedOutputs.push(parentCommand);\n }\n\n return combinedOutputs as T;\n }\n\n private isSendInput(input: unknown): input is { lg_tool_call: ToolCall } {\n return (\n typeof input === 'object' && input != null && 'lg_tool_call' in input\n );\n }\n\n private isMessagesState(\n input: unknown\n ): input is { messages: BaseMessage[] } {\n return (\n typeof input === 'object' &&\n input != null &&\n 'messages' in input &&\n Array.isArray((input as { messages: unknown }).messages) &&\n (input as { messages: unknown[] }).messages.every(isBaseMessage)\n );\n }\n}\n\nfunction areToolCallsInvoked(\n message: AIMessage,\n invokedToolIds?: Set<string>\n): boolean {\n if (!invokedToolIds || invokedToolIds.size === 0) return false;\n return (\n message.tool_calls?.every(\n (toolCall) => toolCall.id != null && invokedToolIds.has(toolCall.id)\n ) ?? false\n );\n}\n\nexport function toolsCondition<T extends string>(\n state: BaseMessage[] | typeof MessagesAnnotation.State,\n toolNode: T,\n invokedToolIds?: Set<string>\n): T | typeof END {\n const message: AIMessage = Array.isArray(state)\n ? state[state.length - 1]\n : state.messages[state.messages.length - 1];\n\n if (\n 'tool_calls' in message &&\n (message.tool_calls?.length ?? 0) > 0 &&\n !areToolCallsInvoked(message, invokedToolIds)\n ) {\n return toolNode;\n } else {\n return END;\n }\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,448 @@
1
+ import { z } from 'zod';
2
+ import { config } from 'dotenv';
3
+ import fetch from 'node-fetch';
4
+ import { HttpsProxyAgent } from 'https-proxy-agent';
5
+ import { getEnvironmentVariable } from '@langchain/core/utils/env';
6
+ import { tool } from '@langchain/core/tools';
7
+ import { getCodeBaseURL } from './CodeExecutor.mjs';
8
+ import { EnvVar, Constants } from '../common/enum.mjs';
9
+
10
+ // src/tools/ToolSearchRegex.ts
11
+ config();
12
+ /** Maximum allowed regex pattern length */
13
+ const MAX_PATTERN_LENGTH = 200;
14
+ /** Maximum allowed regex nesting depth */
15
+ const MAX_REGEX_COMPLEXITY = 5;
16
+ /** Default search timeout in milliseconds */
17
+ const SEARCH_TIMEOUT = 5000;
18
+ const ToolSearchRegexSchema = z.object({
19
+ query: z
20
+ .string()
21
+ .min(1)
22
+ .max(MAX_PATTERN_LENGTH)
23
+ .describe('Regex pattern to search tool names and descriptions. Special regex characters will be sanitized for safety.'),
24
+ fields: z
25
+ .array(z.enum(['name', 'description', 'parameters']))
26
+ .optional()
27
+ .default(['name', 'description'])
28
+ .describe('Which fields to search. Default: name and description'),
29
+ max_results: z
30
+ .number()
31
+ .int()
32
+ .min(1)
33
+ .max(50)
34
+ .optional()
35
+ .default(10)
36
+ .describe('Maximum number of matching tools to return'),
37
+ });
38
+ /**
39
+ * Escapes special regex characters in a string to use as a literal pattern.
40
+ * @param pattern - The string to escape
41
+ * @returns The escaped string safe for use in a RegExp
42
+ */
43
+ function escapeRegexSpecialChars(pattern) {
44
+ return pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
45
+ }
46
+ /**
47
+ * Counts the maximum nesting depth of groups in a regex pattern.
48
+ * @param pattern - The regex pattern to analyze
49
+ * @returns The maximum nesting depth
50
+ */
51
+ function countNestedGroups(pattern) {
52
+ let maxDepth = 0;
53
+ let currentDepth = 0;
54
+ for (let i = 0; i < pattern.length; i++) {
55
+ if (pattern[i] === '(' && (i === 0 || pattern[i - 1] !== '\\')) {
56
+ currentDepth++;
57
+ maxDepth = Math.max(maxDepth, currentDepth);
58
+ }
59
+ else if (pattern[i] === ')' && (i === 0 || pattern[i - 1] !== '\\')) {
60
+ currentDepth = Math.max(0, currentDepth - 1);
61
+ }
62
+ }
63
+ return maxDepth;
64
+ }
65
+ /**
66
+ * Detects nested quantifiers that can cause catastrophic backtracking.
67
+ * Patterns like (a+)+, (a*)*, (a+)*, etc.
68
+ * @param pattern - The regex pattern to check
69
+ * @returns True if nested quantifiers are detected
70
+ */
71
+ function hasNestedQuantifiers(pattern) {
72
+ const nestedQuantifierPattern = /\([^)]*[+*][^)]*\)[+*?]/;
73
+ return nestedQuantifierPattern.test(pattern);
74
+ }
75
+ /**
76
+ * Checks if a regex pattern contains potentially dangerous constructs.
77
+ * @param pattern - The regex pattern to validate
78
+ * @returns True if the pattern is dangerous
79
+ */
80
+ function isDangerousPattern(pattern) {
81
+ if (hasNestedQuantifiers(pattern)) {
82
+ return true;
83
+ }
84
+ if (countNestedGroups(pattern) > MAX_REGEX_COMPLEXITY) {
85
+ return true;
86
+ }
87
+ const dangerousPatterns = [
88
+ /\.\{1000,\}/, // Excessive wildcards
89
+ /\(\?=\.\{100,\}\)/, // Runaway lookaheads
90
+ /\([^)]*\|\s*\){20,}/, // Excessive alternation (rough check)
91
+ /\(\.\*\)\+/, // (.*)+
92
+ /\(\.\+\)\+/, // (.+)+
93
+ /\(\.\*\)\*/, // (.*)*
94
+ /\(\.\+\)\*/, // (.+)*
95
+ ];
96
+ for (const dangerous of dangerousPatterns) {
97
+ if (dangerous.test(pattern)) {
98
+ return true;
99
+ }
100
+ }
101
+ return false;
102
+ }
103
+ /**
104
+ * Sanitizes a regex pattern for safe execution.
105
+ * If the pattern is dangerous, it will be escaped to a literal string search.
106
+ * @param pattern - The regex pattern to sanitize
107
+ * @returns Object containing the safe pattern and whether it was escaped
108
+ */
109
+ function sanitizeRegex(pattern) {
110
+ if (isDangerousPattern(pattern)) {
111
+ return {
112
+ safe: escapeRegexSpecialChars(pattern),
113
+ wasEscaped: true,
114
+ };
115
+ }
116
+ try {
117
+ new RegExp(pattern);
118
+ return { safe: pattern, wasEscaped: false };
119
+ }
120
+ catch {
121
+ return {
122
+ safe: escapeRegexSpecialChars(pattern),
123
+ wasEscaped: true,
124
+ };
125
+ }
126
+ }
127
+ /**
128
+ * Simplifies tool parameters for search purposes.
129
+ * Extracts only the essential structure needed for parameter name searching.
130
+ * @param parameters - The tool's JSON schema parameters
131
+ * @returns Simplified parameters object
132
+ */
133
+ function simplifyParametersForSearch(parameters) {
134
+ if (!parameters) {
135
+ return undefined;
136
+ }
137
+ if (parameters.properties) {
138
+ return {
139
+ type: parameters.type,
140
+ properties: Object.fromEntries(Object.entries(parameters.properties).map(([key, value]) => [
141
+ key,
142
+ { type: value.type },
143
+ ])),
144
+ };
145
+ }
146
+ return { type: parameters.type };
147
+ }
148
+ /**
149
+ * Generates the JavaScript search script to be executed in the sandbox.
150
+ * Uses plain JavaScript for maximum compatibility with the Code API.
151
+ * @param deferredTools - Array of tool metadata to search through
152
+ * @param fields - Which fields to search
153
+ * @param maxResults - Maximum number of results to return
154
+ * @param sanitizedPattern - The sanitized regex pattern
155
+ * @returns The JavaScript code string
156
+ */
157
+ function generateSearchScript(deferredTools, fields, maxResults, sanitizedPattern) {
158
+ const lines = [
159
+ '// Tool definitions (injected)',
160
+ 'var tools = ' + JSON.stringify(deferredTools) + ';',
161
+ 'var searchFields = ' + JSON.stringify(fields) + ';',
162
+ 'var maxResults = ' + maxResults + ';',
163
+ 'var pattern = ' + JSON.stringify(sanitizedPattern) + ';',
164
+ '',
165
+ '// Compile regex (pattern is sanitized client-side)',
166
+ 'var regex;',
167
+ 'try {',
168
+ ' regex = new RegExp(pattern, \'i\');',
169
+ '} catch (e) {',
170
+ ' regex = new RegExp(pattern.replace(/[.*+?^${}()[\\]\\\\|]/g, "\\\\$&"), "i");',
171
+ '}',
172
+ '',
173
+ '// Search logic',
174
+ 'var results = [];',
175
+ '',
176
+ 'for (var j = 0; j < tools.length; j++) {',
177
+ ' var tool = tools[j];',
178
+ ' var bestScore = 0;',
179
+ ' var matchedField = \'\';',
180
+ ' var snippet = \'\';',
181
+ '',
182
+ ' // Search name (highest priority)',
183
+ ' if (searchFields.indexOf(\'name\') >= 0 && regex.test(tool.name)) {',
184
+ ' bestScore = 0.95;',
185
+ ' matchedField = \'name\';',
186
+ ' snippet = tool.name;',
187
+ ' }',
188
+ '',
189
+ ' // Search description (medium priority)',
190
+ ' if (searchFields.indexOf(\'description\') >= 0 && tool.description && regex.test(tool.description)) {',
191
+ ' if (bestScore === 0) {',
192
+ ' bestScore = 0.75;',
193
+ ' matchedField = \'description\';',
194
+ ' snippet = tool.description.substring(0, 100);',
195
+ ' }',
196
+ ' }',
197
+ '',
198
+ ' // Search parameter names (lower priority)',
199
+ ' if (searchFields.indexOf(\'parameters\') >= 0 && tool.parameters && tool.parameters.properties) {',
200
+ ' var paramNames = Object.keys(tool.parameters.properties).join(\' \');',
201
+ ' if (regex.test(paramNames)) {',
202
+ ' if (bestScore === 0) {',
203
+ ' bestScore = 0.60;',
204
+ ' matchedField = \'parameters\';',
205
+ ' snippet = paramNames;',
206
+ ' }',
207
+ ' }',
208
+ ' }',
209
+ '',
210
+ ' if (bestScore > 0) {',
211
+ ' results.push({',
212
+ ' tool_name: tool.name,',
213
+ ' match_score: bestScore,',
214
+ ' matched_field: matchedField,',
215
+ ' snippet: snippet',
216
+ ' });',
217
+ ' }',
218
+ '}',
219
+ '',
220
+ '// Sort by score (descending) and limit results',
221
+ 'results.sort(function(a, b) { return b.match_score - a.match_score; });',
222
+ 'var topResults = results.slice(0, maxResults);',
223
+ '',
224
+ '// Output as JSON',
225
+ 'console.log(JSON.stringify({',
226
+ ' tool_references: topResults.map(function(r) {',
227
+ ' return {',
228
+ ' tool_name: r.tool_name,',
229
+ ' match_score: r.match_score,',
230
+ ' matched_field: r.matched_field,',
231
+ ' snippet: r.snippet',
232
+ ' };',
233
+ ' }),',
234
+ ' total_tools_searched: tools.length,',
235
+ ' pattern_used: pattern',
236
+ '}));',
237
+ ];
238
+ return lines.join('\n');
239
+ }
240
+ /**
241
+ * Parses the search results from stdout JSON.
242
+ * @param stdout - The stdout string containing JSON results
243
+ * @returns Parsed search response
244
+ */
245
+ function parseSearchResults(stdout) {
246
+ const jsonMatch = stdout.trim();
247
+ const parsed = JSON.parse(jsonMatch);
248
+ return parsed;
249
+ }
250
+ /**
251
+ * Formats search results into a human-readable string.
252
+ * @param searchResponse - The parsed search response
253
+ * @returns Formatted string for LLM consumption
254
+ */
255
+ function formatSearchResults(searchResponse) {
256
+ const { tool_references, total_tools_searched, pattern_used } = searchResponse;
257
+ if (tool_references.length === 0) {
258
+ return `No tools matched the pattern "${pattern_used}".\nTotal tools searched: ${total_tools_searched}`;
259
+ }
260
+ let response = `Found ${tool_references.length} matching tools:\n\n`;
261
+ for (const ref of tool_references) {
262
+ response += `- ${ref.tool_name} (score: ${ref.match_score.toFixed(2)})\n`;
263
+ response += ` Matched in: ${ref.matched_field}\n`;
264
+ response += ` Snippet: ${ref.snippet}\n\n`;
265
+ }
266
+ response += `Total tools searched: ${total_tools_searched}\n`;
267
+ response += `Pattern used: ${pattern_used}`;
268
+ return response;
269
+ }
270
+ /**
271
+ * Creates a Tool Search Regex tool for discovering tools from a large registry.
272
+ *
273
+ * This tool enables AI agents to dynamically discover tools from a large library
274
+ * without loading all tool definitions into the LLM context window. The agent
275
+ * can search for relevant tools on-demand using regex patterns.
276
+ *
277
+ * The tool registry can be provided either:
278
+ * 1. At initialization time via params.toolRegistry
279
+ * 2. At runtime via config.configurable.toolRegistry when invoking
280
+ *
281
+ * @param params - Configuration parameters for the tool (toolRegistry is optional)
282
+ * @returns A LangChain DynamicStructuredTool for tool searching
283
+ *
284
+ * @example
285
+ * // Option 1: Registry at initialization
286
+ * const tool = createToolSearchRegexTool({ apiKey, toolRegistry });
287
+ * await tool.invoke({ query: 'expense' });
288
+ *
289
+ * @example
290
+ * // Option 2: Registry at runtime
291
+ * const tool = createToolSearchRegexTool({ apiKey });
292
+ * await tool.invoke(
293
+ * { query: 'expense' },
294
+ * { configurable: { toolRegistry, onlyDeferred: true } }
295
+ * );
296
+ */
297
+ function createToolSearchRegexTool(initParams = {}) {
298
+ const apiKey = initParams[EnvVar.CODE_API_KEY] ??
299
+ initParams.apiKey ??
300
+ getEnvironmentVariable(EnvVar.CODE_API_KEY) ??
301
+ '';
302
+ if (!apiKey) {
303
+ throw new Error('No API key provided for tool search regex tool.');
304
+ }
305
+ const baseEndpoint = initParams.baseUrl ?? getCodeBaseURL();
306
+ const EXEC_ENDPOINT = `${baseEndpoint}/exec`;
307
+ const defaultOnlyDeferred = initParams.onlyDeferred ?? true;
308
+ const description = `
309
+ Searches through available tools to find ones matching your query pattern.
310
+
311
+ Usage:
312
+ - Provide a regex pattern to search tool names and descriptions.
313
+ - Use this when you need to discover tools for a specific task.
314
+ - Results include tool names, match quality scores, and snippets showing where the match occurred.
315
+ - Higher scores (0.9+) indicate name matches, medium scores (0.7+) indicate description matches.
316
+ `.trim();
317
+ return tool(async (params, config) => {
318
+ const { query, fields = ['name', 'description'], max_results = 10, } = params;
319
+ // Extra params injected by ToolNode (follows web_search pattern)
320
+ const { toolRegistry: paramToolRegistry, onlyDeferred: paramOnlyDeferred, } = config.toolCall ?? {};
321
+ const { safe: sanitizedPattern, wasEscaped } = sanitizeRegex(query);
322
+ let warningMessage = '';
323
+ if (wasEscaped) {
324
+ warningMessage =
325
+ 'Note: The provided pattern was converted to a literal search for safety.\n\n';
326
+ }
327
+ // Priority: ToolNode injection (via config.toolCall) > initialization params
328
+ const toolRegistry = paramToolRegistry ?? initParams.toolRegistry;
329
+ const onlyDeferred = paramOnlyDeferred !== undefined
330
+ ? paramOnlyDeferred
331
+ : defaultOnlyDeferred;
332
+ if (toolRegistry == null) {
333
+ return [
334
+ `${warningMessage}Error: No tool registry provided. Configure toolRegistry at agent level or initialization.`,
335
+ {
336
+ tool_references: [],
337
+ metadata: {
338
+ total_searched: 0,
339
+ pattern: sanitizedPattern,
340
+ error: 'No tool registry provided',
341
+ },
342
+ },
343
+ ];
344
+ }
345
+ const toolsArray = Array.from(toolRegistry.values());
346
+ const deferredTools = toolsArray
347
+ .filter((lcTool) => {
348
+ if (onlyDeferred === true) {
349
+ return lcTool.defer_loading === true;
350
+ }
351
+ return true;
352
+ })
353
+ .map((lcTool) => ({
354
+ name: lcTool.name,
355
+ description: lcTool.description ?? '',
356
+ parameters: simplifyParametersForSearch(lcTool.parameters),
357
+ }));
358
+ if (deferredTools.length === 0) {
359
+ return [
360
+ `${warningMessage}No tools available to search. The tool registry is empty or no deferred tools are registered.`,
361
+ {
362
+ tool_references: [],
363
+ metadata: {
364
+ total_searched: 0,
365
+ pattern: sanitizedPattern,
366
+ },
367
+ },
368
+ ];
369
+ }
370
+ const searchScript = generateSearchScript(deferredTools, fields, max_results, sanitizedPattern);
371
+ const postData = {
372
+ lang: 'js',
373
+ code: searchScript,
374
+ timeout: SEARCH_TIMEOUT,
375
+ };
376
+ try {
377
+ const fetchOptions = {
378
+ method: 'POST',
379
+ headers: {
380
+ 'Content-Type': 'application/json',
381
+ 'User-Agent': 'LibreChat/1.0',
382
+ 'X-API-Key': apiKey,
383
+ },
384
+ body: JSON.stringify(postData),
385
+ };
386
+ if (process.env.PROXY != null && process.env.PROXY !== '') {
387
+ fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);
388
+ }
389
+ const response = await fetch(EXEC_ENDPOINT, fetchOptions);
390
+ if (!response.ok) {
391
+ throw new Error(`HTTP error! status: ${response.status}`);
392
+ }
393
+ const result = await response.json();
394
+ if (result.stderr && result.stderr.trim()) {
395
+ // eslint-disable-next-line no-console
396
+ console.warn('[ToolSearchRegex] stderr:', result.stderr);
397
+ }
398
+ if (!result.stdout || !result.stdout.trim()) {
399
+ return [
400
+ `${warningMessage}No tools matched the pattern "${sanitizedPattern}".\nTotal tools searched: ${deferredTools.length}`,
401
+ {
402
+ tool_references: [],
403
+ metadata: {
404
+ total_searched: deferredTools.length,
405
+ pattern: sanitizedPattern,
406
+ },
407
+ },
408
+ ];
409
+ }
410
+ const searchResponse = parseSearchResults(result.stdout);
411
+ const formattedOutput = `${warningMessage}${formatSearchResults(searchResponse)}`;
412
+ return [
413
+ formattedOutput,
414
+ {
415
+ tool_references: searchResponse.tool_references,
416
+ metadata: {
417
+ total_searched: searchResponse.total_tools_searched,
418
+ pattern: searchResponse.pattern_used,
419
+ },
420
+ },
421
+ ];
422
+ }
423
+ catch (error) {
424
+ // eslint-disable-next-line no-console
425
+ console.error('[ToolSearchRegex] Error:', error);
426
+ const errorMessage = error instanceof Error ? error.message : String(error);
427
+ return [
428
+ `Tool search failed: ${errorMessage}\n\nSuggestion: Try a simpler search pattern or search for specific tool names.`,
429
+ {
430
+ tool_references: [],
431
+ metadata: {
432
+ total_searched: 0,
433
+ pattern: sanitizedPattern,
434
+ error: errorMessage,
435
+ },
436
+ },
437
+ ];
438
+ }
439
+ }, {
440
+ name: Constants.TOOL_SEARCH_REGEX,
441
+ description,
442
+ schema: ToolSearchRegexSchema,
443
+ responseFormat: Constants.CONTENT_AND_ARTIFACT,
444
+ });
445
+ }
446
+
447
+ export { countNestedGroups, createToolSearchRegexTool, escapeRegexSpecialChars, hasNestedQuantifiers, isDangerousPattern, sanitizeRegex };
448
+ //# sourceMappingURL=ToolSearchRegex.mjs.map