UNPKG

n8n

Version:

n8n Workflow Automation Tool

423 lines • 23 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.AgentRuntimeReconstructionService = void 0; const agents_1 = require("@n8n/agents"); const http_proxy_agent_1 = require("@n8n/ai-utilities/http-proxy-agent"); const api_types_1 = require("@n8n/api-types"); const backend_common_1 = require("@n8n/backend-common"); const backend_network_1 = require("@n8n/backend-network"); const config_1 = require("@n8n/config"); const db_1 = require("@n8n/db"); const di_1 = require("@n8n/di"); const n8n_workflow_1 = require("n8n-workflow"); const nanoid_1 = require("nanoid"); const active_executions_1 = require("../../active-executions"); const constants_1 = require("../../constants"); const credentials_finder_service_1 = require("../../credentials/credentials-finder.service"); const node_execution_1 = require("../../node-execution"); const oauth_service_1 = require("../../oauth/oauth.service"); const check_access_1 = require("../../permissions.ee/check-access"); const ai_service_1 = require("../../services/ai.service"); const proxy_token_manager_1 = require("../../services/proxy-token-manager"); const url_service_1 = require("../../services/url.service"); const ai_proxy_fetch_1 = require("../../utils/ai-proxy-fetch"); const workflow_finder_service_1 = require("../../workflows/workflow-finder.service"); const agent_knowledge_gate_1 = require("./agent-knowledge-gate"); const agent_knowledge_sandbox_service_1 = require("./agent-knowledge-sandbox.service"); const agent_chat_integration_1 = require("./integrations/agent-chat-integration"); const integration_tools_1 = require("./integrations/integration-tools"); const n8n_checkpoint_storage_1 = require("./integrations/n8n-checkpoint-storage"); const n8n_memory_1 = require("./integrations/n8n-memory"); const from_json_config_1 = require("./json-config/from-json-config"); const mcp_client_factory_1 = require("./json-config/mcp-client-factory"); const model_config_1 = require("./json-config/model-config"); const agent_file_repository_1 = require("./repositories/agent-file.repository"); const agent_repository_1 = require("./repositories/agent.repository"); const agent_secure_runtime_1 = require("./runtime/agent-secure-runtime"); const delegate_sub_agent_tool_1 = require("./sub-agents/delegate-sub-agent-tool"); const sub_agent_foreground_runner_1 = require("./sub-agents/sub-agent-foreground-runner"); const tool_registry_1 = require("./tool-registry"); const environment_tool_1 = require("./tools/environment-tool"); const workflow_tool_workflow_resolver_1 = require("./tools/workflow-tool-workflow-resolver"); const sub_agent_resolver_1 = require("./utils/sub-agent-resolver"); async function getChatIntegrationToolServices() { const { IntegrationMessageContextService } = await import('./integrations/integration-message-context.service.js'); const { ChatIntegrationActionExecutor } = await import('./integrations/integration-action-executor.js'); const { ChatIntegrationContextQueryExecutor } = await import('./integrations/integration-context-query-executor.js'); return { messageContextStore: di_1.Container.get(IntegrationMessageContextService), actionExecutor: di_1.Container.get(ChatIntegrationActionExecutor), queryExecutor: di_1.Container.get(ChatIntegrationContextQueryExecutor), }; } async function getWorkflowRunner() { const { WorkflowRunner } = await import('../../workflow-runner.js'); return di_1.Container.get(WorkflowRunner); } let AgentRuntimeReconstructionService = class AgentRuntimeReconstructionService { constructor(logger, agentRepository, agentFileRepository, activeExecutions, workflowRepository, urlService, n8nCheckpointStorage, secureRuntime, ephemeralNodeExecutor, n8nMemory, oauthService, agentsConfig, aiService, outboundHttp, agentKnowledgeSandboxService, ssrfConfig, ssrfProtectionService, credentialsFinderService, workflowFinderService) { this.logger = logger; this.agentRepository = agentRepository; this.agentFileRepository = agentFileRepository; this.activeExecutions = activeExecutions; this.workflowRepository = workflowRepository; this.urlService = urlService; this.n8nCheckpointStorage = n8nCheckpointStorage; this.secureRuntime = secureRuntime; this.ephemeralNodeExecutor = ephemeralNodeExecutor; this.n8nMemory = n8nMemory; this.oauthService = oauthService; this.agentsConfig = agentsConfig; this.aiService = aiService; this.outboundHttp = outboundHttp; this.agentKnowledgeSandboxService = agentKnowledgeSandboxService; this.ssrfConfig = ssrfConfig; this.ssrfProtectionService = ssrfProtectionService; this.credentialsFinderService = credentialsFinderService; this.workflowFinderService = workflowFinderService; } async reconstructFromAgentEntity(agentEntity, credentialProvider, integrationType, user, instrumentation) { let config = agentEntity.schema; if (!config) { throw new n8n_workflow_1.UserError('Agent has no JSON config.'); } if (user && config.tools?.length) { config = { ...config, tools: await this.filterToolsForUser(config.tools, agentEntity.projectId, user), }; } const toolsByName = {}; const toolDescriptors = {}; for (const [_toolId, toolEntry] of Object.entries(agentEntity.tools ?? {})) { toolsByName[toolEntry.descriptor.name] = toolEntry.code; toolDescriptors[_toolId] = toolEntry.descriptor; } const subAgentDelegation = await this.createSubAgentDelegationConfig(config, agentEntity.projectId); return await this.reconstructRuntime({ config, memoryOwnerAgentId: agentEntity.id, projectId: agentEntity.projectId, credentialProvider, toolDescriptors, toolCodeByName: toolsByName, skills: agentEntity.skills ?? {}, runtimeProfile: 'top-level', parentAgentIdForDelegation: agentEntity.id, integrationType, credentialIntegrations: agentEntity.integrations ?? [], subAgentDelegation, user, instrumentation, }); } async filterToolsForUser(tools, projectId, user) { const canExecute = await (0, check_access_1.userHasScopes)(user, ['workflow:execute'], false, { projectId }); const filtered = []; for (const ref of tools) { if (ref.type === 'custom') { filtered.push(ref); continue; } if (!canExecute) continue; if (ref.type === 'node') { const credentialIds = Object.values(ref.node.credentials ?? {}) .map((credential) => credential.id) .filter((id) => Boolean(id)); const accessibleCredentials = await Promise.all(credentialIds.map(async (id) => await this.credentialsFinderService.findCredentialForUser(id, user, [ 'credential:read', ]))); if (accessibleCredentials.some((credential) => credential === null)) continue; filtered.push(ref); continue; } const workflow = await (0, workflow_tool_workflow_resolver_1.findWorkflowToolWorkflow)(this.workflowRepository, ref.workflow, projectId); if (!workflow) continue; const accessibleWorkflow = await this.workflowFinderService.findWorkflowForUser(workflow.id, user, ['workflow:execute']); if (!accessibleWorkflow) continue; filtered.push(ref); } return filtered; } async reconstructFromResolvedSource(params) { let config = params.config; if (params.user && config.tools?.length) { config = { ...config, tools: await this.filterToolsForUser(config.tools, params.projectId, params.user), }; } const subAgentDelegation = await this.createSubAgentDelegationConfig(config, params.projectId); return await this.reconstructRuntime({ ...params, config, credentialIntegrations: [], subAgentDelegation, }); } async reconstructRuntime(options) { const { config, memoryOwnerAgentId, projectId, credentialProvider, toolDescriptors, toolCodeByName, skills, runtimeProfile, parentAgentIdForDelegation, integrationType, credentialIntegrations, subAgentDelegation, user, instrumentation, } = options; const toolExecutor = this.secureRuntime.createToolExecutor(toolCodeByName); const toolResolver = this.makeToolResolver(projectId, instrumentation); const resolvedTools = []; const aiProxyFetch = (0, ai_proxy_fetch_1.createAiProxyFetch)(this.outboundHttp); const aiMcpFetch = instrumentation?.mcpFetch ?? (0, ai_proxy_fetch_1.createAiMcpFetch)(this.outboundHttp, this.ssrfConfig, this.ssrfProtectionService); const webSearchFetch = (0, ai_proxy_fetch_1.createWebSearchFetch)(this.outboundHttp, this.ssrfConfig, this.ssrfProtectionService); const buildMcpClient = async (server) => await (0, mcp_client_factory_1.buildMcpClientForServer)(server, { credentialProvider, oauthService: this.oauthService, projectId, proxyFetch: aiMcpFetch, onConnectionFailed: (event) => { this.logger.warn('Skipped MCP server that failed to connect', { agentId: memoryOwnerAgentId, serverName: event.server, error: event.error, }); }, ...(instrumentation?.onMcpToolCallSettled !== undefined && { onToolCallSettled: async (event) => await instrumentation.onMcpToolCallSettled?.({ serverName: server.name, ...event, }), }), }); const reconstructed = await (0, from_json_config_1.buildFromJson)(config, toolDescriptors, { toolExecutor, credentialProvider, resolveTool: async (ref) => { const resolved = await toolResolver(ref); if (resolved) resolvedTools.push(resolved); return resolved; }, skills, memoryFactory: this.getMemoryFactory(memoryOwnerAgentId), buildMcpClient, resolveManagedEmbeddingProviderOptions: async () => await this.resolveManagedEmbeddingProviderOptions(projectId), modelFetch: instrumentation?.modelFetch ?? aiProxyFetch, fallbackWebSearch: instrumentation?.webSearch, attachAuthPendingMcpServers: instrumentation?.mcpFetch !== undefined, webSearchFetch, }); await this.injectRuntimeDependencies({ agent: reconstructed, agentId: memoryOwnerAgentId, projectId, credentialProvider, runtimeProfile, config, subAgentDelegation, parentAgentIdForDelegation: parentAgentIdForDelegation ?? memoryOwnerAgentId, integrationType, credentialIntegrations, user, instrumentation, }); return { agent: reconstructed, toolRegistry: (0, tool_registry_1.buildToolRegistry)(resolvedTools) }; } async createSubAgentDelegationConfig(config, projectId) { const configuredAgents = config.subAgents?.agents ?? []; const sourcesById = {}; const availableSubAgents = []; for (const { agentId, agent, useWhen } of await (0, sub_agent_resolver_1.resolveUniqueSubAgents)({ refs: configuredAgents, projectId, agentRepository: this.agentRepository, })) { if (!agent?.activeVersionId) continue; sourcesById[agentId] = { agentId }; availableSubAgents.push({ id: agentId, name: agent.name, ...(useWhen ? { useWhen } : {}), }); } return { sourcesById, availableSubAgents }; } getMemoryFactory(agentId) { return (_params) => this.n8nMemory.getImplementation(agentId); } async resolveManagedEmbeddingProviderOptions(ownerId) { if (!this.aiService.isProxyEnabled()) return null; const client = await this.aiService.getClient(); const baseURL = client.getApiProxyBaseUrl().replace(/\/$/, '') + '/openai/'; const tokenManager = new proxy_token_manager_1.ProxyTokenManager(async () => { return await client.getBuilderApiProxyToken({ id: ownerId }, { userMessageId: (0, nanoid_1.nanoid)() }); }); return { baseURL, apiKey: 'proxy-managed', fetch: async (input, init) => { const headers = new Headers(init?.headers); const auth = await tokenManager.getAuthHeaders(); for (const [key, value] of Object.entries(auth)) { headers.set(key, value); } for (const [key, value] of Object.entries((0, api_types_1.buildProxyHeaders)({ feature: 'agent-builder', n8nVersion: constants_1.N8N_VERSION }))) { headers.set(key, value); } return await (0, http_proxy_agent_1.proxyFetch)(input, { ...init, headers }); }, }; } makeToolResolver(projectId, instrumentation) { const instrumentToolAdditionalData = instrumentation?.configureToolAdditionalData; return async (ref) => { if (ref.type === 'workflow') { const { resolveWorkflowTool } = await import('./tools/workflow-tool-factory.js'); return await resolveWorkflowTool(ref, { workflowRepository: this.workflowRepository, workflowRunner: await getWorkflowRunner(), activeExecutions: this.activeExecutions, projectId, webhookBaseUrl: this.urlService.getWebhookBaseUrl(), instrumentToolAdditionalData, }); } if (ref.type === 'node') { const { resolveNodeTool } = await import('./tools/node-tool-factory.js'); return await resolveNodeTool(ref, { executor: this.ephemeralNodeExecutor, projectId, instrumentToolAdditionalData, }); } return null; }; } async injectRuntimeDependencies(params) { const { agent, agentId, projectId, credentialProvider, runtimeProfile, config, subAgentDelegation, parentAgentIdForDelegation, integrationType, credentialIntegrations, user, instrumentation, } = params; agent.tool((0, environment_tool_1.createGetEnvironmentTool)()); if (runtimeProfile !== 'inline' && (0, agent_knowledge_gate_1.isAgentKnowledgeBaseEnabled)(this.agentsConfig, this.aiService.isProxyEnabled()) && (await this.agentFileRepository.hasFilesForAgent(agentId))) { const { createKnowledgeRetrievalTools } = await import('./tools/knowledge/search-knowledge.tool.js'); agent.tool(createKnowledgeRetrievalTools({ projectId, agentId, sandboxService: this.agentKnowledgeSandboxService, })); } if (runtimeProfile === 'top-level') { const includeN8nChat = integrationType === api_types_1.N8N_CHAT_INTEGRATION_TYPE; if (credentialIntegrations.length > 0 || includeN8nChat) { const integrationRegistry = di_1.Container.get(agent_chat_integration_1.ChatIntegrationRegistry); const { messageContextStore, actionExecutor, queryExecutor } = await getChatIntegrationToolServices(); const descriptors = (0, integration_tools_1.getIntegrationToolConnectionDescriptors)(credentialIntegrations, agentId, (integrationConfig) => { const integrationDef = integrationRegistry.get(integrationConfig.type); return { contextToolDefinitions: integrationDef?.contextToolDefinitions, actionToolDefinitions: integrationDef?.actionToolDefinitions, contextQueries: integrationDef?.contextQueries, actions: integrationDef?.actions, contextToolGuidance: integrationDef?.contextToolGuidance, actionToolGuidance: integrationDef?.actionToolGuidance, }; }); if (includeN8nChat) { const n8nChat = integrationRegistry.require(api_types_1.N8N_CHAT_INTEGRATION_TYPE); const n8nChatIntegration = { type: api_types_1.N8N_CHAT_INTEGRATION_TYPE, }; descriptors.push({ agentId, integration: n8nChatIntegration, integrationConnectionId: api_types_1.N8N_CHAT_INTEGRATION_TYPE, contextToolName: api_types_1.N8N_CHAT_CONTEXT_TOOL_NAME, actionToolName: api_types_1.N8N_CHAT_ACTION_TOOL_NAME, contextQueries: [...n8nChat.contextQueries], actions: [...n8nChat.actions], contextToolDefinitions: [...n8nChat.contextToolDefinitions], actionToolDefinitions: [...n8nChat.actionToolDefinitions], contextToolGuidance: n8nChat.contextToolGuidance, actionToolGuidance: n8nChat.actionToolGuidance, }); } for (const descriptor of descriptors) { agent.tool((0, integration_tools_1.createIntegrationContextTool)({ descriptor, messageContextStore, queryExecutor })); agent.tool((0, integration_tools_1.createIntegrationActionTool)({ descriptor, messageContextStore, actionExecutor })); } } } if (runtimeProfile === 'top-level') { await this.attachSubAgentDelegationTool({ agent, config, parentAgentId: parentAgentIdForDelegation, projectId, credentialProvider, delegation: subAgentDelegation, user, instrumentation, }); this.attachWriteTodosTool(agent, agentId); } if (runtimeProfile !== 'inline' && !agent.hasCheckpointStorage()) { agent.checkpoint(this.n8nCheckpointStorage.getStorage(agentId)); } } async attachSubAgentDelegationTool(params) { const { agent, config, parentAgentId, projectId, credentialProvider, delegation, user, instrumentation, } = params; const inlineSubAgentModelsByDifficulty = await this.resolveInlineSubAgentModelsByDifficulty(config, credentialProvider); agent.tool((0, delegate_sub_agent_tool_1.createN8nDelegateSubAgentTool)({ runner: di_1.Container.get(sub_agent_foreground_runner_1.SubAgentForegroundRunner), ...delegation, projectId, parentAgentId, credentialProvider, user, instrumentation, policy: this.buildSubAgentPolicy(config), ...(inlineSubAgentModelsByDifficulty !== undefined ? { inlineSubAgentModelsByDifficulty } : {}), resolveInlineSubAgentProviderTools: (modelConfig) => (0, from_json_config_1.buildProviderToolsForModel)(config, modelConfig), })); this.logger.debug('Injected delegate_subagent tool', { agentId: parentAgentId }); } async resolveInlineSubAgentModelsByDifficulty(config, credentialProvider) { const mappings = config.subAgents?.modelsByDifficulty; if (!mappings) return undefined; const resolved = {}; for (const difficulty of api_types_1.SUB_AGENT_TASK_DIFFICULTIES) { const mapping = mappings[difficulty]; if (!mapping) continue; resolved[difficulty] = await (0, model_config_1.resolveCredentialAwareModelConfig)(mapping.model, mapping.credential, credentialProvider); } return Object.keys(resolved).length > 0 ? resolved : undefined; } attachWriteTodosTool(agent, agentId) { agent.tool((0, agents_1.createWriteTodosTool)()); this.logger.debug('Injected write_todos tool', { agentId }); } buildSubAgentPolicy(config) { return { maxChildren: config.subAgents?.maxChildren ?? api_types_1.SUB_AGENT_MAX_CHILDREN_DEFAULT, }; } }; exports.AgentRuntimeReconstructionService = AgentRuntimeReconstructionService; exports.AgentRuntimeReconstructionService = AgentRuntimeReconstructionService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, agent_repository_1.AgentRepository, agent_file_repository_1.AgentFileRepository, active_executions_1.ActiveExecutions, db_1.WorkflowRepository, url_service_1.UrlService, n8n_checkpoint_storage_1.N8NCheckpointStorage, agent_secure_runtime_1.AgentSecureRuntime, node_execution_1.EphemeralNodeExecutor, n8n_memory_1.N8nMemory, oauth_service_1.OauthService, config_1.AgentsConfig, ai_service_1.AiService, backend_network_1.OutboundHttp, agent_knowledge_sandbox_service_1.AgentKnowledgeSandboxService, config_1.SsrfProtectionConfig, backend_network_1.SsrfProtectionService, credentials_finder_service_1.CredentialsFinderService, workflow_finder_service_1.WorkflowFinderService]) ], AgentRuntimeReconstructionService); //# sourceMappingURL=agent-runtime-reconstruction.service.js.map