@librechat/agents 3.2.65 → 3.2.67

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 (78) hide show
  1. package/dist/cjs/graphs/Graph.cjs +15 -2
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/hooks/createToolPolicyHook.cjs +5 -5
  4. package/dist/cjs/hooks/createToolPolicyHook.cjs.map +1 -1
  5. package/dist/cjs/instrumentation.cjs +15 -3
  6. package/dist/cjs/instrumentation.cjs.map +1 -1
  7. package/dist/cjs/langfuseToolOutputTracing.cjs +1 -2
  8. package/dist/cjs/langfuseToolOutputTracing.cjs.map +1 -1
  9. package/dist/cjs/langfuseTraceShaping.cjs +51 -24
  10. package/dist/cjs/langfuseTraceShaping.cjs.map +1 -1
  11. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +8 -0
  12. package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
  13. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs +8 -0
  14. package/dist/cjs/llm/bedrock/utils/message_inputs.cjs.map +1 -1
  15. package/dist/cjs/tools/BashExecutor.cjs +9 -8
  16. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  17. package/dist/cjs/tools/CodeExecutor.cjs +9 -7
  18. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolSearch.cjs +59 -14
  20. package/dist/cjs/tools/ToolSearch.cjs.map +1 -1
  21. package/dist/cjs/tools/search/tool.cjs +5 -3
  22. package/dist/cjs/tools/search/tool.cjs.map +1 -1
  23. package/dist/esm/graphs/Graph.mjs +15 -2
  24. package/dist/esm/graphs/Graph.mjs.map +1 -1
  25. package/dist/esm/hooks/createToolPolicyHook.mjs +5 -5
  26. package/dist/esm/hooks/createToolPolicyHook.mjs.map +1 -1
  27. package/dist/esm/instrumentation.mjs +15 -3
  28. package/dist/esm/instrumentation.mjs.map +1 -1
  29. package/dist/esm/langfuseToolOutputTracing.mjs +1 -2
  30. package/dist/esm/langfuseToolOutputTracing.mjs.map +1 -1
  31. package/dist/esm/langfuseTraceShaping.mjs +51 -24
  32. package/dist/esm/langfuseTraceShaping.mjs.map +1 -1
  33. package/dist/esm/llm/anthropic/utils/message_inputs.mjs +8 -0
  34. package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
  35. package/dist/esm/llm/bedrock/utils/message_inputs.mjs +8 -0
  36. package/dist/esm/llm/bedrock/utils/message_inputs.mjs.map +1 -1
  37. package/dist/esm/tools/BashExecutor.mjs +9 -8
  38. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  39. package/dist/esm/tools/CodeExecutor.mjs +9 -7
  40. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  41. package/dist/esm/tools/ToolSearch.mjs +59 -14
  42. package/dist/esm/tools/ToolSearch.mjs.map +1 -1
  43. package/dist/esm/tools/search/tool.mjs +5 -3
  44. package/dist/esm/tools/search/tool.mjs.map +1 -1
  45. package/dist/types/graphs/Graph.d.ts +2 -0
  46. package/dist/types/hooks/createToolPolicyHook.d.ts +12 -15
  47. package/dist/types/langfuseTraceShaping.d.ts +12 -6
  48. package/dist/types/tools/BashExecutor.d.ts +7 -6
  49. package/dist/types/tools/CodeExecutor.d.ts +7 -5
  50. package/dist/types/tools/search/types.d.ts +2 -0
  51. package/dist/types/types/graph.d.ts +10 -3
  52. package/package.json +1 -1
  53. package/src/graphs/Graph.ts +21 -3
  54. package/src/hooks/__tests__/createToolPolicyHook.test.ts +13 -4
  55. package/src/hooks/createToolPolicyHook.ts +16 -19
  56. package/src/instrumentation.ts +20 -0
  57. package/src/langfuseToolOutputTracing.ts +2 -4
  58. package/src/langfuseTraceShaping.ts +73 -20
  59. package/src/llm/anthropic/utils/cross-provider-server-tools.test.ts +110 -0
  60. package/src/llm/anthropic/utils/message_inputs.ts +15 -0
  61. package/src/llm/bedrock/utils/cross-provider-server-tools.test.ts +122 -0
  62. package/src/llm/bedrock/utils/message_inputs.ts +13 -0
  63. package/src/specs/langfuse-instrumentation.test.ts +64 -0
  64. package/src/specs/langfuse-routing.integration.test.ts +49 -4
  65. package/src/specs/langfuse-tool-output-tracing.test.ts +5 -4
  66. package/src/specs/langfuse-trace-shaping.test.ts +80 -9
  67. package/src/specs/subagent.test.ts +180 -0
  68. package/src/tools/BashExecutor.ts +9 -8
  69. package/src/tools/CodeExecutor.ts +9 -7
  70. package/src/tools/ToolSearch.ts +81 -23
  71. package/src/tools/__tests__/BashExecutor.test.ts +16 -5
  72. package/src/tools/__tests__/CodeExecutor.stateful.test.ts +17 -6
  73. package/src/tools/__tests__/ToolSearch.test.ts +151 -0
  74. package/src/tools/__tests__/hitl.test.ts +114 -1
  75. package/src/tools/search/source-processing.test.ts +57 -0
  76. package/src/tools/search/tool.ts +13 -1
  77. package/src/tools/search/types.ts +2 -0
  78. package/src/types/graph.ts +10 -3
@@ -1 +1 @@
1
- {"version":3,"file":"BashExecutor.cjs","names":["getCodeBaseURL","CODE_ARTIFACT_PATH_GUIDANCE","BASH_SHELL_GUIDANCE","resolveCodeApiAuthHeaders","HttpsProxyAgent","buildCodeApiHttpErrorMessage","emptyOutputMessage","appendTmpScratchReminder","appendCodeSessionFileSummary","appendFailedExecutionFileReminder"],"sources":["../../../src/tools/BashExecutor.ts"],"sourcesContent":["import { config } from 'dotenv';\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport {\n BASH_SHELL_GUIDANCE,\n CODE_ARTIFACT_PATH_GUIDANCE,\n appendFailedExecutionFileReminder,\n appendTmpScratchReminder,\n appendCodeSessionFileSummary,\n emptyOutputMessage,\n buildCodeApiHttpErrorMessage,\n getCodeBaseURL,\n resolveCodeApiAuthHeaders,\n} from './CodeExecutor';\nimport { Constants } from '@/common';\n\nconfig();\n\nconst baseEndpoint = getCodeBaseURL();\nconst EXEC_ENDPOINT = `${baseEndpoint}/exec`;\n\nexport const BashExecutionToolSchema = {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: `The bash command or script to execute.\n- The environment is stateless; variables and state don't persist between executions.\n- Prior /mnt/data files are available and can be modified in place.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- ${BASH_SHELL_GUIDANCE}\n- Input code **IS ALREADY** displayed to the user, so **DO NOT** repeat it in your response unless asked.\n- Output code **IS NOT** displayed to the user, so **DO** write all desired output explicitly.\n- IMPORTANT: You MUST explicitly print/output ALL results you want the user to see.\n- Use \\`echo\\`, \\`printf\\`, or \\`cat\\` for all outputs.`,\n },\n args: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Additional arguments to execute the command with. This should only be used if the input command requires additional arguments to run.',\n },\n },\n required: ['command'],\n} as const;\n\nexport const BashExecutionToolDescription = `\nRuns bash commands and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- ${BASH_SHELL_GUIDANCE}\n- NEVER use this tool to execute malicious commands.\n`.trim();\n\n/**\n * Bash statefulness is filesystem-tier: on a warm session the machine (files\n * including /tmp, installed packages, background processes) persists between\n * calls, but each call may start a fresh shell — so shell variables and cwd\n * are NOT reliable, and the machine can be reset at any time. Only /mnt/data\n * is durable.\n */\nexport const STATEFUL_BASH_NOTE =\n 'Session state (best-effort): commands in this conversation usually run on the same machine, so files (including /tmp), installed packages, and running background processes from earlier calls typically persist. Each call may still start a fresh shell — do not rely on shell variables or the working directory carrying over — and the machine may be reset at any time. Only /mnt/data is durable.';\n\nexport const StatefulBashExecutionToolDescription = `\nRuns bash commands and returns stdout/stderr output from a session-based execution environment, similar to a long-running machine.\n\n${STATEFUL_BASH_NOTE}\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- ${BASH_SHELL_GUIDANCE}\n- NEVER use this tool to execute malicious commands.\n`.trim();\n\n/**\n * Supplemental prompt documenting the tool-output reference feature.\n *\n * Hosts should append this (separated by a blank line) to the base\n * {@link BashExecutionToolDescription} only when\n * `RunConfig.toolOutputReferences.enabled` is `true`. When the feature\n * is disabled, including this text would tell the LLM to emit\n * `{{tool0turn0}}` placeholders that pass through unsubstituted and\n * leak into the shell.\n */\nexport const BashToolOutputReferencesGuide = `\nReferencing previous tool outputs:\n- Every successful tool result is tagged with a reference key of the form \\`tool<idx>turn<turn>\\` (e.g., \\`tool0turn0\\`). The key appears either as a \\`[ref: tool0turn0]\\` prefix line or, when the output is a JSON object, as a \\`_ref\\` field on the object.\n- To pipe a previous tool output into this tool, embed the placeholder \\`{{tool<idx>turn<turn>}}\\` literally anywhere in the \\`command\\` string (or any string arg). It will be substituted with the stored output verbatim before the command runs.\n- The substituted value is the original output string (no \\`[ref: …]\\` prefix, no \\`_ref\\` key), so it is safe to pipe directly into \\`jq\\`, \\`grep\\`, \\`awk\\`, etc.\n- Example (simple ASCII output): \\`echo '{{tool0turn0}}' | jq '.foo'\\` takes the full output of the first tool from the first turn and pipes it into jq.\n- For payloads that may contain quotes, parentheses, backticks, or arbitrary bytes (random/binary data, JSON with embedded quotes, multi-line strings), prefer a quoted-delimiter heredoc over \\`echo '…'\\`. The heredoc body is not interpreted by the shell, so substituted payloads pass through unchanged.\n- Heredoc example: \\`wc -c << 'EOF'\\\\n{{tool0turn0}}\\\\nEOF\\` (the quotes around \\`'EOF'\\` disable interpolation inside the body).\n- Unknown reference keys are left in place and surfaced as \\`[unresolved refs: …]\\` after the output.\n`.trim();\n\n/**\n * Composes the bash tool description, optionally appending the\n * tool-output references guide. Hosts that enable\n * `RunConfig.toolOutputReferences` should pass `enableToolOutputReferences: true`\n * when registering the tool so the LLM learns the `{{…}}` syntax it\n * will actually be able to use.\n */\nexport function buildBashExecutionToolDescription(options?: {\n enableToolOutputReferences?: boolean;\n statefulSessions?: boolean;\n}): string {\n const base =\n options?.statefulSessions === true\n ? StatefulBashExecutionToolDescription\n : BashExecutionToolDescription;\n if (options?.enableToolOutputReferences === true) {\n return `${base}\\n\\n${BashToolOutputReferencesGuide}`;\n }\n return base;\n}\n\nconst STATELESS_BASH_PARAM_NOTE =\n 'The environment is stateless; variables and state don\\'t persist between executions.';\nconst STATEFUL_BASH_PARAM_NOTE =\n 'Files, installed packages, and background processes usually persist between calls, but each call may start a fresh shell (do not rely on shell variables or cwd) and the machine may reset. Only /mnt/data is durable.';\n\nexport function buildBashExecutionToolSchema(opts?: {\n statefulSessions?: boolean;\n}): typeof BashExecutionToolSchema {\n const note =\n opts?.statefulSessions === true\n ? STATEFUL_BASH_PARAM_NOTE\n : STATELESS_BASH_PARAM_NOTE;\n const commandDescription =\n BashExecutionToolSchema.properties.command.description.replace(\n STATELESS_BASH_PARAM_NOTE,\n note\n );\n return {\n ...BashExecutionToolSchema,\n properties: {\n ...BashExecutionToolSchema.properties,\n command: {\n ...BashExecutionToolSchema.properties.command,\n description: commandDescription,\n },\n },\n } as typeof BashExecutionToolSchema;\n}\n\nexport const BashExecutionToolName = Constants.BASH_TOOL;\n\n/**\n * Default bash tool definition using the base description.\n *\n * When `RunConfig.toolOutputReferences.enabled` is `true`, build a\n * reference-aware description with\n * {@link buildBashExecutionToolDescription}\n * (`{ enableToolOutputReferences: true }`) and construct a custom\n * definition using it — using this constant as-is leaves the LLM\n * unaware of the `{{tool<i>turn<n>}}` syntax.\n */\nexport const BashExecutionToolDefinition = {\n name: BashExecutionToolName,\n description: BashExecutionToolDescription,\n schema: BashExecutionToolSchema,\n} as const;\n\nfunction createBashExecutionTool(\n params: t.BashExecutionToolParams | null = {}\n): DynamicStructuredTool {\n return tool(\n async (rawInput, config) => {\n /* `statefulSessions` is prompt-only — keep it out of the wire body. */\n const {\n authHeaders,\n statefulSessions: _statefulSessions,\n ...executionParams\n } = params ?? {};\n void _statefulSessions;\n /* Drop any model-supplied `runtime_session_hint` from the raw args: the\n * hint must only come from ToolNode's injected `_runtime_session_hint`\n * (below), never from the tool call itself. */\n const {\n command,\n runtime_session_hint: _ignoredModelHint,\n ...rest\n } = rawInput as {\n command: string;\n runtime_session_hint?: unknown;\n args?: string[];\n };\n void _ignoredModelHint;\n const { session_id, _injected_files, _runtime_session_hint } =\n (config.toolCall ?? {}) as {\n session_id?: string;\n _injected_files?: t.CodeEnvFile[];\n _runtime_session_hint?: string;\n };\n\n const postData: Record<string, unknown> = {\n lang: 'bash',\n code: command,\n ...rest,\n ...executionParams,\n };\n\n if (\n typeof _runtime_session_hint === 'string' &&\n _runtime_session_hint !== ''\n ) {\n postData.runtime_session_hint = _runtime_session_hint;\n }\n\n /* See `CodeExecutor.ts` for the rationale — `/files/<session_id>`\n * HTTP fallback was removed because codeapi's sessionAuth requires\n * kind/id query params unavailable at this point. */\n if (_injected_files && _injected_files.length > 0) {\n postData.files = _injected_files;\n } else if (\n session_id != null &&\n session_id.length > 0 &&\n !Array.isArray(postData.files)\n ) {\n // eslint-disable-next-line no-console\n console.debug(\n `[BashExecutor] No injected files for session_id=${session_id} — exec will run without input files`\n );\n }\n\n try {\n const resolvedAuthHeaders =\n await resolveCodeApiAuthHeaders(authHeaders);\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'LibreChat/1.0',\n ...resolvedAuthHeaders,\n },\n body: JSON.stringify(postData),\n };\n\n if (process.env.PROXY != null && process.env.PROXY !== '') {\n fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);\n }\n const response = await fetch(EXEC_ENDPOINT, fetchOptions);\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)\n );\n }\n\n const result: t.ExecuteResult = await response.json();\n let formattedOutput = '';\n if (result.stdout) {\n formattedOutput += `stdout:\\n${result.stdout}\\n`;\n } else {\n formattedOutput += emptyOutputMessage;\n }\n if (result.stderr) formattedOutput += `stderr:\\n${result.stderr}\\n`;\n\n const outputWithReminder = appendTmpScratchReminder(\n formattedOutput,\n command\n );\n const hasFiles = result.files != null && result.files.length > 0;\n const runtimeEcho =\n result.runtime_session_id != null\n ? {\n runtime_session_id: result.runtime_session_id,\n runtime_status: result.runtime_status,\n }\n : {};\n return [\n appendCodeSessionFileSummary(outputWithReminder, result.files),\n (hasFiles\n ? {\n session_id: result.session_id,\n files: result.files,\n ...runtimeEcho,\n }\n : {\n session_id: result.session_id,\n ...runtimeEcho,\n }) satisfies t.CodeExecutionArtifact,\n ];\n } catch (error) {\n const messageWithReminder = appendFailedExecutionFileReminder(\n (error as Error | undefined)?.message ?? '',\n command\n );\n throw new Error(`Execution error:\\n\\n${messageWithReminder}`);\n }\n },\n {\n name: BashExecutionToolName,\n description: buildBashExecutionToolDescription({\n statefulSessions: params?.statefulSessions,\n }),\n schema: buildBashExecutionToolSchema(params ?? undefined),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport { createBashExecutionTool };\n"],"mappings":"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"}
1
+ {"version":3,"file":"BashExecutor.cjs","names":["getCodeBaseURL","CODE_ARTIFACT_PATH_GUIDANCE","BASH_SHELL_GUIDANCE","resolveCodeApiAuthHeaders","HttpsProxyAgent","buildCodeApiHttpErrorMessage","emptyOutputMessage","appendTmpScratchReminder","appendCodeSessionFileSummary","appendFailedExecutionFileReminder"],"sources":["../../../src/tools/BashExecutor.ts"],"sourcesContent":["import { config } from 'dotenv';\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport {\n BASH_SHELL_GUIDANCE,\n CODE_ARTIFACT_PATH_GUIDANCE,\n appendFailedExecutionFileReminder,\n appendTmpScratchReminder,\n appendCodeSessionFileSummary,\n emptyOutputMessage,\n buildCodeApiHttpErrorMessage,\n getCodeBaseURL,\n resolveCodeApiAuthHeaders,\n} from './CodeExecutor';\nimport { Constants } from '@/common';\n\nconfig();\n\nconst baseEndpoint = getCodeBaseURL();\nconst EXEC_ENDPOINT = `${baseEndpoint}/exec`;\n\nexport const BashExecutionToolSchema = {\n type: 'object',\n properties: {\n command: {\n type: 'string',\n description: `The bash command or script to execute.\n- The environment is stateless; variables and state don't persist between executions.\n- Prior /mnt/data files are available and can be modified in place.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- ${BASH_SHELL_GUIDANCE}\n- Input code **IS ALREADY** displayed to the user, so **DO NOT** repeat it in your response unless asked.\n- Output code **IS NOT** displayed to the user, so **DO** write all desired output explicitly.\n- IMPORTANT: You MUST explicitly print/output ALL results you want the user to see.\n- Use \\`echo\\`, \\`printf\\`, or \\`cat\\` for all outputs.`,\n },\n args: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Additional arguments to execute the command with. This should only be used if the input command requires additional arguments to run.',\n },\n },\n required: ['command'],\n} as const;\n\nexport const BashExecutionToolDescription = `\nRuns bash commands and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- ${BASH_SHELL_GUIDANCE}\n- NEVER use this tool to execute malicious commands.\n`.trim();\n\n/**\n * Bash statefulness is filesystem-tier and scoped to `/mnt/data`. The machine\n * is warm across calls, but each call runs in a fresh sandbox (new process\n * tree + private /tmp), so background processes are reaped when the call ends\n * and anything written outside /mnt/data is discarded. The note must not\n * promise otherwise: a model told background processes survive will start a\n * server in one call and assume it is listening in the next.\n */\nexport const STATEFUL_BASH_NOTE =\n 'Session state: commands in this conversation run on the same warm machine, so files written to /mnt/data persist between calls. Each call runs in a fresh, isolated sandbox: shell variables, the working directory, /tmp, and background processes do NOT survive after the call returns — a process started in one call is terminated when that call ends. Only /mnt/data is durable (the machine itself may also be reset at any time).';\n\nexport const StatefulBashExecutionToolDescription = `\nRuns bash commands and returns stdout/stderr output. Commands in this conversation share one warm machine with a persistent /mnt/data, but each command runs in its own isolated sandbox (not a persistent shell session).\n\n${STATEFUL_BASH_NOTE}\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- ${BASH_SHELL_GUIDANCE}\n- NEVER use this tool to execute malicious commands.\n`.trim();\n\n/**\n * Supplemental prompt documenting the tool-output reference feature.\n *\n * Hosts should append this (separated by a blank line) to the base\n * {@link BashExecutionToolDescription} only when\n * `RunConfig.toolOutputReferences.enabled` is `true`. When the feature\n * is disabled, including this text would tell the LLM to emit\n * `{{tool0turn0}}` placeholders that pass through unsubstituted and\n * leak into the shell.\n */\nexport const BashToolOutputReferencesGuide = `\nReferencing previous tool outputs:\n- Every successful tool result is tagged with a reference key of the form \\`tool<idx>turn<turn>\\` (e.g., \\`tool0turn0\\`). The key appears either as a \\`[ref: tool0turn0]\\` prefix line or, when the output is a JSON object, as a \\`_ref\\` field on the object.\n- To pipe a previous tool output into this tool, embed the placeholder \\`{{tool<idx>turn<turn>}}\\` literally anywhere in the \\`command\\` string (or any string arg). It will be substituted with the stored output verbatim before the command runs.\n- The substituted value is the original output string (no \\`[ref: …]\\` prefix, no \\`_ref\\` key), so it is safe to pipe directly into \\`jq\\`, \\`grep\\`, \\`awk\\`, etc.\n- Example (simple ASCII output): \\`echo '{{tool0turn0}}' | jq '.foo'\\` takes the full output of the first tool from the first turn and pipes it into jq.\n- For payloads that may contain quotes, parentheses, backticks, or arbitrary bytes (random/binary data, JSON with embedded quotes, multi-line strings), prefer a quoted-delimiter heredoc over \\`echo '…'\\`. The heredoc body is not interpreted by the shell, so substituted payloads pass through unchanged.\n- Heredoc example: \\`wc -c << 'EOF'\\\\n{{tool0turn0}}\\\\nEOF\\` (the quotes around \\`'EOF'\\` disable interpolation inside the body).\n- Unknown reference keys are left in place and surfaced as \\`[unresolved refs: …]\\` after the output.\n`.trim();\n\n/**\n * Composes the bash tool description, optionally appending the\n * tool-output references guide. Hosts that enable\n * `RunConfig.toolOutputReferences` should pass `enableToolOutputReferences: true`\n * when registering the tool so the LLM learns the `{{…}}` syntax it\n * will actually be able to use.\n */\nexport function buildBashExecutionToolDescription(options?: {\n enableToolOutputReferences?: boolean;\n statefulSessions?: boolean;\n}): string {\n const base =\n options?.statefulSessions === true\n ? StatefulBashExecutionToolDescription\n : BashExecutionToolDescription;\n if (options?.enableToolOutputReferences === true) {\n return `${base}\\n\\n${BashToolOutputReferencesGuide}`;\n }\n return base;\n}\n\nconst STATELESS_BASH_PARAM_NOTE =\n 'The environment is stateless; variables and state don\\'t persist between executions.';\nconst STATEFUL_BASH_PARAM_NOTE =\n 'Files written to /mnt/data persist between calls on the same warm machine. Each call runs in a fresh sandbox: shell variables, cwd, /tmp, and background processes do NOT survive the call. Only /mnt/data is durable.';\n\nexport function buildBashExecutionToolSchema(opts?: {\n statefulSessions?: boolean;\n}): typeof BashExecutionToolSchema {\n const note =\n opts?.statefulSessions === true\n ? STATEFUL_BASH_PARAM_NOTE\n : STATELESS_BASH_PARAM_NOTE;\n const commandDescription =\n BashExecutionToolSchema.properties.command.description.replace(\n STATELESS_BASH_PARAM_NOTE,\n note\n );\n return {\n ...BashExecutionToolSchema,\n properties: {\n ...BashExecutionToolSchema.properties,\n command: {\n ...BashExecutionToolSchema.properties.command,\n description: commandDescription,\n },\n },\n } as typeof BashExecutionToolSchema;\n}\n\nexport const BashExecutionToolName = Constants.BASH_TOOL;\n\n/**\n * Default bash tool definition using the base description.\n *\n * When `RunConfig.toolOutputReferences.enabled` is `true`, build a\n * reference-aware description with\n * {@link buildBashExecutionToolDescription}\n * (`{ enableToolOutputReferences: true }`) and construct a custom\n * definition using it — using this constant as-is leaves the LLM\n * unaware of the `{{tool<i>turn<n>}}` syntax.\n */\nexport const BashExecutionToolDefinition = {\n name: BashExecutionToolName,\n description: BashExecutionToolDescription,\n schema: BashExecutionToolSchema,\n} as const;\n\nfunction createBashExecutionTool(\n params: t.BashExecutionToolParams | null = {}\n): DynamicStructuredTool {\n return tool(\n async (rawInput, config) => {\n /* `statefulSessions` is prompt-only — keep it out of the wire body. */\n const {\n authHeaders,\n statefulSessions: _statefulSessions,\n ...executionParams\n } = params ?? {};\n void _statefulSessions;\n /* Drop any model-supplied `runtime_session_hint` from the raw args: the\n * hint must only come from ToolNode's injected `_runtime_session_hint`\n * (below), never from the tool call itself. */\n const {\n command,\n runtime_session_hint: _ignoredModelHint,\n ...rest\n } = rawInput as {\n command: string;\n runtime_session_hint?: unknown;\n args?: string[];\n };\n void _ignoredModelHint;\n const { session_id, _injected_files, _runtime_session_hint } =\n (config.toolCall ?? {}) as {\n session_id?: string;\n _injected_files?: t.CodeEnvFile[];\n _runtime_session_hint?: string;\n };\n\n const postData: Record<string, unknown> = {\n lang: 'bash',\n code: command,\n ...rest,\n ...executionParams,\n };\n\n if (\n typeof _runtime_session_hint === 'string' &&\n _runtime_session_hint !== ''\n ) {\n postData.runtime_session_hint = _runtime_session_hint;\n }\n\n /* See `CodeExecutor.ts` for the rationale — `/files/<session_id>`\n * HTTP fallback was removed because codeapi's sessionAuth requires\n * kind/id query params unavailable at this point. */\n if (_injected_files && _injected_files.length > 0) {\n postData.files = _injected_files;\n } else if (\n session_id != null &&\n session_id.length > 0 &&\n !Array.isArray(postData.files)\n ) {\n // eslint-disable-next-line no-console\n console.debug(\n `[BashExecutor] No injected files for session_id=${session_id} — exec will run without input files`\n );\n }\n\n try {\n const resolvedAuthHeaders =\n await resolveCodeApiAuthHeaders(authHeaders);\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'LibreChat/1.0',\n ...resolvedAuthHeaders,\n },\n body: JSON.stringify(postData),\n };\n\n if (process.env.PROXY != null && process.env.PROXY !== '') {\n fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);\n }\n const response = await fetch(EXEC_ENDPOINT, fetchOptions);\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)\n );\n }\n\n const result: t.ExecuteResult = await response.json();\n let formattedOutput = '';\n if (result.stdout) {\n formattedOutput += `stdout:\\n${result.stdout}\\n`;\n } else {\n formattedOutput += emptyOutputMessage;\n }\n if (result.stderr) formattedOutput += `stderr:\\n${result.stderr}\\n`;\n\n const outputWithReminder = appendTmpScratchReminder(\n formattedOutput,\n command\n );\n const hasFiles = result.files != null && result.files.length > 0;\n const runtimeEcho =\n result.runtime_session_id != null\n ? {\n runtime_session_id: result.runtime_session_id,\n runtime_status: result.runtime_status,\n }\n : {};\n return [\n appendCodeSessionFileSummary(outputWithReminder, result.files),\n (hasFiles\n ? {\n session_id: result.session_id,\n files: result.files,\n ...runtimeEcho,\n }\n : {\n session_id: result.session_id,\n ...runtimeEcho,\n }) satisfies t.CodeExecutionArtifact,\n ];\n } catch (error) {\n const messageWithReminder = appendFailedExecutionFileReminder(\n (error as Error | undefined)?.message ?? '',\n command\n );\n throw new Error(`Execution error:\\n\\n${messageWithReminder}`);\n }\n },\n {\n name: BashExecutionToolName,\n description: buildBashExecutionToolDescription({\n statefulSessions: params?.statefulSessions,\n }),\n schema: buildBashExecutionToolSchema(params ?? undefined),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport { createBashExecutionTool };\n"],"mappings":"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"}
@@ -98,14 +98,16 @@ Usage:
98
98
  - NEVER use this tool to execute malicious code.
