@n8n/n8n-nodes-langchain 2.2.2 → 2.3.0

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../utils/agent-execution/buildSteps.ts"],"sourcesContent":["import { AIMessage } from '@langchain/core/messages';\nimport { nodeNameToToolName } from 'n8n-workflow';\nimport type { EngineResponse, IDataObject } from 'n8n-workflow';\n\nimport type {\n\tRequestResponseMetadata,\n\tToolCallData,\n\tThinkingContentBlock,\n\tRedactedThinkingContentBlock,\n\tToolUseContentBlock,\n} from './types';\n\n/**\n * Provider-specific metadata extracted from tool action metadata\n */\ninterface ProviderMetadata {\n\t/** Gemini thought_signature for extended thinking */\n\tthoughtSignature?: string;\n\t/** Anthropic thinking content */\n\tthinkingContent?: string;\n\t/** Anthropic thinking type (thinking or redacted_thinking) */\n\tthinkingType?: 'thinking' | 'redacted_thinking';\n\t/** Anthropic thinking signature */\n\tthinkingSignature?: string;\n}\n\n/**\n * Extracts provider-specific metadata from tool action metadata.\n * Validates and normalizes metadata from different LLM providers.\n *\n * @param metadata - The request/response metadata from tool action\n * @returns Extracted and validated provider metadata\n */\nfunction extractProviderMetadata(metadata?: RequestResponseMetadata): ProviderMetadata {\n\tif (!metadata) return {};\n\n\t// Extract Google/Gemini metadata\n\tconst thoughtSignature =\n\t\ttypeof metadata.google?.thoughtSignature === 'string'\n\t\t\t? metadata.google.thoughtSignature\n\t\t\t: undefined;\n\n\t// Extract Anthropic metadata\n\tconst thinkingContent =\n\t\ttypeof metadata.anthropic?.thinkingContent === 'string'\n\t\t\t? metadata.anthropic.thinkingContent\n\t\t\t: undefined;\n\n\tconst thinkingType =\n\t\tmetadata.anthropic?.thinkingType === 'thinking' ||\n\t\tmetadata.anthropic?.thinkingType === 'redacted_thinking'\n\t\t\t? metadata.anthropic.thinkingType\n\t\t\t: undefined;\n\n\tconst thinkingSignature =\n\t\ttypeof metadata.anthropic?.thinkingSignature === 'string'\n\t\t\t? metadata.anthropic.thinkingSignature\n\t\t\t: undefined;\n\n\treturn {\n\t\tthoughtSignature,\n\t\tthinkingContent,\n\t\tthinkingType,\n\t\tthinkingSignature,\n\t};\n}\n\n/**\n * Builds Anthropic-specific content blocks for thinking mode.\n * Creates an array with thinking block followed by tool_use block.\n *\n * IMPORTANT: The thinking block must come before tool_use in the message.\n * When content is an array, LangChain ignores tool_calls field for Anthropic,\n * so tool_use blocks must be in the content array.\n *\n * @param thinkingContent - The thinking content from Anthropic\n * @param thinkingType - Type of thinking block (thinking or redacted_thinking)\n * @param thinkingSignature - Optional signature for thinking block\n * @param toolInput - The tool input data\n * @param toolId - The tool call ID\n * @param toolName - The tool name\n * @returns Array of content blocks with thinking and tool_use\n */\nfunction buildAnthropicContentBlocks(\n\tthinkingContent: string,\n\tthinkingType: 'thinking' | 'redacted_thinking',\n\tthinkingSignature: string | undefined,\n\ttoolInput: IDataObject,\n\ttoolId: string,\n\ttoolName: string,\n): Array<ThinkingContentBlock | RedactedThinkingContentBlock | ToolUseContentBlock> {\n\t// Create thinking block with correct field names for Anthropic API\n\tconst thinkingBlock: ThinkingContentBlock | RedactedThinkingContentBlock =\n\t\tthinkingType === 'thinking'\n\t\t\t? {\n\t\t\t\t\ttype: 'thinking',\n\t\t\t\t\tthinking: thinkingContent,\n\t\t\t\t\tsignature: thinkingSignature ?? '', // Use original signature if available\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\ttype: 'redacted_thinking',\n\t\t\t\t\tdata: thinkingContent,\n\t\t\t\t};\n\n\t// Create tool_use block (required for Anthropic when using structured content)\n\tconst toolInputData = toolInput.input;\n\tconst toolUseBlock: ToolUseContentBlock = {\n\t\ttype: 'tool_use',\n\t\tid: toolId,\n\t\tname: toolName,\n\t\tinput:\n\t\t\ttoolInputData && typeof toolInputData === 'object'\n\t\t\t\t? (toolInputData as Record<string, unknown>)\n\t\t\t\t: {},\n\t};\n\n\treturn [thinkingBlock, toolUseBlock];\n}\n\n/**\n * Builds message content for AI message, handling provider-specific formats.\n * For Anthropic thinking mode, creates content blocks with thinking and tool_use.\n * For other providers, creates simple string content.\n *\n * @param providerMetadata - Provider-specific metadata\n * @param toolInput - The tool input data\n * @param toolId - The tool call ID\n * @param toolName - The tool name\n * @param nodeName - The node name for fallback string content\n * @returns Message content (string or content blocks array)\n */\nfunction buildMessageContent(\n\tproviderMetadata: ProviderMetadata,\n\ttoolInput: IDataObject,\n\ttoolId: string,\n\ttoolName: string,\n\tnodeName: string,\n): string | Array<ThinkingContentBlock | RedactedThinkingContentBlock | ToolUseContentBlock> {\n\tconst { thinkingContent, thinkingType, thinkingSignature } = providerMetadata;\n\n\t// Anthropic thinking mode: build content blocks\n\tif (thinkingContent && thinkingType) {\n\t\treturn buildAnthropicContentBlocks(\n\t\t\tthinkingContent,\n\t\t\tthinkingType,\n\t\t\tthinkingSignature,\n\t\t\ttoolInput,\n\t\t\ttoolId,\n\t\t\ttoolName,\n\t\t);\n\t}\n\n\t// Default: simple string content\n\treturn `Calling ${nodeName} with input: ${JSON.stringify(toolInput)}`;\n}\n\n/**\n * Rebuilds the agent steps from previous tool call responses.\n * This is used to continue agent execution after tool calls have been made.\n *\n * This is a generalized version that can be used across different agent types\n * (Tools Agent, OpenAI Functions Agent, etc.).\n *\n * @param response - The engine response containing tool call results\n * @param itemIndex - The current item index being processed\n * @returns Array of tool call data representing the agent steps\n */\nexport function buildSteps(\n\tresponse: EngineResponse<RequestResponseMetadata> | undefined,\n\titemIndex: number,\n): ToolCallData[] {\n\tconst steps: ToolCallData[] = [];\n\n\tif (response) {\n\t\tconst responses = response?.actionResponses ?? [];\n\n\t\tif (response.metadata?.previousRequests) {\n\t\t\tsteps.push.apply(steps, response.metadata.previousRequests);\n\t\t}\n\n\t\tfor (const tool of responses) {\n\t\t\tif (tool.action?.metadata?.itemIndex !== itemIndex) continue;\n\n\t\t\tconst toolInput: IDataObject = {\n\t\t\t\t...tool.action.input,\n\t\t\t\tid: tool.action.id,\n\t\t\t};\n\t\t\tif (!toolInput || !tool.data) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst step = steps.find((step) => step.action.toolCallId === toolInput.id);\n\t\t\tif (step) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Extract provider-specific metadata (Gemini, Anthropic, etc.)\n\t\t\tconst providerMetadata = extractProviderMetadata(tool.action.metadata);\n\n\t\t\t// Build tool ID and name for reuse\n\t\t\tconst toolId = typeof toolInput?.id === 'string' ? toolInput.id : 'reconstructed_call';\n\t\t\tconst toolName = nodeNameToToolName(tool.action.nodeName);\n\n\t\t\t// Build the tool call object with thought_signature if present (for Gemini)\n\t\t\tconst toolCall = {\n\t\t\t\tid: toolId,\n\t\t\t\tname: toolName,\n\t\t\t\targs: toolInput,\n\t\t\t\ttype: 'tool_call' as const,\n\t\t\t\tadditional_kwargs: {\n\t\t\t\t\t...(providerMetadata.thoughtSignature && {\n\t\t\t\t\t\tthought_signature: providerMetadata.thoughtSignature,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Build message content using provider-specific logic\n\t\t\tconst messageContent = buildMessageContent(\n\t\t\t\tproviderMetadata,\n\t\t\t\ttoolInput,\n\t\t\t\ttoolId,\n\t\t\t\ttoolName,\n\t\t\t\ttool.action.nodeName,\n\t\t\t);\n\n\t\t\tconst syntheticAIMessage = new AIMessage({\n\t\t\t\tcontent: messageContent,\n\t\t\t\t// Note: tool_calls is only used when content is a string\n\t\t\t\t// When content is an array (thinking mode), tool_use blocks are in the content array\n\t\t\t\t...(typeof messageContent === 'string' && { tool_calls: [toolCall] }),\n\t\t\t});\n\n\t\t\tconst toolInputForResult = toolInput.input;\n\n\t\t\t// Build observation from tool result data or error information\n\t\t\t// When tool execution fails, ai_tool may be missing but error info should be preserved\n\t\t\tconst aiToolData = tool.data?.data?.ai_tool?.[0]?.map((item) => item?.json);\n\t\t\tlet observation: string;\n\t\t\tif (aiToolData && aiToolData.length > 0) {\n\t\t\t\tobservation = JSON.stringify(aiToolData);\n\t\t\t} else if (tool.data?.error) {\n\t\t\t\t// Include error information in observation so the agent can see what went wrong\n\t\t\t\t// tool.data is ITaskData which has error?: ExecutionError\n\t\t\t\tconst errorInfo = {\n\t\t\t\t\terror: tool.data.error.message ?? 'Unknown error',\n\t\t\t\t\t...(tool.data.error.name && { errorType: tool.data.error.name }),\n\t\t\t\t};\n\t\t\t\tobservation = JSON.stringify(errorInfo);\n\t\t\t} else {\n\t\t\t\tobservation = JSON.stringify('');\n\t\t\t}\n\n\t\t\tconst toolResult = {\n\t\t\t\taction: {\n\t\t\t\t\ttool: nodeNameToToolName(tool.action.nodeName),\n\t\t\t\t\ttoolInput:\n\t\t\t\t\t\ttoolInputForResult && typeof toolInputForResult === 'object'\n\t\t\t\t\t\t\t? (toolInputForResult as IDataObject)\n\t\t\t\t\t\t\t: {},\n\t\t\t\t\tlog: toolInput.log || syntheticAIMessage.content,\n\t\t\t\t\tmessageLog: [syntheticAIMessage],\n\t\t\t\t\ttoolCallId: toolInput?.id,\n\t\t\t\t\ttype: toolInput.type || 'tool_call',\n\t\t\t\t},\n\t\t\t\tobservation,\n\t\t\t};\n\n\t\t\tsteps.push(toolResult);\n\t\t}\n\t}\n\treturn steps;\n}\n"],"mappings":"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","names":["step"]}
1
+ {"version":3,"sources":["../../../utils/agent-execution/buildSteps.ts"],"sourcesContent":["import { AIMessage } from '@langchain/core/messages';\nimport { nodeNameToToolName } from 'n8n-workflow';\nimport type { EngineResponse, IDataObject } from 'n8n-workflow';\n\nimport type {\n\tRequestResponseMetadata,\n\tToolCallData,\n\tThinkingContentBlock,\n\tRedactedThinkingContentBlock,\n\tToolUseContentBlock,\n} from './types';\n\n/**\n * Provider-specific metadata extracted from tool action metadata\n */\ninterface ProviderMetadata {\n\t/** Gemini thought_signature for extended thinking */\n\tthoughtSignature?: string;\n\t/** Anthropic thinking content */\n\tthinkingContent?: string;\n\t/** Anthropic thinking type (thinking or redacted_thinking) */\n\tthinkingType?: 'thinking' | 'redacted_thinking';\n\t/** Anthropic thinking signature */\n\tthinkingSignature?: string;\n}\n\n/**\n * Extracts provider-specific metadata from tool action metadata.\n * Validates and normalizes metadata from different LLM providers.\n *\n * @param metadata - The request/response metadata from tool action\n * @returns Extracted and validated provider metadata\n */\nfunction extractProviderMetadata(metadata?: RequestResponseMetadata): ProviderMetadata {\n\tif (!metadata) return {};\n\n\t// Extract Google/Gemini metadata\n\tconst thoughtSignature =\n\t\ttypeof metadata.google?.thoughtSignature === 'string'\n\t\t\t? metadata.google.thoughtSignature\n\t\t\t: undefined;\n\n\t// Extract Anthropic metadata\n\tconst thinkingContent =\n\t\ttypeof metadata.anthropic?.thinkingContent === 'string'\n\t\t\t? metadata.anthropic.thinkingContent\n\t\t\t: undefined;\n\n\tconst thinkingType =\n\t\tmetadata.anthropic?.thinkingType === 'thinking' ||\n\t\tmetadata.anthropic?.thinkingType === 'redacted_thinking'\n\t\t\t? metadata.anthropic.thinkingType\n\t\t\t: undefined;\n\n\tconst thinkingSignature =\n\t\ttypeof metadata.anthropic?.thinkingSignature === 'string'\n\t\t\t? metadata.anthropic.thinkingSignature\n\t\t\t: undefined;\n\n\treturn {\n\t\tthoughtSignature,\n\t\tthinkingContent,\n\t\tthinkingType,\n\t\tthinkingSignature,\n\t};\n}\n\n/**\n * Builds Anthropic-specific content blocks for thinking mode.\n * Creates an array with thinking block followed by tool_use block.\n *\n * IMPORTANT: The thinking block must come before tool_use in the message.\n * When content is an array, LangChain ignores tool_calls field for Anthropic,\n * so tool_use blocks must be in the content array.\n *\n * @param thinkingContent - The thinking content from Anthropic\n * @param thinkingType - Type of thinking block (thinking or redacted_thinking)\n * @param thinkingSignature - Optional signature for thinking block\n * @param toolInput - The tool input data\n * @param toolId - The tool call ID\n * @param toolName - The tool name\n * @returns Array of content blocks with thinking and tool_use\n */\nfunction buildAnthropicContentBlocks(\n\tthinkingContent: string,\n\tthinkingType: 'thinking' | 'redacted_thinking',\n\tthinkingSignature: string | undefined,\n\ttoolInput: IDataObject,\n\ttoolId: string,\n\ttoolName: string,\n): Array<ThinkingContentBlock | RedactedThinkingContentBlock | ToolUseContentBlock> {\n\t// Create thinking block with correct field names for Anthropic API\n\tconst thinkingBlock: ThinkingContentBlock | RedactedThinkingContentBlock =\n\t\tthinkingType === 'thinking'\n\t\t\t? {\n\t\t\t\t\ttype: 'thinking',\n\t\t\t\t\tthinking: thinkingContent,\n\t\t\t\t\tsignature: thinkingSignature ?? '', // Use original signature if available\n\t\t\t\t}\n\t\t\t: {\n\t\t\t\t\ttype: 'redacted_thinking',\n\t\t\t\t\tdata: thinkingContent,\n\t\t\t\t};\n\n\t// Create tool_use block (required for Anthropic when using structured content)\n\tconst toolInputData = toolInput.input;\n\tconst toolUseBlock: ToolUseContentBlock = {\n\t\ttype: 'tool_use',\n\t\tid: toolId,\n\t\tname: toolName,\n\t\tinput:\n\t\t\ttoolInputData && typeof toolInputData === 'object'\n\t\t\t\t? (toolInputData as Record<string, unknown>)\n\t\t\t\t: {},\n\t};\n\n\treturn [thinkingBlock, toolUseBlock];\n}\n\n/**\n * Builds message content for AI message, handling provider-specific formats.\n * For Anthropic thinking mode, creates content blocks with thinking and tool_use.\n * For other providers, creates simple string content.\n *\n * @param providerMetadata - Provider-specific metadata\n * @param toolInput - The tool input data\n * @param toolId - The tool call ID\n * @param toolName - The tool name\n * @param nodeName - The node name for fallback string content\n * @returns Message content (string or content blocks array)\n */\nfunction buildMessageContent(\n\tproviderMetadata: ProviderMetadata,\n\ttoolInput: IDataObject,\n\ttoolId: string,\n\ttoolName: string,\n\tnodeName: string,\n): string | Array<ThinkingContentBlock | RedactedThinkingContentBlock | ToolUseContentBlock> {\n\tconst { thinkingContent, thinkingType, thinkingSignature } = providerMetadata;\n\n\t// Anthropic thinking mode: build content blocks\n\tif (thinkingContent && thinkingType) {\n\t\treturn buildAnthropicContentBlocks(\n\t\t\tthinkingContent,\n\t\t\tthinkingType,\n\t\t\tthinkingSignature,\n\t\t\ttoolInput,\n\t\t\ttoolId,\n\t\t\ttoolName,\n\t\t);\n\t}\n\n\t// Default: simple string content\n\treturn `Calling ${nodeName} with input: ${JSON.stringify(toolInput)}`;\n}\n\n/**\n * Rebuilds the agent steps from previous tool call responses.\n * This is used to continue agent execution after tool calls have been made.\n *\n * This is a generalized version that can be used across different agent types\n * (Tools Agent, OpenAI Functions Agent, etc.).\n *\n * @param response - The engine response containing tool call results\n * @param itemIndex - The current item index being processed\n * @returns Array of tool call data representing the agent steps\n */\nexport function buildSteps(\n\tresponse: EngineResponse<RequestResponseMetadata> | undefined,\n\titemIndex: number,\n): ToolCallData[] {\n\tconst steps: ToolCallData[] = [];\n\n\tif (response) {\n\t\tconst responses = response?.actionResponses ?? [];\n\n\t\tif (response.metadata?.previousRequests) {\n\t\t\tsteps.push.apply(steps, response.metadata.previousRequests);\n\t\t}\n\n\t\tfor (const tool of responses) {\n\t\t\tif (tool.action?.metadata?.itemIndex !== itemIndex) continue;\n\n\t\t\tconst toolInput: IDataObject = {\n\t\t\t\t...tool.action.input,\n\t\t\t\tid: tool.action.id,\n\t\t\t};\n\t\t\tif (!toolInput || !tool.data) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst step = steps.find((step) => step.action.toolCallId === toolInput.id);\n\t\t\tif (step) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Extract provider-specific metadata (Gemini, Anthropic, etc.)\n\t\t\tconst providerMetadata = extractProviderMetadata(tool.action.metadata);\n\n\t\t\t// Build tool ID and name for reuse\n\t\t\tconst toolId = typeof toolInput?.id === 'string' ? toolInput.id : 'reconstructed_call';\n\t\t\tconst toolName = nodeNameToToolName(tool.action.nodeName);\n\n\t\t\t// Build the tool call object with thought_signature if present (for Gemini)\n\t\t\tconst toolCall = {\n\t\t\t\tid: toolId,\n\t\t\t\tname: toolName,\n\t\t\t\targs: toolInput,\n\t\t\t\ttype: 'tool_call' as const,\n\t\t\t\tadditional_kwargs: {\n\t\t\t\t\t...(providerMetadata.thoughtSignature && {\n\t\t\t\t\t\tthought_signature: providerMetadata.thoughtSignature,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// Build message content using provider-specific logic\n\t\t\tconst messageContent = buildMessageContent(\n\t\t\t\tproviderMetadata,\n\t\t\t\ttoolInput,\n\t\t\t\ttoolId,\n\t\t\t\ttoolName,\n\t\t\t\ttool.action.nodeName,\n\t\t\t);\n\n\t\t\tconst syntheticAIMessage = new AIMessage({\n\t\t\t\tcontent: messageContent,\n\t\t\t\t// Note: tool_calls is only used when content is a string\n\t\t\t\t// When content is an array (thinking mode), tool_use blocks are in the content array\n\t\t\t\t...(typeof messageContent === 'string' && { tool_calls: [toolCall] }),\n\t\t\t});\n\n\t\t\t// Extract tool input arguments for the result\n\t\t\t// Exclude metadata fields: id, log, type - always keep as object for type consistency\n\t\t\tconst { id, log, type, ...toolInputForResult } = toolInput;\n\n\t\t\t// Build observation from tool result data or error information\n\t\t\t// When tool execution fails, ai_tool may be missing but error info should be preserved\n\t\t\tconst aiToolData = tool.data?.data?.ai_tool?.[0]?.map((item) => item?.json);\n\t\t\tlet observation: string;\n\t\t\tif (aiToolData && aiToolData.length > 0) {\n\t\t\t\tobservation = JSON.stringify(aiToolData);\n\t\t\t} else if (tool.data?.error) {\n\t\t\t\t// Include error information in observation so the agent can see what went wrong\n\t\t\t\t// tool.data is ITaskData which has error?: ExecutionError\n\t\t\t\tconst errorInfo = {\n\t\t\t\t\terror: tool.data.error.message ?? 'Unknown error',\n\t\t\t\t\t...(tool.data.error.name && { errorType: tool.data.error.name }),\n\t\t\t\t};\n\t\t\t\tobservation = JSON.stringify(errorInfo);\n\t\t\t} else {\n\t\t\t\tobservation = JSON.stringify('');\n\t\t\t}\n\n\t\t\tconst toolResult = {\n\t\t\t\taction: {\n\t\t\t\t\ttool: nodeNameToToolName(tool.action.nodeName),\n\t\t\t\t\ttoolInput: toolInputForResult,\n\t\t\t\t\tlog: toolInput.log || syntheticAIMessage.content,\n\t\t\t\t\tmessageLog: [syntheticAIMessage],\n\t\t\t\t\ttoolCallId: toolInput?.id,\n\t\t\t\t\ttype: toolInput.type || 'tool_call',\n\t\t\t\t},\n\t\t\t\tobservation,\n\t\t\t};\n\n\t\t\tsteps.push(toolResult);\n\t\t}\n\t}\n\treturn steps;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,sBAA0B;AAC1B,0BAAmC;AAgCnC,SAAS,wBAAwB,UAAsD;AACtF,MAAI,CAAC,SAAU,QAAO,CAAC;AAGvB,QAAM,mBACL,OAAO,SAAS,QAAQ,qBAAqB,WAC1C,SAAS,OAAO,mBAChB;AAGJ,QAAM,kBACL,OAAO,SAAS,WAAW,oBAAoB,WAC5C,SAAS,UAAU,kBACnB;AAEJ,QAAM,eACL,SAAS,WAAW,iBAAiB,cACrC,SAAS,WAAW,iBAAiB,sBAClC,SAAS,UAAU,eACnB;AAEJ,QAAM,oBACL,OAAO,SAAS,WAAW,sBAAsB,WAC9C,SAAS,UAAU,oBACnB;AAEJ,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAkBA,SAAS,4BACR,iBACA,cACA,mBACA,WACA,QACA,UACmF;AAEnF,QAAM,gBACL,iBAAiB,aACd;AAAA,IACA,MAAM;AAAA,IACN,UAAU;AAAA,IACV,WAAW,qBAAqB;AAAA;AAAA,EACjC,IACC;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,EACP;AAGH,QAAM,gBAAgB,UAAU;AAChC,QAAM,eAAoC;AAAA,IACzC,MAAM;AAAA,IACN,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,OACC,iBAAiB,OAAO,kBAAkB,WACtC,gBACD,CAAC;AAAA,EACN;AAEA,SAAO,CAAC,eAAe,YAAY;AACpC;AAcA,SAAS,oBACR,kBACA,WACA,QACA,UACA,UAC4F;AAC5F,QAAM,EAAE,iBAAiB,cAAc,kBAAkB,IAAI;AAG7D,MAAI,mBAAmB,cAAc;AACpC,WAAO;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAGA,SAAO,WAAW,QAAQ,gBAAgB,KAAK,UAAU,SAAS,CAAC;AACpE;AAaO,SAAS,WACf,UACA,WACiB;AACjB,QAAM,QAAwB,CAAC;AAE/B,MAAI,UAAU;AACb,UAAM,YAAY,UAAU,mBAAmB,CAAC;AAEhD,QAAI,SAAS,UAAU,kBAAkB;AACxC,YAAM,KAAK,MAAM,OAAO,SAAS,SAAS,gBAAgB;AAAA,IAC3D;AAEA,eAAW,QAAQ,WAAW;AAC7B,UAAI,KAAK,QAAQ,UAAU,cAAc,UAAW;AAEpD,YAAM,YAAyB;AAAA,QAC9B,GAAG,KAAK,OAAO;AAAA,QACf,IAAI,KAAK,OAAO;AAAA,MACjB;AACA,UAAI,CAAC,aAAa,CAAC,KAAK,MAAM;AAC7B;AAAA,MACD;AAEA,YAAM,OAAO,MAAM,KAAK,CAACA,UAASA,MAAK,OAAO,eAAe,UAAU,EAAE;AACzE,UAAI,MAAM;AACT;AAAA,MACD;AAGA,YAAM,mBAAmB,wBAAwB,KAAK,OAAO,QAAQ;AAGrE,YAAM,SAAS,OAAO,WAAW,OAAO,WAAW,UAAU,KAAK;AAClE,YAAM,eAAW,wCAAmB,KAAK,OAAO,QAAQ;AAGxD,YAAM,WAAW;AAAA,QAChB,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,MAAM;AAAA,QACN,MAAM;AAAA,QACN,mBAAmB;AAAA,UAClB,GAAI,iBAAiB,oBAAoB;AAAA,YACxC,mBAAmB,iBAAiB;AAAA,UACrC;AAAA,QACD;AAAA,MACD;AAGA,YAAM,iBAAiB;AAAA,QACtB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK,OAAO;AAAA,MACb;AAEA,YAAM,qBAAqB,IAAI,0BAAU;AAAA,QACxC,SAAS;AAAA;AAAA;AAAA,QAGT,GAAI,OAAO,mBAAmB,YAAY,EAAE,YAAY,CAAC,QAAQ,EAAE;AAAA,MACpE,CAAC;AAID,YAAM,EAAE,IAAI,KAAK,MAAM,GAAG,mBAAmB,IAAI;AAIjD,YAAM,aAAa,KAAK,MAAM,MAAM,UAAU,CAAC,GAAG,IAAI,CAAC,SAAS,MAAM,IAAI;AAC1E,UAAI;AACJ,UAAI,cAAc,WAAW,SAAS,GAAG;AACxC,sBAAc,KAAK,UAAU,UAAU;AAAA,MACxC,WAAW,KAAK,MAAM,OAAO;AAG5B,cAAM,YAAY;AAAA,UACjB,OAAO,KAAK,KAAK,MAAM,WAAW;AAAA,UAClC,GAAI,KAAK,KAAK,MAAM,QAAQ,EAAE,WAAW,KAAK,KAAK,MAAM,KAAK;AAAA,QAC/D;AACA,sBAAc,KAAK,UAAU,SAAS;AAAA,MACvC,OAAO;AACN,sBAAc,KAAK,UAAU,EAAE;AAAA,MAChC;AAEA,YAAM,aAAa;AAAA,QAClB,QAAQ;AAAA,UACP,UAAM,wCAAmB,KAAK,OAAO,QAAQ;AAAA,UAC7C,WAAW;AAAA,UACX,KAAK,UAAU,OAAO,mBAAmB;AAAA,UACzC,YAAY,CAAC,kBAAkB;AAAA,UAC/B,YAAY,WAAW;AAAA,UACvB,MAAM,UAAU,QAAQ;AAAA,QACzB;AAAA,QACA;AAAA,MACD;AAEA,YAAM,KAAK,UAAU;AAAA,IACtB;AAAA,EACD;AACA,SAAO;AACR;","names":["step"]}
@@ -18,23 +18,80 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var memoryManagement_exports = {};
20
20
  __export(memoryManagement_exports, {
21
+ buildMessagesFromSteps: () => buildMessagesFromSteps,
21
22
  buildToolContext: () => buildToolContext,
23
+ extractToolCallId: () => extractToolCallId,
22
24
  loadMemory: () => loadMemory,
23
25
  saveToMemory: () => saveToMemory
24
26
  });
25
27
  module.exports = __toCommonJS(memoryManagement_exports);
26
28
  var import_messages = require("@langchain/core/messages");
29
+ function extractToolCallId(toolCallId, toolName) {
30
+ if (typeof toolCallId === "string" && toolCallId.length > 0) {
31
+ return toolCallId;
32
+ }
33
+ if (typeof toolCallId === "object" && toolCallId !== null && !Array.isArray(toolCallId) && "id" in toolCallId) {
34
+ const id = toolCallId.id;
35
+ if (typeof id === "string" && id.length > 0) {
36
+ return id;
37
+ }
38
+ }
39
+ if (Array.isArray(toolCallId) && toolCallId.length > 0) {
40
+ return extractToolCallId(toolCallId[0], toolName);
41
+ }
42
+ return `synthetic_${toolName}_${Date.now()}`;
43
+ }
44
+ function buildMessagesFromSteps(steps) {
45
+ const messages = [];
46
+ for (let i = 0; i < steps.length; i++) {
47
+ const step = steps[i];
48
+ const existingAIMessage = step.action.messageLog?.[0];
49
+ const existingToolCallId = existingAIMessage?.tool_calls?.[0]?.id;
50
+ const toolCallId = existingToolCallId ?? extractToolCallId(step.action.toolCallId, step.action.tool);
51
+ const aiMessage = existingAIMessage ?? new import_messages.AIMessage({
52
+ content: `Calling ${step.action.tool} with input: ${JSON.stringify(step.action.toolInput)}`,
53
+ tool_calls: [
54
+ {
55
+ id: toolCallId,
56
+ name: step.action.tool,
57
+ args: step.action.toolInput,
58
+ type: "tool_call"
59
+ }
60
+ ]
61
+ });
62
+ const toolMessage = new import_messages.ToolMessage({
63
+ content: step.observation,
64
+ tool_call_id: toolCallId,
65
+ name: step.action.tool
66
+ });
67
+ messages.push(aiMessage);
68
+ messages.push(toolMessage);
69
+ }
70
+ return messages;
71
+ }
27
72
  function buildToolContext(steps) {
28
73
  return steps.map(
29
74
  (step) => `Tool: ${step.action.tool}, Input: ${JSON.stringify(step.action.toolInput)}, Result: ${step.observation}`
30
75
  ).join("; ");
31
76
  }
77
+ function cleanupOrphanedMessages(chatHistory) {
78
+ while (chatHistory.length > 0 && chatHistory[0] instanceof import_messages.ToolMessage) {
79
+ chatHistory.shift();
80
+ }
81
+ const firstMessage = chatHistory[0];
82
+ const hasOrphanedAIMessage = firstMessage instanceof import_messages.AIMessage && (firstMessage.tool_calls?.length ?? 0) > 0 && !(chatHistory[1] instanceof import_messages.ToolMessage);
83
+ if (hasOrphanedAIMessage) {
84
+ chatHistory.shift();
85
+ }
86
+ return chatHistory;
87
+ }
32
88
  async function loadMemory(memory, model, maxTokens) {
33
89
  if (!memory) {
34
90
  return void 0;
35
91
  }
36
92
  const memoryVariables = await memory.loadMemoryVariables({});
37
93
  let chatHistory = memoryVariables["chat_history"] || [];
94
+ chatHistory = cleanupOrphanedMessages(chatHistory);
38
95
  if (maxTokens && model) {
39
96
  chatHistory = await (0, import_messages.trimMessages)(chatHistory, {
40
97
  strategy: "last",
@@ -44,23 +101,41 @@ async function loadMemory(memory, model, maxTokens) {
44
101
  startOn: "human",
45
102
  allowPartial: true
46
103
  });
104
+ chatHistory = cleanupOrphanedMessages(chatHistory);
47
105
  }
48
106
  return chatHistory;
49
107
  }
50
- async function saveToMemory(input, output, memory, steps) {
108
+ async function saveToMemory(input, output, memory, steps, previousStepsCount) {
51
109
  if (!output || !memory) {
52
110
  return;
53
111
  }
54
- let fullOutput = output;
55
- if (steps && steps.length > 0) {
56
- const toolContext = buildToolContext(steps);
57
- fullOutput = `[Used tools: ${toolContext}] ${fullOutput}`;
112
+ if (!steps || steps.length === 0) {
113
+ await memory.saveContext({ input }, { output });
114
+ return;
115
+ }
116
+ const newSteps = previousStepsCount ? steps.slice(previousStepsCount) : steps;
117
+ if (newSteps.length === 0) {
118
+ await memory.saveContext({ input }, { output });
119
+ return;
120
+ }
121
+ if (!("addMessages" in memory.chatHistory) || typeof memory.chatHistory.addMessages !== "function") {
122
+ const toolContext = buildToolContext(newSteps);
123
+ const fullOutput = `[Used tools: ${toolContext}] ${output}`;
124
+ await memory.saveContext({ input }, { output: fullOutput });
125
+ return;
58
126
  }
59
- await memory.saveContext({ input }, { output: fullOutput });
127
+ const messages = [];
128
+ messages.push(new import_messages.HumanMessage(input));
129
+ const toolMessages = buildMessagesFromSteps(newSteps);
130
+ messages.push.apply(messages, toolMessages);
131
+ messages.push(new import_messages.AIMessage(output));
132
+ await memory.chatHistory.addMessages(messages);
60
133
  }
61
134
  // Annotate the CommonJS export names for ESM import in node:
62
135
  0 && (module.exports = {
136
+ buildMessagesFromSteps,
63
137
  buildToolContext,
138
+ extractToolCallId,
64
139
  loadMemory,
65
140
  saveToMemory
66
141
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../utils/agent-execution/memoryManagement.ts"],"sourcesContent":["import type { BaseChatModel } from '@langchain/core/language_models/chat_models';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { trimMessages } from '@langchain/core/messages';\nimport type { BaseChatMemory } from '@langchain/classic/memory';\n\nimport type { ToolCallData } from './types';\n\n/**\n * Builds a formatted string representation of tool calls for memory storage.\n * This creates a consistent format that can be used across both streaming and non-streaming modes.\n *\n * @param steps - Array of tool call data with actions and observations\n * @returns Formatted string of tool calls separated by semicolons\n *\n * @example\n * ```typescript\n * const context = buildToolContext([{\n * action: { tool: 'calculator', toolInput: { expression: '2+2' }, ... },\n * observation: '4'\n * }]);\n * // Returns: \"Tool: calculator, Input: {\"expression\":\"2+2\"}, Result: 4\"\n * ```\n */\nexport function buildToolContext(steps: ToolCallData[]): string {\n\treturn steps\n\t\t.map(\n\t\t\t(step) =>\n\t\t\t\t`Tool: ${step.action.tool}, Input: ${JSON.stringify(step.action.toolInput)}, Result: ${step.observation}`,\n\t\t)\n\t\t.join('; ');\n}\n\n/**\n * Loads chat history from memory and optionally trims it to fit within token limits.\n *\n * @param memory - The memory instance to load from\n * @param model - Optional chat model for token counting (required if maxTokens is specified)\n * @param maxTokens - Optional maximum number of tokens to load from memory\n * @returns Array of base messages representing the chat history\n *\n * @example\n * ```typescript\n * // Load all history\n * const messages = await loadMemory(memory);\n *\n * // Load with token limit\n * const messages = await loadMemory(memory, model, 2000);\n * ```\n */\nexport async function loadMemory(\n\tmemory?: BaseChatMemory,\n\tmodel?: BaseChatModel,\n\tmaxTokens?: number,\n): Promise<BaseMessage[] | undefined> {\n\tif (!memory) {\n\t\treturn undefined;\n\t}\n\tconst memoryVariables = await memory.loadMemoryVariables({});\n\tlet chatHistory = (memoryVariables['chat_history'] as BaseMessage[]) || [];\n\n\t// Trim messages if token limit is specified and model is available\n\tif (maxTokens && model) {\n\t\tchatHistory = await trimMessages(chatHistory, {\n\t\t\tstrategy: 'last',\n\t\t\tmaxTokens,\n\t\t\ttokenCounter: model,\n\t\t\tincludeSystem: true,\n\t\t\tstartOn: 'human',\n\t\t\tallowPartial: true,\n\t\t});\n\t}\n\n\treturn chatHistory;\n}\n\n/**\n * Saves a conversation turn (user input + agent output) to memory.\n *\n * @param memory - The memory instance to save to\n * @param input - The user input/prompt\n * @param output - The agent's output/response\n *\n * @example\n * ```typescript\n * await saveToMemory(memory, 'What is 2+2?', 'The answer is 4');\n * ```\n */\nexport async function saveToMemory(\n\tinput: string,\n\toutput: string,\n\tmemory?: BaseChatMemory,\n\tsteps?: ToolCallData[],\n): Promise<void> {\n\tif (!output || !memory) {\n\t\treturn;\n\t}\n\tlet fullOutput = output;\n\tif (steps && steps.length > 0) {\n\t\tconst toolContext = buildToolContext(steps);\n\t\tfullOutput = `[Used tools: ${toolContext}] ${fullOutput}`;\n\t}\n\n\tawait memory.saveContext({ input }, { output: fullOutput });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,sBAA6B;AAqBtB,SAAS,iBAAiB,OAA+B;AAC/D,SAAO,MACL;AAAA,IACA,CAAC,SACA,SAAS,KAAK,OAAO,IAAI,YAAY,KAAK,UAAU,KAAK,OAAO,SAAS,CAAC,aAAa,KAAK,WAAW;AAAA,EACzG,EACC,KAAK,IAAI;AACZ;AAmBA,eAAsB,WACrB,QACA,OACA,WACqC;AACrC,MAAI,CAAC,QAAQ;AACZ,WAAO;AAAA,EACR;AACA,QAAM,kBAAkB,MAAM,OAAO,oBAAoB,CAAC,CAAC;AAC3D,MAAI,cAAe,gBAAgB,cAAc,KAAuB,CAAC;AAGzE,MAAI,aAAa,OAAO;AACvB,kBAAc,UAAM,8BAAa,aAAa;AAAA,MAC7C,UAAU;AAAA,MACV;AAAA,MACA,cAAc;AAAA,MACd,eAAe;AAAA,MACf,SAAS;AAAA,MACT,cAAc;AAAA,IACf,CAAC;AAAA,EACF;AAEA,SAAO;AACR;AAcA,eAAsB,aACrB,OACA,QACA,QACA,OACgB;AAChB,MAAI,CAAC,UAAU,CAAC,QAAQ;AACvB;AAAA,EACD;AACA,MAAI,aAAa;AACjB,MAAI,SAAS,MAAM,SAAS,GAAG;AAC9B,UAAM,cAAc,iBAAiB,KAAK;AAC1C,iBAAa,gBAAgB,WAAW,KAAK,UAAU;AAAA,EACxD;AAEA,QAAM,OAAO,YAAY,EAAE,MAAM,GAAG,EAAE,QAAQ,WAAW,CAAC;AAC3D;","names":[]}
1
+ {"version":3,"sources":["../../../utils/agent-execution/memoryManagement.ts"],"sourcesContent":["import type { BaseChatMemory } from '@langchain/classic/memory';\nimport type { BaseChatModel } from '@langchain/core/language_models/chat_models';\nimport type { BaseMessage } from '@langchain/core/messages';\nimport { AIMessage, HumanMessage, ToolMessage, trimMessages } from '@langchain/core/messages';\nimport type { IDataObject, GenericValue } from 'n8n-workflow';\n\nimport type { ToolCallData } from './types';\n\n/**\n * Extracts a string tool_call_id from various possible formats.\n * Handles the complex type: IDataObject | GenericValue | GenericValue[] | IDataObject[]\n *\n * @param toolCallId - The tool call ID in various possible formats\n * @param toolName - The tool name, used for generating synthetic IDs\n * @returns A valid string tool_call_id\n *\n * @example\n * ```typescript\n * extractToolCallId('call-123', 'calculator') // Returns: 'call-123'\n * extractToolCallId({ id: 'call-456' }, 'search') // Returns: 'call-456'\n * extractToolCallId(['call-789'], 'weather') // Returns: 'call-789'\n * extractToolCallId(null, 'unknown') // Returns: 'synthetic_unknown_1234567890'\n * ```\n */\nexport function extractToolCallId(\n\ttoolCallId: IDataObject | GenericValue | GenericValue[] | IDataObject[],\n\ttoolName: string,\n): string {\n\t// Case 1: Already a string\n\tif (typeof toolCallId === 'string' && toolCallId.length > 0) {\n\t\treturn toolCallId;\n\t}\n\n\t// Case 2: Object with 'id' property\n\tif (\n\t\ttypeof toolCallId === 'object' &&\n\t\ttoolCallId !== null &&\n\t\t!Array.isArray(toolCallId) &&\n\t\t'id' in toolCallId\n\t) {\n\t\tconst id = toolCallId.id;\n\t\tif (typeof id === 'string' && id.length > 0) {\n\t\t\treturn id;\n\t\t}\n\t}\n\n\t// Case 3: Array - recursively extract from first element\n\tif (Array.isArray(toolCallId) && toolCallId.length > 0) {\n\t\treturn extractToolCallId(toolCallId[0], toolName);\n\t}\n\n\t// Fallback: Generate synthetic ID\n\treturn `synthetic_${toolName}_${Date.now()}`;\n}\n\n/**\n * Converts ToolCallData array into LangChain message sequence.\n * Creates alternating AIMessage (with tool_calls) and ToolMessage pairs.\n *\n * @param steps - Array of tool call data with actions and observations\n * @returns Array of BaseMessage objects (AIMessage and ToolMessage pairs)\n *\n * @example\n * ```typescript\n * const messages = buildMessagesFromSteps([{\n * action: {\n * tool: 'calculator',\n * toolInput: { expression: '2+2' },\n * messageLog: [aiMessageWithToolCalls],\n * toolCallId: 'call-123'\n * },\n * observation: '4'\n * }]);\n * // Returns: [AIMessage with tool_calls, ToolMessage with result]\n * ```\n */\nexport function buildMessagesFromSteps(steps: ToolCallData[]): BaseMessage[] {\n\tconst messages: BaseMessage[] = [];\n\n\tfor (let i = 0; i < steps.length; i++) {\n\t\tconst step = steps[i];\n\n\t\t// Try to extract existing AIMessage and its tool_call ID\n\t\tconst existingAIMessage = step.action.messageLog?.[0];\n\t\tconst existingToolCallId = existingAIMessage?.tool_calls?.[0]?.id;\n\n\t\t// Use existing ID if available, otherwise extract from step data\n\t\tconst toolCallId =\n\t\t\texistingToolCallId ?? extractToolCallId(step.action.toolCallId, step.action.tool);\n\n\t\t// Use existing AIMessage or create a synthetic one\n\t\tconst aiMessage =\n\t\t\texistingAIMessage ??\n\t\t\tnew AIMessage({\n\t\t\t\tcontent: `Calling ${step.action.tool} with input: ${JSON.stringify(step.action.toolInput)}`,\n\t\t\t\ttool_calls: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: toolCallId,\n\t\t\t\t\t\tname: step.action.tool,\n\t\t\t\t\t\targs: step.action.toolInput,\n\t\t\t\t\t\ttype: 'tool_call',\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t});\n\n\t\t// Create ToolMessage with the observation result\n\t\tconst toolMessage = new ToolMessage({\n\t\t\tcontent: step.observation,\n\t\t\ttool_call_id: toolCallId,\n\t\t\tname: step.action.tool,\n\t\t});\n\n\t\t// Add both messages\n\t\tmessages.push(aiMessage);\n\t\tmessages.push(toolMessage);\n\t}\n\n\treturn messages;\n}\n\n/**\n * Builds a formatted string representation of tool calls for memory storage.\n * This creates a consistent format that can be used across both streaming and non-streaming modes.\n *\n * @deprecated Used only as fallback for custom memory implementations that don't support addMessages\n * @param steps - Array of tool call data with actions and observations\n * @returns Formatted string of tool calls separated by semicolons\n *\n * @example\n * ```typescript\n * const context = buildToolContext([{\n * action: { tool: 'calculator', toolInput: { expression: '2+2' }, ... },\n * observation: '4'\n * }]);\n * // Returns: \"Tool: calculator, Input: {\"expression\":\"2+2\"}, Result: 4\"\n * ```\n */\nexport function buildToolContext(steps: ToolCallData[]): string {\n\treturn steps\n\t\t.map(\n\t\t\t(step) =>\n\t\t\t\t`Tool: ${step.action.tool}, Input: ${JSON.stringify(step.action.toolInput)}, Result: ${step.observation}`,\n\t\t)\n\t\t.join('; ');\n}\n\n/**\n * Removes orphaned ToolMessages and AIMessages with tool_calls from the start of chat history.\n * This happens when memory trimming cuts messages mid-turn, leaving incomplete tool call sequences.\n *\n * @param chatHistory - Array of messages to clean up\n * @returns Cleaned array with orphaned messages removed from the start\n */\nfunction cleanupOrphanedMessages(chatHistory: BaseMessage[]): BaseMessage[] {\n\t// Remove orphaned ToolMessages at the start\n\twhile (chatHistory.length > 0 && chatHistory[0] instanceof ToolMessage) {\n\t\tchatHistory.shift();\n\t}\n\n\t// Remove AIMessages with tool_calls if they don't have following ToolMessages\n\tconst firstMessage = chatHistory[0];\n\tconst hasOrphanedAIMessage =\n\t\tfirstMessage instanceof AIMessage &&\n\t\t(firstMessage.tool_calls?.length ?? 0) > 0 &&\n\t\t!(chatHistory[1] instanceof ToolMessage);\n\n\tif (hasOrphanedAIMessage) {\n\t\tchatHistory.shift();\n\t}\n\n\treturn chatHistory;\n}\n\n/**\n * Loads chat history from memory and optionally trims it to fit within token limits.\n * Automatically cleans up orphaned tool messages that may result from memory trimming.\n *\n * @param memory - The memory instance to load from\n * @param model - Optional chat model for token counting (required if maxTokens is specified)\n * @param maxTokens - Optional maximum number of tokens to load from memory\n * @returns Array of base messages representing the chat history\n *\n * @example\n * ```typescript\n * // Load all history\n * const messages = await loadMemory(memory);\n *\n * // Load with token limit\n * const messages = await loadMemory(memory, model, 2000);\n * ```\n */\nexport async function loadMemory(\n\tmemory?: BaseChatMemory,\n\tmodel?: BaseChatModel,\n\tmaxTokens?: number,\n): Promise<BaseMessage[] | undefined> {\n\tif (!memory) {\n\t\treturn undefined;\n\t}\n\tconst memoryVariables = await memory.loadMemoryVariables({});\n\tlet chatHistory = (memoryVariables['chat_history'] as BaseMessage[]) || [];\n\n\t// Clean up any orphaned messages from previous trimming operations\n\tchatHistory = cleanupOrphanedMessages(chatHistory);\n\n\t// Trim messages if token limit is specified and model is available\n\tif (maxTokens && model) {\n\t\tchatHistory = await trimMessages(chatHistory, {\n\t\t\tstrategy: 'last',\n\t\t\tmaxTokens,\n\t\t\ttokenCounter: model,\n\t\t\tincludeSystem: true,\n\t\t\tstartOn: 'human',\n\t\t\tallowPartial: true,\n\t\t});\n\n\t\t// Clean up again after trimming, as it may create new orphans\n\t\tchatHistory = cleanupOrphanedMessages(chatHistory);\n\t}\n\n\treturn chatHistory;\n}\n\n/**\n * Saves a conversation turn (user input + agent output) to memory.\n * Uses LangChain-native message types (AIMessage with tool_calls, ToolMessage)\n * when tools are involved, preserving semantic structure for LLMs.\n *\n * @param input - The user input/prompt\n * @param output - The agent's output/response\n * @param memory - The memory instance to save to\n * @param steps - Optional tool call data to save as proper message sequence\n * @param previousStepsCount - Number of steps from previous turns (to filter out duplicates)\n *\n * @example\n * ```typescript\n * // Simple conversation (no tools)\n * await saveToMemory('What is 2+2?', 'The answer is 4', memory);\n *\n * // With tool calls (saves full message sequence)\n * await saveToMemory('Calculate 2+2', 'The answer is 4', memory, steps, 0);\n * ```\n */\nexport async function saveToMemory(\n\tinput: string,\n\toutput: string,\n\tmemory?: BaseChatMemory,\n\tsteps?: ToolCallData[],\n\tpreviousStepsCount?: number,\n): Promise<void> {\n\tif (!output || !memory) {\n\t\treturn;\n\t}\n\n\t// No tool calls: use simple saveContext (backwards compatible)\n\tif (!steps || steps.length === 0) {\n\t\tawait memory.saveContext({ input }, { output });\n\t\treturn;\n\t}\n\n\t// Filter out previous steps to avoid duplicates (they're already in memory)\n\tconst newSteps = previousStepsCount ? steps.slice(previousStepsCount) : steps;\n\n\tif (newSteps.length === 0) {\n\t\tawait memory.saveContext({ input }, { output });\n\t\treturn;\n\t}\n\n\t// Check if memory supports addMessages (feature detection)\n\tif (\n\t\t!('addMessages' in memory.chatHistory) ||\n\t\ttypeof memory.chatHistory.addMessages !== 'function'\n\t) {\n\t\t// Fallback: use old string format (only with new steps to avoid duplicates)\n\t\tconst toolContext = buildToolContext(newSteps);\n\t\tconst fullOutput = `[Used tools: ${toolContext}] ${output}`;\n\t\tawait memory.saveContext({ input }, { output: fullOutput });\n\t\treturn;\n\t}\n\n\t// Build full conversation sequence using LangChain-native message types\n\tconst messages: BaseMessage[] = [];\n\n\t// 1. User input\n\tmessages.push(new HumanMessage(input));\n\n\t// 2. Tool call sequence (AIMessage with tool_calls → ToolMessage for each)\n\tconst toolMessages = buildMessagesFromSteps(newSteps);\n\tmessages.push.apply(messages, toolMessages);\n\n\t// 3. Final AI response (no tool_calls)\n\tmessages.push(new AIMessage(output));\n\n\t// 4. Save all messages in bulk\n\tawait memory.chatHistory.addMessages(messages);\n}\n"],"mappings":"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","names":[]}
@@ -50,7 +50,22 @@ class N8nStructuredOutputParser extends import_output_parsers.StructuredOutputPa
50
50
  [{ json: { action: "parse", text } }]
51
51
  ]);
52
52
  try {
53
- const jsonString = text.includes("```") ? text.split(/```(?:json)?/)[1] : text;
53
+ let jsonString = text.trim();
54
+ const lines = jsonString.split("\n");
55
+ let fenceStartIndex = -1;
56
+ let fenceEndIndex = -1;
57
+ for (let i = 0; i < lines.length; i++) {
58
+ const trimmedLine = lines[i].trim();
59
+ if (fenceStartIndex === -1 && trimmedLine.match(/^```(?:json)?$/)) {
60
+ fenceStartIndex = i;
61
+ } else if (fenceStartIndex !== -1 && trimmedLine === "```") {
62
+ fenceEndIndex = i;
63
+ break;
64
+ }
65
+ }
66
+ if (fenceStartIndex !== -1 && fenceEndIndex !== -1) {
67
+ jsonString = lines.slice(fenceStartIndex + 1, fenceEndIndex).join("\n");
68
+ }
54
69
  const json = JSON.parse(jsonString.trim());
55
70
  const parsed = await this.schema.parseAsync(json);
56
71
  let result = (0, import_get.default)(parsed, [STRUCTURED_OUTPUT_KEY, STRUCTURED_OUTPUT_OBJECT_KEY]) ?? (0, import_get.default)(parsed, [STRUCTURED_OUTPUT_KEY, STRUCTURED_OUTPUT_ARRAY_KEY]) ?? (0, import_get.default)(parsed, STRUCTURED_OUTPUT_KEY) ?? parsed;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../utils/output_parsers/N8nStructuredOutputParser.ts"],"sourcesContent":["import type { Callbacks } from '@langchain/core/callbacks/manager';\nimport { StructuredOutputParser } from '@langchain/classic/output_parsers';\nimport get from 'lodash/get';\nimport type { ISupplyDataFunctions } from 'n8n-workflow';\nimport { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';\nimport { z } from 'zod';\n\nimport { logAiEvent, unwrapNestedOutput } from '../helpers';\n\nconst STRUCTURED_OUTPUT_KEY = '__structured__output';\nconst STRUCTURED_OUTPUT_OBJECT_KEY = '__structured__output__object';\nconst STRUCTURED_OUTPUT_ARRAY_KEY = '__structured__output__array';\n\nexport class N8nStructuredOutputParser extends StructuredOutputParser<\n\tz.ZodType<object, z.ZodTypeDef, object>\n> {\n\tconstructor(\n\t\tprivate context: ISupplyDataFunctions,\n\t\tzodSchema: z.ZodSchema<object>,\n\t) {\n\t\tsuper(zodSchema);\n\t}\n\n\tlc_namespace = ['langchain', 'output_parsers', 'structured'];\n\n\tasync parse(\n\t\ttext: string,\n\t\t_callbacks?: Callbacks,\n\t\terrorMapper?: (error: Error) => Error,\n\t): Promise<object> {\n\t\tconst { index } = this.context.addInputData(NodeConnectionTypes.AiOutputParser, [\n\t\t\t[{ json: { action: 'parse', text } }],\n\t\t]);\n\n\t\ttry {\n\t\t\tconst jsonString = text.includes('```') ? text.split(/```(?:json)?/)[1] : text;\n\t\t\tconst json = JSON.parse(jsonString.trim());\n\t\t\tconst parsed = await this.schema.parseAsync(json);\n\n\t\t\tlet result = (get(parsed, [STRUCTURED_OUTPUT_KEY, STRUCTURED_OUTPUT_OBJECT_KEY]) ??\n\t\t\t\tget(parsed, [STRUCTURED_OUTPUT_KEY, STRUCTURED_OUTPUT_ARRAY_KEY]) ??\n\t\t\t\tget(parsed, STRUCTURED_OUTPUT_KEY) ??\n\t\t\t\tparsed) as Record<string, unknown>;\n\n\t\t\t// Unwrap any doubly-nested output structures (e.g., {output: {output: {...}}})\n\t\t\tresult = unwrapNestedOutput(result);\n\n\t\t\tlogAiEvent(this.context, 'ai-output-parsed', { text, response: result });\n\n\t\t\tthis.context.addOutputData(NodeConnectionTypes.AiOutputParser, index, [\n\t\t\t\t[{ json: { action: 'parse', response: result } }],\n\t\t\t]);\n\n\t\t\treturn result;\n\t\t} catch (e) {\n\t\t\tconst nodeError = new NodeOperationError(\n\t\t\t\tthis.context.getNode(),\n\t\t\t\t\"Model output doesn't fit required format\",\n\t\t\t\t{\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"To continue the execution when this happens, change the 'On Error' parameter in the root node's settings\",\n\t\t\t\t},\n\t\t\t);\n\n\t\t\t// Add additional context to the error\n\t\t\tif (e instanceof SyntaxError) {\n\t\t\t\tnodeError.context.outputParserFailReason = 'Invalid JSON in model output';\n\t\t\t} else if (\n\t\t\t\t(typeof text === 'string' && text.trim() === '{}') ||\n\t\t\t\t(e instanceof z.ZodError &&\n\t\t\t\t\te.issues?.[0] &&\n\t\t\t\t\te.issues?.[0].code === 'invalid_type' &&\n\t\t\t\t\te.issues?.[0].path?.[0] === 'output' &&\n\t\t\t\t\te.issues?.[0].expected === 'object' &&\n\t\t\t\t\te.issues?.[0].received === 'undefined')\n\t\t\t) {\n\t\t\t\tnodeError.context.outputParserFailReason = 'Model output wrapper is an empty object';\n\t\t\t} else if (e instanceof z.ZodError) {\n\t\t\t\tnodeError.context.outputParserFailReason =\n\t\t\t\t\t'Model output does not match the expected schema';\n\t\t\t}\n\n\t\t\tlogAiEvent(this.context, 'ai-output-parsed', {\n\t\t\t\ttext,\n\t\t\t\tresponse: e.message ?? e,\n\t\t\t});\n\n\t\t\tthis.context.addOutputData(NodeConnectionTypes.AiOutputParser, index, nodeError);\n\t\t\tif (errorMapper) {\n\t\t\t\tthrow errorMapper(e);\n\t\t\t}\n\n\t\t\tthrow nodeError;\n\t\t}\n\t}\n\n\tstatic async fromZodJsonSchema(\n\t\tzodSchema: z.ZodSchema<object>,\n\t\tnodeVersion: number,\n\t\tcontext: ISupplyDataFunctions,\n\t): Promise<N8nStructuredOutputParser> {\n\t\tlet returnSchema: z.ZodType<object, z.ZodTypeDef, object>;\n\t\tif (nodeVersion === 1) {\n\t\t\treturnSchema = z.object({\n\t\t\t\t[STRUCTURED_OUTPUT_KEY]: z\n\t\t\t\t\t.object({\n\t\t\t\t\t\t[STRUCTURED_OUTPUT_OBJECT_KEY]: zodSchema.optional(),\n\t\t\t\t\t\t[STRUCTURED_OUTPUT_ARRAY_KEY]: z.array(zodSchema).optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.describe(\n\t\t\t\t\t\t`Wrapper around the output data. It can only contain ${STRUCTURED_OUTPUT_OBJECT_KEY} or ${STRUCTURED_OUTPUT_ARRAY_KEY} but never both.`,\n\t\t\t\t\t)\n\t\t\t\t\t.refine(\n\t\t\t\t\t\t(data) => {\n\t\t\t\t\t\t\t// Validate that one and only one of the properties exists\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\tBoolean(data[STRUCTURED_OUTPUT_OBJECT_KEY]) !==\n\t\t\t\t\t\t\t\tBoolean(data[STRUCTURED_OUTPUT_ARRAY_KEY])\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t'One and only one of __structured__output__object and __structured__output__array should be present.',\n\t\t\t\t\t\t\tpath: [STRUCTURED_OUTPUT_KEY],\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t});\n\t\t} else if (nodeVersion < 1.3) {\n\t\t\treturnSchema = z.object({\n\t\t\t\toutput: zodSchema.optional(),\n\t\t\t});\n\t\t} else {\n\t\t\treturnSchema = z.object({\n\t\t\t\toutput: zodSchema,\n\t\t\t});\n\t\t}\n\n\t\treturn new N8nStructuredOutputParser(context, returnSchema);\n\t}\n\n\tgetSchema() {\n\t\treturn this.schema;\n\t}\n}\n"],"mappings":"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","names":["get"]}
1
+ {"version":3,"sources":["../../../utils/output_parsers/N8nStructuredOutputParser.ts"],"sourcesContent":["import type { Callbacks } from '@langchain/core/callbacks/manager';\nimport { StructuredOutputParser } from '@langchain/classic/output_parsers';\nimport get from 'lodash/get';\nimport type { ISupplyDataFunctions } from 'n8n-workflow';\nimport { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';\nimport { z } from 'zod';\n\nimport { logAiEvent, unwrapNestedOutput } from '../helpers';\n\nconst STRUCTURED_OUTPUT_KEY = '__structured__output';\nconst STRUCTURED_OUTPUT_OBJECT_KEY = '__structured__output__object';\nconst STRUCTURED_OUTPUT_ARRAY_KEY = '__structured__output__array';\n\nexport class N8nStructuredOutputParser extends StructuredOutputParser<\n\tz.ZodType<object, z.ZodTypeDef, object>\n> {\n\tconstructor(\n\t\tprivate context: ISupplyDataFunctions,\n\t\tzodSchema: z.ZodSchema<object>,\n\t) {\n\t\tsuper(zodSchema);\n\t}\n\n\tlc_namespace = ['langchain', 'output_parsers', 'structured'];\n\n\tasync parse(\n\t\ttext: string,\n\t\t_callbacks?: Callbacks,\n\t\terrorMapper?: (error: Error) => Error,\n\t): Promise<object> {\n\t\tconst { index } = this.context.addInputData(NodeConnectionTypes.AiOutputParser, [\n\t\t\t[{ json: { action: 'parse', text } }],\n\t\t]);\n\n\t\ttry {\n\t\t\t// Extract JSON from markdown code fence if present\n\t\t\t// Use line-based approach to avoid matching backticks inside JSON content\n\t\t\tlet jsonString = text.trim();\n\n\t\t\t// Look for markdown code fence by finding lines that start with ```\n\t\t\tconst lines = jsonString.split('\\n');\n\t\t\tlet fenceStartIndex = -1;\n\t\t\tlet fenceEndIndex = -1;\n\n\t\t\tfor (let i = 0; i < lines.length; i++) {\n\t\t\t\tconst trimmedLine = lines[i].trim();\n\t\t\t\t// Opening fence: line starting with ``` optionally followed by language identifier\n\t\t\t\tif (fenceStartIndex === -1 && trimmedLine.match(/^```(?:json)?$/)) {\n\t\t\t\t\tfenceStartIndex = i;\n\t\t\t\t} else if (fenceStartIndex !== -1 && trimmedLine === '```') {\n\t\t\t\t\t// Closing fence: line with just ```\n\t\t\t\t\tfenceEndIndex = i;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we found both opening and closing fences, extract the content between them\n\t\t\tif (fenceStartIndex !== -1 && fenceEndIndex !== -1) {\n\t\t\t\tjsonString = lines.slice(fenceStartIndex + 1, fenceEndIndex).join('\\n');\n\t\t\t}\n\n\t\t\tconst json = JSON.parse(jsonString.trim());\n\t\t\tconst parsed = await this.schema.parseAsync(json);\n\n\t\t\tlet result = (get(parsed, [STRUCTURED_OUTPUT_KEY, STRUCTURED_OUTPUT_OBJECT_KEY]) ??\n\t\t\t\tget(parsed, [STRUCTURED_OUTPUT_KEY, STRUCTURED_OUTPUT_ARRAY_KEY]) ??\n\t\t\t\tget(parsed, STRUCTURED_OUTPUT_KEY) ??\n\t\t\t\tparsed) as Record<string, unknown>;\n\n\t\t\t// Unwrap any doubly-nested output structures (e.g., {output: {output: {...}}})\n\t\t\tresult = unwrapNestedOutput(result);\n\n\t\t\tlogAiEvent(this.context, 'ai-output-parsed', { text, response: result });\n\n\t\t\tthis.context.addOutputData(NodeConnectionTypes.AiOutputParser, index, [\n\t\t\t\t[{ json: { action: 'parse', response: result } }],\n\t\t\t]);\n\n\t\t\treturn result;\n\t\t} catch (e) {\n\t\t\tconst nodeError = new NodeOperationError(\n\t\t\t\tthis.context.getNode(),\n\t\t\t\t\"Model output doesn't fit required format\",\n\t\t\t\t{\n\t\t\t\t\tdescription:\n\t\t\t\t\t\t\"To continue the execution when this happens, change the 'On Error' parameter in the root node's settings\",\n\t\t\t\t},\n\t\t\t);\n\n\t\t\t// Add additional context to the error\n\t\t\tif (e instanceof SyntaxError) {\n\t\t\t\tnodeError.context.outputParserFailReason = 'Invalid JSON in model output';\n\t\t\t} else if (\n\t\t\t\t(typeof text === 'string' && text.trim() === '{}') ||\n\t\t\t\t(e instanceof z.ZodError &&\n\t\t\t\t\te.issues?.[0] &&\n\t\t\t\t\te.issues?.[0].code === 'invalid_type' &&\n\t\t\t\t\te.issues?.[0].path?.[0] === 'output' &&\n\t\t\t\t\te.issues?.[0].expected === 'object' &&\n\t\t\t\t\te.issues?.[0].received === 'undefined')\n\t\t\t) {\n\t\t\t\tnodeError.context.outputParserFailReason = 'Model output wrapper is an empty object';\n\t\t\t} else if (e instanceof z.ZodError) {\n\t\t\t\tnodeError.context.outputParserFailReason =\n\t\t\t\t\t'Model output does not match the expected schema';\n\t\t\t}\n\n\t\t\tlogAiEvent(this.context, 'ai-output-parsed', {\n\t\t\t\ttext,\n\t\t\t\tresponse: e.message ?? e,\n\t\t\t});\n\n\t\t\tthis.context.addOutputData(NodeConnectionTypes.AiOutputParser, index, nodeError);\n\t\t\tif (errorMapper) {\n\t\t\t\tthrow errorMapper(e);\n\t\t\t}\n\n\t\t\tthrow nodeError;\n\t\t}\n\t}\n\n\tstatic async fromZodJsonSchema(\n\t\tzodSchema: z.ZodSchema<object>,\n\t\tnodeVersion: number,\n\t\tcontext: ISupplyDataFunctions,\n\t): Promise<N8nStructuredOutputParser> {\n\t\tlet returnSchema: z.ZodType<object, z.ZodTypeDef, object>;\n\t\tif (nodeVersion === 1) {\n\t\t\treturnSchema = z.object({\n\t\t\t\t[STRUCTURED_OUTPUT_KEY]: z\n\t\t\t\t\t.object({\n\t\t\t\t\t\t[STRUCTURED_OUTPUT_OBJECT_KEY]: zodSchema.optional(),\n\t\t\t\t\t\t[STRUCTURED_OUTPUT_ARRAY_KEY]: z.array(zodSchema).optional(),\n\t\t\t\t\t})\n\t\t\t\t\t.describe(\n\t\t\t\t\t\t`Wrapper around the output data. It can only contain ${STRUCTURED_OUTPUT_OBJECT_KEY} or ${STRUCTURED_OUTPUT_ARRAY_KEY} but never both.`,\n\t\t\t\t\t)\n\t\t\t\t\t.refine(\n\t\t\t\t\t\t(data) => {\n\t\t\t\t\t\t\t// Validate that one and only one of the properties exists\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\tBoolean(data[STRUCTURED_OUTPUT_OBJECT_KEY]) !==\n\t\t\t\t\t\t\t\tBoolean(data[STRUCTURED_OUTPUT_ARRAY_KEY])\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tmessage:\n\t\t\t\t\t\t\t\t'One and only one of __structured__output__object and __structured__output__array should be present.',\n\t\t\t\t\t\t\tpath: [STRUCTURED_OUTPUT_KEY],\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t});\n\t\t} else if (nodeVersion < 1.3) {\n\t\t\treturnSchema = z.object({\n\t\t\t\toutput: zodSchema.optional(),\n\t\t\t});\n\t\t} else {\n\t\t\treturnSchema = z.object({\n\t\t\t\toutput: zodSchema,\n\t\t\t});\n\t\t}\n\n\t\treturn new N8nStructuredOutputParser(context, returnSchema);\n\t}\n\n\tgetSchema() {\n\t\treturn this.schema;\n\t}\n}\n"],"mappings":"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","names":["get"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@n8n/n8n-nodes-langchain",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -160,7 +160,7 @@
160
160
  "jest-mock-extended": "^3.0.4",
161
161
  "tsup": "^8.5.0",
162
162
  "@n8n/eslint-plugin-community-nodes": "0.7.0",
163
- "n8n-core": "2.2.2"
163
+ "n8n-core": "2.3.0"
164
164
  },
165
165
  "dependencies": {
166
166
  "@aws-sdk/client-sso-oidc": "3.808.0",
@@ -226,17 +226,17 @@
226
226
  "temp": "0.9.4",
227
227
  "tmp-promise": "3.0.3",
228
228
  "undici": "^6.21.0",
229
- "weaviate-client": "3.6.2",
229
+ "weaviate-client": "3.9.0",
230
230
  "zod": "3.25.67",
231
231
  "zod-to-json-schema": "3.23.3",
232
232
  "@n8n/client-oauth2": "1.0.0-rc.0",
233
- "@n8n/config": "2.1.0",
233
+ "@n8n/config": "2.2.0",
234
234
  "@n8n/di": "0.10.0",
235
- "@n8n/json-schema-to-zod": "1.6.0",
236
235
  "@n8n/errors": "0.5.0",
236
+ "@n8n/json-schema-to-zod": "1.6.0",
237
237
  "@n8n/typescript-config": "1.3.0",
238
- "n8n-workflow": "2.2.2",
239
- "n8n-nodes-base": "2.2.2"
238
+ "n8n-workflow": "2.3.0",
239
+ "n8n-nodes-base": "2.3.0"
240
240
  },
241
241
  "license": "SEE LICENSE IN LICENSE.md",
242
242
  "homepage": "https://n8n.io",