n8n
Version:
n8n Workflow Automation Tool
289 lines • 13.9 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EphemeralNodeExecutor = exports.AGENT_PROVIDER_NODE_WHITELIST = void 0;
exports.isAgentProviderNode = isAgentProviderNode;
exports.isUsableAsAgentTool = isUsableAsAgentTool;
const backend_common_1 = require("@n8n/backend-common");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const uuid_1 = require("uuid");
const node_types_1 = require("../node-types");
const workflow_execute_additional_data_1 = require("../workflow-execute-additional-data");
const OPERATION_BLACKLIST = [n8n_workflow_1.SEND_AND_WAIT_OPERATION, 'dispatchAndWait'];
exports.AGENT_PROVIDER_NODE_WHITELIST = new Set([...n8n_workflow_1.AI_VENDOR_NODE_TYPES]);
function isAgentProviderNode(nodeType) {
return exports.AGENT_PROVIDER_NODE_WHITELIST.has(nodeType);
}
function isUsableAsAgentTool(description) {
if (description.usableAsTool)
return true;
const outputs = description.outputs;
if (!Array.isArray(outputs))
return false;
return outputs.some((o) => {
if (typeof o === 'string')
return o === n8n_workflow_1.NodeConnectionTypes.AiTool;
if (o && typeof o === 'object' && 'type' in o) {
return o.type === n8n_workflow_1.NodeConnectionTypes.AiTool;
}
return false;
});
}
let EphemeralNodeExecutor = class EphemeralNodeExecutor {
constructor(nodeTypes, credentialsRepository, sharedCredentialsRepository, logger) {
this.nodeTypes = nodeTypes;
this.credentialsRepository = credentialsRepository;
this.sharedCredentialsRepository = sharedCredentialsRepository;
this.logger = logger;
}
async resolveInlineCredentials(projectId, credentials) {
if (!credentials || Object.keys(credentials).length === 0) {
return null;
}
const accessible = await this.credentialsRepository.findAllCredentialsForProject(projectId);
const resolved = {};
for (const [credType, credName] of Object.entries(credentials)) {
const matches = accessible.filter((c) => c.type === credType && c.name.toLowerCase() === credName.toLowerCase());
if (matches.length === 0) {
throw new n8n_workflow_1.UserError(`No accessible credential found for type "${credType}" with name "${credName}"`, { extra: { credType, credName } });
}
if (matches.length > 1) {
throw new n8n_workflow_1.UserError(`Multiple credentials match type "${credType}" with name "${credName}". Use a unique credential name.`, { extra: { credType, credName } });
}
const entity = matches[0];
resolved[credType] = { id: entity.id, name: entity.name };
}
return resolved;
}
async verifyCredentialDetailsForProject(projectId, details) {
const verified = {};
for (const [credType, d] of Object.entries(details)) {
if (!d.id) {
throw new n8n_workflow_1.UserError(`Credential reference for "${credType}" is missing an id (required for execution).`, { extra: { credType, name: d.name } });
}
const sharedCredential = await this.sharedCredentialsRepository.findOne({
where: { credentialsId: d.id, projectId },
relations: { credentials: true },
});
if (!sharedCredential) {
throw new n8n_workflow_1.UserError(`Credential "${d.name}" is not accessible or does not exist.`, {
extra: { credType, credentialId: d.id },
});
}
if (sharedCredential.credentials.type !== credType) {
throw new n8n_workflow_1.UserError(`Credential "${sharedCredential.credentials.name}" has type "${sharedCredential.credentials.type}" but the node expects credential slot "${credType}".`, {
extra: {
credType,
actualType: sharedCredential.credentials.type,
credentialId: d.id,
},
});
}
verified[credType] = {
id: sharedCredential.credentials.id,
name: sharedCredential.credentials.name,
};
}
return verified;
}
validateNodeForExecution(nodeType, typeVersion, nodeParameters) {
const resolved = this.nodeTypes.getByNameAndVersion(nodeType, typeVersion);
if (!isUsableAsAgentTool(resolved.description) && !isAgentProviderNode(nodeType)) {
throw new n8n_workflow_1.UserError('Node is not usable as a tool', { extra: { nodeType } });
}
if (resolved.description.group.includes('trigger')) {
throw new n8n_workflow_1.UserError('Trigger nodes cannot be executed standalone', { extra: { nodeType } });
}
const operation = nodeParameters.operation;
if (operation && typeof operation === 'string' && OPERATION_BLACKLIST.includes(operation)) {
throw new n8n_workflow_1.UserError(`The "${operation}" is not supported for agent tool execution.`, {
extra: { nodeType, operation },
});
}
}
async buildEphemeralContextParts(tool, inputItems) {
const node = {
id: (0, uuid_1.v4)(),
name: 'Target Node',
type: tool.nodeType,
typeVersion: tool.nodeTypeVersion,
position: [0, 0],
parameters: tool.nodeParameters,
credentials: tool.credentials ?? undefined,
};
const workflow = new n8n_workflow_1.Workflow({
nodes: [node],
connections: {},
active: false,
nodeTypes: this.nodeTypes,
});
const additionalData = await (0, workflow_execute_additional_data_1.getBase)({ projectId: tool.projectId });
const runExecutionData = (0, n8n_workflow_1.createEmptyRunExecutionData)();
const inputData = { main: [inputItems] };
const executeData = { node, data: inputData, source: null };
const mode = 'internal';
return {
node,
workflow,
additionalData,
runExecutionData,
inputData,
executeData,
mode,
};
}
async executeNodeDirectly(tool, inputItems) {
const parts = await this.buildEphemeralContextParts(tool, inputItems);
const context = new n8n_core_1.ExecuteContext(parts.workflow, parts.node, parts.additionalData, parts.mode, parts.runExecutionData, 0, inputItems, parts.inputData, parts.executeData, []);
const nodeType = this.nodeTypes.getByNameAndVersion(tool.nodeType, tool.nodeTypeVersion);
if (!nodeType.execute) {
return { status: 'error', data: [], error: 'Node type does not have an execute method' };
}
let output;
try {
output =
nodeType instanceof n8n_workflow_1.Node
? await nodeType.execute(context)
: await nodeType.execute.call(context);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.logger.debug('Node execution failed', { nodeType: tool.nodeType, error: message });
return { status: 'error', data: [], error: message };
}
if (!Array.isArray(output) || !output[0]) {
return { status: 'error', data: [], error: 'No output data' };
}
return { status: 'success', data: output[0] };
}
async executeInline(request) {
try {
this.validateNodeForExecution(request.nodeType, request.nodeTypeVersion, request.nodeParameters);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
this.logger.debug('Node execution validation failed', {
nodeType: request.nodeType,
error: message,
});
return {
status: 'error',
data: [],
error: `Cannot execute node "${request.nodeType}": ${message}`,
};
}
const fromNames = (await this.resolveInlineCredentials(request.projectId, request.credentials)) ?? {};
let fromDetails = {};
if (request.credentialDetails && Object.keys(request.credentialDetails).length > 0) {
fromDetails = await this.verifyCredentialDetailsForProject(request.projectId, request.credentialDetails);
}
const mergedCredentials = Object.keys(fromNames).length === 0 && Object.keys(fromDetails).length === 0
? null
: { ...fromNames, ...fromDetails };
const tool = {
projectId: request.projectId,
nodeType: request.nodeType,
nodeTypeVersion: request.nodeTypeVersion,
nodeParameters: request.nodeParameters,
credentials: mergedCredentials,
};
const nodeType = this.nodeTypes.getByNameAndVersion(tool.nodeType, tool.nodeTypeVersion);
if (typeof nodeType.supplyData === 'function') {
return await this.invokeSupplyDataTool(tool, request.inputData);
}
return await this.executeNodeDirectly(tool, request.inputData);
}
async withSupplyDataTool(tool, inputItems, onTool) {
const parts = await this.buildEphemeralContextParts(tool, inputItems);
const closeFunctions = [];
const context = new n8n_core_1.SupplyDataContext(parts.workflow, parts.node, parts.additionalData, parts.mode, parts.runExecutionData, 0, inputItems, parts.inputData, n8n_workflow_1.NodeConnectionTypes.AiTool, parts.executeData, closeFunctions);
const nodeType = this.nodeTypes.getByNameAndVersion(tool.nodeType, tool.nodeTypeVersion);
if (typeof nodeType.supplyData !== 'function') {
return { ok: false, error: 'Node does not implement supplyData' };
}
try {
const supplyDataResult = await nodeType.supplyData.call(context, 0);
const response = supplyDataResult.response;
if (response instanceof n8n_core_1.StructuredToolkit) {
return { ok: true, value: await onTool(response) };
}
if (response && typeof response.invoke === 'function') {
return { ok: true, value: await onTool(response) };
}
return {
ok: false,
error: `Node "${tool.nodeType}" did not return a valid LangChain tool or toolkit`,
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { ok: false, error: message };
}
finally {
for (const closeFunction of closeFunctions) {
try {
await closeFunction();
}
catch (error) {
this.logger.warn(`Error closing tool resource: ${String(error)}`);
}
}
}
}
async invokeSupplyDataTool(tool, inputItems) {
const toolArgs = (inputItems[0]?.json ?? {});
const result = await this.withSupplyDataTool(tool, inputItems, async (response) => {
if (response instanceof n8n_core_1.StructuredToolkit) {
throw new Error(`Node "${tool.nodeType}" returned a StructuredToolkit (${response.tools.length} tools); multi-tool dispatch via the ephemeral runtime isn't supported yet.`);
}
return (await response.invoke(toolArgs));
});
if (!result.ok) {
this.logger.debug('supplyData tool invocation failed', {
nodeType: tool.nodeType,
error: result.error,
});
return { status: 'error', data: [], error: result.error };
}
return {
status: 'success',
data: [{ json: { response: result.value } }],
};
}
async introspectSupplyDataToolSchema(tool) {
const result = await this.withSupplyDataTool(tool, [], (response) => {
if (response instanceof n8n_core_1.StructuredToolkit)
return null;
const maybeSchema = response.schema;
return maybeSchema ?? null;
});
if (!result.ok) {
this.logger.warn('supplyData tool introspection failed', {
nodeType: tool.nodeType,
error: result.error,
});
return null;
}
return result.value;
}
};
exports.EphemeralNodeExecutor = EphemeralNodeExecutor;
exports.EphemeralNodeExecutor = EphemeralNodeExecutor = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [node_types_1.NodeTypes,
db_1.CredentialsRepository,
db_1.SharedCredentialsRepository,
backend_common_1.Logger])
], EphemeralNodeExecutor);
//# sourceMappingURL=ephemeral-node-executor.js.map