@librechat/agents 3.2.58 → 3.2.60

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 (67) hide show
  1. package/dist/cjs/graphs/Graph.cjs +31 -7
  2. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  3. package/dist/cjs/main.cjs +7 -0
  4. package/dist/cjs/run.cjs +4 -0
  5. package/dist/cjs/run.cjs.map +1 -1
  6. package/dist/cjs/stream.cjs +2 -1
  7. package/dist/cjs/stream.cjs.map +1 -1
  8. package/dist/cjs/tools/BashExecutor.cjs +58 -9
  9. package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
  10. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +4 -2
  11. package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
  12. package/dist/cjs/tools/CodeExecutor.cjs +57 -7
  13. package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
  14. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +9 -3
  15. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  16. package/dist/cjs/tools/ToolNode.cjs +114 -11
  17. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  18. package/dist/cjs/tools/eagerEventExecution.cjs +18 -1
  19. package/dist/cjs/tools/eagerEventExecution.cjs.map +1 -1
  20. package/dist/esm/graphs/Graph.mjs +31 -7
  21. package/dist/esm/graphs/Graph.mjs.map +1 -1
  22. package/dist/esm/main.mjs +3 -3
  23. package/dist/esm/run.mjs +4 -0
  24. package/dist/esm/run.mjs.map +1 -1
  25. package/dist/esm/stream.mjs +2 -1
  26. package/dist/esm/stream.mjs.map +1 -1
  27. package/dist/esm/tools/BashExecutor.mjs +56 -10
  28. package/dist/esm/tools/BashExecutor.mjs.map +1 -1
  29. package/dist/esm/tools/BashProgrammaticToolCalling.mjs +4 -2
  30. package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
  31. package/dist/esm/tools/CodeExecutor.mjs +54 -8
  32. package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
  33. package/dist/esm/tools/ProgrammaticToolCalling.mjs +9 -3
  34. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  35. package/dist/esm/tools/ToolNode.mjs +115 -12
  36. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  37. package/dist/esm/tools/eagerEventExecution.mjs +18 -2
  38. package/dist/esm/tools/eagerEventExecution.mjs.map +1 -1
  39. package/dist/types/graphs/Graph.d.ts +21 -3
  40. package/dist/types/run.d.ts +1 -0
  41. package/dist/types/tools/BashExecutor.d.ts +13 -0
  42. package/dist/types/tools/CodeExecutor.d.ts +14 -0
  43. package/dist/types/tools/ToolNode.d.ts +60 -1
  44. package/dist/types/tools/eagerEventExecution.d.ts +8 -0
  45. package/dist/types/types/hitl.d.ts +49 -3
  46. package/dist/types/types/run.d.ts +21 -0
  47. package/dist/types/types/tools.d.ts +95 -1
  48. package/package.json +1 -1
  49. package/src/graphs/Graph.ts +74 -28
  50. package/src/run.ts +4 -0
  51. package/src/specs/ask-user-question-batch.test.ts +289 -0
  52. package/src/specs/tool-error-resume.test.ts +194 -0
  53. package/src/stream.ts +17 -1
  54. package/src/tools/BashExecutor.ts +107 -14
  55. package/src/tools/BashProgrammaticToolCalling.ts +20 -1
  56. package/src/tools/CodeExecutor.ts +113 -9
  57. package/src/tools/ProgrammaticToolCalling.ts +27 -1
  58. package/src/tools/ToolNode.ts +208 -31
  59. package/src/tools/__tests__/BashExecutor.test.ts +39 -0
  60. package/src/tools/__tests__/CodeExecutor.stateful.test.ts +113 -0
  61. package/src/tools/__tests__/ToolNode.session.test.ts +86 -0
  62. package/src/tools/__tests__/eagerEventExecution.session.test.ts +92 -0
  63. package/src/tools/__tests__/hitl.test.ts +48 -0
  64. package/src/tools/eagerEventExecution.ts +32 -5
  65. package/src/types/hitl.ts +49 -3
  66. package/src/types/run.ts +21 -0
  67. package/src/types/tools.ts +102 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ProgrammaticToolCalling.cjs","names":["resolveCodeApiRunTimeoutMs","CODE_ARTIFACT_PATH_GUIDANCE","createCodeApiRunTimeoutSchema","resolveCodeApiAuthHeaders","HttpsProxyAgent","buildCodeApiHttpErrorMessage","obj","emptyOutputMessage","appendCodeSessionFileSummary","appendTmpScratchReminder","getCodeBaseURL","clampCodeApiRunTimeoutMs","appendFailedExecutionFileReminder"],"sources":["../../../src/tools/ProgrammaticToolCalling.ts"],"sourcesContent":["// src/tools/ProgrammaticToolCalling.ts\nimport { config } from 'dotenv';\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { ProgrammaticToolCallingJsonSchema } from './ptcTimeout';\nimport type * as t from '@/types';\nimport {\n CODE_ARTIFACT_PATH_GUIDANCE,\n appendCodeSessionFileSummary,\n appendFailedExecutionFileReminder,\n buildCodeApiHttpErrorMessage,\n emptyOutputMessage,\n getCodeBaseURL,\n appendTmpScratchReminder,\n resolveCodeApiAuthHeaders,\n} from './CodeExecutor';\nimport {\n clampCodeApiRunTimeoutMs,\n createCodeApiRunTimeoutSchema,\n resolveCodeApiRunTimeoutMs,\n} from './ptcTimeout';\nimport { Constants } from '@/common';\n\nconfig();\n\n/** Default max round-trips to prevent infinite loops */\nconst DEFAULT_MAX_ROUND_TRIPS = 20;\n\nconst DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();\n\n// ============================================================================\n// Description Components (Single Source of Truth)\n// ============================================================================\n\nconst STATELESS_WARNING = `CRITICAL - STATELESS EXECUTION:\nEach call is a fresh Python interpreter. Variables, imports, and data do NOT persist between calls.\nYou MUST complete your entire workflow in ONE code block: query → process → output.\nDO NOT split work across multiple calls expecting to reuse variables.`;\n\nconst CORE_RULES = `Rules:\n- One call: state does not persist\n- Auto-wrapped async; use await, no main()/asyncio.run()\n- Tools are pre-defined—DO NOT write function definitions\n- Call tools with keyword args only (await tool(arg=value), never pass a dict)\n- Tool results are decoded Python values (dict/list/str)\n- Only print() output returns to the model\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- timeout caps one sandbox run/replay iteration, not the total multi-round-trip workflow`;\n\nconst ADDITIONAL_RULES =\n '- Tool names normalized: hyphens→underscores, keywords get `_tool` suffix';\n\nconst EXAMPLES = `Example (Complete workflow in one call):\n # Query data\n data = await query_database(sql=\"SELECT * FROM users\")\n # Process it\n df = pd.DataFrame(data)\n summary = df.groupby('region').sum()\n # Output results\n await write_to_sheet(spreadsheet_id=sid, data=summary.to_dict())\n print(f\"Wrote {len(summary)} rows\")\n\nExample (Parallel calls):\n sf, ny = await asyncio.gather(get_weather(city=\"SF\"), get_weather(city=\"NY\"))\n print(f\"SF: {sf}, NY: {ny}\")`;\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst CODE_PARAM_DESCRIPTION = `Python code that calls tools programmatically. Tools are available as async functions.\n\n${STATELESS_WARNING}\n\nYour code is auto-wrapped in async context. Just write logic with await—no boilerplate needed.\n\n${EXAMPLES}\n\n${CORE_RULES}`;\n\nexport function createProgrammaticToolCallingSchema(\n maxRunTimeoutMs = DEFAULT_RUN_TIMEOUT_MS\n): ProgrammaticToolCallingJsonSchema {\n return {\n type: 'object',\n properties: {\n code: {\n type: 'string',\n minLength: 1,\n description: CODE_PARAM_DESCRIPTION,\n },\n timeout: createCodeApiRunTimeoutSchema(maxRunTimeoutMs),\n },\n required: ['code'],\n } as const;\n}\n\nexport const ProgrammaticToolCallingSchema =\n createProgrammaticToolCallingSchema();\n\nexport const ProgrammaticToolCallingName = Constants.PROGRAMMATIC_TOOL_CALLING;\n\nexport const ProgrammaticToolCallingDescription = `\nRun tools via Python code. Auto-wrapped in async context—just use \\`await\\` directly.\n\n${STATELESS_WARNING}\n\n${CORE_RULES}\n${ADDITIONAL_RULES}\n\nWhen to use: loops, conditionals, parallel (\\`asyncio.gather\\`), multi-step pipelines.\n\n${EXAMPLES}\n`.trim();\n\nexport const ProgrammaticToolCallingDefinition = {\n name: ProgrammaticToolCallingName,\n description: ProgrammaticToolCallingDescription,\n schema: ProgrammaticToolCallingSchema,\n} as const;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/** Python reserved keywords that get `_tool` suffix in Code API */\nconst PYTHON_KEYWORDS = new Set([\n 'False',\n 'None',\n 'True',\n 'and',\n 'as',\n 'assert',\n 'async',\n 'await',\n 'break',\n 'class',\n 'continue',\n 'def',\n 'del',\n 'elif',\n 'else',\n 'except',\n 'finally',\n 'for',\n 'from',\n 'global',\n 'if',\n 'import',\n 'in',\n 'is',\n 'lambda',\n 'nonlocal',\n 'not',\n 'or',\n 'pass',\n 'raise',\n 'return',\n 'try',\n 'while',\n 'with',\n 'yield',\n]);\n\nexport type FetchSessionFilesScope =\n | { kind: 'skill'; id: string; version: number }\n | { kind: 'agent' | 'user'; id: string; version?: never };\n\ntype CodeApiSessionFileWire = {\n id?: unknown;\n name?: unknown;\n metadata?: unknown;\n resource_id?: unknown;\n storage_session_id?: unknown;\n};\n\ntype CodeApiSessionFileMetadata = {\n 'original-filename'?: unknown;\n};\n\nfunction isFetchSessionFilesScope(\n value: unknown\n): value is FetchSessionFilesScope {\n if (value == null || typeof value !== 'object') {\n return false;\n }\n const scope = value as { kind?: unknown; id?: unknown; version?: unknown };\n if (\n (scope.kind === 'agent' || scope.kind === 'user') &&\n typeof scope.id === 'string'\n ) {\n return true;\n }\n return (\n scope.kind === 'skill' &&\n typeof scope.id === 'string' &&\n typeof scope.version === 'number'\n );\n}\n\nfunction isCodeApiAuthHeaders(\n value: string | t.CodeApiAuthHeaders | undefined\n): value is t.CodeApiAuthHeaders {\n return value != null && typeof value !== 'string';\n}\n\nfunction isCodeApiSessionFileWire(\n value: unknown\n): value is CodeApiSessionFileWire {\n return value != null && typeof value === 'object';\n}\n\nfunction isCodeApiSessionFileMetadata(\n value: unknown\n): value is CodeApiSessionFileMetadata {\n return value != null && typeof value === 'object';\n}\n\nfunction normalizeSessionFile(\n file: CodeApiSessionFileWire,\n sessionId: string,\n scope?: FetchSessionFilesScope\n): t.CodeEnvFile {\n const metadata = isCodeApiSessionFileMetadata(file.metadata)\n ? file.metadata\n : undefined;\n const rawName = typeof file.name === 'string' ? file.name : '';\n const nameParts = rawName.split('/');\n const fallbackId = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';\n const id =\n typeof file.id === 'string' && file.id !== '' ? file.id : fallbackId;\n const originalFilename = metadata?.['original-filename'];\n const name =\n typeof originalFilename === 'string' ? originalFilename : rawName;\n const storage_session_id =\n typeof file.storage_session_id === 'string'\n ? file.storage_session_id\n : sessionId;\n const resource_id =\n typeof file.resource_id === 'string' && file.resource_id !== ''\n ? file.resource_id\n : (scope?.id ?? id);\n\n if (scope?.kind === 'skill') {\n return {\n storage_session_id,\n kind: 'skill',\n id,\n resource_id,\n name,\n version: scope.version,\n };\n }\n if (scope != null) {\n return {\n storage_session_id,\n kind: scope.kind,\n id,\n resource_id,\n name,\n };\n }\n return {\n storage_session_id,\n kind: 'user',\n id,\n resource_id: id,\n name,\n };\n}\n\n/**\n * Normalizes a tool name to Python identifier format.\n * Must match the Code API's `normalizePythonFunctionName` exactly:\n * 1. Replace hyphens and spaces with underscores\n * 2. Remove any other invalid characters\n * 3. Prefix with underscore if starts with number\n * 4. Append `_tool` if it's a Python keyword\n * @param name - The tool name to normalize\n * @returns Normalized Python-safe identifier\n */\nexport function normalizeToPythonIdentifier(name: string): string {\n let normalized = name.replace(/[-\\s]/g, '_');\n\n normalized = normalized.replace(/[^a-zA-Z0-9_]/g, '');\n\n if (/^[0-9]/.test(normalized)) {\n normalized = '_' + normalized;\n }\n\n if (PYTHON_KEYWORDS.has(normalized)) {\n normalized = normalized + '_tool';\n }\n\n return normalized;\n}\n\n/**\n * Extracts tool names that are actually called in the Python code.\n * Handles hyphen/underscore conversion since Python identifiers use underscores.\n * @param code - The Python code to analyze\n * @param toolNameMap - Map from normalized Python name to original tool name\n * @returns Set of original tool names found in the code\n */\nexport function extractUsedToolNames(\n code: string,\n toolNameMap: Map<string, string>\n): Set<string> {\n const usedTools = new Set<string>();\n\n for (const [pythonName, originalName] of toolNameMap) {\n const escapedName = pythonName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`\\\\b${escapedName}\\\\s*\\\\(`, 'g');\n\n if (pattern.test(code)) {\n usedTools.add(originalName);\n }\n }\n\n return usedTools;\n}\n\n/**\n * Filters tool definitions to only include tools actually used in the code.\n * Handles the hyphen-to-underscore conversion for Python compatibility.\n * @param toolDefs - All available tool definitions\n * @param code - The Python code to analyze\n * @param debug - Enable debug logging\n * @returns Filtered array of tool definitions\n */\nexport function filterToolsByUsage(\n toolDefs: t.LCTool[],\n code: string,\n debug = false\n): t.LCTool[] {\n const toolNameMap = new Map<string, string>();\n for (const tool of toolDefs) {\n const pythonName = normalizeToPythonIdentifier(tool.name);\n toolNameMap.set(pythonName, tool.name);\n }\n\n const usedToolNames = extractUsedToolNames(code, toolNameMap);\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Tool filtering: found ${usedToolNames.size}/${toolDefs.length} tools in code`\n );\n if (usedToolNames.size > 0) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Matched tools: ${Array.from(usedToolNames).join(', ')}`\n );\n }\n }\n\n if (usedToolNames.size === 0) {\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n '[PTC Debug] No tools detected in code - sending all tools as fallback'\n );\n }\n return toolDefs;\n }\n\n return toolDefs.filter((tool) => usedToolNames.has(tool.name));\n}\n\n/**\n * Fetches files from a previous session to make them available for the current execution.\n * Files are returned as CodeEnvFile references to be included in the request.\n * @param baseUrl - The base URL for the Code API\n * @param sessionId - The session ID to fetch files from\n * @param scope - Resource scope used by CodeAPI to authorize the session\n * @param proxy - Optional HTTP proxy URL\n * @returns Array of CodeEnvFile references, or empty array if fetch fails\n */\nexport async function fetchSessionFiles(\n baseUrl: string,\n sessionId: string,\n proxy?: string,\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeEnvFile[]>;\nexport async function fetchSessionFiles(\n baseUrl: string,\n sessionId: string,\n scope: FetchSessionFilesScope,\n proxyOrAuthHeaders?: string | t.CodeApiAuthHeaders,\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeEnvFile[]>;\nexport async function fetchSessionFiles(\n baseUrl: string,\n sessionId: string,\n scopeOrProxy?: FetchSessionFilesScope | string,\n proxyOrAuthHeaders?: string | t.CodeApiAuthHeaders,\n scopedAuthHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeEnvFile[]> {\n try {\n const scope = isFetchSessionFilesScope(scopeOrProxy)\n ? scopeOrProxy\n : undefined;\n let proxy: string | undefined;\n let authHeaders: t.CodeApiAuthHeaders | undefined;\n if (scope == null) {\n proxy = typeof scopeOrProxy === 'string' ? scopeOrProxy : undefined;\n authHeaders = isCodeApiAuthHeaders(proxyOrAuthHeaders)\n ? proxyOrAuthHeaders\n : undefined;\n } else if (typeof proxyOrAuthHeaders === 'string') {\n proxy = proxyOrAuthHeaders;\n authHeaders = scopedAuthHeaders;\n } else {\n authHeaders = proxyOrAuthHeaders ?? scopedAuthHeaders;\n }\n const query = new URLSearchParams({ detail: 'full' });\n if (scope != null) {\n query.set('kind', scope.kind);\n query.set('id', scope.id);\n if (scope.kind === 'skill') {\n query.set('version', String(scope.version));\n }\n }\n const filesEndpoint = `${baseUrl}/files/${encodeURIComponent(sessionId)}?${query.toString()}`;\n const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders);\n const fetchOptions: RequestInit = {\n method: 'GET',\n headers: {\n 'User-Agent': 'LibreChat/1.0',\n ...resolvedAuthHeaders,\n },\n };\n\n if (proxy != null && proxy !== '') {\n fetchOptions.agent = new HttpsProxyAgent(proxy);\n }\n\n const response = await fetch(filesEndpoint, fetchOptions);\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('GET', filesEndpoint, response)\n );\n }\n\n const files = await response.json();\n if (!Array.isArray(files) || files.length === 0) {\n return [];\n }\n\n return files\n .filter(isCodeApiSessionFileWire)\n .map((file) => normalizeSessionFile(file, sessionId, scope));\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn(\n `Failed to fetch files for session: ${sessionId}, ${(error as Error).message}`\n );\n return [];\n }\n}\n\n/**\n * Makes an HTTP request to the Code API.\n * @param endpoint - The API endpoint URL\n * @param body - The request body\n * @param proxy - Optional HTTP proxy URL\n * @returns The parsed API response\n */\nexport async function makeRequest(\n endpoint: string,\n body: Record<string, unknown>,\n proxy?: string,\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.ProgrammaticExecutionResponse> {\n const resolvedAuthHeaders = 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(body),\n };\n\n if (proxy != null && proxy !== '') {\n fetchOptions.agent = new HttpsProxyAgent(proxy);\n }\n\n const response = await fetch(endpoint, fetchOptions);\n\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('POST', endpoint, response)\n );\n }\n\n return (await response.json()) as t.ProgrammaticExecutionResponse;\n}\n\n/**\n * Unwraps tool responses that may be formatted as tuples or content blocks.\n * MCP tools return [content, artifacts], we need to extract the raw data.\n * @param result - The raw result from tool.invoke()\n * @param isMCPTool - Whether this is an MCP tool (has mcp property)\n * @returns Unwrapped raw data (string, object, or parsed JSON)\n */\nexport function unwrapToolResponse(\n result: unknown,\n isMCPTool: boolean\n): unknown {\n // Only unwrap if this is an MCP tool and result is a tuple\n if (!isMCPTool) {\n return result;\n }\n\n /**\n * Checks if a value is a content block object (has type and text).\n */\n const isContentBlock = (value: unknown): boolean => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const obj = value as Record<string, unknown>;\n return typeof obj.type === 'string';\n };\n\n /**\n * Checks if an array is an array of content blocks.\n */\n const isContentBlockArray = (arr: unknown[]): boolean => {\n return arr.length > 0 && arr.every(isContentBlock);\n };\n\n /**\n * Extracts text from a single content block object.\n * Returns the text if it's a text block, otherwise returns null.\n */\n const extractTextFromBlock = (block: unknown): string | null => {\n if (typeof block !== 'object' || block === null) return null;\n const b = block as Record<string, unknown>;\n if (b.type === 'text' && typeof b.text === 'string') {\n return b.text;\n }\n return null;\n };\n\n /**\n * Extracts text from content blocks (array or single object).\n * Returns combined text or null if no text blocks found.\n */\n const extractTextFromContent = (content: unknown): string | null => {\n // Single content block object: { type: 'text', text: '...' }\n if (\n typeof content === 'object' &&\n content !== null &&\n !Array.isArray(content)\n ) {\n const text = extractTextFromBlock(content);\n if (text !== null) return text;\n }\n\n // Array of content blocks: [{ type: 'text', text: '...' }, ...]\n if (Array.isArray(content) && content.length > 0) {\n const texts = content\n .map(extractTextFromBlock)\n .filter((t): t is string => t !== null);\n if (texts.length > 0) {\n return texts.join('\\n');\n }\n }\n\n return null;\n };\n\n /**\n * Tries to parse a string as JSON if it looks like JSON.\n */\n const maybeParseJSON = (str: string): unknown => {\n const trimmed = str.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n try {\n return JSON.parse(trimmed);\n } catch {\n return str;\n }\n }\n return str;\n };\n\n // Handle array of content blocks at top level FIRST\n // (before checking for tuple, since both are arrays)\n if (Array.isArray(result) && isContentBlockArray(result)) {\n const extractedText = extractTextFromContent(result);\n if (extractedText !== null) {\n return maybeParseJSON(extractedText);\n }\n }\n\n // Check if result is a tuple/array with [content, artifacts]\n if (Array.isArray(result) && result.length >= 1) {\n const [content] = result;\n\n // If first element is a string, return it (possibly parsed as JSON)\n if (typeof content === 'string') {\n return maybeParseJSON(content);\n }\n\n // Try to extract text from content blocks\n const extractedText = extractTextFromContent(content);\n if (extractedText !== null) {\n return maybeParseJSON(extractedText);\n }\n\n // If first element is an object (but not a text block), return it\n if (typeof content === 'object' && content !== null) {\n return content;\n }\n }\n\n // Handle single content block object at top level (not in tuple)\n const extractedText = extractTextFromContent(result);\n if (extractedText !== null) {\n return maybeParseJSON(extractedText);\n }\n\n // Not a formatted response, return as-is\n return result;\n}\n\ntype ToolInputSchemaKind = {\n object: boolean;\n string: boolean;\n};\n\nfunction detectSchemaKind(schema: unknown): ToolInputSchemaKind {\n const kind: ToolInputSchemaKind = { object: false, string: false };\n\n if (!schema || typeof schema !== 'object') {\n return kind;\n }\n\n const jsonSchemaType = (schema as { type?: unknown }).type;\n if (jsonSchemaType === 'object') {\n kind.object = true;\n } else if (jsonSchemaType === 'string') {\n kind.string = true;\n } else if (Array.isArray(jsonSchemaType)) {\n kind.object = jsonSchemaType.includes('object');\n kind.string = jsonSchemaType.includes('string');\n }\n\n const zodDef = (schema as { _def?: unknown })._def;\n if (!zodDef || typeof zodDef !== 'object') {\n return kind;\n }\n\n const zodType = (zodDef as { type?: unknown; typeName?: unknown }).type;\n const zodTypeName = (zodDef as { type?: unknown; typeName?: unknown })\n .typeName;\n\n if (zodType === 'object' || zodTypeName === 'ZodObject') {\n kind.object = true;\n } else if (zodType === 'string' || zodTypeName === 'ZodString') {\n kind.string = true;\n }\n\n const innerSchema =\n (\n zodDef as {\n innerType?: unknown;\n schema?: unknown;\n type?: unknown;\n }\n ).innerType ?? (zodDef as { schema?: unknown }).schema;\n if (innerSchema) {\n const innerKind = detectSchemaKind(innerSchema);\n kind.object ||= innerKind.object;\n kind.string ||= innerKind.string;\n }\n\n const options = (zodDef as { options?: unknown }).options;\n if (Array.isArray(options)) {\n for (const option of options) {\n const optionKind = detectSchemaKind(option);\n kind.object ||= optionKind.object;\n kind.string ||= optionKind.string;\n }\n }\n\n return kind;\n}\n\nfunction getToolInputSchemaKind(tool: t.GenericTool): ToolInputSchemaKind {\n if (tool.constructor.name === 'DynamicTool') {\n return { object: false, string: true };\n }\n\n const schema = (tool as { schema?: unknown }).schema;\n return detectSchemaKind(schema);\n}\n\nfunction normalizeToolInput(\n input: t.PTCToolCall['input'],\n tool: t.GenericTool\n): t.PTCToolCall['input'] {\n const schemaKind = getToolInputSchemaKind(tool);\n\n if (typeof input !== 'string') {\n if (!schemaKind.string || schemaKind.object) {\n return input;\n }\n\n const inputValue = (input as { input?: unknown }).input;\n if (typeof inputValue === 'string') {\n return input;\n }\n\n return JSON.stringify(input);\n }\n\n if (!schemaKind.object || schemaKind.string) {\n return input;\n }\n\n const trimmed = input.trim();\n if (!trimmed.startsWith('{')) {\n return input;\n }\n\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n !Array.isArray(parsed)\n ) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n return input;\n }\n\n return input;\n}\n\n/**\n * Executes tools in parallel when requested by the API.\n * Uses Promise.all for parallel execution, catching individual errors.\n * Unwraps formatted responses (e.g., MCP tool tuples) to raw data.\n * @param toolCalls - Array of tool calls from the API\n * @param toolMap - Map of tool names to executable tools\n * @returns Array of tool results\n */\nexport async function executeTools(\n toolCalls: t.PTCToolCall[],\n toolMap: t.ToolMap,\n programmaticToolName = Constants.PROGRAMMATIC_TOOL_CALLING\n): Promise<t.PTCToolResult[]> {\n const executions = toolCalls.map(async (call): Promise<t.PTCToolResult> => {\n const tool = toolMap.get(call.name);\n\n if (!tool) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: `Tool '${call.name}' not found. Available tools: ${Array.from(toolMap.keys()).join(', ')}`,\n };\n }\n\n try {\n const result = await tool.invoke(normalizeToolInput(call.input, tool), {\n metadata: { [programmaticToolName]: true },\n });\n\n const isMCPTool = tool.mcp === true;\n const unwrappedResult = unwrapToolResponse(result, isMCPTool);\n\n return {\n call_id: call.id,\n result: unwrappedResult,\n is_error: false,\n };\n } catch (error) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: (error as Error).message || 'Tool execution failed',\n };\n }\n });\n\n return await Promise.all(executions);\n}\n\n/**\n * Formats the completed response for the agent.\n *\n * Output includes stdout/stderr plus a compact session-file summary\n * when artifacts were persisted. The artifact still carries every\n * file so the host's session map stays in sync.\n *\n * @param response - The completed API response\n * @returns Tuple of [formatted string, artifact]\n */\nexport function formatCompletedResponse(\n response: t.ProgrammaticExecutionResponse,\n sourceCode = ''\n): [string, t.ProgrammaticExecutionArtifact] {\n let formatted = '';\n\n if (response.stdout != null && response.stdout !== '') {\n formatted += `stdout:\\n${response.stdout}\\n`;\n } else {\n formatted += emptyOutputMessage;\n }\n\n if (response.stderr != null && response.stderr !== '') {\n formatted += `stderr:\\n${response.stderr}\\n`;\n }\n\n const outputWithReminder = appendTmpScratchReminder(formatted, sourceCode);\n\n return [\n appendCodeSessionFileSummary(outputWithReminder, response.files),\n {\n session_id: response.session_id,\n files: response.files,\n } satisfies t.ProgrammaticExecutionArtifact,\n ];\n}\n\n// ============================================================================\n// Tool Factory\n// ============================================================================\n\n/**\n * Creates a Programmatic Tool Calling tool for complex multi-tool workflows.\n *\n * This tool enables AI agents to write Python code that orchestrates multiple\n * tool calls programmatically, reducing LLM round-trips and token usage.\n *\n * The tool map must be provided at runtime via config.configurable.toolMap.\n *\n * @param params - Configuration parameters (baseUrl, maxRoundTrips, proxy)\n * @returns A LangChain DynamicStructuredTool for programmatic tool calling\n *\n * @example\n * const ptcTool = createProgrammaticToolCallingTool({ maxRoundTrips: 20 });\n *\n * const [output, artifact] = await ptcTool.invoke(\n * { code, tools },\n * { configurable: { toolMap } }\n * );\n */\nexport function createProgrammaticToolCallingTool(\n initParams: t.ProgrammaticToolCallingParams = {}\n): DynamicStructuredTool {\n const baseUrl = initParams.baseUrl ?? getCodeBaseURL();\n const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;\n const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);\n const proxy = initParams.proxy ?? process.env.PROXY;\n const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';\n const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;\n\n return tool(\n async (rawParams, config) => {\n const params = rawParams as { code: string; timeout?: number };\n const { code } = params;\n const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);\n\n // Extra params injected by ToolNode (follows web_search pattern).\n const toolCall = (config.toolCall ?? {}) as ToolCall &\n Partial<t.ProgrammaticCache> & {\n session_id?: string;\n _injected_files?: t.CodeEnvFile[];\n };\n const { toolMap, toolDefs, session_id, _injected_files } = toolCall;\n\n if (toolMap == null || toolMap.size === 0) {\n throw new Error(\n 'No toolMap provided. ' +\n 'ToolNode should inject this from AgentContext when invoked through the graph.'\n );\n }\n\n if (toolDefs == null || toolDefs.length === 0) {\n throw new Error(\n 'No tool definitions provided. ' +\n 'Either pass tools in the input or ensure ToolNode injects toolDefs.'\n );\n }\n\n let roundTrip = 0;\n\n try {\n // ====================================================================\n // Phase 1: Filter tools and make initial request\n // ====================================================================\n\n const effectiveTools = filterToolsByUsage(toolDefs, code, debug);\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Sending ${effectiveTools.length} tools to API ` +\n `(filtered from ${toolDefs.length})`\n );\n }\n\n /**\n * File injection: `_injected_files` from ToolNode session\n * context. The legacy `/files/<session_id>` HTTP fallback was\n * removed (see `CodeExecutor.ts`) — codeapi's sessionAuth now\n * requires kind/id query params unavailable at this point.\n */\n let files: t.CodeEnvFile[] | undefined;\n if (_injected_files && _injected_files.length > 0) {\n files = _injected_files;\n } else if (session_id != null && session_id.length > 0) {\n // eslint-disable-next-line no-console\n console.debug(\n `[ProgrammaticToolCalling] No injected files for session_id=${session_id} — exec will run without input files`\n );\n }\n\n let response = await makeRequest(\n EXEC_ENDPOINT,\n {\n code,\n tools: effectiveTools,\n session_id,\n timeout,\n ...(files && files.length > 0 ? { files } : {}),\n },\n proxy,\n initParams.authHeaders\n );\n\n // ====================================================================\n // Phase 2: Handle response loop\n // ====================================================================\n\n while (response.status === 'tool_call_required') {\n roundTrip++;\n\n if (roundTrip > maxRoundTrips) {\n throw new Error(\n `Exceeded maximum round trips (${maxRoundTrips}). ` +\n 'This may indicate an infinite loop, excessive tool calls, ' +\n 'or a logic error in your code.'\n );\n }\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Round trip ${roundTrip}: ${response.tool_calls?.length ?? 0} tool(s) to execute`\n );\n }\n\n const toolResults = await executeTools(\n response.tool_calls ?? [],\n toolMap\n );\n\n response = await makeRequest(\n EXEC_ENDPOINT,\n {\n continuation_token: response.continuation_token,\n tool_results: toolResults,\n },\n proxy,\n initParams.authHeaders\n );\n }\n\n // ====================================================================\n // Phase 3: Handle final state\n // ====================================================================\n\n if (response.status === 'completed') {\n return formatCompletedResponse(response, code);\n }\n\n if (response.status === 'error') {\n throw new Error(\n `Execution error: ${response.error}` +\n (response.stderr != null && response.stderr !== ''\n ? `\\n\\nStderr:\\n${response.stderr}`\n : '')\n );\n }\n\n throw new Error(`Unexpected response status: ${response.status}`);\n } catch (error) {\n const messageWithReminder = appendFailedExecutionFileReminder(\n (error as Error).message,\n code\n );\n throw new Error(\n `Programmatic execution failed: ${messageWithReminder}`\n );\n }\n },\n {\n name: Constants.PROGRAMMATIC_TOOL_CALLING,\n description: ProgrammaticToolCallingDescription,\n schema: createProgrammaticToolCallingSchema(maxRunTimeoutMs),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProgrammaticToolCalling.cjs","names":["resolveCodeApiRunTimeoutMs","CODE_ARTIFACT_PATH_GUIDANCE","createCodeApiRunTimeoutSchema","resolveCodeApiAuthHeaders","HttpsProxyAgent","buildCodeApiHttpErrorMessage","obj","emptyOutputMessage","appendCodeSessionFileSummary","appendTmpScratchReminder","getCodeBaseURL","clampCodeApiRunTimeoutMs","appendFailedExecutionFileReminder"],"sources":["../../../src/tools/ProgrammaticToolCalling.ts"],"sourcesContent":["// src/tools/ProgrammaticToolCalling.ts\nimport { config } from 'dotenv';\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type { ProgrammaticToolCallingJsonSchema } from './ptcTimeout';\nimport type * as t from '@/types';\nimport {\n CODE_ARTIFACT_PATH_GUIDANCE,\n appendCodeSessionFileSummary,\n appendFailedExecutionFileReminder,\n buildCodeApiHttpErrorMessage,\n emptyOutputMessage,\n getCodeBaseURL,\n appendTmpScratchReminder,\n resolveCodeApiAuthHeaders,\n} from './CodeExecutor';\nimport {\n clampCodeApiRunTimeoutMs,\n createCodeApiRunTimeoutSchema,\n resolveCodeApiRunTimeoutMs,\n} from './ptcTimeout';\nimport { Constants } from '@/common';\n\nconfig();\n\n/** Default max round-trips to prevent infinite loops */\nconst DEFAULT_MAX_ROUND_TRIPS = 20;\n\nconst DEFAULT_RUN_TIMEOUT_MS = resolveCodeApiRunTimeoutMs();\n\n// ============================================================================\n// Description Components (Single Source of Truth)\n// ============================================================================\n\nconst STATELESS_WARNING = `CRITICAL - STATELESS EXECUTION:\nEach call is a fresh Python interpreter. Variables, imports, and data do NOT persist between calls.\nYou MUST complete your entire workflow in ONE code block: query → process → output.\nDO NOT split work across multiple calls expecting to reuse variables.`;\n\nconst CORE_RULES = `Rules:\n- One call: state does not persist\n- Auto-wrapped async; use await, no main()/asyncio.run()\n- Tools are pre-defined—DO NOT write function definitions\n- Call tools with keyword args only (await tool(arg=value), never pass a dict)\n- Tool results are decoded Python values (dict/list/str)\n- Only print() output returns to the model\n- ${CODE_ARTIFACT_PATH_GUIDANCE}\n- timeout caps one sandbox run/replay iteration, not the total multi-round-trip workflow`;\n\nconst ADDITIONAL_RULES =\n '- Tool names normalized: hyphens→underscores, keywords get `_tool` suffix';\n\nconst EXAMPLES = `Example (Complete workflow in one call):\n # Query data\n data = await query_database(sql=\"SELECT * FROM users\")\n # Process it\n df = pd.DataFrame(data)\n summary = df.groupby('region').sum()\n # Output results\n await write_to_sheet(spreadsheet_id=sid, data=summary.to_dict())\n print(f\"Wrote {len(summary)} rows\")\n\nExample (Parallel calls):\n sf, ny = await asyncio.gather(get_weather(city=\"SF\"), get_weather(city=\"NY\"))\n print(f\"SF: {sf}, NY: {ny}\")`;\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst CODE_PARAM_DESCRIPTION = `Python code that calls tools programmatically. Tools are available as async functions.\n\n${STATELESS_WARNING}\n\nYour code is auto-wrapped in async context. Just write logic with await—no boilerplate needed.\n\n${EXAMPLES}\n\n${CORE_RULES}`;\n\nexport function createProgrammaticToolCallingSchema(\n maxRunTimeoutMs = DEFAULT_RUN_TIMEOUT_MS\n): ProgrammaticToolCallingJsonSchema {\n return {\n type: 'object',\n properties: {\n code: {\n type: 'string',\n minLength: 1,\n description: CODE_PARAM_DESCRIPTION,\n },\n timeout: createCodeApiRunTimeoutSchema(maxRunTimeoutMs),\n },\n required: ['code'],\n } as const;\n}\n\nexport const ProgrammaticToolCallingSchema =\n createProgrammaticToolCallingSchema();\n\nexport const ProgrammaticToolCallingName = Constants.PROGRAMMATIC_TOOL_CALLING;\n\nexport const ProgrammaticToolCallingDescription = `\nRun tools via Python code. Auto-wrapped in async context—just use \\`await\\` directly.\n\n${STATELESS_WARNING}\n\n${CORE_RULES}\n${ADDITIONAL_RULES}\n\nWhen to use: loops, conditionals, parallel (\\`asyncio.gather\\`), multi-step pipelines.\n\n${EXAMPLES}\n`.trim();\n\nexport const ProgrammaticToolCallingDefinition = {\n name: ProgrammaticToolCallingName,\n description: ProgrammaticToolCallingDescription,\n schema: ProgrammaticToolCallingSchema,\n} as const;\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/** Python reserved keywords that get `_tool` suffix in Code API */\nconst PYTHON_KEYWORDS = new Set([\n 'False',\n 'None',\n 'True',\n 'and',\n 'as',\n 'assert',\n 'async',\n 'await',\n 'break',\n 'class',\n 'continue',\n 'def',\n 'del',\n 'elif',\n 'else',\n 'except',\n 'finally',\n 'for',\n 'from',\n 'global',\n 'if',\n 'import',\n 'in',\n 'is',\n 'lambda',\n 'nonlocal',\n 'not',\n 'or',\n 'pass',\n 'raise',\n 'return',\n 'try',\n 'while',\n 'with',\n 'yield',\n]);\n\nexport type FetchSessionFilesScope =\n | { kind: 'skill'; id: string; version: number }\n | { kind: 'agent' | 'user'; id: string; version?: never };\n\ntype CodeApiSessionFileWire = {\n id?: unknown;\n name?: unknown;\n metadata?: unknown;\n resource_id?: unknown;\n storage_session_id?: unknown;\n};\n\ntype CodeApiSessionFileMetadata = {\n 'original-filename'?: unknown;\n};\n\nfunction isFetchSessionFilesScope(\n value: unknown\n): value is FetchSessionFilesScope {\n if (value == null || typeof value !== 'object') {\n return false;\n }\n const scope = value as { kind?: unknown; id?: unknown; version?: unknown };\n if (\n (scope.kind === 'agent' || scope.kind === 'user') &&\n typeof scope.id === 'string'\n ) {\n return true;\n }\n return (\n scope.kind === 'skill' &&\n typeof scope.id === 'string' &&\n typeof scope.version === 'number'\n );\n}\n\nfunction isCodeApiAuthHeaders(\n value: string | t.CodeApiAuthHeaders | undefined\n): value is t.CodeApiAuthHeaders {\n return value != null && typeof value !== 'string';\n}\n\nfunction isCodeApiSessionFileWire(\n value: unknown\n): value is CodeApiSessionFileWire {\n return value != null && typeof value === 'object';\n}\n\nfunction isCodeApiSessionFileMetadata(\n value: unknown\n): value is CodeApiSessionFileMetadata {\n return value != null && typeof value === 'object';\n}\n\nfunction normalizeSessionFile(\n file: CodeApiSessionFileWire,\n sessionId: string,\n scope?: FetchSessionFilesScope\n): t.CodeEnvFile {\n const metadata = isCodeApiSessionFileMetadata(file.metadata)\n ? file.metadata\n : undefined;\n const rawName = typeof file.name === 'string' ? file.name : '';\n const nameParts = rawName.split('/');\n const fallbackId = nameParts.length > 1 ? nameParts[1].split('.')[0] : '';\n const id =\n typeof file.id === 'string' && file.id !== '' ? file.id : fallbackId;\n const originalFilename = metadata?.['original-filename'];\n const name =\n typeof originalFilename === 'string' ? originalFilename : rawName;\n const storage_session_id =\n typeof file.storage_session_id === 'string'\n ? file.storage_session_id\n : sessionId;\n const resource_id =\n typeof file.resource_id === 'string' && file.resource_id !== ''\n ? file.resource_id\n : (scope?.id ?? id);\n\n if (scope?.kind === 'skill') {\n return {\n storage_session_id,\n kind: 'skill',\n id,\n resource_id,\n name,\n version: scope.version,\n };\n }\n if (scope != null) {\n return {\n storage_session_id,\n kind: scope.kind,\n id,\n resource_id,\n name,\n };\n }\n return {\n storage_session_id,\n kind: 'user',\n id,\n resource_id: id,\n name,\n };\n}\n\n/**\n * Normalizes a tool name to Python identifier format.\n * Must match the Code API's `normalizePythonFunctionName` exactly:\n * 1. Replace hyphens and spaces with underscores\n * 2. Remove any other invalid characters\n * 3. Prefix with underscore if starts with number\n * 4. Append `_tool` if it's a Python keyword\n * @param name - The tool name to normalize\n * @returns Normalized Python-safe identifier\n */\nexport function normalizeToPythonIdentifier(name: string): string {\n let normalized = name.replace(/[-\\s]/g, '_');\n\n normalized = normalized.replace(/[^a-zA-Z0-9_]/g, '');\n\n if (/^[0-9]/.test(normalized)) {\n normalized = '_' + normalized;\n }\n\n if (PYTHON_KEYWORDS.has(normalized)) {\n normalized = normalized + '_tool';\n }\n\n return normalized;\n}\n\n/**\n * Extracts tool names that are actually called in the Python code.\n * Handles hyphen/underscore conversion since Python identifiers use underscores.\n * @param code - The Python code to analyze\n * @param toolNameMap - Map from normalized Python name to original tool name\n * @returns Set of original tool names found in the code\n */\nexport function extractUsedToolNames(\n code: string,\n toolNameMap: Map<string, string>\n): Set<string> {\n const usedTools = new Set<string>();\n\n for (const [pythonName, originalName] of toolNameMap) {\n const escapedName = pythonName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`\\\\b${escapedName}\\\\s*\\\\(`, 'g');\n\n if (pattern.test(code)) {\n usedTools.add(originalName);\n }\n }\n\n return usedTools;\n}\n\n/**\n * Filters tool definitions to only include tools actually used in the code.\n * Handles the hyphen-to-underscore conversion for Python compatibility.\n * @param toolDefs - All available tool definitions\n * @param code - The Python code to analyze\n * @param debug - Enable debug logging\n * @returns Filtered array of tool definitions\n */\nexport function filterToolsByUsage(\n toolDefs: t.LCTool[],\n code: string,\n debug = false\n): t.LCTool[] {\n const toolNameMap = new Map<string, string>();\n for (const tool of toolDefs) {\n const pythonName = normalizeToPythonIdentifier(tool.name);\n toolNameMap.set(pythonName, tool.name);\n }\n\n const usedToolNames = extractUsedToolNames(code, toolNameMap);\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Tool filtering: found ${usedToolNames.size}/${toolDefs.length} tools in code`\n );\n if (usedToolNames.size > 0) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Matched tools: ${Array.from(usedToolNames).join(', ')}`\n );\n }\n }\n\n if (usedToolNames.size === 0) {\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n '[PTC Debug] No tools detected in code - sending all tools as fallback'\n );\n }\n return toolDefs;\n }\n\n return toolDefs.filter((tool) => usedToolNames.has(tool.name));\n}\n\n/**\n * Fetches files from a previous session to make them available for the current execution.\n * Files are returned as CodeEnvFile references to be included in the request.\n * @param baseUrl - The base URL for the Code API\n * @param sessionId - The session ID to fetch files from\n * @param scope - Resource scope used by CodeAPI to authorize the session\n * @param proxy - Optional HTTP proxy URL\n * @returns Array of CodeEnvFile references, or empty array if fetch fails\n */\nexport async function fetchSessionFiles(\n baseUrl: string,\n sessionId: string,\n proxy?: string,\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeEnvFile[]>;\nexport async function fetchSessionFiles(\n baseUrl: string,\n sessionId: string,\n scope: FetchSessionFilesScope,\n proxyOrAuthHeaders?: string | t.CodeApiAuthHeaders,\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeEnvFile[]>;\nexport async function fetchSessionFiles(\n baseUrl: string,\n sessionId: string,\n scopeOrProxy?: FetchSessionFilesScope | string,\n proxyOrAuthHeaders?: string | t.CodeApiAuthHeaders,\n scopedAuthHeaders?: t.CodeApiAuthHeaders\n): Promise<t.CodeEnvFile[]> {\n try {\n const scope = isFetchSessionFilesScope(scopeOrProxy)\n ? scopeOrProxy\n : undefined;\n let proxy: string | undefined;\n let authHeaders: t.CodeApiAuthHeaders | undefined;\n if (scope == null) {\n proxy = typeof scopeOrProxy === 'string' ? scopeOrProxy : undefined;\n authHeaders = isCodeApiAuthHeaders(proxyOrAuthHeaders)\n ? proxyOrAuthHeaders\n : undefined;\n } else if (typeof proxyOrAuthHeaders === 'string') {\n proxy = proxyOrAuthHeaders;\n authHeaders = scopedAuthHeaders;\n } else {\n authHeaders = proxyOrAuthHeaders ?? scopedAuthHeaders;\n }\n const query = new URLSearchParams({ detail: 'full' });\n if (scope != null) {\n query.set('kind', scope.kind);\n query.set('id', scope.id);\n if (scope.kind === 'skill') {\n query.set('version', String(scope.version));\n }\n }\n const filesEndpoint = `${baseUrl}/files/${encodeURIComponent(sessionId)}?${query.toString()}`;\n const resolvedAuthHeaders = await resolveCodeApiAuthHeaders(authHeaders);\n const fetchOptions: RequestInit = {\n method: 'GET',\n headers: {\n 'User-Agent': 'LibreChat/1.0',\n ...resolvedAuthHeaders,\n },\n };\n\n if (proxy != null && proxy !== '') {\n fetchOptions.agent = new HttpsProxyAgent(proxy);\n }\n\n const response = await fetch(filesEndpoint, fetchOptions);\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('GET', filesEndpoint, response)\n );\n }\n\n const files = await response.json();\n if (!Array.isArray(files) || files.length === 0) {\n return [];\n }\n\n return files\n .filter(isCodeApiSessionFileWire)\n .map((file) => normalizeSessionFile(file, sessionId, scope));\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn(\n `Failed to fetch files for session: ${sessionId}, ${(error as Error).message}`\n );\n return [];\n }\n}\n\n/**\n * Makes an HTTP request to the Code API.\n * @param endpoint - The API endpoint URL\n * @param body - The request body\n * @param proxy - Optional HTTP proxy URL\n * @returns The parsed API response\n */\nexport async function makeRequest(\n endpoint: string,\n body: Record<string, unknown>,\n proxy?: string,\n authHeaders?: t.CodeApiAuthHeaders\n): Promise<t.ProgrammaticExecutionResponse> {\n const resolvedAuthHeaders = 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(body),\n };\n\n if (proxy != null && proxy !== '') {\n fetchOptions.agent = new HttpsProxyAgent(proxy);\n }\n\n const response = await fetch(endpoint, fetchOptions);\n\n if (!response.ok) {\n throw new Error(\n await buildCodeApiHttpErrorMessage('POST', endpoint, response)\n );\n }\n\n return (await response.json()) as t.ProgrammaticExecutionResponse;\n}\n\n/**\n * Unwraps tool responses that may be formatted as tuples or content blocks.\n * MCP tools return [content, artifacts], we need to extract the raw data.\n * @param result - The raw result from tool.invoke()\n * @param isMCPTool - Whether this is an MCP tool (has mcp property)\n * @returns Unwrapped raw data (string, object, or parsed JSON)\n */\nexport function unwrapToolResponse(\n result: unknown,\n isMCPTool: boolean\n): unknown {\n // Only unwrap if this is an MCP tool and result is a tuple\n if (!isMCPTool) {\n return result;\n }\n\n /**\n * Checks if a value is a content block object (has type and text).\n */\n const isContentBlock = (value: unknown): boolean => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const obj = value as Record<string, unknown>;\n return typeof obj.type === 'string';\n };\n\n /**\n * Checks if an array is an array of content blocks.\n */\n const isContentBlockArray = (arr: unknown[]): boolean => {\n return arr.length > 0 && arr.every(isContentBlock);\n };\n\n /**\n * Extracts text from a single content block object.\n * Returns the text if it's a text block, otherwise returns null.\n */\n const extractTextFromBlock = (block: unknown): string | null => {\n if (typeof block !== 'object' || block === null) return null;\n const b = block as Record<string, unknown>;\n if (b.type === 'text' && typeof b.text === 'string') {\n return b.text;\n }\n return null;\n };\n\n /**\n * Extracts text from content blocks (array or single object).\n * Returns combined text or null if no text blocks found.\n */\n const extractTextFromContent = (content: unknown): string | null => {\n // Single content block object: { type: 'text', text: '...' }\n if (\n typeof content === 'object' &&\n content !== null &&\n !Array.isArray(content)\n ) {\n const text = extractTextFromBlock(content);\n if (text !== null) return text;\n }\n\n // Array of content blocks: [{ type: 'text', text: '...' }, ...]\n if (Array.isArray(content) && content.length > 0) {\n const texts = content\n .map(extractTextFromBlock)\n .filter((t): t is string => t !== null);\n if (texts.length > 0) {\n return texts.join('\\n');\n }\n }\n\n return null;\n };\n\n /**\n * Tries to parse a string as JSON if it looks like JSON.\n */\n const maybeParseJSON = (str: string): unknown => {\n const trimmed = str.trim();\n if (trimmed.startsWith('{') || trimmed.startsWith('[')) {\n try {\n return JSON.parse(trimmed);\n } catch {\n return str;\n }\n }\n return str;\n };\n\n // Handle array of content blocks at top level FIRST\n // (before checking for tuple, since both are arrays)\n if (Array.isArray(result) && isContentBlockArray(result)) {\n const extractedText = extractTextFromContent(result);\n if (extractedText !== null) {\n return maybeParseJSON(extractedText);\n }\n }\n\n // Check if result is a tuple/array with [content, artifacts]\n if (Array.isArray(result) && result.length >= 1) {\n const [content] = result;\n\n // If first element is a string, return it (possibly parsed as JSON)\n if (typeof content === 'string') {\n return maybeParseJSON(content);\n }\n\n // Try to extract text from content blocks\n const extractedText = extractTextFromContent(content);\n if (extractedText !== null) {\n return maybeParseJSON(extractedText);\n }\n\n // If first element is an object (but not a text block), return it\n if (typeof content === 'object' && content !== null) {\n return content;\n }\n }\n\n // Handle single content block object at top level (not in tuple)\n const extractedText = extractTextFromContent(result);\n if (extractedText !== null) {\n return maybeParseJSON(extractedText);\n }\n\n // Not a formatted response, return as-is\n return result;\n}\n\ntype ToolInputSchemaKind = {\n object: boolean;\n string: boolean;\n};\n\nfunction detectSchemaKind(schema: unknown): ToolInputSchemaKind {\n const kind: ToolInputSchemaKind = { object: false, string: false };\n\n if (!schema || typeof schema !== 'object') {\n return kind;\n }\n\n const jsonSchemaType = (schema as { type?: unknown }).type;\n if (jsonSchemaType === 'object') {\n kind.object = true;\n } else if (jsonSchemaType === 'string') {\n kind.string = true;\n } else if (Array.isArray(jsonSchemaType)) {\n kind.object = jsonSchemaType.includes('object');\n kind.string = jsonSchemaType.includes('string');\n }\n\n const zodDef = (schema as { _def?: unknown })._def;\n if (!zodDef || typeof zodDef !== 'object') {\n return kind;\n }\n\n const zodType = (zodDef as { type?: unknown; typeName?: unknown }).type;\n const zodTypeName = (zodDef as { type?: unknown; typeName?: unknown })\n .typeName;\n\n if (zodType === 'object' || zodTypeName === 'ZodObject') {\n kind.object = true;\n } else if (zodType === 'string' || zodTypeName === 'ZodString') {\n kind.string = true;\n }\n\n const innerSchema =\n (\n zodDef as {\n innerType?: unknown;\n schema?: unknown;\n type?: unknown;\n }\n ).innerType ?? (zodDef as { schema?: unknown }).schema;\n if (innerSchema) {\n const innerKind = detectSchemaKind(innerSchema);\n kind.object ||= innerKind.object;\n kind.string ||= innerKind.string;\n }\n\n const options = (zodDef as { options?: unknown }).options;\n if (Array.isArray(options)) {\n for (const option of options) {\n const optionKind = detectSchemaKind(option);\n kind.object ||= optionKind.object;\n kind.string ||= optionKind.string;\n }\n }\n\n return kind;\n}\n\nfunction getToolInputSchemaKind(tool: t.GenericTool): ToolInputSchemaKind {\n if (tool.constructor.name === 'DynamicTool') {\n return { object: false, string: true };\n }\n\n const schema = (tool as { schema?: unknown }).schema;\n return detectSchemaKind(schema);\n}\n\nfunction normalizeToolInput(\n input: t.PTCToolCall['input'],\n tool: t.GenericTool\n): t.PTCToolCall['input'] {\n const schemaKind = getToolInputSchemaKind(tool);\n\n if (typeof input !== 'string') {\n if (!schemaKind.string || schemaKind.object) {\n return input;\n }\n\n const inputValue = (input as { input?: unknown }).input;\n if (typeof inputValue === 'string') {\n return input;\n }\n\n return JSON.stringify(input);\n }\n\n if (!schemaKind.object || schemaKind.string) {\n return input;\n }\n\n const trimmed = input.trim();\n if (!trimmed.startsWith('{')) {\n return input;\n }\n\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n !Array.isArray(parsed)\n ) {\n return parsed as Record<string, unknown>;\n }\n } catch {\n return input;\n }\n\n return input;\n}\n\n/**\n * Executes tools in parallel when requested by the API.\n * Uses Promise.all for parallel execution, catching individual errors.\n * Unwraps formatted responses (e.g., MCP tool tuples) to raw data.\n * @param toolCalls - Array of tool calls from the API\n * @param toolMap - Map of tool names to executable tools\n * @returns Array of tool results\n */\nexport async function executeTools(\n toolCalls: t.PTCToolCall[],\n toolMap: t.ToolMap,\n programmaticToolName = Constants.PROGRAMMATIC_TOOL_CALLING\n): Promise<t.PTCToolResult[]> {\n const executions = toolCalls.map(async (call): Promise<t.PTCToolResult> => {\n const tool = toolMap.get(call.name);\n\n if (!tool) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: `Tool '${call.name}' not found. Available tools: ${Array.from(toolMap.keys()).join(', ')}`,\n };\n }\n\n try {\n const result = await tool.invoke(normalizeToolInput(call.input, tool), {\n metadata: { [programmaticToolName]: true },\n });\n\n const isMCPTool = tool.mcp === true;\n const unwrappedResult = unwrapToolResponse(result, isMCPTool);\n\n return {\n call_id: call.id,\n result: unwrappedResult,\n is_error: false,\n };\n } catch (error) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: (error as Error).message || 'Tool execution failed',\n };\n }\n });\n\n return await Promise.all(executions);\n}\n\n/**\n * Formats the completed response for the agent.\n *\n * Output includes stdout/stderr plus a compact session-file summary\n * when artifacts were persisted. The artifact still carries every\n * file so the host's session map stays in sync.\n *\n * @param response - The completed API response\n * @returns Tuple of [formatted string, artifact]\n */\nexport function formatCompletedResponse(\n response: t.ProgrammaticExecutionResponse,\n sourceCode = ''\n): [string, t.ProgrammaticExecutionArtifact] {\n let formatted = '';\n\n if (response.stdout != null && response.stdout !== '') {\n formatted += `stdout:\\n${response.stdout}\\n`;\n } else {\n formatted += emptyOutputMessage;\n }\n\n if (response.stderr != null && response.stderr !== '') {\n formatted += `stderr:\\n${response.stderr}\\n`;\n }\n\n const outputWithReminder = appendTmpScratchReminder(formatted, sourceCode);\n\n return [\n appendCodeSessionFileSummary(outputWithReminder, response.files),\n {\n session_id: response.session_id,\n files: response.files,\n ...(response.runtime_session_id != null\n ? {\n runtime_session_id: response.runtime_session_id,\n runtime_status: response.runtime_status,\n }\n : {}),\n } satisfies t.ProgrammaticExecutionArtifact,\n ];\n}\n\n// ============================================================================\n// Tool Factory\n// ============================================================================\n\n/**\n * Creates a Programmatic Tool Calling tool for complex multi-tool workflows.\n *\n * This tool enables AI agents to write Python code that orchestrates multiple\n * tool calls programmatically, reducing LLM round-trips and token usage.\n *\n * The tool map must be provided at runtime via config.configurable.toolMap.\n *\n * @param params - Configuration parameters (baseUrl, maxRoundTrips, proxy)\n * @returns A LangChain DynamicStructuredTool for programmatic tool calling\n *\n * @example\n * const ptcTool = createProgrammaticToolCallingTool({ maxRoundTrips: 20 });\n *\n * const [output, artifact] = await ptcTool.invoke(\n * { code, tools },\n * { configurable: { toolMap } }\n * );\n */\nexport function createProgrammaticToolCallingTool(\n initParams: t.ProgrammaticToolCallingParams = {}\n): DynamicStructuredTool {\n const baseUrl = initParams.baseUrl ?? getCodeBaseURL();\n const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;\n const maxRunTimeoutMs = resolveCodeApiRunTimeoutMs(initParams.runTimeoutMs);\n const proxy = initParams.proxy ?? process.env.PROXY;\n const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';\n const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;\n\n return tool(\n async (rawParams, config) => {\n const params = rawParams as { code: string; timeout?: number };\n const { code } = params;\n const timeout = clampCodeApiRunTimeoutMs(params.timeout, maxRunTimeoutMs);\n\n // Extra params injected by ToolNode (follows web_search pattern).\n const toolCall = (config.toolCall ?? {}) as ToolCall &\n Partial<t.ProgrammaticCache> & {\n session_id?: string;\n _injected_files?: t.CodeEnvFile[];\n _runtime_session_hint?: string;\n };\n const {\n toolMap,\n toolDefs,\n session_id,\n _injected_files,\n _runtime_session_hint,\n } = toolCall;\n\n if (toolMap == null || toolMap.size === 0) {\n throw new Error(\n 'No toolMap provided. ' +\n 'ToolNode should inject this from AgentContext when invoked through the graph.'\n );\n }\n\n if (toolDefs == null || toolDefs.length === 0) {\n throw new Error(\n 'No tool definitions provided. ' +\n 'Either pass tools in the input or ensure ToolNode injects toolDefs.'\n );\n }\n\n let roundTrip = 0;\n\n try {\n // ====================================================================\n // Phase 1: Filter tools and make initial request\n // ====================================================================\n\n const effectiveTools = filterToolsByUsage(toolDefs, code, debug);\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Sending ${effectiveTools.length} tools to API ` +\n `(filtered from ${toolDefs.length})`\n );\n }\n\n /**\n * File injection: `_injected_files` from ToolNode session\n * context. The legacy `/files/<session_id>` HTTP fallback was\n * removed (see `CodeExecutor.ts`) — codeapi's sessionAuth now\n * requires kind/id query params unavailable at this point.\n */\n let files: t.CodeEnvFile[] | undefined;\n if (_injected_files && _injected_files.length > 0) {\n files = _injected_files;\n } else if (session_id != null && session_id.length > 0) {\n // eslint-disable-next-line no-console\n console.debug(\n `[ProgrammaticToolCalling] No injected files for session_id=${session_id} — exec will run without input files`\n );\n }\n\n /* Stateful sessions: hint rides the INITIAL request only; the server\n * binds continuation round-trips to the same runtime via the\n * continuation_token. Additive — ignored by stateless servers. PTC\n * keeps its stateless prompt in v1; only the wire hint plumbs here. */\n const runtimeSessionHint =\n typeof _runtime_session_hint === 'string' &&\n _runtime_session_hint !== ''\n ? _runtime_session_hint\n : undefined;\n\n let response = await makeRequest(\n EXEC_ENDPOINT,\n {\n code,\n tools: effectiveTools,\n session_id,\n timeout,\n ...(files && files.length > 0 ? { files } : {}),\n ...(runtimeSessionHint != null\n ? { runtime_session_hint: runtimeSessionHint }\n : {}),\n },\n proxy,\n initParams.authHeaders\n );\n\n // ====================================================================\n // Phase 2: Handle response loop\n // ====================================================================\n\n while (response.status === 'tool_call_required') {\n roundTrip++;\n\n if (roundTrip > maxRoundTrips) {\n throw new Error(\n `Exceeded maximum round trips (${maxRoundTrips}). ` +\n 'This may indicate an infinite loop, excessive tool calls, ' +\n 'or a logic error in your code.'\n );\n }\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Round trip ${roundTrip}: ${response.tool_calls?.length ?? 0} tool(s) to execute`\n );\n }\n\n const toolResults = await executeTools(\n response.tool_calls ?? [],\n toolMap\n );\n\n response = await makeRequest(\n EXEC_ENDPOINT,\n {\n continuation_token: response.continuation_token,\n tool_results: toolResults,\n },\n proxy,\n initParams.authHeaders\n );\n }\n\n // ====================================================================\n // Phase 3: Handle final state\n // ====================================================================\n\n if (response.status === 'completed') {\n return formatCompletedResponse(response, code);\n }\n\n if (response.status === 'error') {\n throw new Error(\n `Execution error: ${response.error}` +\n (response.stderr != null && response.stderr !== ''\n ? `\\n\\nStderr:\\n${response.stderr}`\n : '')\n );\n }\n\n throw new Error(`Unexpected response status: ${response.status}`);\n } catch (error) {\n const messageWithReminder = appendFailedExecutionFileReminder(\n (error as Error).message,\n code\n );\n throw new Error(\n `Programmatic execution failed: ${messageWithReminder}`\n );\n }\n },\n {\n name: Constants.PROGRAMMATIC_TOOL_CALLING,\n description: ProgrammaticToolCallingDescription,\n schema: createProgrammaticToolCallingSchema(maxRunTimeoutMs),\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n"],"mappings":"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"}
