mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
185 lines
5.7 KiB
TypeScript
185 lines
5.7 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
import type {
|
|
CloseFunction,
|
|
IExecuteData,
|
|
IExecuteFunctions,
|
|
INodeExecutionData,
|
|
IRunExecutionData,
|
|
ITaskDataConnections,
|
|
IWorkflowExecuteAdditionalData,
|
|
Workflow,
|
|
WorkflowExecuteMode,
|
|
SupplyData,
|
|
AINodeConnectionType,
|
|
} from 'n8n-workflow';
|
|
import {
|
|
NodeConnectionType,
|
|
NodeOperationError,
|
|
ExecutionBaseError,
|
|
ApplicationError,
|
|
} from 'n8n-workflow';
|
|
|
|
import { createNodeAsTool } from '@/CreateNodeAsTool';
|
|
// eslint-disable-next-line import/no-cycle
|
|
import { SupplyDataContext } from '@/node-execution-context';
|
|
import type { ExecuteContext, WebhookContext } from '@/node-execution-context';
|
|
|
|
export async function getInputConnectionData(
|
|
this: ExecuteContext | WebhookContext | SupplyDataContext,
|
|
workflow: Workflow,
|
|
runExecutionData: IRunExecutionData,
|
|
parentRunIndex: number,
|
|
connectionInputData: INodeExecutionData[],
|
|
parentInputData: ITaskDataConnections,
|
|
additionalData: IWorkflowExecuteAdditionalData,
|
|
executeData: IExecuteData,
|
|
mode: WorkflowExecuteMode,
|
|
closeFunctions: CloseFunction[],
|
|
connectionType: AINodeConnectionType,
|
|
itemIndex: number,
|
|
abortSignal?: AbortSignal,
|
|
): Promise<unknown> {
|
|
const parentNode = this.getNode();
|
|
|
|
const inputConfiguration = this.nodeInputs.find((input) => input.type === connectionType);
|
|
if (inputConfiguration === undefined) {
|
|
throw new ApplicationError('Node does not have input of type', {
|
|
extra: { nodeName: parentNode.name, connectionType },
|
|
});
|
|
}
|
|
|
|
const connectedNodes = this.getConnectedNodes(connectionType);
|
|
if (connectedNodes.length === 0) {
|
|
if (inputConfiguration.required) {
|
|
throw new NodeOperationError(
|
|
parentNode,
|
|
`A ${inputConfiguration?.displayName ?? connectionType} sub-node must be connected and enabled`,
|
|
);
|
|
}
|
|
return inputConfiguration.maxConnections === 1 ? undefined : [];
|
|
}
|
|
|
|
if (
|
|
inputConfiguration.maxConnections !== undefined &&
|
|
connectedNodes.length > inputConfiguration.maxConnections
|
|
) {
|
|
throw new NodeOperationError(
|
|
parentNode,
|
|
`Only ${inputConfiguration.maxConnections} ${connectionType} sub-nodes are/is allowed to be connected`,
|
|
);
|
|
}
|
|
|
|
const nodes: SupplyData[] = [];
|
|
for (const connectedNode of connectedNodes) {
|
|
const connectedNodeType = workflow.nodeTypes.getByNameAndVersion(
|
|
connectedNode.type,
|
|
connectedNode.typeVersion,
|
|
);
|
|
const contextFactory = (runIndex: number, inputData: ITaskDataConnections) =>
|
|
new SupplyDataContext(
|
|
workflow,
|
|
connectedNode,
|
|
additionalData,
|
|
mode,
|
|
runExecutionData,
|
|
runIndex,
|
|
connectionInputData,
|
|
inputData,
|
|
connectionType,
|
|
executeData,
|
|
closeFunctions,
|
|
abortSignal,
|
|
);
|
|
|
|
if (!connectedNodeType.supplyData) {
|
|
if (connectedNodeType.description.outputs.includes(NodeConnectionType.AiTool)) {
|
|
/**
|
|
* This keeps track of how many times this specific AI tool node has been invoked.
|
|
* It is incremented on every invocation of the tool to keep the output of each invocation separate from each other.
|
|
*/
|
|
let toolRunIndex = 0;
|
|
const supplyData = createNodeAsTool({
|
|
node: connectedNode,
|
|
nodeType: connectedNodeType,
|
|
handleToolInvocation: async (toolArgs) => {
|
|
const runIndex = toolRunIndex++;
|
|
const context = contextFactory(runIndex, {});
|
|
context.addInputData(NodeConnectionType.AiTool, [[{ json: toolArgs }]]);
|
|
|
|
try {
|
|
// Execute the sub-node with the proxied context
|
|
const result = await connectedNodeType.execute?.call(
|
|
context as unknown as IExecuteFunctions,
|
|
);
|
|
|
|
// Process and map the results
|
|
const mappedResults = result?.[0]?.flatMap((item) => item.json);
|
|
|
|
// Add output data to the context
|
|
context.addOutputData(NodeConnectionType.AiTool, runIndex, [
|
|
[{ json: { response: mappedResults } }],
|
|
]);
|
|
|
|
// Return the stringified results
|
|
return JSON.stringify(mappedResults);
|
|
} catch (error) {
|
|
const nodeError = new NodeOperationError(connectedNode, error as Error);
|
|
context.addOutputData(NodeConnectionType.AiTool, runIndex, nodeError);
|
|
return 'Error during node execution: ' + nodeError.description;
|
|
}
|
|
},
|
|
});
|
|
nodes.push(supplyData);
|
|
} else {
|
|
throw new ApplicationError('Node does not have a `supplyData` method defined', {
|
|
extra: { nodeName: connectedNode.name },
|
|
});
|
|
}
|
|
} else {
|
|
const context = contextFactory(parentRunIndex, parentInputData);
|
|
try {
|
|
const supplyData = await connectedNodeType.supplyData.call(context, itemIndex);
|
|
if (supplyData.closeFunction) {
|
|
closeFunctions.push(supplyData.closeFunction);
|
|
}
|
|
nodes.push(supplyData);
|
|
} catch (error) {
|
|
// Propagate errors from sub-nodes
|
|
if (error instanceof ExecutionBaseError) {
|
|
if (error.functionality === 'configuration-node') throw error;
|
|
} else {
|
|
error = new NodeOperationError(connectedNode, error, {
|
|
itemIndex,
|
|
});
|
|
}
|
|
|
|
let currentNodeRunIndex = 0;
|
|
if (runExecutionData.resultData.runData.hasOwnProperty(parentNode.name)) {
|
|
currentNodeRunIndex = runExecutionData.resultData.runData[parentNode.name].length;
|
|
}
|
|
|
|
// Display the error on the node which is causing it
|
|
await context.addExecutionDataFunctions(
|
|
'input',
|
|
error,
|
|
connectionType,
|
|
parentNode.name,
|
|
currentNodeRunIndex,
|
|
);
|
|
|
|
// Display on the calling node which node has the error
|
|
throw new NodeOperationError(connectedNode, `Error in sub-node ${connectedNode.name}`, {
|
|
itemIndex,
|
|
functionality: 'configuration-node',
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
description: error.message,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return inputConfiguration.maxConnections === 1
|
|
? (nodes || [])[0]?.response
|
|
: nodes.map((node) => node.response);
|
|
}
|