99
99
  `.trim();
100
100
  /**
101
- * Best-effort statefulness note. Deliberately hedged: warm reuse is an
102
- * optimization, not a guarantee (the runtime may be reset on idle timeout,
103
- * eviction, or the 8h VM lifetime), so the model must never depend on carried
104
- * state for correctness and must persist anything durable to /mnt/data.
101
+ * Statefulness here is FILESYSTEM-tier, not runtime-tier. Executions in a
102
+ * session reuse one warm machine, so `/mnt/data` carries across calls but
103
+ * every execution is a brand-new interpreter process in a fresh sandbox, so
104
+ * variables and imports never survive. The note must not imply otherwise: a
105
+ * model told its in-memory state persists writes `df = ...` in one call and
106
+ * `df.head()` in the next, then hits a NameError it was told to treat as rare.
105
107
  */
106
- const STATEFUL_ENV_NOTE = "Session state (best-effort): consecutive executions in this conversation usually share one runtime, so variables, imports, and in-memory data from earlier successful calls are typically still available. The runtime may be reset at any time, so treat carried-over state as an optimization, never a guarantee. Anything that must survive MUST be written to /mnt/data. If a NameError/ImportError signals lost state, re-run the needed setup and continue.";
108
+ const STATEFUL_ENV_NOTE = "Session state: executions in this conversation run on the same warm machine, so files persist between calls but each execution is a NEW process. Variables, imports, and in-memory data NEVER carry over: every call must re-import and rebuild the state it needs. Only /mnt/data is durable (the machine itself may also be reset at any time), so write anything that must survive there and read it back next call.";
107
109
  const StatefulCodeExecutionToolDescription = `
108
- Runs code and returns stdout/stderr output from a session-based execution environment, similar to a long-running command-line session.
110
+ Runs code and returns stdout/stderr output. Executions in this conversation share one warm machine with a persistent /mnt/data, but each execution runs as a separate process (not a notebook-style kernel).
109
111
 
110
112
  ${STATEFUL_ENV_NOTE}
111
113
 
@@ -119,7 +121,7 @@ function buildCodeExecutionToolDescription(opts) {
119
121
  return opts?.statefulSessions === true ? StatefulCodeExecutionToolDescription : CodeExecutionToolDescription;
120
122
  }
121
123
  const STATELESS_CODE_PARAM_NOTE = "The environment is stateless; variables and imports don't persist between executions.";