@@ -207,6 +207,13 @@ var ToolNode = class extends require_run.RunnableCallable {
207
207
  agentLangfuse;
208
208
  toolCallStepIds;
209
209
  errorHandler;
210
+ /**
211
+ * Tool call ids whose `errorHandler` did NOT dispatch the error completion
212
+ * event (it returned `false` or threw). The output loop must dispatch the
213
+ * completion for these itself — skipping them there would strand the
214
+ * client's tool-call part without a terminal event.
215
+ */
216
+ undispatchedToolErrors = /* @__PURE__ */ new Set();
210
217
  toolUsageCount;
211
218
  /** Maps toolCallId → turn captured in runTool, used by handleRunToolCompletions */
212
219
  toolCallTurns = /* @__PURE__ */ new Map();
@@ -249,6 +256,21 @@ var ToolNode = class extends require_run.RunnableCallable {
249
256
  /** Tool names that bypass event dispatch and execute directly (e.g., graph-managed handoff tools) */
250
257
  directToolNames;
251
258
  /**
259
+ * Tool names whose in-process body may raise a LangGraph `interrupt()`
260
+ * mid-execution (e.g. `ask_user_question`). Used only to REORDER within
261
+ * the direct group: a tool named here that is *already* direct (a real
262
+ * in-process graphTool — the only kind whose body can reach
263
+ * `interrupt()`) is scheduled ahead of its non-interrupting direct
264
+ * siblings, so a mid-body interrupt unwinds the ToolNode before a
265
+ * non-idempotent sibling executes and LangGraph's resume-time batch
266
+ * re-execution can't double it. This set is deliberately NOT folded
267
+ * into direct classification: a name that resolves to a schema-only
268
+ * event stub (an inherited `toolDefinition` with no executable
269
+ * instance) stays event-dispatched — forcing it direct would invoke
270
+ * the stub, which throws. See {@link t.ToolNodeOptions.interruptingToolNames}.
271
+ */
272
+ interruptingToolNames;
273
+ /**
252
274
  * File checkpointer extracted from the local coding tool bundle when
253
275
  * `toolExecution.local.fileCheckpointing === true`. Exposed via
254
276
  * {@link getFileCheckpointer}. Undefined when checkpointing is off
@@ -287,7 +309,7 @@ var ToolNode = class extends require_run.RunnableCallable {
287
309
  * other's in-flight state.
288
310
  */
289
311
  anonBatchCounter = 0;
290
- constructor({ tools, toolMap, name, tags, trace, runLangfuse, agentLangfuse, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, agentId, executingAgentId, directToolNames, codeSessionToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer }) {
312
+ constructor({ tools, toolMap, name, tags, trace, runLangfuse, agentLangfuse, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, sessions, eventDrivenMode, eagerEventToolExecution, eagerEventToolExecutions, eagerEventToolUsageCount, agentId, executingAgentId, directToolNames, interruptingToolNames, codeSessionToolNames, maxContextTokens, maxToolResultChars, hookRegistry, humanInTheLoop, toolOutputReferences, toolOutputRegistry, toolExecution, fileCheckpointer }) {
291
313
  super({
292
314
  name: name ?? TOOL_NODE_RUN_NAME,
293
315
  tags,
@@ -314,6 +336,7 @@ var ToolNode = class extends require_run.RunnableCallable {
314
336
  this.agentId = agentId;
315
337
  this.executingAgentId = executingAgentId ?? agentId;
316
338
  this.directToolNames = directToolNames;
339
+ this.interruptingToolNames = interruptingToolNames != null && interruptingToolNames.size > 0 ? interruptingToolNames : void 0;
317
340
  this.codeSessionToolNames = codeSessionToolNames != null && codeSessionToolNames.length > 0 ? new Set(codeSessionToolNames) : void 0;
318
341
  this.maxToolResultChars = maxToolResultChars ?? require_truncation.calculateMaxToolResultChars(maxContextTokens);
319
342
  this.hookRegistry = hookRegistry;
@@ -580,6 +603,17 @@ var ToolNode = class extends require_run.RunnableCallable {
580
603
  };
581
604
  if (codeSession?.files != null && codeSession.files.length > 0) invokeParams._injected_files = codeSession.files.map((file) => toInjectedFileRef(file, execSessionId));
582
605
  }
606
+ /**
607
+ * Stateful runtime session hint — orthogonal to the transient
608
+ * exec-session above, and injected independently (a first call has a
609
+ * hint but no exec session yet). Explicit host hint wins; otherwise
610
+ * fall back to the conversation's thread_id.
611
+ */
612
+ const runtimeSessionHint = this.resolveRuntimeSessionHint(config);
613
+ if (runtimeSessionHint != null) invokeParams = {
614
+ ...invokeParams,
615
+ _runtime_session_hint: runtimeSessionHint
616
+ };
583
617
  }
584
618
  /**
585
619
  * Forward the graph state as langgraph 1.4's `runtime.state` so
@@ -659,12 +693,20 @@ var ToolNode = class extends require_run.RunnableCallable {
659
693
  if (!this.handleToolErrors) throw e;
660
694
  if ((0, _langchain_langgraph.isGraphInterrupt)(e)) throw e;
661
695
  if (this.errorHandler) try {
662
- await this.errorHandler({
696
+ if (await this.errorHandler({
663
697
  error: e,
664
698
  id: call.id,
665
699
  name: call.name,
666
700
  input: call.args
667
- }, config.metadata);
701
+ }, config.metadata) === false && call.id != null && call.id !== "")
702
+ /**
703
+ * The handler could not dispatch the error completion (typically
704
+ * a resume pass, where a fast-failing tool errors before the
705
+ * step replay registers its run step). Remember the call so the
706
+ * output loop dispatches the completion itself instead of
707
+ * assuming the handler covered it.
708
+ */
709
+ this.undispatchedToolErrors.add(call.id);
668
710
  } catch (handlerError) {
669
711
  console.error("Error in errorHandler:", {
670
712
  toolName: call.name,
@@ -1060,6 +1102,11 @@ var ToolNode = class extends require_run.RunnableCallable {
1060
1102
  if (name === "") return false;
1061
1103
  return require_enum.CODE_EXECUTION_TOOLS.has(name) || this.codeSessionToolNames?.has(name) === true;
1062
1104
  }
1105
+ /** Delegates to the shared resolver so the direct and event-driven planning
1106
+ * paths derive the runtime session hint identically. */
1107
+ resolveRuntimeSessionHint(config) {
1108
+ return require_eagerEventExecution.resolveRuntimeSessionHint(this.toolExecution, config.configurable?.thread_id);
1109
+ }
1063
1110
  storeCodeSessionFromResults(results, requestMap) {
1064
1111
  if (!this.sessions) return;
1065
1112
  for (let i = 0; i < results.length; i++) {
@@ -1094,7 +1141,8 @@ var ToolNode = class extends require_run.RunnableCallable {
1094
1141
  if ((0, _langchain_langgraph.isCommand)(output)) continue;
1095
1142
  const toolMessage = output;
1096
1143
  const toolCallId = call.id ?? "";
1097
- if (toolMessage.status === "error" && this.errorHandler != null) continue;
1144
+ if (toolMessage.status === "error" && this.errorHandler != null) if (this.undispatchedToolErrors.has(toolCallId)) this.undispatchedToolErrors.delete(toolCallId);
1145
+ else continue;
1098
1146
  if (this.sessions && this.participatesInCodeSession(call.name)) {
1099
1147
  const artifact = toolMessage.artifact;
1100
1148
  const execSessionId = artifact?.session_id;
@@ -1547,12 +1595,14 @@ var ToolNode = class extends require_run.RunnableCallable {
1547
1595
  const plan = require_eagerEventExecution.buildToolExecutionRequestPlan({
1548
1596
  toolCalls: approvedEntries.map((entry) => {
1549
1597
  const codeSessionContext = this.participatesInCodeSession(entry.call.name) || entry.call.name === "skill" || entry.call.name === "read_file" ? this.getCodeSessionContext() : void 0;
1598
+ const runtimeSessionHint = this.participatesInCodeSession(entry.call.name) ? this.resolveRuntimeSessionHint(config) : void 0;
1550
1599
  return {
1551
1600
  id: entry.call.id,
1552
1601
  name: entry.call.name,
1553
1602
  args: entry.args,
1554
1603
  stepId: entry.stepId,
1555
- codeSessionContext
1604
+ codeSessionContext,
1605
+ runtimeSessionHint
1556
1606
  };
1557
1607
  }),
1558
1608
  usageCount: this.toolUsageCount,
@@ -1923,6 +1973,61 @@ var ToolNode = class extends require_run.RunnableCallable {
1923
1973
  return converted;
1924
1974
  }
1925
1975
  /**
1976
+ * Execute a group of direct (in-process) tool calls with interrupt-safe
1977
+ * ordering, returning outputs aligned 1:1 with `directCalls`.
1978
+ *
1979
+ * Fast path (the common case): when no call in the group is in
1980
+ * `interruptingToolNames`, this is a single `Promise.all` — byte-for-byte
1981
+ * the prior behavior, so ordinary batches are unaffected.
1982
+ *
1983
+ * Interrupt-safe path: when the group contains an interrupting tool (e.g.
1984
+ * `ask_user_question`, whose body raises a LangGraph `interrupt()` to
1985
+ * collect a human answer), the interrupting calls run as their own awaited
1986
+ * group **first**; only after they all settle without interrupting do the
1987
+ * remaining (potentially non-idempotent) siblings run. If an interrupting
1988
+ * call throws a `GraphInterrupt`, the `await` below rejects and unwinds the
1989
+ * whole ToolNode *before* any non-interrupting sibling has started — so a
1990
+ * sibling with real side effects (send_email, billing) never executes on
1991
+ * the first pass. On the resume pass LangGraph re-runs the batch from the
1992
+ * top; the interrupting tool resolves with the host's answer instead of
1993
+ * throwing, and the siblings execute for the FIRST time, exactly once.
1994
+ *
1995
+ * Without this ordering, a flat `Promise.all` starts every sibling
1996
+ * concurrently, so a non-idempotent sibling can complete its side effect
1997
+ * before the interrupt unwinds and then run a SECOND time on resume — the
1998
+ * duplicate side effect this method exists to prevent. Interrupting tools
1999
+ * are expected to be side-effect-free (they only suspend), so running them
2000
+ * as a group and re-running them on resume is harmless.
2001
+ *
2002
+ * `batchIndices[i]` is `directCalls[i]`'s position within the parent
2003
+ * ToolNode batch (used for `{{tool<i>turn<n>}}` registration); it is
2004
+ * preserved regardless of execution order. `baseContext` carries the
2005
+ * batch-scoped fields every call shares; `batchIndex` is filled in
2006
+ * per-call here.
2007
+ */
2008
+ async runDirectBatchInterruptSafe(directCalls, batchIndices, config, baseContext) {
2009
+ const runOne = (call, position) => this.runDirectToolWithLifecycleHooks(call, config, {
2010
+ ...baseContext,
2011
+ batchIndex: batchIndices[position]
2012
+ });
2013
+ const interrupting = this.interruptingToolNames;
2014
+ if (!(interrupting != null && directCalls.some((call) => interrupting.has(call.name)))) return Promise.all(directCalls.map((call, i) => runOne(call, i)));
2015
+ const outputs = new Array(directCalls.length);
2016
+ const interruptingPositions = [];
2017
+ const regularPositions = [];
2018
+ for (let i = 0; i < directCalls.length; i++) if (interrupting.has(directCalls[i].name)) interruptingPositions.push(i);
2019
+ else regularPositions.push(i);
2020
+ const interruptingOutputs = await Promise.all(interruptingPositions.map((i) => runOne(directCalls[i], i)));
2021
+ interruptingPositions.forEach((i, k) => {
2022
+ outputs[i] = interruptingOutputs[k];
2023
+ });
2024
+ const regularOutputs = await Promise.all(regularPositions.map((i) => runOne(directCalls[i], i)));
2025
+ regularPositions.forEach((i, k) => {
2026
+ outputs[i] = regularOutputs[k];
2027
+ });
2028
+ return outputs;
2029
+ }
2030
+ /**
1926
2031
  * Execute all tool calls via ON_TOOL_EXECUTE event dispatch.
1927
2032
  * Injected messages are placed AFTER ToolMessages to respect provider
1928
2033
  * message ordering (AIMessage tool_calls must be immediately followed
@@ -2059,15 +2164,14 @@ var ToolNode = class extends require_run.RunnableCallable {
2059
2164
  }
2060
2165
  }
2061
2166
  const directAdditionalContexts = [];
2062
- const directOutputs = directCalls.length > 0 ? await Promise.all(directCalls.map((call, i) => this.runDirectToolWithLifecycleHooks(call, config, {
2063
- batchIndex: directIndices[i],
2167
+ const directOutputs = directCalls.length > 0 ? await this.runDirectBatchInterruptSafe(directCalls, directIndices, config, {
2064
2168
  turn,
2065
2169
  batchScopeId,
2066
2170
  resolvedArgsByCallId,
2067
2171
  preBatchSnapshot,
2068
2172
  additionalContextsSink: directAdditionalContexts,
2069
2173
  runInput: input
2070
- }))) : [];
2174
+ }) : [];
2071
2175
  if (directCalls.length > 0 && directOutputs.length > 0) await this.handleRunToolCompletions(directCalls, directOutputs, config, resolvedArgsByCallId);
2072
2176
  const eventResult = eventCalls.length > 0 ? await this.dispatchToolEvents(eventCalls, config, {
2073
2177
  batchIndices: eventIndices,
@@ -2095,15 +2199,14 @@ var ToolNode = class extends require_run.RunnableCallable {
2095
2199
  } else {
2096
2200
  const preBatchSnapshot = this.toolOutputRegistry?.snapshot(batchScopeId);
2097
2201
  const directAdditionalContexts = [];
2098
- const toolOutputs = await Promise.all(filteredCalls.map((call, i) => this.runDirectToolWithLifecycleHooks(call, config, {
2099
- batchIndex: i,
2202
+ const toolOutputs = await this.runDirectBatchInterruptSafe(filteredCalls, filteredCalls.map((_call, i) => i), config, {
2100
2203
  turn,
2101
2204
  batchScopeId,
2102
2205
  resolvedArgsByCallId,
2103
2206
  preBatchSnapshot,
2104
2207
  additionalContextsSink: directAdditionalContexts,
2105
2208
  runInput: input
2106
- })));
2209
+ });
2107
2210
  await this.handleRunToolCompletions(filteredCalls, toolOutputs, config, resolvedArgsByCallId);
2108
2211
  outputs = directAdditionalContexts.length > 0 ? [...toolOutputs, new _langchain_core_messages.HumanMessage({
2109
2212
  content: directAdditionalContexts.join("\n\n"),