UNPKG

n8n

Version:

n8n Workflow Automation Tool

289 lines 13.9 kB
"use strict"; 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