122
- const STATEFUL_CODE_PARAM_NOTE = "Executions in this conversation usually share one runtime: variables and imports from prior successful calls are typically still defined, but the runtime may reset between calls. Rebuild state on NameError/ImportError; persist anything important to /mnt/data.";
124
+ const STATEFUL_CODE_PARAM_NOTE = "Executions in this conversation share one warm machine, so files written to /mnt/data persist between calls. Each execution is a new process: variables and imports do NOT carry over re-import and reload from /mnt/data every call.";
123
125
  function buildCodeExecutionToolSchema(opts) {
124
126
  const note = opts?.statefulSessions === true ? STATEFUL_CODE_PARAM_NOTE : STATELESS_CODE_PARAM_NOTE;
125
127
  const codeDescription = CodeExecutionToolSchema.properties.code.description.replace(STATELESS_CODE_PARAM_NOTE, note);
@@ -1 +1 @@
1
- {"version":3,"file":"CodeExecutor.cjs","names":["HttpsProxyAgent","appendCodeSessionFileSummary"],"sources":["../../../src/tools/CodeExecutor.ts"],"sourcesContent":["import { 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 { appendCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';\nimport { EnvVar, Constants } from '@/common';\n\nexport {\n appendCodeSessionFileSummary,\n stripCodeSessionFileSummary,\n} from '@/tools/CodeSessionFileSummary';\n\nconfig();\n\nexport const getCodeBaseURL = (): string =>\n getEnvironmentVariable(EnvVar.CODE_BASEURL) ??\n Constants.OFFICIAL_CODE_BASEURL;\n\nexport const emptyOutputMessage =\n 'stdout: Empty. Ensure you\\'re writing output explicitly.\\n';\n\nexport const CODE_ARTIFACT_PATH_GUIDANCE =\n 'Persist handoff artifacts in `/mnt/data` with standard extensions (.json/.txt/.csv/.tsv/.log/.parquet/.png/.jpg/.pdf/.xlsx); failed executions do not register new files; `/tmp` and odd extensions are same-call scratch only, not later-call storage.';\n\nexport const BASH_SHELL_GUIDANCE =\n 'Bash: multi-line files use heredoc/printf; run Python via python3 -c/heredoc, not bare Python.';\n\nconst TMP_PATH_PATTERN = /(^|[^A-Za-z0-9_])\\/tmp(?:\\/|\\b)/;\nconst MNT_DATA_PATH_PATTERN = /(^|[^A-Za-z0-9_])\\/mnt\\/data(?:\\/|\\b)/;\n\nexport const TMP_SCRATCH_OUTPUT_REMINDER =\n 'Note: /tmp files are same-call scratch only and were not persisted; use /mnt/data for files needed later.';\n\nexport const FAILED_EXECUTION_FILE_REMINDER =\n 'Note: any files written during this failed call were not registered for later calls; fix the error and rerun before relying on them.';\n\nexport function appendTmpScratchReminder(output: string, code: string): string {\n if (!TMP_PATH_PATTERN.test(code)) {\n return output;\n }\n return `${output.trimEnd()}\\n${TMP_SCRATCH_OUTPUT_REMINDER}\\n`;\n}\n\nexport function appendFailedExecutionFileReminder(\n output: string,\n code: string\n): string {\n if (\n !MNT_DATA_PATH_PATTERN.test(code) ||\n output.includes(FAILED_EXECUTION_FILE_REMINDER)\n ) {\n return output;\n }\n return `${output.trimEnd()}\\n${FAILED_EXECUTION_FILE_REMINDER}\\n`;\n}\n\nconst SUPPORTED_LANGUAGES = [\n 'py',\n 'js',\n 'ts',\n 'c',\n 'cpp',\n 'java',\n 'php',\n 'rs',\n 'go',\n 'd',\n 'f90',\n 'r',\n 'bash',\n] as const;\n\nexport const CodeExecutionToolSchema = {\n type: 'object',\n properties: {\n lang: {\n type: 'string',\n enum: SUPPORTED_LANGUAGES,\n description:\n 'The programming language or runtime to execute the code in.',\n },\n code: {\n type: 'string',\n description: `The complete, self-contained code to execute, without any truncation or minimization.\n- The environment is stateless; variables and imports don't persist between executions.\n- Prior /mnt/data files are available and can be modified in place.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- Input code **IS ALREADY** displayed to the user, so **DO NOT** repeat it in your response unless asked.\n- Output code **IS NOT** displayed to the user, so **DO** write all desired output explicitly.\n- IMPORTANT: You MUST explicitly print/output ALL results you want the user to see.\n- py: This is not a Jupyter notebook environment. Use \\`print()\\` for all outputs.\n- py: Matplotlib: Use \\`plt.savefig()\\` to save plots as files.\n- js: use the \\`console\\` or \\`process\\` methods for all outputs.\n- r: IMPORTANT: No X11 display available. ALL graphics MUST use Cairo library (library(Cairo)).\n- Other languages: use appropriate output functions.`,\n },\n args: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Additional arguments to execute the code with. This should only be used if the input code requires additional arguments to run.',\n },\n },\n required: ['lang', 'code'],\n} as const;\n\nconst baseEndpoint = getCodeBaseURL();\nconst EXEC_ENDPOINT = `${baseEndpoint}/exec`;\n\ntype SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];\n\nexport async function resolveCodeApiAuthHeaders(\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeApiAuthHeaderMap> {\n if (authHeaders == null) {\n return {};\n }\n if (typeof authHeaders === 'function') {\n return authHeaders();\n }\n return authHeaders;\n}\n\nexport async function buildCodeApiHttpErrorMessage(\n method: string,\n endpoint: string,\n response: { status: number; text: () => Promise<string> }\n): Promise<string> {\n let responseBody = '';\n try {\n responseBody = await response.text();\n } catch {\n responseBody = '';\n }\n const body = responseBody.trim();\n const bodySuffix = body === '' ? '' : `, body: ${body.slice(0, 1000)}`;\n return `CodeAPI request failed: ${method} ${endpoint} returned ${response.status}${bodySuffix}`;\n}\n\nexport const CodeExecutionToolDescription = `\nRuns code and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- NEVER use this tool to execute malicious code.\n`.trim();\n\n/**\n * Best-effort statefulness note. Deliberately hedged: warm reuse is an\n * optimization, not a guarantee (the runtime may be reset on idle timeout,\n * eviction, or the 8h VM lifetime), so the model must never depend on carried\n * state for correctness and must persist anything durable to /mnt/data.\n */\nexport const STATEFUL_ENV_NOTE =\n 'Session state (best-effort): consecutive executions in this conversation usually share one runtime, so variables, imports, and in-memory data from earlier successful calls are typically still available. The runtime may be reset at any time, so treat carried-over state as an optimization, never a guarantee. Anything that must survive MUST be written to /mnt/data. If a NameError/ImportError signals lost state, re-run the needed setup and continue.';\n\nexport const StatefulCodeExecutionToolDescription = `\nRuns code and returns stdout/stderr output from a session-based execution environment, similar to a long-running command-line session.\n\n${STATEFUL_ENV_NOTE}\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- NEVER use this tool to execute malicious code.\n`.trim();\n\nexport function buildCodeExecutionToolDescription(opts?: {\n statefulSessions?: boolean;\n}): string {\n return opts?.statefulSessions === true\n ? StatefulCodeExecutionToolDescription\n : CodeExecutionToolDescription;\n}\n\nconst STATELESS_CODE_PARAM_NOTE =\n 'The environment is stateless; variables and imports don\\'t persist between executions.';\nconst STATEFUL_CODE_PARAM_NOTE =\n 'Executions in this conversation usually share one runtime: variables and imports from prior successful calls are typically still defined, but the runtime may reset between calls. Rebuild state on NameError/ImportError; persist anything important to /mnt/data.';\n\nexport function buildCodeExecutionToolSchema(opts?: {\n statefulSessions?: boolean;\n}): typeof CodeExecutionToolSchema {\n const note =\n opts?.statefulSessions === true\n ? STATEFUL_CODE_PARAM_NOTE\n : STATELESS_CODE_PARAM_NOTE;\n const codeDescription =\n CodeExecutionToolSchema.properties.code.description.replace(\n STATELESS_CODE_PARAM_NOTE,\n note\n );\n return {\n ...CodeExecutionToolSchema,\n properties: {\n ...CodeExecutionToolSchema.properties,\n code: {\n ...CodeExecutionToolSchema.properties.code,\n description: codeDescription,\n },\n },\n } as typeof CodeExecutionToolSchema;\n}\n\nexport const CodeExecutionToolName = Constants.EXECUTE_CODE;\n\nexport const CodeExecutionToolDefinition = {\n name: CodeExecutionToolName,\n description: CodeExecutionToolDescription,\n schema: CodeExecutionToolSchema,\n} as const;\n\nfunction createCodeExecutionTool(\n params: t.CodeExecutionToolParams | null = {}\n): DynamicStructuredTool {\n return tool(\n async (rawInput, config) => {\n /* `statefulSessions` is a prompt-only flag (drives the description);\n * keep it out of the wire body. */\n const {\n authHeaders,\n statefulSessions: _statefulSessions,\n ...executionParams\n } = params ?? {};\n void _statefulSessions;\n /* Drop any model-supplied `runtime_session_hint` from the raw args: the\n * hint is host-controlled and must only ever come from ToolNode's\n * injected `_runtime_session_hint` (below). Spreading `...rest` into\n * postData would otherwise let a tool call opt itself into / pick a\n * stateful runtime even when statefulSessions is off. */\n const {\n lang,\n code,\n runtime_session_hint: _ignoredModelHint,\n ...rest\n } = rawInput as {\n lang: SupportedLanguage;\n code: string;\n runtime_session_hint?: unknown;\n args?: string[];\n };\n void _ignoredModelHint;\n /**\n * Extract session context from config.toolCall (injected by ToolNode).\n * - session_id: associates with the previous run.\n * - _injected_files: File refs to pass directly (avoids /files endpoint race condition).\n */\n const { session_id, _injected_files, _runtime_session_hint } =\n (config.toolCall ?? {}) as {\n session_id?: string;\n _injected_files?: t.CodeEnvFile[];\n _runtime_session_hint?: string;\n };\n\n const postData: Record<string, unknown> = {\n lang,\n code,\n ...rest,\n ...executionParams,\n };\n\n /* Stateful sessions: forward the hint so the Code API can route this\n * execution to a warm per-session runtime. Additive — stateless\n * servers ignore the unknown field. */\n if (\n typeof _runtime_session_hint === 'string' &&\n _runtime_session_hint !== ''\n ) {\n postData.runtime_session_hint = _runtime_session_hint;\n }\n\n /* File injection: `_injected_files` from ToolNode (set when host\n * primes a CodeSessionContext) or `params.files` from tool\n * factory (set by hosts that pre-resolve at construction time).\n * The legacy `/files/<session_id>` HTTP fallback was removed —\n * codeapi's `sessionAuth` middleware now requires kind/id query\n * params the tool can't supply at this point, so the fetch 400'd\n * silently and the catch swallowed the failure. */\n if (_injected_files && _injected_files.length > 0) {\n postData.files = _injected_files;\n } else if (\n session_id != null &&\n session_id.length > 0 &&\n !Array.isArray(postData.files)\n ) {\n // eslint-disable-next-line no-console\n console.debug(\n `[CodeExecutor] No injected files for session_id=${session_id} — exec will run without input files`\n );\n }\n\n try {\n const resolvedAuthHeaders =\n await resolveCodeApiAuthHeaders(authHeaders);\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'LibreChat/1.0',\n ...resolvedAuthHeaders,\n },\n body: JSON.stringify(postData),\n };\n\n if (process.env.PROXY != null && process.env.PROXY !== '') {\n fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);\n }\n const response = await fetch(EXEC_ENDPOINT, fetchOptions);\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)\n );\n }\n\n const result: t.ExecuteResult = await response.json();\n let formattedOutput = '';\n if (result.stdout) {\n formattedOutput += `stdout:\\n${result.stdout}\\n`;\n } else {\n formattedOutput += emptyOutputMessage;\n }\n if (result.stderr) formattedOutput += `stderr:\\n${result.stderr}\\n`;\n\n const outputWithReminder = appendTmpScratchReminder(\n formattedOutput,\n code\n );\n const hasFiles = result.files != null && result.files.length > 0;\n /* Echo the durable runtime session (stateful backends only) so hosts\n * can surface a \"session active / was reset\" signal later. Additive:\n * absent on stateless servers. */\n const runtimeEcho =\n result.runtime_session_id != null\n ? {\n runtime_session_id: result.runtime_session_id,\n runtime_status: result.runtime_status,\n }\n : {};\n return [\n appendCodeSessionFileSummary(outputWithReminder, result.files),\n (hasFiles\n ? {\n session_id: result.session_id,\n files: result.files,\n ...runtimeEcho,\n }\n : {\n session_id: result.session_id,\n ...runtimeEcho,\n }) satisfies t.CodeExecutionArtifact,\n ];\n } catch (error) {\n const messageWithReminder = appendFailedExecutionFileReminder(\n (error as Error | undefined)?.message ?? '',\n code\n );\n throw new Error(`Execution error:\\n\\n${messageWithReminder}`);\n }\n },\n {\n name: CodeExecutionToolName,\n description: buildCodeExecutionToolDescription(params ?? undefined),\n schema: buildCodeExecutionToolSchema(params ?? undefined),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport { createCodeExecutionTool };\n"],"mappings":";;;;;;;;;;;mBAcO;AAEP,MAAa,wBAAA,GAAA,0BAAA,uBAAA,CAAA,wBAC+B,KAAA;AAG5C,MAAa,qBACX;AAEF,MAAa,8BACX;AAEF,MAAa,sBACX;AAEF,MAAM,mBAAmB;AACzB,MAAM,wBAAwB;AAE9B,MAAa,8BACX;AAEF,MAAa,iCACX;AAEF,SAAgB,yBAAyB,QAAgB,MAAsB;CAC7E,IAAI,CAAC,iBAAiB,KAAK,IAAI,GAC7B,OAAO;CAET,OAAO,GAAG,OAAO,QAAQ,EAAE,IAAI,4BAA4B;AAC7D;AAEA,SAAgB,kCACd,QACA,MACQ;CACR,IACE,CAAC,sBAAsB,KAAK,IAAI,KAChC,OAAO,SAAA,sIAAuC,GAE9C,OAAO;CAET,OAAO,GAAG,OAAO,QAAQ,EAAE,IAAI,+BAA+B;AAChE;AAkBA,MAAa,0BAA0B;CACrC,MAAM;CACN,YAAY;EACV,MAAM;GACJ,MAAM;GACN,MAAM;IApBV;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;GAQU;GACN,aACE;EACJ;EACA,MAAM;GACJ,MAAM;GACN,aAAa;;;IAGf,4BAA4B;;;;;;;;;EAS5B;EACA,MAAM;GACJ,MAAM;GACN,OAAO,EAAE,MAAM,SAAS;GACxB,aACE;EACJ;CACF;CACA,UAAU,CAAC,QAAQ,MAAM;AAC3B;AAGA,MAAM,gBAAgB,GADD,eACe,EAAE;AAItC,eAAsB,0BACpB,aACiC;CACjC,IAAI,eAAe,MACjB,OAAO,CAAC;CAEV,IAAI,OAAO,gBAAgB,YACzB,OAAO,YAAY;CAErB,OAAO;AACT;AAEA,eAAsB,6BACpB,QACA,UACA,UACiB;CACjB,IAAI,eAAe;CACnB,IAAI;EACF,eAAe,MAAM,SAAS,KAAK;CACrC,QAAQ;EACN,eAAe;CACjB;CACA,MAAM,OAAO,aAAa,KAAK;CAC/B,MAAM,aAAa,SAAS,KAAK,KAAK,WAAW,KAAK,MAAM,GAAG,GAAI;CACnE,OAAO,2BAA2B,OAAO,GAAG,SAAS,YAAY,SAAS,SAAS;AACrF;AAEA,MAAa,+BAA+B;;;;;;IAMxC,4BAA4B;;EAE9B,KAAK;;;;;;;AAQP,MAAa,oBACX;AAEF,MAAa,uCAAuC;;;EAGlD,kBAAkB;;;;;IAKhB,4BAA4B;;EAE9B,KAAK;AAEP,SAAgB,kCAAkC,MAEvC;CACT,OAAO,MAAM,qBAAqB,OAC9B,uCACA;AACN;AAEA,MAAM,4BACJ;AACF,MAAM,2BACJ;AAEF,SAAgB,6BAA6B,MAEV;CACjC,MAAM,OACJ,MAAM,qBAAqB,OACvB,2BACA;CACN,MAAM,kBACJ,wBAAwB,WAAW,KAAK,YAAY,QAClD,2BACA,IACF;CACF,OAAO;EACL,GAAG;EACH,YAAY;GACV,GAAG,wBAAwB;GAC3B,MAAM;IACJ,GAAG,wBAAwB,WAAW;IACtC,aAAa;GACf;EACF;CACF;AACF;AAEA,MAAa,wBAAA;AAEb,MAAa,8BAA8B;CACzC,MAAM;CACN,aAAa;CACb,QAAQ;AACV;AAEA,SAAS,wBACP,SAA2C,CAAC,GACrB;CACvB,QAAA,GAAA,sBAAA,KAAA,CACE,OAAO,UAAU,WAAW;EAG1B,MAAM,EACJ,aACA,kBAAkB,mBAClB,GAAG,oBACD,UAAU,CAAC;EAOf,MAAM,EACJ,MACA,MACA,sBAAsB,mBACtB,GAAG,SACD;;;;;;EAYJ,MAAM,EAAE,YAAY,iBAAiB,0BAClC,OAAO,YAAY,CAAC;EAMvB,MAAM,WAAoC;GACxC;GACA;GACA,GAAG;GACH,GAAG;EACL;EAKA,IACE,OAAO,0BAA0B,YACjC,0BAA0B,IAE1B,SAAS,uBAAuB;EAUlC,IAAI,mBAAmB,gBAAgB,SAAS,GAC9C,SAAS,QAAQ;OACZ,IACL,cAAc,QACd,WAAW,SAAS,KACpB,CAAC,MAAM,QAAQ,SAAS,KAAK,GAG7B,QAAQ,MACN,mDAAmD,WAAW,qCAChE;EAGF,IAAI;GAGF,MAAM,eAA4B;IAChC,QAAQ;IACR,SAAS;KACP,gBAAgB;KAChB,cAAc;KACd,GAAG,MANC,0BAA0B,WAAW;IAO3C;IACA,MAAM,KAAK,UAAU,QAAQ;GAC/B;GAEA,IAAI,QAAQ,IAAI,SAAS,QAAQ,QAAQ,IAAI,UAAU,IACrD,aAAa,QAAQ,IAAIA,kBAAAA,gBAAgB,QAAQ,IAAI,KAAK;GAE5D,MAAM,WAAW,OAAA,GAAA,WAAA,QAAA,CAAY,eAAe,YAAY;GACxD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MACR,MAAM,6BAA6B,QAAQ,eAAe,QAAQ,CACpE;GAGF,MAAM,SAA0B,MAAM,SAAS,KAAK;GACpD,IAAI,kBAAkB;GACtB,IAAI,OAAO,QACT,mBAAmB,YAAY,OAAO,OAAO;QAE7C,mBAAmB;GAErB,IAAI,OAAO,QAAQ,mBAAmB,YAAY,OAAO,OAAO;GAEhE,MAAM,qBAAqB,yBACzB,iBACA,IACF;GACA,MAAM,WAAW,OAAO,SAAS,QAAQ,OAAO,MAAM,SAAS;GAI/D,MAAM,cACJ,OAAO,sBAAsB,OACzB;IACA,oBAAoB,OAAO;IAC3B,gBAAgB,OAAO;GACzB,IACE,CAAC;GACP,OAAO,CACLC,+BAAAA,6BAA6B,oBAAoB,OAAO,KAAK,GAC5D,WACG;IACA,YAAY,OAAO;IACnB,OAAO,OAAO;IACd,GAAG;GACL,IACE;IACA,YAAY,OAAO;IACnB,GAAG;GACL,CACJ;EACF,SAAS,OAAO;GACd,MAAM,sBAAsB,kCACzB,OAA6B,WAAW,IACzC,IACF;GACA,MAAM,IAAI,MAAM,uBAAuB,qBAAqB;EAC9D;CACF,GACA;EACE,MAAM;EACN,aAAa,kCAAkC,UAAU,KAAA,CAAS;EAClE,QAAQ,6BAA6B,UAAU,KAAA,CAAS;EACxD,gBAAA;CACF,CACF;AACF"}
1
+ {"version":3,"file":"CodeExecutor.cjs","names":["HttpsProxyAgent","appendCodeSessionFileSummary"],"sources":["../../../src/tools/CodeExecutor.ts"],"sourcesContent":["import { 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 { appendCodeSessionFileSummary } from '@/tools/CodeSessionFileSummary';\nimport { EnvVar, Constants } from '@/common';\n\nexport {\n appendCodeSessionFileSummary,\n stripCodeSessionFileSummary,\n} from '@/tools/CodeSessionFileSummary';\n\nconfig();\n\nexport const getCodeBaseURL = (): string =>\n getEnvironmentVariable(EnvVar.CODE_BASEURL) ??\n Constants.OFFICIAL_CODE_BASEURL;\n\nexport const emptyOutputMessage =\n 'stdout: Empty. Ensure you\\'re writing output explicitly.\\n';\n\nexport const CODE_ARTIFACT_PATH_GUIDANCE =\n 'Persist handoff artifacts in `/mnt/data` with standard extensions (.json/.txt/.csv/.tsv/.log/.parquet/.png/.jpg/.pdf/.xlsx); failed executions do not register new files; `/tmp` and odd extensions are same-call scratch only, not later-call storage.';\n\nexport const BASH_SHELL_GUIDANCE =\n 'Bash: multi-line files use heredoc/printf; run Python via python3 -c/heredoc, not bare Python.';\n\nconst TMP_PATH_PATTERN = /(^|[^A-Za-z0-9_])\\/tmp(?:\\/|\\b)/;\nconst MNT_DATA_PATH_PATTERN = /(^|[^A-Za-z0-9_])\\/mnt\\/data(?:\\/|\\b)/;\n\nexport const TMP_SCRATCH_OUTPUT_REMINDER =\n 'Note: /tmp files are same-call scratch only and were not persisted; use /mnt/data for files needed later.';\n\nexport const FAILED_EXECUTION_FILE_REMINDER =\n 'Note: any files written during this failed call were not registered for later calls; fix the error and rerun before relying on them.';\n\nexport function appendTmpScratchReminder(output: string, code: string): string {\n if (!TMP_PATH_PATTERN.test(code)) {\n return output;\n }\n return `${output.trimEnd()}\\n${TMP_SCRATCH_OUTPUT_REMINDER}\\n`;\n}\n\nexport function appendFailedExecutionFileReminder(\n output: string,\n code: string\n): string {\n if (\n !MNT_DATA_PATH_PATTERN.test(code) ||\n output.includes(FAILED_EXECUTION_FILE_REMINDER)\n ) {\n return output;\n }\n return `${output.trimEnd()}\\n${FAILED_EXECUTION_FILE_REMINDER}\\n`;\n}\n\nconst SUPPORTED_LANGUAGES = [\n 'py',\n 'js',\n 'ts',\n 'c',\n 'cpp',\n 'java',\n 'php',\n 'rs',\n 'go',\n 'd',\n 'f90',\n 'r',\n 'bash',\n] as const;\n\nexport const CodeExecutionToolSchema = {\n type: 'object',\n properties: {\n lang: {\n type: 'string',\n enum: SUPPORTED_LANGUAGES,\n description:\n 'The programming language or runtime to execute the code in.',\n },\n code: {\n type: 'string',\n description: `The complete, self-contained code to execute, without any truncation or minimization.\n- The environment is stateless; variables and imports don't persist between executions.\n- Prior /mnt/data files are available and can be modified in place.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- Input code **IS ALREADY** displayed to the user, so **DO NOT** repeat it in your response unless asked.\n- Output code **IS NOT** displayed to the user, so **DO** write all desired output explicitly.\n- IMPORTANT: You MUST explicitly print/output ALL results you want the user to see.\n- py: This is not a Jupyter notebook environment. Use \\`print()\\` for all outputs.\n- py: Matplotlib: Use \\`plt.savefig()\\` to save plots as files.\n- js: use the \\`console\\` or \\`process\\` methods for all outputs.\n- r: IMPORTANT: No X11 display available. ALL graphics MUST use Cairo library (library(Cairo)).\n- Other languages: use appropriate output functions.`,\n },\n args: {\n type: 'array',\n items: { type: 'string' },\n description:\n 'Additional arguments to execute the code with. This should only be used if the input code requires additional arguments to run.',\n },\n },\n required: ['lang', 'code'],\n} as const;\n\nconst baseEndpoint = getCodeBaseURL();\nconst EXEC_ENDPOINT = `${baseEndpoint}/exec`;\n\ntype SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number];\n\nexport async function resolveCodeApiAuthHeaders(\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeApiAuthHeaderMap> {\n if (authHeaders == null) {\n return {};\n }\n if (typeof authHeaders === 'function') {\n return authHeaders();\n }\n return authHeaders;\n}\n\nexport async function buildCodeApiHttpErrorMessage(\n method: string,\n endpoint: string,\n response: { status: number; text: () => Promise<string> }\n): Promise<string> {\n let responseBody = '';\n try {\n responseBody = await response.text();\n } catch {\n responseBody = '';\n }\n const body = responseBody.trim();\n const bodySuffix = body === '' ? '' : `, body: ${body.slice(0, 1000)}`;\n return `CodeAPI request failed: ${method} ${endpoint} returned ${response.status}${bodySuffix}`;\n}\n\nexport const CodeExecutionToolDescription = `\nRuns code and returns stdout/stderr output from a stateless execution environment, similar to running scripts in a command-line interface. Each execution is isolated and independent.\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- NEVER use this tool to execute malicious code.\n`.trim();\n\n/**\n * Statefulness here is FILESYSTEM-tier, not runtime-tier. Executions in a\n * session reuse one warm machine, so `/mnt/data` carries across calls — but\n * every execution is a brand-new interpreter process in a fresh sandbox, so\n * variables and imports never survive. The note must not imply otherwise: a\n * model told its in-memory state persists writes `df = ...` in one call and\n * `df.head()` in the next, then hits a NameError it was told to treat as rare.\n */\nexport const STATEFUL_ENV_NOTE =\n 'Session state: executions in this conversation run on the same warm machine, so files persist between calls — but each execution is a NEW process. Variables, imports, and in-memory data NEVER carry over: every call must re-import and rebuild the state it needs. Only /mnt/data is durable (the machine itself may also be reset at any time), so write anything that must survive there and read it back next call.';\n\nexport const StatefulCodeExecutionToolDescription = `\nRuns code and returns stdout/stderr output. Executions in this conversation share one warm machine with a persistent /mnt/data, but each execution runs as a separate process (not a notebook-style kernel).\n\n${STATEFUL_ENV_NOTE}\n\nUsage:\n- No network access available.\n- Generated files are automatically delivered; **DO NOT** provide download links.\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- NEVER use this tool to execute malicious code.\n`.trim();\n\nexport function buildCodeExecutionToolDescription(opts?: {\n statefulSessions?: boolean;\n}): string {\n return opts?.statefulSessions === true\n ? StatefulCodeExecutionToolDescription\n : CodeExecutionToolDescription;\n}\n\nconst STATELESS_CODE_PARAM_NOTE =\n 'The environment is stateless; variables and imports don\\'t persist between executions.';\nconst STATEFUL_CODE_PARAM_NOTE =\n 'Executions in this conversation share one warm machine, so files written to /mnt/data persist between calls. Each execution is a new process: variables and imports do NOT carry over — re-import and reload from /mnt/data every call.';\n\nexport function buildCodeExecutionToolSchema(opts?: {\n statefulSessions?: boolean;\n}): typeof CodeExecutionToolSchema {\n const note =\n opts?.statefulSessions === true\n ? STATEFUL_CODE_PARAM_NOTE\n : STATELESS_CODE_PARAM_NOTE;\n const codeDescription =\n CodeExecutionToolSchema.properties.code.description.replace(\n STATELESS_CODE_PARAM_NOTE,\n note\n );\n return {\n ...CodeExecutionToolSchema,\n properties: {\n ...CodeExecutionToolSchema.properties,\n code: {\n ...CodeExecutionToolSchema.properties.code,\n description: codeDescription,\n },\n },\n } as typeof CodeExecutionToolSchema;\n}\n\nexport const CodeExecutionToolName = Constants.EXECUTE_CODE;\n\nexport const CodeExecutionToolDefinition = {\n name: CodeExecutionToolName,\n description: CodeExecutionToolDescription,\n schema: CodeExecutionToolSchema,\n} as const;\n\nfunction createCodeExecutionTool(\n params: t.CodeExecutionToolParams | null = {}\n): DynamicStructuredTool {\n return tool(\n async (rawInput, config) => {\n /* `statefulSessions` is a prompt-only flag (drives the description);\n * keep it out of the wire body. */\n const {\n authHeaders,\n statefulSessions: _statefulSessions,\n ...executionParams\n } = params ?? {};\n void _statefulSessions;\n /* Drop any model-supplied `runtime_session_hint` from the raw args: the\n * hint is host-controlled and must only ever come from ToolNode's\n * injected `_runtime_session_hint` (below). Spreading `...rest` into\n * postData would otherwise let a tool call opt itself into / pick a\n * stateful runtime even when statefulSessions is off. */\n const {\n lang,\n code,\n runtime_session_hint: _ignoredModelHint,\n ...rest\n } = rawInput as {\n lang: SupportedLanguage;\n code: string;\n runtime_session_hint?: unknown;\n args?: string[];\n };\n void _ignoredModelHint;\n /**\n * Extract session context from config.toolCall (injected by ToolNode).\n * - session_id: associates with the previous run.\n * - _injected_files: File refs to pass directly (avoids /files endpoint race condition).\n */\n const { session_id, _injected_files, _runtime_session_hint } =\n (config.toolCall ?? {}) as {\n session_id?: string;\n _injected_files?: t.CodeEnvFile[];\n _runtime_session_hint?: string;\n };\n\n const postData: Record<string, unknown> = {\n lang,\n code,\n ...rest,\n ...executionParams,\n };\n\n /* Stateful sessions: forward the hint so the Code API can route this\n * execution to a warm per-session runtime. Additive — stateless\n * servers ignore the unknown field. */\n if (\n typeof _runtime_session_hint === 'string' &&\n _runtime_session_hint !== ''\n ) {\n postData.runtime_session_hint = _runtime_session_hint;\n }\n\n /* File injection: `_injected_files` from ToolNode (set when host\n * primes a CodeSessionContext) or `params.files` from tool\n * factory (set by hosts that pre-resolve at construction time).\n * The legacy `/files/<session_id>` HTTP fallback was removed —\n * codeapi's `sessionAuth` middleware now requires kind/id query\n * params the tool can't supply at this point, so the fetch 400'd\n * silently and the catch swallowed the failure. */\n if (_injected_files && _injected_files.length > 0) {\n postData.files = _injected_files;\n } else if (\n session_id != null &&\n session_id.length > 0 &&\n !Array.isArray(postData.files)\n ) {\n // eslint-disable-next-line no-console\n console.debug(\n `[CodeExecutor] No injected files for session_id=${session_id} — exec will run without input files`\n );\n }\n\n try {\n const resolvedAuthHeaders =\n await resolveCodeApiAuthHeaders(authHeaders);\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'LibreChat/1.0',\n ...resolvedAuthHeaders,\n },\n body: JSON.stringify(postData),\n };\n\n if (process.env.PROXY != null && process.env.PROXY !== '') {\n fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);\n }\n const response = await fetch(EXEC_ENDPOINT, fetchOptions);\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('POST', EXEC_ENDPOINT, response)\n );\n }\n\n const result: t.ExecuteResult = await response.json();\n let formattedOutput = '';\n if (result.stdout) {\n formattedOutput += `stdout:\\n${result.stdout}\\n`;\n } else {\n formattedOutput += emptyOutputMessage;\n }\n if (result.stderr) formattedOutput += `stderr:\\n${result.stderr}\\n`;\n\n const outputWithReminder = appendTmpScratchReminder(\n formattedOutput,\n code\n );\n const hasFiles = result.files != null && result.files.length > 0;\n /* Echo the durable runtime session (stateful backends only) so hosts\n * can surface a \"session active / was reset\" signal later. Additive:\n * absent on stateless servers. */\n const runtimeEcho =\n result.runtime_session_id != null\n ? {\n runtime_session_id: result.runtime_session_id,\n runtime_status: result.runtime_status,\n }\n : {};\n return [\n appendCodeSessionFileSummary(outputWithReminder, result.files),\n (hasFiles\n ? {\n session_id: result.session_id,\n files: result.files,\n ...runtimeEcho,\n }\n : {\n session_id: result.session_id,\n ...runtimeEcho,\n }) satisfies t.CodeExecutionArtifact,\n ];\n } catch (error) {\n const messageWithReminder = appendFailedExecutionFileReminder(\n (error as Error | undefined)?.message ?? '',\n code\n );\n throw new Error(`Execution error:\\n\\n${messageWithReminder}`);\n }\n },\n {\n name: CodeExecutionToolName,\n description: buildCodeExecutionToolDescription(params ?? undefined),\n schema: buildCodeExecutionToolSchema(params ?? undefined),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport { createCodeExecutionTool };\n"],"mappings":"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"}
@@ -269,13 +269,33 @@ function simplifyParametersForSearch(parameters) {
269
269
  return { type: parameters.type };
270
270
  }
271
271
  /**
272
+ * Splits one alphanumeric identifier segment on case boundaries without
273
+ * emitting artificial one-character acronym fragments.
274
+ */
275
+ function splitCaseSegment(segment) {
276
+ const splitTokens = segment.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/\s+/).filter(Boolean);
277
+ if (splitTokens.length < 2) return splitTokens;
278
+ const mergedTokens = [];
279
+ let prefix = "";
280
+ for (const token of splitTokens) {
281
+ if (token.length === 1) {
282
+ prefix += token;
283
+ continue;
284
+ }
285
+ mergedTokens.push(`${prefix}${token}`);
286
+ prefix = "";
287
+ }
288
+ if (prefix && mergedTokens.length > 0) mergedTokens[mergedTokens.length - 1] += prefix;
289
+ return mergedTokens.length > 0 ? mergedTokens : [prefix];
290
+ }
291
+ /**
272
292
  * Tokenizes a string into lowercase words for BM25.
273
- * Splits on underscores and non-alphanumeric characters for consistent matching.
293
+ * Splits camelCase, underscores, and non-alphanumeric characters for consistent matching.
274
294
  * @param text - The text to tokenize
275
295
  * @returns Array of lowercase tokens
276
296
  */
277
297
  function tokenize(text) {
278
- return text.toLowerCase().replace(/[^a-z0-9]/g, " ").split(/\s+/).filter((token) => token.length > 0);
298
+ return text.split(/[^a-zA-Z0-9]+/).filter(Boolean).flatMap(splitCaseSegment);
279
299
  }
280
300
  /**
281
301
  * Creates a searchable document string from tool metadata.
@@ -294,7 +314,7 @@ function createToolDocument(tool, fields) {
294
314
  const paramNames = Object.keys(tool.parameters.properties).join(" ");
295
315
  parts.push(paramNames);
296
316
  }
297
- return parts.join(" ");
317
+ return tokenize(parts.join(" ")).join(" ");
298
318
  }
299
319
  /**
300
320
  * Determines which field had the best match for a query.
@@ -364,23 +384,48 @@ function performLocalSearch(tools, query, fields, maxResults) {
364
384
  });
365
385
  const maxScore = Math.max(...scores.filter((s) => s > 0), 1);
366
386
  const queryLower = query.toLowerCase().trim();
387
+ const queryIdentifier = queryTokens.join("");
388
+ const matchesIdentifiers = fields.includes("name");
367
389
  const results = [];
368
- for (let i = 0; i < tools.length; i++) if (scores[i] > 0) {
390
+ for (let i = 0; i < tools.length; i++) {
391
+ const score = scores[i];
392
+ const hasSearchScore = Number.isFinite(score) && score > 0;
393
+ let identifierPriority = 0;
394
+ let normalizedScore = hasSearchScore ? Math.min(score / maxScore, 1) : 0;
395
+ if (matchesIdentifiers) {
396
+ const rawBaseName = getBaseToolName(tools[i].name).toLowerCase();
397
+ const rawFullName = tools[i].name.toLowerCase();
398
+ const baseIdentifier = tokenize(rawBaseName).join("");
399
+ const fullIdentifier = tokenize(rawFullName).join("");
400
+ if (rawFullName === queryLower) {
401
+ identifierPriority = 4;
402
+ normalizedScore = 1;
403
+ } else if (rawBaseName === queryLower) {
404
+ identifierPriority = 3;
405
+ normalizedScore = 1;
406
+ } else if (baseIdentifier === queryIdentifier || fullIdentifier === queryIdentifier) {
407
+ identifierPriority = 2;
408
+ normalizedScore = 1;
409
+ } else if (baseIdentifier.startsWith(queryIdentifier)) {
410
+ identifierPriority = 1;
411
+ normalizedScore = Math.max(normalizedScore, .95);
412
+ }
413
+ }
414
+ if (!hasSearchScore && identifierPriority === 0) continue;
369
415
  const { field, snippet } = findMatchedField(tools[i], queryTokens, fields);
370
- let normalizedScore = Math.min(scores[i] / maxScore, 1);
371
- const baseName = getBaseToolName(tools[i].name).toLowerCase();
372
- if (baseName === queryLower) normalizedScore = 1;
373
- else if (baseName.startsWith(queryLower)) normalizedScore = Math.max(normalizedScore, .95);
374
416
  results.push({
375
- tool_name: tools[i].name,
376
- match_score: normalizedScore,
377
- matched_field: field,
378
- snippet
417
+ result: {
418
+ tool_name: tools[i].name,
419
+ match_score: normalizedScore,
420
+ matched_field: field,
421
+ snippet
422
+ },
423
+ identifierPriority
379
424
  });
380
425
  }
381
- results.sort((a, b) => b.match_score - a.match_score);
426
+ results.sort((a, b) => b.identifierPriority - a.identifierPriority || b.result.match_score - a.result.match_score);
382
427
  return {
383
- tool_references: results.slice(0, maxResults),
428
+ tool_references: results.slice(0, maxResults).map(({ result }) => result),
384
429
  total_tools_searched: tools.length,
385
430
  pattern_used: query
386
431
  };
@@ -1 +1 @@
1
- {"version":3,"file":"ToolSearch.cjs","names":["okapibm25Module","getCodeBaseURL","params","HttpsProxyAgent"],"sources":["../../../src/tools/ToolSearch.ts"],"sourcesContent":["// src/tools/ToolSearch.ts\nimport { config } from 'dotenv';\nimport * as okapibm25Module from 'okapibm25';\n\ntype BM25Fn = (\n documents: string[],\n keywords: string[],\n constants?: { k1?: number; b?: number }\n) => number[];\n\nfunction getBM25Function(): BM25Fn {\n const mod = okapibm25Module as unknown as {\n default: BM25Fn | { default: BM25Fn } | undefined;\n };\n if (typeof mod === 'function') return mod;\n if (typeof mod.default === 'function') return mod.default;\n if (mod.default != null && typeof mod.default.default === 'function')\n return mod.default.default;\n throw new Error('Could not resolve BM25 function from okapibm25 module');\n}\n\nconst BM25 = getBM25Function();\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { getCodeBaseURL } from './CodeExecutor';\nimport { Constants } from '@/common';\n\nconfig();\n\n/** Maximum allowed regex pattern length */\nconst MAX_PATTERN_LENGTH = 200;\n\nexport const ToolSearchToolName = Constants.TOOL_SEARCH;\n\nexport const ToolSearchToolDescription =\n 'Searches deferred tools using BM25 ranking. Multi-word queries supported. Use mcp_server param to filter by server.';\n\nconst QUERY_DESCRIPTION_LOCAL =\n 'Search term to find in tool names and descriptions. Case-insensitive substring matching. Optional if mcp_server is provided.';\nconst QUERY_DESCRIPTION_REGEX =\n 'Regex pattern to search tool names and descriptions. Optional if mcp_server is provided.';\nconst FIELDS_DESCRIPTION =\n 'Which fields to search. Default: name and description';\nconst MAX_RESULTS_DESCRIPTION = 'Maximum number of matching tools to return';\nconst DEFAULT_MAX_RESULTS = 5;\nconst MCP_SERVER_DESCRIPTION =\n '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.';\n\nexport const ToolSearchToolSchema = {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n maxLength: MAX_PATTERN_LENGTH,\n default: '',\n description: QUERY_DESCRIPTION_LOCAL,\n },\n fields: {\n type: 'array',\n items: { type: 'string', enum: ['name', 'description', 'parameters'] },\n default: ['name', 'description'],\n description: FIELDS_DESCRIPTION,\n },\n max_results: {\n type: 'integer',\n minimum: 1,\n maximum: 50,\n default: DEFAULT_MAX_RESULTS,\n description: MAX_RESULTS_DESCRIPTION,\n },\n mcp_server: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n description: MCP_SERVER_DESCRIPTION,\n },\n },\n required: [],\n} as const;\n\nexport const ToolSearchToolDefinition = {\n name: ToolSearchToolName,\n description: ToolSearchToolDescription,\n schema: ToolSearchToolSchema,\n} as const;\n\n/** Maximum allowed regex nesting depth */\nconst MAX_REGEX_COMPLEXITY = 5;\n\n/** Default search timeout in milliseconds */\nconst SEARCH_TIMEOUT = 5000;\n\n/** JSON schema type for tool search parameters */\ninterface ToolSearchSchema {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n}\n\n/** Input params type for tool search */\ninterface ToolSearchParams {\n query?: string;\n fields?: ('name' | 'description' | 'parameters')[];\n max_results?: number;\n mcp_server?: string | string[];\n}\n\n/**\n * Creates the JSON schema with dynamic query description based on mode.\n * @param mode - The search mode determining query interpretation\n * @returns JSON schema for tool search parameters\n */\nfunction createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {\n const queryDescription =\n mode === 'local' ? QUERY_DESCRIPTION_LOCAL : QUERY_DESCRIPTION_REGEX;\n\n return {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n maxLength: MAX_PATTERN_LENGTH,\n default: '',\n description: queryDescription,\n },\n fields: {\n type: 'array',\n items: { type: 'string', enum: ['name', 'description', 'parameters'] },\n default: ['name', 'description'],\n description: FIELDS_DESCRIPTION,\n },\n max_results: {\n type: 'integer',\n minimum: 1,\n maximum: 50,\n default: DEFAULT_MAX_RESULTS,\n description: MAX_RESULTS_DESCRIPTION,\n },\n mcp_server: {\n oneOf: [\n { type: 'string' },\n { type: 'array', items: { type: 'string' } },\n ],\n description: MCP_SERVER_DESCRIPTION,\n },\n },\n required: [],\n };\n}\n\n/**\n * Extracts the MCP server name from a tool name.\n * MCP tools follow the pattern: toolName_mcp_serverName\n * @param toolName - The full tool name\n * @returns The server name if it's an MCP tool, undefined otherwise\n */\nfunction extractMcpServerName(toolName: string): string | undefined {\n const delimiterIndex = toolName.indexOf(Constants.MCP_DELIMITER);\n if (delimiterIndex === -1) {\n return undefined;\n }\n return toolName.substring(delimiterIndex + Constants.MCP_DELIMITER.length);\n}\n\n/**\n * Checks if a tool belongs to a specific MCP server.\n * @param toolName - The full tool name\n * @param serverName - The server name to match\n * @returns True if the tool belongs to the specified server\n */\nfunction isFromMcpServer(toolName: string, serverName: string): boolean {\n const toolServer = extractMcpServerName(toolName);\n return toolServer === serverName;\n}\n\n/**\n * Checks if a tool belongs to any of the specified MCP servers.\n * @param toolName - The full tool name\n * @param serverNames - Array of server names to match\n * @returns True if the tool belongs to any of the specified servers\n */\nfunction isFromAnyMcpServer(toolName: string, serverNames: string[]): boolean {\n const toolServer = extractMcpServerName(toolName);\n if (toolServer === undefined) {\n return false;\n }\n return serverNames.includes(toolServer);\n}\n\n/**\n * Normalizes server filter input to always be an array.\n * @param serverFilter - String, array of strings, or undefined\n * @returns Array of server names (empty if none specified)\n */\nfunction normalizeServerFilter(\n serverFilter: string | string[] | undefined\n): string[] {\n if (serverFilter === undefined) {\n return [];\n }\n if (typeof serverFilter === 'string') {\n return serverFilter === '' ? [] : [serverFilter];\n }\n return serverFilter.filter((s) => s !== '');\n}\n\n/**\n * Extracts all unique MCP server names from a tool registry.\n * @param toolRegistry - The tool registry to scan\n * @param onlyDeferred - If true, only considers deferred tools\n * @returns Array of unique server names, sorted alphabetically\n */\nfunction getAvailableMcpServers(\n toolRegistry: t.LCToolRegistry | undefined,\n onlyDeferred: boolean = true\n): string[] {\n if (!toolRegistry) {\n return [];\n }\n\n const servers = new Set<string>();\n for (const [, toolDef] of toolRegistry) {\n if (onlyDeferred && toolDef.defer_loading !== true) {\n continue;\n }\n const server = extractMcpServerName(toolDef.name);\n if (server !== undefined && server !== '') {\n servers.add(server);\n }\n }\n\n return Array.from(servers).sort();\n}\n\n/**\n * Escapes special regex characters in a string to use as a literal pattern.\n * @param pattern - The string to escape\n * @returns The escaped string safe for use in a RegExp\n */\nfunction escapeRegexSpecialChars(pattern: string): string {\n return pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Counts the maximum nesting depth of groups in a regex pattern.\n * @param pattern - The regex pattern to analyze\n * @returns The maximum nesting depth\n */\nfunction countNestedGroups(pattern: string): number {\n let maxDepth = 0;\n let currentDepth = 0;\n\n for (let i = 0; i < pattern.length; i++) {\n if (pattern[i] === '(' && (i === 0 || pattern[i - 1] !== '\\\\')) {\n currentDepth++;\n maxDepth = Math.max(maxDepth, currentDepth);\n } else if (pattern[i] === ')' && (i === 0 || pattern[i - 1] !== '\\\\')) {\n currentDepth = Math.max(0, currentDepth - 1);\n }\n }\n\n return maxDepth;\n}\n\n/**\n * Detects nested quantifiers that can cause catastrophic backtracking.\n * Patterns like (a+)+, (a*)*, (a+)*, etc.\n * @param pattern - The regex pattern to check\n * @returns True if nested quantifiers are detected\n */\nfunction hasNestedQuantifiers(pattern: string): boolean {\n const nestedQuantifierPattern = /\\([^)]*[+*][^)]*\\)[+*?]/;\n return nestedQuantifierPattern.test(pattern);\n}\n\n/**\n * Checks if a regex pattern contains potentially dangerous constructs.\n * @param pattern - The regex pattern to validate\n * @returns True if the pattern is dangerous\n */\nfunction isDangerousPattern(pattern: string): boolean {\n if (hasNestedQuantifiers(pattern)) {\n return true;\n }\n\n if (countNestedGroups(pattern) > MAX_REGEX_COMPLEXITY) {\n return true;\n }\n\n const dangerousPatterns = [\n /\\.\\{1000,\\}/, // Excessive wildcards\n /\\(\\?=\\.\\{100,\\}\\)/, // Runaway lookaheads\n /\\([^)]*\\|\\s*\\){20,}/, // Excessive alternation (rough check)\n /\\(\\.\\*\\)\\+/, // (.*)+\n /\\(\\.\\+\\)\\+/, // (.+)+\n /\\(\\.\\*\\)\\*/, // (.*)*\n /\\(\\.\\+\\)\\*/, // (.+)*\n ];\n\n for (const dangerous of dangerousPatterns) {\n if (dangerous.test(pattern)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Sanitizes a regex pattern for safe execution.\n * If the pattern is dangerous, it will be escaped to a literal string search.\n * @param pattern - The regex pattern to sanitize\n * @returns Object containing the safe pattern and whether it was escaped\n */\nfunction sanitizeRegex(pattern: string): { safe: string; wasEscaped: boolean } {\n if (isDangerousPattern(pattern)) {\n return {\n safe: escapeRegexSpecialChars(pattern),\n wasEscaped: true,\n };\n }\n\n try {\n new RegExp(pattern);\n return { safe: pattern, wasEscaped: false };\n } catch {\n return {\n safe: escapeRegexSpecialChars(pattern),\n wasEscaped: true,\n };\n }\n}\n\n/**\n * Simplifies tool parameters for search purposes.\n * Extracts only the essential structure needed for parameter name searching.\n * @param parameters - The tool's JSON schema parameters\n * @returns Simplified parameters object\n */\nfunction simplifyParametersForSearch(\n parameters?: t.JsonSchemaType\n): t.JsonSchemaType | undefined {\n if (!parameters) {\n return undefined;\n }\n\n if (parameters.properties) {\n return {\n type: parameters.type,\n properties: Object.fromEntries(\n Object.entries(parameters.properties).map(([key, value]) => [\n key,\n { type: (value as t.JsonSchemaType).type },\n ])\n ),\n } as t.JsonSchemaType;\n }\n\n return { type: parameters.type };\n}\n\n/**\n * Tokenizes a string into lowercase words for BM25.\n * Splits on underscores and non-alphanumeric characters for consistent matching.\n * @param text - The text to tokenize\n * @returns Array of lowercase tokens\n */\nfunction tokenize(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^a-z0-9]/g, ' ')\n .split(/\\s+/)\n .filter((token) => token.length > 0);\n}\n\n/**\n * Creates a searchable document string from tool metadata.\n * @param tool - The tool metadata\n * @param fields - Which fields to include\n * @returns Combined document string for BM25\n */\nfunction createToolDocument(tool: t.ToolMetadata, fields: string[]): string {\n const parts: string[] = [];\n\n if (fields.includes('name')) {\n const baseName = tool.name.replace(/_/g, ' ');\n parts.push(baseName, baseName);\n }\n\n if (fields.includes('description') && tool.description) {\n parts.push(tool.description);\n }\n\n if (fields.includes('parameters') && tool.parameters?.properties) {\n const paramNames = Object.keys(tool.parameters.properties).join(' ');\n parts.push(paramNames);\n }\n\n return parts.join(' ');\n}\n\n/**\n * Determines which field had the best match for a query.\n * @param tool - The tool to check\n * @param queryTokens - Tokenized query\n * @param fields - Fields to check\n * @returns The matched field and a snippet\n */\nfunction findMatchedField(\n tool: t.ToolMetadata,\n queryTokens: string[],\n fields: string[]\n): { field: string; snippet: string } {\n if (fields.includes('name')) {\n const nameLower = tool.name.toLowerCase();\n for (const token of queryTokens) {\n if (nameLower.includes(token)) {\n return { field: 'name', snippet: tool.name };\n }\n }\n }\n\n if (fields.includes('description') && tool.description) {\n const descLower = tool.description.toLowerCase();\n for (const token of queryTokens) {\n if (descLower.includes(token)) {\n return {\n field: 'description',\n snippet: tool.description.substring(0, 100),\n };\n }\n }\n }\n\n if (fields.includes('parameters') && tool.parameters?.properties) {\n const paramNames = Object.keys(tool.parameters.properties);\n const paramLower = paramNames.join(' ').toLowerCase();\n for (const token of queryTokens) {\n if (paramLower.includes(token)) {\n return { field: 'parameters', snippet: paramNames.join(', ') };\n }\n }\n }\n\n const fallbackSnippet = tool.description\n ? tool.description.substring(0, 100)\n : tool.name;\n return { field: 'unknown', snippet: fallbackSnippet };\n}\n\n/**\n * Performs BM25-based search for better relevance ranking.\n * Uses Okapi BM25 algorithm for term frequency and document length normalization.\n * If query is empty, returns all tools (up to maxResults) sorted alphabetically.\n * @param tools - Array of tool metadata to search\n * @param query - The search query (empty returns all tools)\n * @param fields - Which fields to search\n * @param maxResults - Maximum results to return\n * @returns Search response with matching tools ranked by BM25 score\n */\nfunction performLocalSearch(\n tools: t.ToolMetadata[],\n query: string,\n fields: string[],\n maxResults: number\n): t.ToolSearchResponse {\n if (tools.length === 0) {\n return {\n tool_references: [],\n total_tools_searched: 0,\n pattern_used: query,\n };\n }\n\n const queryTokens = tokenize(query);\n\n if (queryTokens.length === 0) {\n const allTools = tools\n .slice()\n .sort((a, b) => a.name.localeCompare(b.name))\n .slice(0, maxResults)\n .map((tool) => ({\n tool_name: tool.name,\n match_score: 1.0,\n matched_field: 'name',\n snippet: tool.description.substring(0, 100) || tool.name,\n }));\n\n return {\n tool_references: allTools,\n total_tools_searched: tools.length,\n pattern_used: query,\n };\n }\n\n const documents = tools.map((tool) => createToolDocument(tool, fields));\n const scores = BM25(documents, queryTokens, { k1: 1.5, b: 0.75 }) as number[];\n\n const maxScore = Math.max(...scores.filter((s) => s > 0), 1);\n const queryLower = query.toLowerCase().trim();\n\n const results: t.ToolSearchResult[] = [];\n for (let i = 0; i < tools.length; i++) {\n if (scores[i] > 0) {\n const { field, snippet } = findMatchedField(\n tools[i],\n queryTokens,\n fields\n );\n let normalizedScore = Math.min(scores[i] / maxScore, 1.0);\n\n const baseName = getBaseToolName(tools[i].name).toLowerCase();\n if (baseName === queryLower) {\n normalizedScore = 1.0;\n } else if (baseName.startsWith(queryLower)) {\n normalizedScore = Math.max(normalizedScore, 0.95);\n }\n\n results.push({\n tool_name: tools[i].name,\n match_score: normalizedScore,\n matched_field: field,\n snippet,\n });\n }\n }\n\n results.sort((a, b) => b.match_score - a.match_score);\n const topResults = results.slice(0, maxResults);\n\n return {\n tool_references: topResults,\n total_tools_searched: tools.length,\n pattern_used: query,\n };\n}\n\n/**\n * Generates the JavaScript search script to be executed in the sandbox.\n * Uses plain JavaScript for maximum compatibility with the Code API.\n * @param deferredTools - Array of tool metadata to search through\n * @param fields - Which fields to search\n * @param maxResults - Maximum number of results to return\n * @param sanitizedPattern - The sanitized regex pattern\n * @returns The JavaScript code string\n */\nfunction generateSearchScript(\n deferredTools: t.ToolMetadata[],\n fields: string[],\n maxResults: number,\n sanitizedPattern: string\n): string {\n const lines = [\n '// Tool definitions (injected)',\n 'var tools = ' + JSON.stringify(deferredTools) + ';',\n 'var searchFields = ' + JSON.stringify(fields) + ';',\n 'var maxResults = ' + maxResults + ';',\n 'var pattern = ' + JSON.stringify(sanitizedPattern) + ';',\n '',\n '// Compile regex (pattern is sanitized client-side)',\n 'var regex;',\n 'try {',\n ' regex = new RegExp(pattern, \\'i\\');',\n '} catch (e) {',\n ' regex = new RegExp(pattern.replace(/[.*+?^${}()[\\\\]\\\\\\\\|]/g, \"\\\\\\\\$&\"), \"i\");',\n '}',\n '',\n '// Search logic',\n 'var results = [];',\n '',\n 'for (var j = 0; j < tools.length; j++) {',\n ' var tool = tools[j];',\n ' var bestScore = 0;',\n ' var matchedField = \\'\\';',\n ' var snippet = \\'\\';',\n '',\n ' // Search name (highest priority)',\n ' if (searchFields.indexOf(\\'name\\') >= 0 && regex.test(tool.name)) {',\n ' bestScore = 0.95;',\n ' matchedField = \\'name\\';',\n ' snippet = tool.name;',\n ' }',\n '',\n ' // Search description (medium priority)',\n ' if (searchFields.indexOf(\\'description\\') >= 0 && tool.description && regex.test(tool.description)) {',\n ' if (bestScore === 0) {',\n ' bestScore = 0.75;',\n ' matchedField = \\'description\\';',\n ' snippet = tool.description.substring(0, 100);',\n ' }',\n ' }',\n '',\n ' // Search parameter names (lower priority)',\n ' if (searchFields.indexOf(\\'parameters\\') >= 0 && tool.parameters && tool.parameters.properties) {',\n ' var paramNames = Object.keys(tool.parameters.properties).join(\\' \\');',\n ' if (regex.test(paramNames)) {',\n ' if (bestScore === 0) {',\n ' bestScore = 0.60;',\n ' matchedField = \\'parameters\\';',\n ' snippet = paramNames;',\n ' }',\n ' }',\n ' }',\n '',\n ' if (bestScore > 0) {',\n ' results.push({',\n ' tool_name: tool.name,',\n ' match_score: bestScore,',\n ' matched_field: matchedField,',\n ' snippet: snippet',\n ' });',\n ' }',\n '}',\n '',\n '// Sort by score (descending) and limit results',\n 'results.sort(function(a, b) { return b.match_score - a.match_score; });',\n 'var topResults = results.slice(0, maxResults);',\n '',\n '// Output as JSON',\n 'console.log(JSON.stringify({',\n ' tool_references: topResults.map(function(r) {',\n ' return {',\n ' tool_name: r.tool_name,',\n ' match_score: r.match_score,',\n ' matched_field: r.matched_field,',\n ' snippet: r.snippet',\n ' };',\n ' }),',\n ' total_tools_searched: tools.length,',\n ' pattern_used: pattern',\n '}));',\n ];\n return lines.join('\\n');\n}\n\n/**\n * Parses the search results from stdout JSON.\n * @param stdout - The stdout string containing JSON results\n * @returns Parsed search response\n */\nfunction parseSearchResults(stdout: string): t.ToolSearchResponse {\n const jsonMatch = stdout.trim();\n const parsed = JSON.parse(jsonMatch) as t.ToolSearchResponse;\n return parsed;\n}\n\n/**\n * Formats search results as structured JSON for efficient parsing.\n * @param searchResponse - The parsed search response\n * @param nameFormat - Whether to show 'full' names (tool_mcp_server) or 'base' names (tool only)\n * @returns JSON string with search results\n */\nfunction formatSearchResults(\n searchResponse: t.ToolSearchResponse,\n nameFormat: t.McpNameFormat = 'full'\n): string {\n const { tool_references, total_tools_searched, pattern_used } =\n searchResponse;\n const useFullName = nameFormat === 'full';\n\n const output = {\n found: tool_references.length,\n tools: tool_references.map((ref) => ({\n name: useFullName ? ref.tool_name : getBaseToolName(ref.tool_name),\n score: Number(ref.match_score.toFixed(2)),\n matched_in: ref.matched_field,\n snippet: ref.snippet,\n })),\n total_searched: total_tools_searched,\n query: pattern_used,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Extracts the base tool name (without MCP server suffix) from a full tool name.\n * @param toolName - The full tool name\n * @returns The base tool name without server suffix\n */\nfunction getBaseToolName(toolName: string): string {\n const delimiterIndex = toolName.indexOf(Constants.MCP_DELIMITER);\n if (delimiterIndex === -1) {\n return toolName;\n }\n return toolName.substring(0, delimiterIndex);\n}\n\n/**\n * Checks whether a tool has any defined parameters in its JSON schema.\n * @param parameters - The tool's JSON schema parameters\n * @returns true if the tool has at least one parameter property\n */\nfunction hasParams(parameters?: t.JsonSchemaType): boolean {\n return (\n parameters?.properties != null &&\n Object.keys(parameters.properties).length > 0\n );\n}\n\n/**\n * Generates a compact listing of deferred tools grouped by server.\n * Format: \"server: tool1, tool2(\\u2026), tool3\"\n * Tools with parameters are annotated with (\\u2026) to signal\n * that the LLM should discover the schema via tool_search before calling.\n * Non-MCP tools are grouped under \"other\".\n * @param toolRegistry - The tool registry\n * @param onlyDeferred - Whether to only include deferred tools\n * @returns Formatted string with tools grouped by server\n */\nfunction getDeferredToolsListing(\n toolRegistry: t.LCToolRegistry | undefined,\n onlyDeferred: boolean\n): string {\n if (!toolRegistry) {\n return '';\n }\n\n const toolsByServer: Record<string, string[]> = {};\n\n for (const lcTool of toolRegistry.values()) {\n if (onlyDeferred && lcTool.defer_loading !== true) {\n continue;\n }\n\n const toolName = lcTool.name;\n const serverName = extractMcpServerName(toolName) ?? 'other';\n const baseName = getBaseToolName(toolName);\n const displayName = hasParams(lcTool.parameters)\n ? `${baseName}(\\u2026)`\n : baseName;\n\n if (!(serverName in toolsByServer)) {\n toolsByServer[serverName] = [];\n }\n toolsByServer[serverName].push(displayName);\n }\n\n const serverNames = Object.keys(toolsByServer).sort((a, b) => {\n if (a === 'other') return 1;\n if (b === 'other') return -1;\n return a.localeCompare(b);\n });\n\n if (serverNames.length === 0) {\n return '';\n }\n\n const lines = serverNames.map(\n (server) => `${server}: ${toolsByServer[server].join(', ')}`\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Formats a server listing response as structured JSON.\n * NOTE: This is a PREVIEW only - tools are NOT discovered/loaded.\n * @param tools - Array of tool metadata from the server(s)\n * @param serverNames - The MCP server name(s)\n * @param nameFormat - Whether to show 'full' names (tool_mcp_server) or 'base' names (tool only)\n * @returns JSON string showing all tools grouped by server\n */\nfunction formatServerListing(\n tools: t.ToolMetadata[],\n serverNames: string | string[],\n nameFormat: t.McpNameFormat = 'full'\n): string {\n const servers = Array.isArray(serverNames) ? serverNames : [serverNames];\n const useFullName = nameFormat === 'full';\n\n if (tools.length === 0) {\n return JSON.stringify(\n {\n listing_mode: true,\n servers,\n total_tools: 0,\n tools_by_server: {},\n hint: 'No tools found from the specified MCP server(s).',\n },\n null,\n 2\n );\n }\n\n const toolsByServer: Record<\n string,\n Array<{ name: string; description: string }>\n > = {};\n for (const tool of tools) {\n const server = extractMcpServerName(tool.name) ?? 'unknown';\n if (!(server in toolsByServer)) {\n toolsByServer[server] = [];\n }\n toolsByServer[server].push({\n name: useFullName ? tool.name : getBaseToolName(tool.name),\n description:\n tool.description.length > 100\n ? tool.description.substring(0, 97) + '...'\n : tool.description,\n });\n }\n\n const exampleToolName = useFullName\n ? (tools[0]?.name ?? 'tool_name')\n : getBaseToolName(tools[0]?.name ?? 'tool_name');\n\n const output = {\n listing_mode: true,\n servers,\n total_tools: tools.length,\n tools_by_server: toolsByServer,\n hint: `To use a tool, search for it by name (e.g., query: \"${exampleToolName}\") to load it.`,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Creates a Tool Search tool for discovering tools from a large registry.\n *\n * This tool enables AI agents to dynamically discover tools from a large library\n * without loading all tool definitions into the LLM context window. The agent\n * can search for relevant tools on-demand.\n *\n * **Modes:**\n * - `code_interpreter` (default): Uses external sandbox for regex search. Safer for complex patterns.\n * - `local`: Uses safe substring matching locally. No network call, faster, completely safe from ReDoS.\n *\n * The tool registry can be provided either:\n * 1. At initialization time via params.toolRegistry\n * 2. At runtime via config.configurable.toolRegistry when invoking\n *\n * @param params - Configuration parameters for the tool (toolRegistry is optional)\n * @returns A LangChain DynamicStructuredTool for tool searching\n *\n * @example\n * // Option 1: Code interpreter mode (regex via sandbox)\n * const tool = createToolSearch({ toolRegistry });\n * await tool.invoke({ query: 'expense.*report' });\n *\n * @example\n * // Option 2: Local mode (safe substring search)\n * const tool = createToolSearch({ mode: 'local', toolRegistry });\n * await tool.invoke({ query: 'expense' });\n */\nfunction createToolSearch(\n initParams: t.ToolSearchParams = {}\n): DynamicStructuredTool {\n const mode: t.ToolSearchMode = initParams.mode ?? 'code_interpreter';\n const defaultOnlyDeferred = initParams.onlyDeferred ?? true;\n const mcpNameFormat: t.McpNameFormat = initParams.mcpNameFormat ?? 'full';\n const schema = createToolSearchSchema(mode);\n\n const baseEndpoint = initParams.baseUrl ?? getCodeBaseURL();\n const EXEC_ENDPOINT = `${baseEndpoint}/exec`;\n\n const deferredToolsListing = getDeferredToolsListing(\n initParams.toolRegistry,\n defaultOnlyDeferred\n );\n\n const toolsListSection =\n deferredToolsListing.length > 0\n ? `\n\nDeferred tools (search to load; \\u2026 = has params, search first):\n${deferredToolsListing}`\n : '';\n\n const mcpNote =\n deferredToolsListing.includes(Constants.MCP_DELIMITER) ||\n deferredToolsListing.split('\\n').some((line) => !line.startsWith('other:'))\n ? `\n- MCP tools use format: toolName${Constants.MCP_DELIMITER}serverName\n- Use mcp_server param to filter by server`\n : '';\n\n const description =\n mode === 'local'\n ? `\nSearches deferred tools using BM25 ranking. Multi-word queries supported.\n${mcpNote}${toolsListSection}\n`.trim()\n : `\nSearches deferred tools by regex pattern.\n${mcpNote}${toolsListSection}\n`.trim();\n\n return tool(\n async (rawParams, config) => {\n const params = rawParams as ToolSearchParams;\n const {\n query = '',\n fields = ['name', 'description'],\n max_results = DEFAULT_MAX_RESULTS,\n mcp_server,\n } = params;\n\n const {\n toolRegistry: paramToolRegistry,\n onlyDeferred: paramOnlyDeferred,\n mcpServer: paramMcpServer,\n } = config.toolCall ?? {};\n\n const toolRegistry = paramToolRegistry ?? initParams.toolRegistry;\n const onlyDeferred =\n paramOnlyDeferred !== undefined\n ? paramOnlyDeferred\n : defaultOnlyDeferred;\n const rawServerFilter =\n mcp_server ?? paramMcpServer ?? initParams.mcpServer;\n const serverFilters = normalizeServerFilter(rawServerFilter);\n const hasServerFilter = serverFilters.length > 0;\n\n if (toolRegistry == null) {\n return [\n 'Error: No tool registry provided. Configure toolRegistry at agent level or initialization.',\n {\n tool_references: [],\n metadata: {\n total_searched: 0,\n pattern: query,\n error: 'No tool registry provided',\n },\n },\n ];\n }\n\n const toolsArray: t.LCTool[] = Array.from(toolRegistry.values());\n const deferredTools: t.ToolMetadata[] = toolsArray\n .filter((lcTool) => {\n if (onlyDeferred === true && lcTool.defer_loading !== true) {\n return false;\n }\n if (\n hasServerFilter &&\n !isFromAnyMcpServer(lcTool.name, serverFilters)\n ) {\n return false;\n }\n return true;\n })\n .map((lcTool) => ({\n name: lcTool.name,\n description: lcTool.description ?? '',\n parameters: simplifyParametersForSearch(lcTool.parameters),\n }));\n\n if (deferredTools.length === 0) {\n const serverMsg = hasServerFilter\n ? ` from MCP server(s): ${serverFilters.join(', ')}`\n : '';\n return [\n `No tools available to search${serverMsg}. The tool registry is empty or no matching deferred tools are registered.`,\n {\n tool_references: [],\n metadata: {\n total_searched: 0,\n pattern: query,\n mcp_server: serverFilters,\n },\n },\n ];\n }\n\n const isServerListing = hasServerFilter && query === '';\n\n if (isServerListing) {\n const formattedOutput = formatServerListing(\n deferredTools,\n serverFilters,\n mcpNameFormat\n );\n\n return [\n formattedOutput,\n {\n tool_references: [],\n metadata: {\n total_available: deferredTools.length,\n mcp_server: serverFilters,\n listing_mode: true,\n },\n },\n ];\n }\n\n if (mode === 'local') {\n const searchResponse = performLocalSearch(\n deferredTools,\n query,\n fields,\n max_results\n );\n const formattedOutput = formatSearchResults(\n searchResponse,\n mcpNameFormat\n );\n\n return [\n formattedOutput,\n {\n tool_references: searchResponse.tool_references,\n metadata: {\n total_searched: searchResponse.total_tools_searched,\n pattern: searchResponse.pattern_used,\n mcp_server: serverFilters.length > 0 ? serverFilters : undefined,\n },\n },\n ];\n }\n\n const { safe: sanitizedPattern, wasEscaped } = sanitizeRegex(query);\n let warningMessage = '';\n if (wasEscaped) {\n warningMessage =\n 'Note: The provided pattern was converted to a literal search for safety.\\n\\n';\n }\n\n const searchScript = generateSearchScript(\n deferredTools,\n fields,\n max_results,\n sanitizedPattern\n );\n\n const postData = {\n lang: 'js',\n code: searchScript,\n timeout: SEARCH_TIMEOUT,\n };\n\n try {\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'LibreChat/1.0',\n },\n body: JSON.stringify(postData),\n };\n\n if (process.env.PROXY != null && process.env.PROXY !== '') {\n fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);\n }\n\n const response = await fetch(EXEC_ENDPOINT, fetchOptions);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const result: t.ExecuteResult = await response.json();\n\n if (result.stderr && result.stderr.trim()) {\n // eslint-disable-next-line no-console\n console.warn('[ToolSearch] stderr:', result.stderr);\n }\n\n if (!result.stdout || !result.stdout.trim()) {\n return [\n `${warningMessage}No tools matched the pattern \"${sanitizedPattern}\".\\nTotal tools searched: ${deferredTools.length}`,\n {\n tool_references: [],\n metadata: {\n total_searched: deferredTools.length,\n pattern: sanitizedPattern,\n },\n },\n ];\n }\n\n const searchResponse = parseSearchResults(result.stdout);\n const formattedOutput = `${warningMessage}${formatSearchResults(searchResponse, mcpNameFormat)}`;\n\n return [\n formattedOutput,\n {\n tool_references: searchResponse.tool_references,\n metadata: {\n total_searched: searchResponse.total_tools_searched,\n pattern: searchResponse.pattern_used,\n },\n },\n ];\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[ToolSearch] Error:', error);\n\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n return [\n `Tool search failed: ${errorMessage}\\n\\nSuggestion: Try a simpler search pattern or search for specific tool names.`,\n {\n tool_references: [],\n metadata: {\n total_searched: 0,\n pattern: sanitizedPattern,\n error: errorMessage,\n },\n },\n ];\n }\n },\n {\n name: Constants.TOOL_SEARCH,\n description,\n schema,\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport {\n createToolSearch,\n performLocalSearch,\n extractMcpServerName,\n isFromMcpServer,\n isFromAnyMcpServer,\n normalizeServerFilter,\n getAvailableMcpServers,\n getDeferredToolsListing,\n getBaseToolName,\n formatServerListing,\n sanitizeRegex,\n escapeRegexSpecialChars,\n isDangerousPattern,\n countNestedGroups,\n hasNestedQuantifiers,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ToolSearch.cjs","names":["okapibm25Module","getCodeBaseURL","params","HttpsProxyAgent"],"sources":["../../../src/tools/ToolSearch.ts"],"sourcesContent":["// src/tools/ToolSearch.ts\nimport { config } from 'dotenv';\nimport * as okapibm25Module from 'okapibm25';\n\ntype BM25Fn = (\n documents: string[],\n keywords: string[],\n constants?: { k1?: number; b?: number }\n) => number[];\n\nfunction getBM25Function(): BM25Fn {\n const mod = okapibm25Module as unknown as {\n default: BM25Fn | { default: BM25Fn } | undefined;\n };\n if (typeof mod === 'function') return mod;\n if (typeof mod.default === 'function') return mod.default;\n if (mod.default != null && typeof mod.default.default === 'function')\n return mod.default.default;\n throw new Error('Could not resolve BM25 function from okapibm25 module');\n}\n\nconst BM25 = getBM25Function();\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type * as t from '@/types';\nimport { getCodeBaseURL } from './CodeExecutor';\nimport { Constants } from '@/common';\n\nconfig();\n\n/** Maximum allowed regex pattern length */\nconst MAX_PATTERN_LENGTH = 200;\n\nexport const ToolSearchToolName = Constants.TOOL_SEARCH;\n\nexport const ToolSearchToolDescription =\n 'Searches deferred tools using BM25 ranking. Multi-word queries supported. Use mcp_server param to filter by server.';\n\nconst QUERY_DESCRIPTION_LOCAL =\n 'Search term to find in tool names and descriptions. Case-insensitive substring matching. Optional if mcp_server is provided.';\nconst QUERY_DESCRIPTION_REGEX =\n 'Regex pattern to search tool names and descriptions. Optional if mcp_server is provided.';\nconst FIELDS_DESCRIPTION =\n 'Which fields to search. Default: name and description';\nconst MAX_RESULTS_DESCRIPTION = 'Maximum number of matching tools to return';\nconst DEFAULT_MAX_RESULTS = 5;\nconst MCP_SERVER_DESCRIPTION =\n '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.';\n\nexport const ToolSearchToolSchema = {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n maxLength: MAX_PATTERN_LENGTH,\n default: '',\n description: QUERY_DESCRIPTION_LOCAL,\n },\n fields: {\n type: 'array',\n items: { type: 'string', enum: ['name', 'description', 'parameters'] },\n default: ['name', 'description'],\n description: FIELDS_DESCRIPTION,\n },\n max_results: {\n type: 'integer',\n minimum: 1,\n maximum: 50,\n default: DEFAULT_MAX_RESULTS,\n description: MAX_RESULTS_DESCRIPTION,\n },\n mcp_server: {\n oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],\n description: MCP_SERVER_DESCRIPTION,\n },\n },\n required: [],\n} as const;\n\nexport const ToolSearchToolDefinition = {\n name: ToolSearchToolName,\n description: ToolSearchToolDescription,\n schema: ToolSearchToolSchema,\n} as const;\n\n/** Maximum allowed regex nesting depth */\nconst MAX_REGEX_COMPLEXITY = 5;\n\n/** Default search timeout in milliseconds */\nconst SEARCH_TIMEOUT = 5000;\n\n/** JSON schema type for tool search parameters */\ninterface ToolSearchSchema {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n}\n\n/** Input params type for tool search */\ninterface ToolSearchParams {\n query?: string;\n fields?: ('name' | 'description' | 'parameters')[];\n max_results?: number;\n mcp_server?: string | string[];\n}\n\n/**\n * Creates the JSON schema with dynamic query description based on mode.\n * @param mode - The search mode determining query interpretation\n * @returns JSON schema for tool search parameters\n */\nfunction createToolSearchSchema(mode: t.ToolSearchMode): ToolSearchSchema {\n const queryDescription =\n mode === 'local' ? QUERY_DESCRIPTION_LOCAL : QUERY_DESCRIPTION_REGEX;\n\n return {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n maxLength: MAX_PATTERN_LENGTH,\n default: '',\n description: queryDescription,\n },\n fields: {\n type: 'array',\n items: { type: 'string', enum: ['name', 'description', 'parameters'] },\n default: ['name', 'description'],\n description: FIELDS_DESCRIPTION,\n },\n max_results: {\n type: 'integer',\n minimum: 1,\n maximum: 50,\n default: DEFAULT_MAX_RESULTS,\n description: MAX_RESULTS_DESCRIPTION,\n },\n mcp_server: {\n oneOf: [\n { type: 'string' },\n { type: 'array', items: { type: 'string' } },\n ],\n description: MCP_SERVER_DESCRIPTION,\n },\n },\n required: [],\n };\n}\n\n/**\n * Extracts the MCP server name from a tool name.\n * MCP tools follow the pattern: toolName_mcp_serverName\n * @param toolName - The full tool name\n * @returns The server name if it's an MCP tool, undefined otherwise\n */\nfunction extractMcpServerName(toolName: string): string | undefined {\n const delimiterIndex = toolName.indexOf(Constants.MCP_DELIMITER);\n if (delimiterIndex === -1) {\n return undefined;\n }\n return toolName.substring(delimiterIndex + Constants.MCP_DELIMITER.length);\n}\n\n/**\n * Checks if a tool belongs to a specific MCP server.\n * @param toolName - The full tool name\n * @param serverName - The server name to match\n * @returns True if the tool belongs to the specified server\n */\nfunction isFromMcpServer(toolName: string, serverName: string): boolean {\n const toolServer = extractMcpServerName(toolName);\n return toolServer === serverName;\n}\n\n/**\n * Checks if a tool belongs to any of the specified MCP servers.\n * @param toolName - The full tool name\n * @param serverNames - Array of server names to match\n * @returns True if the tool belongs to any of the specified servers\n */\nfunction isFromAnyMcpServer(toolName: string, serverNames: string[]): boolean {\n const toolServer = extractMcpServerName(toolName);\n if (toolServer === undefined) {\n return false;\n }\n return serverNames.includes(toolServer);\n}\n\n/**\n * Normalizes server filter input to always be an array.\n * @param serverFilter - String, array of strings, or undefined\n * @returns Array of server names (empty if none specified)\n */\nfunction normalizeServerFilter(\n serverFilter: string | string[] | undefined\n): string[] {\n if (serverFilter === undefined) {\n return [];\n }\n if (typeof serverFilter === 'string') {\n return serverFilter === '' ? [] : [serverFilter];\n }\n return serverFilter.filter((s) => s !== '');\n}\n\n/**\n * Extracts all unique MCP server names from a tool registry.\n * @param toolRegistry - The tool registry to scan\n * @param onlyDeferred - If true, only considers deferred tools\n * @returns Array of unique server names, sorted alphabetically\n */\nfunction getAvailableMcpServers(\n toolRegistry: t.LCToolRegistry | undefined,\n onlyDeferred: boolean = true\n): string[] {\n if (!toolRegistry) {\n return [];\n }\n\n const servers = new Set<string>();\n for (const [, toolDef] of toolRegistry) {\n if (onlyDeferred && toolDef.defer_loading !== true) {\n continue;\n }\n const server = extractMcpServerName(toolDef.name);\n if (server !== undefined && server !== '') {\n servers.add(server);\n }\n }\n\n return Array.from(servers).sort();\n}\n\n/**\n * Escapes special regex characters in a string to use as a literal pattern.\n * @param pattern - The string to escape\n * @returns The escaped string safe for use in a RegExp\n */\nfunction escapeRegexSpecialChars(pattern: string): string {\n return pattern.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Counts the maximum nesting depth of groups in a regex pattern.\n * @param pattern - The regex pattern to analyze\n * @returns The maximum nesting depth\n */\nfunction countNestedGroups(pattern: string): number {\n let maxDepth = 0;\n let currentDepth = 0;\n\n for (let i = 0; i < pattern.length; i++) {\n if (pattern[i] === '(' && (i === 0 || pattern[i - 1] !== '\\\\')) {\n currentDepth++;\n maxDepth = Math.max(maxDepth, currentDepth);\n } else if (pattern[i] === ')' && (i === 0 || pattern[i - 1] !== '\\\\')) {\n currentDepth = Math.max(0, currentDepth - 1);\n }\n }\n\n return maxDepth;\n}\n\n/**\n * Detects nested quantifiers that can cause catastrophic backtracking.\n * Patterns like (a+)+, (a*)*, (a+)*, etc.\n * @param pattern - The regex pattern to check\n * @returns True if nested quantifiers are detected\n */\nfunction hasNestedQuantifiers(pattern: string): boolean {\n const nestedQuantifierPattern = /\\([^)]*[+*][^)]*\\)[+*?]/;\n return nestedQuantifierPattern.test(pattern);\n}\n\n/**\n * Checks if a regex pattern contains potentially dangerous constructs.\n * @param pattern - The regex pattern to validate\n * @returns True if the pattern is dangerous\n */\nfunction isDangerousPattern(pattern: string): boolean {\n if (hasNestedQuantifiers(pattern)) {\n return true;\n }\n\n if (countNestedGroups(pattern) > MAX_REGEX_COMPLEXITY) {\n return true;\n }\n\n const dangerousPatterns = [\n /\\.\\{1000,\\}/, // Excessive wildcards\n /\\(\\?=\\.\\{100,\\}\\)/, // Runaway lookaheads\n /\\([^)]*\\|\\s*\\){20,}/, // Excessive alternation (rough check)\n /\\(\\.\\*\\)\\+/, // (.*)+\n /\\(\\.\\+\\)\\+/, // (.+)+\n /\\(\\.\\*\\)\\*/, // (.*)*\n /\\(\\.\\+\\)\\*/, // (.+)*\n ];\n\n for (const dangerous of dangerousPatterns) {\n if (dangerous.test(pattern)) {\n return true;\n }\n }\n\n return false;\n}\n\n/**\n * Sanitizes a regex pattern for safe execution.\n * If the pattern is dangerous, it will be escaped to a literal string search.\n * @param pattern - The regex pattern to sanitize\n * @returns Object containing the safe pattern and whether it was escaped\n */\nfunction sanitizeRegex(pattern: string): { safe: string; wasEscaped: boolean } {\n if (isDangerousPattern(pattern)) {\n return {\n safe: escapeRegexSpecialChars(pattern),\n wasEscaped: true,\n };\n }\n\n try {\n new RegExp(pattern);\n return { safe: pattern, wasEscaped: false };\n } catch {\n return {\n safe: escapeRegexSpecialChars(pattern),\n wasEscaped: true,\n };\n }\n}\n\n/**\n * Simplifies tool parameters for search purposes.\n * Extracts only the essential structure needed for parameter name searching.\n * @param parameters - The tool's JSON schema parameters\n * @returns Simplified parameters object\n */\nfunction simplifyParametersForSearch(\n parameters?: t.JsonSchemaType\n): t.JsonSchemaType | undefined {\n if (!parameters) {\n return undefined;\n }\n\n if (parameters.properties) {\n return {\n type: parameters.type,\n properties: Object.fromEntries(\n Object.entries(parameters.properties).map(([key, value]) => [\n key,\n { type: (value as t.JsonSchemaType).type },\n ])\n ),\n } as t.JsonSchemaType;\n }\n\n return { type: parameters.type };\n}\n\n/**\n * Splits one alphanumeric identifier segment on case boundaries without\n * emitting artificial one-character acronym fragments.\n */\nfunction splitCaseSegment(segment: string): string[] {\n const splitTokens = segment\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .toLowerCase()\n .split(/\\s+/)\n .filter(Boolean);\n\n if (splitTokens.length < 2) return splitTokens;\n\n const mergedTokens: string[] = [];\n let prefix = '';\n for (const token of splitTokens) {\n if (token.length === 1) {\n prefix += token;\n continue;\n }\n mergedTokens.push(`${prefix}${token}`);\n prefix = '';\n }\n\n if (prefix && mergedTokens.length > 0) {\n mergedTokens[mergedTokens.length - 1] += prefix;\n }\n return mergedTokens.length > 0 ? mergedTokens : [prefix];\n}\n\n/**\n * Tokenizes a string into lowercase words for BM25.\n * Splits camelCase, underscores, and non-alphanumeric characters for consistent matching.\n * @param text - The text to tokenize\n * @returns Array of lowercase tokens\n */\nfunction tokenize(text: string): string[] {\n return text\n .split(/[^a-zA-Z0-9]+/)\n .filter(Boolean)\n .flatMap(splitCaseSegment);\n}\n\n/**\n * Creates a searchable document string from tool metadata.\n * @param tool - The tool metadata\n * @param fields - Which fields to include\n * @returns Combined document string for BM25\n */\nfunction createToolDocument(tool: t.ToolMetadata, fields: string[]): string {\n const parts: string[] = [];\n\n if (fields.includes('name')) {\n const baseName = tool.name.replace(/_/g, ' ');\n parts.push(baseName, baseName);\n }\n\n if (fields.includes('description') && tool.description) {\n parts.push(tool.description);\n }\n\n if (fields.includes('parameters') && tool.parameters?.properties) {\n const paramNames = Object.keys(tool.parameters.properties).join(' ');\n parts.push(paramNames);\n }\n\n return tokenize(parts.join(' ')).join(' ');\n}\n\n/**\n * Determines which field had the best match for a query.\n * @param tool - The tool to check\n * @param queryTokens - Tokenized query\n * @param fields - Fields to check\n * @returns The matched field and a snippet\n */\nfunction findMatchedField(\n tool: t.ToolMetadata,\n queryTokens: string[],\n fields: string[]\n): { field: string; snippet: string } {\n if (fields.includes('name')) {\n const nameLower = tool.name.toLowerCase();\n for (const token of queryTokens) {\n if (nameLower.includes(token)) {\n return { field: 'name', snippet: tool.name };\n }\n }\n }\n\n if (fields.includes('description') && tool.description) {\n const descLower = tool.description.toLowerCase();\n for (const token of queryTokens) {\n if (descLower.includes(token)) {\n return {\n field: 'description',\n snippet: tool.description.substring(0, 100),\n };\n }\n }\n }\n\n if (fields.includes('parameters') && tool.parameters?.properties) {\n const paramNames = Object.keys(tool.parameters.properties);\n const paramLower = paramNames.join(' ').toLowerCase();\n for (const token of queryTokens) {\n if (paramLower.includes(token)) {\n return { field: 'parameters', snippet: paramNames.join(', ') };\n }\n }\n }\n\n const fallbackSnippet = tool.description\n ? tool.description.substring(0, 100)\n : tool.name;\n return { field: 'unknown', snippet: fallbackSnippet };\n}\n\n/**\n * Performs BM25-based search for better relevance ranking.\n * Uses Okapi BM25 algorithm for term frequency and document length normalization.\n * If query is empty, returns all tools (up to maxResults) sorted alphabetically.\n * @param tools - Array of tool metadata to search\n * @param query - The search query (empty returns all tools)\n * @param fields - Which fields to search\n * @param maxResults - Maximum results to return\n * @returns Search response with matching tools ranked by BM25 score\n */\nfunction performLocalSearch(\n tools: t.ToolMetadata[],\n query: string,\n fields: string[],\n maxResults: number\n): t.ToolSearchResponse {\n if (tools.length === 0) {\n return {\n tool_references: [],\n total_tools_searched: 0,\n pattern_used: query,\n };\n }\n\n const queryTokens = tokenize(query);\n\n if (queryTokens.length === 0) {\n const allTools = tools\n .slice()\n .sort((a, b) => a.name.localeCompare(b.name))\n .slice(0, maxResults)\n .map((tool) => ({\n tool_name: tool.name,\n match_score: 1.0,\n matched_field: 'name',\n snippet: tool.description.substring(0, 100) || tool.name,\n }));\n\n return {\n tool_references: allTools,\n total_tools_searched: tools.length,\n pattern_used: query,\n };\n }\n\n const documents = tools.map((tool) => createToolDocument(tool, fields));\n const scores = BM25(documents, queryTokens, { k1: 1.5, b: 0.75 }) as number[];\n\n const maxScore = Math.max(...scores.filter((s) => s > 0), 1);\n const queryLower = query.toLowerCase().trim();\n const queryIdentifier = queryTokens.join('');\n const matchesIdentifiers = fields.includes('name');\n\n const results: Array<{\n result: t.ToolSearchResult;\n identifierPriority: number;\n }> = [];\n for (let i = 0; i < tools.length; i++) {\n const score = scores[i];\n const hasSearchScore = Number.isFinite(score) && score > 0;\n let identifierPriority = 0;\n let normalizedScore = hasSearchScore ? Math.min(score / maxScore, 1.0) : 0;\n\n if (matchesIdentifiers) {\n const rawBaseName = getBaseToolName(tools[i].name).toLowerCase();\n const rawFullName = tools[i].name.toLowerCase();\n const baseIdentifier = tokenize(rawBaseName).join('');\n const fullIdentifier = tokenize(rawFullName).join('');\n\n if (rawFullName === queryLower) {\n identifierPriority = 4;\n normalizedScore = 1.0;\n } else if (rawBaseName === queryLower) {\n identifierPriority = 3;\n normalizedScore = 1.0;\n } else if (\n baseIdentifier === queryIdentifier ||\n fullIdentifier === queryIdentifier\n ) {\n identifierPriority = 2;\n normalizedScore = 1.0;\n } else if (baseIdentifier.startsWith(queryIdentifier)) {\n identifierPriority = 1;\n normalizedScore = Math.max(normalizedScore, 0.95);\n }\n }\n\n if (!hasSearchScore && identifierPriority === 0) continue;\n\n const { field, snippet } = findMatchedField(tools[i], queryTokens, fields);\n results.push({\n result: {\n tool_name: tools[i].name,\n match_score: normalizedScore,\n matched_field: field,\n snippet,\n },\n identifierPriority,\n });\n }\n\n results.sort(\n (a, b) =>\n b.identifierPriority - a.identifierPriority ||\n b.result.match_score - a.result.match_score\n );\n const topResults = results.slice(0, maxResults).map(({ result }) => result);\n\n return {\n tool_references: topResults,\n total_tools_searched: tools.length,\n pattern_used: query,\n };\n}\n\n/**\n * Generates the JavaScript search script to be executed in the sandbox.\n * Uses plain JavaScript for maximum compatibility with the Code API.\n * @param deferredTools - Array of tool metadata to search through\n * @param fields - Which fields to search\n * @param maxResults - Maximum number of results to return\n * @param sanitizedPattern - The sanitized regex pattern\n * @returns The JavaScript code string\n */\nfunction generateSearchScript(\n deferredTools: t.ToolMetadata[],\n fields: string[],\n maxResults: number,\n sanitizedPattern: string\n): string {\n const lines = [\n '// Tool definitions (injected)',\n 'var tools = ' + JSON.stringify(deferredTools) + ';',\n 'var searchFields = ' + JSON.stringify(fields) + ';',\n 'var maxResults = ' + maxResults + ';',\n 'var pattern = ' + JSON.stringify(sanitizedPattern) + ';',\n '',\n '// Compile regex (pattern is sanitized client-side)',\n 'var regex;',\n 'try {',\n ' regex = new RegExp(pattern, \\'i\\');',\n '} catch (e) {',\n ' regex = new RegExp(pattern.replace(/[.*+?^${}()[\\\\]\\\\\\\\|]/g, \"\\\\\\\\$&\"), \"i\");',\n '}',\n '',\n '// Search logic',\n 'var results = [];',\n '',\n 'for (var j = 0; j < tools.length; j++) {',\n ' var tool = tools[j];',\n ' var bestScore = 0;',\n ' var matchedField = \\'\\';',\n ' var snippet = \\'\\';',\n '',\n ' // Search name (highest priority)',\n ' if (searchFields.indexOf(\\'name\\') >= 0 && regex.test(tool.name)) {',\n ' bestScore = 0.95;',\n ' matchedField = \\'name\\';',\n ' snippet = tool.name;',\n ' }',\n '',\n ' // Search description (medium priority)',\n ' if (searchFields.indexOf(\\'description\\') >= 0 && tool.description && regex.test(tool.description)) {',\n ' if (bestScore === 0) {',\n ' bestScore = 0.75;',\n ' matchedField = \\'description\\';',\n ' snippet = tool.description.substring(0, 100);',\n ' }',\n ' }',\n '',\n ' // Search parameter names (lower priority)',\n ' if (searchFields.indexOf(\\'parameters\\') >= 0 && tool.parameters && tool.parameters.properties) {',\n ' var paramNames = Object.keys(tool.parameters.properties).join(\\' \\');',\n ' if (regex.test(paramNames)) {',\n ' if (bestScore === 0) {',\n ' bestScore = 0.60;',\n ' matchedField = \\'parameters\\';',\n ' snippet = paramNames;',\n ' }',\n ' }',\n ' }',\n '',\n ' if (bestScore > 0) {',\n ' results.push({',\n ' tool_name: tool.name,',\n ' match_score: bestScore,',\n ' matched_field: matchedField,',\n ' snippet: snippet',\n ' });',\n ' }',\n '}',\n '',\n '// Sort by score (descending) and limit results',\n 'results.sort(function(a, b) { return b.match_score - a.match_score; });',\n 'var topResults = results.slice(0, maxResults);',\n '',\n '// Output as JSON',\n 'console.log(JSON.stringify({',\n ' tool_references: topResults.map(function(r) {',\n ' return {',\n ' tool_name: r.tool_name,',\n ' match_score: r.match_score,',\n ' matched_field: r.matched_field,',\n ' snippet: r.snippet',\n ' };',\n ' }),',\n ' total_tools_searched: tools.length,',\n ' pattern_used: pattern',\n '}));',\n ];\n return lines.join('\\n');\n}\n\n/**\n * Parses the search results from stdout JSON.\n * @param stdout - The stdout string containing JSON results\n * @returns Parsed search response\n */\nfunction parseSearchResults(stdout: string): t.ToolSearchResponse {\n const jsonMatch = stdout.trim();\n const parsed = JSON.parse(jsonMatch) as t.ToolSearchResponse;\n return parsed;\n}\n\n/**\n * Formats search results as structured JSON for efficient parsing.\n * @param searchResponse - The parsed search response\n * @param nameFormat - Whether to show 'full' names (tool_mcp_server) or 'base' names (tool only)\n * @returns JSON string with search results\n */\nfunction formatSearchResults(\n searchResponse: t.ToolSearchResponse,\n nameFormat: t.McpNameFormat = 'full'\n): string {\n const { tool_references, total_tools_searched, pattern_used } =\n searchResponse;\n const useFullName = nameFormat === 'full';\n\n const output = {\n found: tool_references.length,\n tools: tool_references.map((ref) => ({\n name: useFullName ? ref.tool_name : getBaseToolName(ref.tool_name),\n score: Number(ref.match_score.toFixed(2)),\n matched_in: ref.matched_field,\n snippet: ref.snippet,\n })),\n total_searched: total_tools_searched,\n query: pattern_used,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Extracts the base tool name (without MCP server suffix) from a full tool name.\n * @param toolName - The full tool name\n * @returns The base tool name without server suffix\n */\nfunction getBaseToolName(toolName: string): string {\n const delimiterIndex = toolName.indexOf(Constants.MCP_DELIMITER);\n if (delimiterIndex === -1) {\n return toolName;\n }\n return toolName.substring(0, delimiterIndex);\n}\n\n/**\n * Checks whether a tool has any defined parameters in its JSON schema.\n * @param parameters - The tool's JSON schema parameters\n * @returns true if the tool has at least one parameter property\n */\nfunction hasParams(parameters?: t.JsonSchemaType): boolean {\n return (\n parameters?.properties != null &&\n Object.keys(parameters.properties).length > 0\n );\n}\n\n/**\n * Generates a compact listing of deferred tools grouped by server.\n * Format: \"server: tool1, tool2(\\u2026), tool3\"\n * Tools with parameters are annotated with (\\u2026) to signal\n * that the LLM should discover the schema via tool_search before calling.\n * Non-MCP tools are grouped under \"other\".\n * @param toolRegistry - The tool registry\n * @param onlyDeferred - Whether to only include deferred tools\n * @returns Formatted string with tools grouped by server\n */\nfunction getDeferredToolsListing(\n toolRegistry: t.LCToolRegistry | undefined,\n onlyDeferred: boolean\n): string {\n if (!toolRegistry) {\n return '';\n }\n\n const toolsByServer: Record<string, string[]> = {};\n\n for (const lcTool of toolRegistry.values()) {\n if (onlyDeferred && lcTool.defer_loading !== true) {\n continue;\n }\n\n const toolName = lcTool.name;\n const serverName = extractMcpServerName(toolName) ?? 'other';\n const baseName = getBaseToolName(toolName);\n const displayName = hasParams(lcTool.parameters)\n ? `${baseName}(\\u2026)`\n : baseName;\n\n if (!(serverName in toolsByServer)) {\n toolsByServer[serverName] = [];\n }\n toolsByServer[serverName].push(displayName);\n }\n\n const serverNames = Object.keys(toolsByServer).sort((a, b) => {\n if (a === 'other') return 1;\n if (b === 'other') return -1;\n return a.localeCompare(b);\n });\n\n if (serverNames.length === 0) {\n return '';\n }\n\n const lines = serverNames.map(\n (server) => `${server}: ${toolsByServer[server].join(', ')}`\n );\n\n return lines.join('\\n');\n}\n\n/**\n * Formats a server listing response as structured JSON.\n * NOTE: This is a PREVIEW only - tools are NOT discovered/loaded.\n * @param tools - Array of tool metadata from the server(s)\n * @param serverNames - The MCP server name(s)\n * @param nameFormat - Whether to show 'full' names (tool_mcp_server) or 'base' names (tool only)\n * @returns JSON string showing all tools grouped by server\n */\nfunction formatServerListing(\n tools: t.ToolMetadata[],\n serverNames: string | string[],\n nameFormat: t.McpNameFormat = 'full'\n): string {\n const servers = Array.isArray(serverNames) ? serverNames : [serverNames];\n const useFullName = nameFormat === 'full';\n\n if (tools.length === 0) {\n return JSON.stringify(\n {\n listing_mode: true,\n servers,\n total_tools: 0,\n tools_by_server: {},\n hint: 'No tools found from the specified MCP server(s).',\n },\n null,\n 2\n );\n }\n\n const toolsByServer: Record<\n string,\n Array<{ name: string; description: string }>\n > = {};\n for (const tool of tools) {\n const server = extractMcpServerName(tool.name) ?? 'unknown';\n if (!(server in toolsByServer)) {\n toolsByServer[server] = [];\n }\n toolsByServer[server].push({\n name: useFullName ? tool.name : getBaseToolName(tool.name),\n description:\n tool.description.length > 100\n ? tool.description.substring(0, 97) + '...'\n : tool.description,\n });\n }\n\n const exampleToolName = useFullName\n ? (tools[0]?.name ?? 'tool_name')\n : getBaseToolName(tools[0]?.name ?? 'tool_name');\n\n const output = {\n listing_mode: true,\n servers,\n total_tools: tools.length,\n tools_by_server: toolsByServer,\n hint: `To use a tool, search for it by name (e.g., query: \"${exampleToolName}\") to load it.`,\n };\n\n return JSON.stringify(output, null, 2);\n}\n\n/**\n * Creates a Tool Search tool for discovering tools from a large registry.\n *\n * This tool enables AI agents to dynamically discover tools from a large library\n * without loading all tool definitions into the LLM context window. The agent\n * can search for relevant tools on-demand.\n *\n * **Modes:**\n * - `code_interpreter` (default): Uses external sandbox for regex search. Safer for complex patterns.\n * - `local`: Uses safe substring matching locally. No network call, faster, completely safe from ReDoS.\n *\n * The tool registry can be provided either:\n * 1. At initialization time via params.toolRegistry\n * 2. At runtime via config.configurable.toolRegistry when invoking\n *\n * @param params - Configuration parameters for the tool (toolRegistry is optional)\n * @returns A LangChain DynamicStructuredTool for tool searching\n *\n * @example\n * // Option 1: Code interpreter mode (regex via sandbox)\n * const tool = createToolSearch({ toolRegistry });\n * await tool.invoke({ query: 'expense.*report' });\n *\n * @example\n * // Option 2: Local mode (safe substring search)\n * const tool = createToolSearch({ mode: 'local', toolRegistry });\n * await tool.invoke({ query: 'expense' });\n */\nfunction createToolSearch(\n initParams: t.ToolSearchParams = {}\n): DynamicStructuredTool {\n const mode: t.ToolSearchMode = initParams.mode ?? 'code_interpreter';\n const defaultOnlyDeferred = initParams.onlyDeferred ?? true;\n const mcpNameFormat: t.McpNameFormat = initParams.mcpNameFormat ?? 'full';\n const schema = createToolSearchSchema(mode);\n\n const baseEndpoint = initParams.baseUrl ?? getCodeBaseURL();\n const EXEC_ENDPOINT = `${baseEndpoint}/exec`;\n\n const deferredToolsListing = getDeferredToolsListing(\n initParams.toolRegistry,\n defaultOnlyDeferred\n );\n\n const toolsListSection =\n deferredToolsListing.length > 0\n ? `\n\nDeferred tools (search to load; \\u2026 = has params, search first):\n${deferredToolsListing}`\n : '';\n\n const mcpNote =\n deferredToolsListing.includes(Constants.MCP_DELIMITER) ||\n deferredToolsListing.split('\\n').some((line) => !line.startsWith('other:'))\n ? `\n- MCP tools use format: toolName${Constants.MCP_DELIMITER}serverName\n- Use mcp_server param to filter by server`\n : '';\n\n const description =\n mode === 'local'\n ? `\nSearches deferred tools using BM25 ranking. Multi-word queries supported.\n${mcpNote}${toolsListSection}\n`.trim()\n : `\nSearches deferred tools by regex pattern.\n${mcpNote}${toolsListSection}\n`.trim();\n\n return tool(\n async (rawParams, config) => {\n const params = rawParams as ToolSearchParams;\n const {\n query = '',\n fields = ['name', 'description'],\n max_results = DEFAULT_MAX_RESULTS,\n mcp_server,\n } = params;\n\n const {\n toolRegistry: paramToolRegistry,\n onlyDeferred: paramOnlyDeferred,\n mcpServer: paramMcpServer,\n } = config.toolCall ?? {};\n\n const toolRegistry = paramToolRegistry ?? initParams.toolRegistry;\n const onlyDeferred =\n paramOnlyDeferred !== undefined\n ? paramOnlyDeferred\n : defaultOnlyDeferred;\n const rawServerFilter =\n mcp_server ?? paramMcpServer ?? initParams.mcpServer;\n const serverFilters = normalizeServerFilter(rawServerFilter);\n const hasServerFilter = serverFilters.length > 0;\n\n if (toolRegistry == null) {\n return [\n 'Error: No tool registry provided. Configure toolRegistry at agent level or initialization.',\n {\n tool_references: [],\n metadata: {\n total_searched: 0,\n pattern: query,\n error: 'No tool registry provided',\n },\n },\n ];\n }\n\n const toolsArray: t.LCTool[] = Array.from(toolRegistry.values());\n const deferredTools: t.ToolMetadata[] = toolsArray\n .filter((lcTool) => {\n if (onlyDeferred === true && lcTool.defer_loading !== true) {\n return false;\n }\n if (\n hasServerFilter &&\n !isFromAnyMcpServer(lcTool.name, serverFilters)\n ) {\n return false;\n }\n return true;\n })\n .map((lcTool) => ({\n name: lcTool.name,\n description: lcTool.description ?? '',\n parameters: simplifyParametersForSearch(lcTool.parameters),\n }));\n\n if (deferredTools.length === 0) {\n const serverMsg = hasServerFilter\n ? ` from MCP server(s): ${serverFilters.join(', ')}`\n : '';\n return [\n `No tools available to search${serverMsg}. The tool registry is empty or no matching deferred tools are registered.`,\n {\n tool_references: [],\n metadata: {\n total_searched: 0,\n pattern: query,\n mcp_server: serverFilters,\n },\n },\n ];\n }\n\n const isServerListing = hasServerFilter && query === '';\n\n if (isServerListing) {\n const formattedOutput = formatServerListing(\n deferredTools,\n serverFilters,\n mcpNameFormat\n );\n\n return [\n formattedOutput,\n {\n tool_references: [],\n metadata: {\n total_available: deferredTools.length,\n mcp_server: serverFilters,\n listing_mode: true,\n },\n },\n ];\n }\n\n if (mode === 'local') {\n const searchResponse = performLocalSearch(\n deferredTools,\n query,\n fields,\n max_results\n );\n const formattedOutput = formatSearchResults(\n searchResponse,\n mcpNameFormat\n );\n\n return [\n formattedOutput,\n {\n tool_references: searchResponse.tool_references,\n metadata: {\n total_searched: searchResponse.total_tools_searched,\n pattern: searchResponse.pattern_used,\n mcp_server: serverFilters.length > 0 ? serverFilters : undefined,\n },\n },\n ];\n }\n\n const { safe: sanitizedPattern, wasEscaped } = sanitizeRegex(query);\n let warningMessage = '';\n if (wasEscaped) {\n warningMessage =\n 'Note: The provided pattern was converted to a literal search for safety.\\n\\n';\n }\n\n const searchScript = generateSearchScript(\n deferredTools,\n fields,\n max_results,\n sanitizedPattern\n );\n\n const postData = {\n lang: 'js',\n code: searchScript,\n timeout: SEARCH_TIMEOUT,\n };\n\n try {\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'LibreChat/1.0',\n },\n body: JSON.stringify(postData),\n };\n\n if (process.env.PROXY != null && process.env.PROXY !== '') {\n fetchOptions.agent = new HttpsProxyAgent(process.env.PROXY);\n }\n\n const response = await fetch(EXEC_ENDPOINT, fetchOptions);\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`);\n }\n\n const result: t.ExecuteResult = await response.json();\n\n if (result.stderr && result.stderr.trim()) {\n // eslint-disable-next-line no-console\n console.warn('[ToolSearch] stderr:', result.stderr);\n }\n\n if (!result.stdout || !result.stdout.trim()) {\n return [\n `${warningMessage}No tools matched the pattern \"${sanitizedPattern}\".\\nTotal tools searched: ${deferredTools.length}`,\n {\n tool_references: [],\n metadata: {\n total_searched: deferredTools.length,\n pattern: sanitizedPattern,\n },\n },\n ];\n }\n\n const searchResponse = parseSearchResults(result.stdout);\n const formattedOutput = `${warningMessage}${formatSearchResults(searchResponse, mcpNameFormat)}`;\n\n return [\n formattedOutput,\n {\n tool_references: searchResponse.tool_references,\n metadata: {\n total_searched: searchResponse.total_tools_searched,\n pattern: searchResponse.pattern_used,\n },\n },\n ];\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error('[ToolSearch] Error:', error);\n\n const errorMessage =\n error instanceof Error ? error.message : String(error);\n return [\n `Tool search failed: ${errorMessage}\\n\\nSuggestion: Try a simpler search pattern or search for specific tool names.`,\n {\n tool_references: [],\n metadata: {\n total_searched: 0,\n pattern: sanitizedPattern,\n error: errorMessage,\n },\n },\n ];\n }\n },\n {\n name: Constants.TOOL_SEARCH,\n description,\n schema,\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n\nexport {\n createToolSearch,\n performLocalSearch,\n extractMcpServerName,\n isFromMcpServer,\n isFromAnyMcpServer,\n normalizeServerFilter,\n getAvailableMcpServers,\n getDeferredToolsListing,\n getBaseToolName,\n formatServerListing,\n sanitizeRegex,\n escapeRegexSpecialChars,\n isDangerousPattern,\n countNestedGroups,\n hasNestedQuantifiers,\n};\n"],"mappings":"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"}
@@ -107,7 +107,7 @@ async function executeParallelSearches({ searchAPI, query, date, country, safeSe
107
107
  data: mergedResults
108
108
  };
109
109
  }
110
- function createSearchProcessor({ searchAPI, safeSearch, supportsImages, supportsVideos, supportsNews, sourceProcessor, onGetHighlights, logger }) {
110
+ function createSearchProcessor({ searchAPI, safeSearch, supportsImages, supportsVideos, supportsNews, sourceProcessor, onGetHighlights, mainExpandBy, separatorExpandBy, logger }) {
111
111
  return async function({ query, date, country, proMode = true, maxSources = 5, onSearchResults, images = false, videos = false, news = false }) {
112
112
  try {
113
113
  const searchResult = await executeParallelSearches({
@@ -129,7 +129,7 @@ function createSearchProcessor({ searchAPI, safeSearch, supportsImages, supports
129
129
  proMode,
130
130
  onGetHighlights,
131
131
  numElements: maxSources
132
- }));
132
+ }), mainExpandBy, separatorExpandBy);
133
133
  } catch (error) {
134
134
  logger.error("Error in search:", error);
135
135
  return {
@@ -181,7 +181,7 @@ function createTool({ schema, search, maxOutputChars, onSearchResults: _onSearch
181
181
  });
182
182
  }
183
183
  const createSearchTool = (config = {}) => {
184
- const { searchProvider = "serper", serperApiKey, searxngInstanceUrl, searxngApiKey, tavilyApiKey, tavilySearchUrl, tavilyExtractUrl, tavilySearchOptions, keenableApiKey, keenableApiUrl, keenableSearchOptions, rerankerType = "cohere", rerankerTimeout, topResults = 5, maxContentLength, chunkSize, chunkOverlap, maxOutputChars, strategies = ["no_extraction"], filterContent = true, safeSearch = 1, scraperProvider = "firecrawl", firecrawlApiKey, firecrawlApiUrl, firecrawlVersion, firecrawlOptions, serperScraperOptions, tavilyScraperOptions, crwApiKey, crwApiUrl, crwSearchOptions, crwScraperOptions, scraperTimeout, jinaApiKey, jinaApiUrl, cohereApiKey, onSearchResults: _onSearchResults, onGetHighlights } = config;
184
+ const { searchProvider = "serper", serperApiKey, searxngInstanceUrl, searxngApiKey, tavilyApiKey, tavilySearchUrl, tavilyExtractUrl, tavilySearchOptions, keenableApiKey, keenableApiUrl, keenableSearchOptions, rerankerType = "cohere", rerankerTimeout, topResults = 5, maxContentLength, chunkSize, chunkOverlap, mainExpandBy, separatorExpandBy, maxOutputChars, strategies = ["no_extraction"], filterContent = true, safeSearch = 1, scraperProvider = "firecrawl", firecrawlApiKey, firecrawlApiUrl, firecrawlVersion, firecrawlOptions, serperScraperOptions, tavilyScraperOptions, crwApiKey, crwApiUrl, crwSearchOptions, crwScraperOptions, scraperTimeout, jinaApiKey, jinaApiUrl, cohereApiKey, onSearchResults: _onSearchResults, onGetHighlights } = config;
185
185
  const logger = config.logger || require_utils.createDefaultLogger();
186
186
  const effectiveTavilySearchOptions = searchProvider === "tavily" && config.safeSearch != null ? {
187
187
  ...tavilySearchOptions,
@@ -275,6 +275,8 @@ const createSearchTool = (config = {}) => {
275
275
  supportsNews: searchProvider !== "keenable",
276
276
  sourceProcessor,
277
277
  onGetHighlights,
278
+ mainExpandBy,
279
+ separatorExpandBy,
278
280
  logger
279
281
  }),
280
282
  schema: toolSchema,