UNPKG

n8n

Version:

n8n Workflow Automation Tool

1,080 lines • 69.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); 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 __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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.AgentsService = void 0; exports.chatThreadId = chatThreadId; const api_types_1 = require("@n8n/api-types"); const fromai_helpers_1 = require("@n8n/ai-utilities/fromai-helpers"); const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const constants_1 = require("@n8n/constants"); const db_1 = require("@n8n/db"); const decorators_1 = require("@n8n/decorators"); const di_1 = require("@n8n/di"); const n8n_workflow_1 = require("n8n-workflow"); const uuid_1 = require("uuid"); const active_executions_1 = require("../../active-executions"); const credentials_service_1 = require("../../credentials/credentials.service"); const conflict_error_1 = require("../../errors/response-errors/conflict.error"); const not_found_error_1 = require("../../errors/response-errors/not-found.error"); const node_definition_resolver_1 = require("../../modules/instance-ai/node-definition-resolver"); const node_execution_1 = require("../../node-execution"); const oauth_service_1 = require("../../oauth/oauth.service"); const publisher_service_1 = require("../../scaling/pubsub/publisher.service"); const url_service_1 = require("../../services/url.service"); const telemetry_1 = require("../../telemetry"); const ttl_map_1 = require("../../utils/ttl-map"); const workflow_runner_1 = require("../../workflow-runner"); const workflow_finder_service_1 = require("../../workflows/workflow-finder.service"); const agents_credential_provider_1 = require("./adapters/agents-credential-provider"); const agent_draft_utils_1 = require("./utils/agent-draft.utils"); const agent_memory_scope_1 = require("./utils/agent-memory-scope"); const execution_to_message_mapper_1 = require("./utils/execution-to-message-mapper"); const agent_resource_id_1 = require("./utils/agent-resource-id"); const agent_execution_service_1 = require("./agent-execution.service"); const agent_skills_service_1 = require("./agent-skills.service"); const agents_tools_service_1 = require("./agents-tools.service"); const builder_tool_names_1 = require("./builder/builder-tool-names"); const llm_provider_defaults_1 = require("./builder/interactive/llm-provider-defaults"); const agent_task_entity_1 = require("./entities/agent-task.entity"); const execution_recorder_1 = require("./execution-recorder"); const agent_chat_integration_1 = require("./integrations/agent-chat-integration"); const integration_action_executor_1 = require("./integrations/integration-action-executor"); const integration_context_query_executor_1 = require("./integrations/integration-context-query-executor"); const integration_message_context_service_1 = require("./integrations/integration-message-context.service"); const integration_tools_1 = require("./integrations/integration-tools"); const integrations_sync_1 = require("./integrations/integrations-sync"); const n8n_checkpoint_storage_1 = require("./integrations/n8n-checkpoint-storage"); const n8n_memory_1 = require("./integrations/n8n-memory"); const environment_tool_1 = require("./tools/environment-tool"); const rich_interaction_tool_1 = require("./integrations/rich-interaction-tool"); const agent_config_composition_1 = require("./json-config/agent-config-composition"); const from_json_config_1 = require("./json-config/from-json-config"); const mcp_client_factory_1 = require("./json-config/mcp-client-factory"); const agent_history_repository_1 = require("./repositories/agent-history.repository"); const agent_task_snapshot_repository_1 = require("./repositories/agent-task-snapshot.repository"); const agent_task_repository_1 = require("./repositories/agent-task.repository"); const agent_repository_1 = require("./repositories/agent.repository"); const agent_secure_runtime_1 = require("./runtime/agent-secure-runtime"); const tool_registry_1 = require("./tool-registry"); const chat_integration_service_1 = require("./integrations/chat-integration.service"); const agent_knowledge_command_service_1 = require("./agent-knowledge-command.service"); const agent_knowledge_service_1 = require("./agent-knowledge.service"); function chatThreadId(agentId, userId) { const baseThreadId = `${builder_tool_names_1.AGENT_THREAD_PREFIX.TEST}${agentId}`; return userId ? `${baseThreadId}:${userId}` : baseThreadId; } function getMaxIterationsChunks() { const id = crypto.randomUUID(); return [ { type: 'text-start', id }, { type: 'text-delta', id, delta: 'The agent has reached the maximum number of iterations and has stopped.', }, { type: 'text-end', id }, ]; } let AgentsService = class AgentsService { computeRuntimeCacheKey(params) { if (params.usePublishedVersion) { const parts = [params.agentId, 'published']; if (params.integrationType) parts.push(params.integrationType); return parts.join(':'); } const parts = [params.agentId, 'draft']; if (params.n8nUserId) parts.push(params.n8nUserId); return parts.join(':'); } clearRuntimes(agentId, options = {}) { for (const key of this.runtimes.keys()) { if (key === agentId || key.startsWith(`${agentId}:`)) { const entry = this.runtimes.get(key); this.runtimes.delete(key); if (entry) this.closeAgentResources(entry.agent, agentId); } } if (options.skipBroadcast) return; if (!this.globalConfig.multiMainSetup.enabled) return; void this.publisher .publishCommand({ command: 'agent-config-changed', payload: { agentId }, }) .catch((error) => { this.logger.warn(`[AgentsService] Failed to publish agent-config-changed for ${agentId}`, { error: error instanceof Error ? error.message : String(error), }); }); } handleAgentConfigChanged(payload) { this.clearRuntimes(payload.agentId, { skipBroadcast: true }); } constructor(logger, agentRepository, projectRelationRepository, workflowRunner, activeExecutions, executionRepository, workflowRepository, userRepository, workflowFinderService, urlService, n8nCheckpointStorage, secureRuntime, ephemeralNodeExecutor, agentsToolsService, n8nMemory, agentExecutionService, agentHistoryRepository, agentSkillsService, agentTaskRepository, agentTaskSnapshotRepository, publisher, agentsConfig, globalConfig, telemetry, chatIntegrationService, agentKnowledgeService, agentKnowledgeCommandService, oauthService) { this.logger = logger; this.agentRepository = agentRepository; this.projectRelationRepository = projectRelationRepository; this.workflowRunner = workflowRunner; this.activeExecutions = activeExecutions; this.executionRepository = executionRepository; this.workflowRepository = workflowRepository; this.userRepository = userRepository; this.workflowFinderService = workflowFinderService; this.urlService = urlService; this.n8nCheckpointStorage = n8nCheckpointStorage; this.secureRuntime = secureRuntime; this.ephemeralNodeExecutor = ephemeralNodeExecutor; this.agentsToolsService = agentsToolsService; this.n8nMemory = n8nMemory; this.agentExecutionService = agentExecutionService; this.agentHistoryRepository = agentHistoryRepository; this.agentSkillsService = agentSkillsService; this.agentTaskRepository = agentTaskRepository; this.agentTaskSnapshotRepository = agentTaskSnapshotRepository; this.publisher = publisher; this.agentsConfig = agentsConfig; this.globalConfig = globalConfig; this.telemetry = telemetry; this.chatIntegrationService = chatIntegrationService; this.agentKnowledgeService = agentKnowledgeService; this.agentKnowledgeCommandService = agentKnowledgeCommandService; this.oauthService = oauthService; this.runtimes = new ttl_map_1.TtlMap(30 * constants_1.Time.minutes.toMilliseconds); } isNodeToolsModuleEnabled() { return this.agentsConfig.modules.includes('node-tools-searcher'); } isKnowledgeBaseModuleEnabled() { return this.agentsConfig.modules.includes('knowledge-base'); } closeAgentResources(agent, agentId) { agent.close().catch((error) => { this.logger.warn('[AgentsService] Failed to close agent resources on eviction', { agentId, error: error instanceof Error ? error.message : String(error), }); }); } createAgentExecutionCounter({ agentId, userId, }) { const attribution = userId ? { user_id: userId } : {}; return { incrementMessageCount: () => this.telemetry.trackAgentExecution({ agent_id: agentId, ...attribution, message_count: 1, }), incrementTokenCount: (tokenCount) => this.telemetry.trackAgentExecution({ agent_id: agentId, ...attribution, token_count: tokenCount, }), incrementToolCallCount: () => this.telemetry.trackAgentExecution({ agent_id: agentId, ...attribution, tool_call_count: 1, }), }; } shouldAttachNodeTools(config) { return this.isNodeToolsModuleEnabled() && (0, api_types_1.isNodeToolsEnabled)(config); } listChatIntegrations() { return di_1.Container.get(agent_chat_integration_1.ChatIntegrationRegistry) .list() .map((i) => ({ type: i.type, label: i.displayLabel, icon: i.displayIcon, credentialTypes: i.credentialTypes, ...(i.builderGuidance ? { capabilities: i.builderGuidance.capabilities, useIntegrationWhen: i.builderGuidance.useIntegrationWhen, useNodeToolWhen: i.builderGuidance.useNodeToolWhen, } : {}), })); } async create(projectId, name) { const defaultConfig = { name, model: '', instructions: '', tools: [], skills: [], }; const agent = this.agentRepository.create({ name, projectId, schema: defaultConfig, versionId: (0, uuid_1.v4)(), }); const saved = await this.agentRepository.save(agent); this.logger.debug('Created SDK agent', { agentId: saved.id, projectId }); return saved; } async findByProjectId(projectId) { return await this.agentRepository.findByProjectId(projectId); } async findById(agentId, projectId) { return await this.agentRepository.findByIdAndProjectId(agentId, projectId); } async updateName(agentId, projectId, name) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { return null; } agent.name = name; if (agent.schema) { agent.schema = { ...agent.schema, name }; } (0, agent_draft_utils_1.markAgentDraftDirty)(agent); const saved = await this.agentRepository.save(agent); this.logger.debug('Updated SDK agent name', { agentId, projectId, name }); return saved; } async updateDescription(agentId, projectId, description, updatedAt) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { return null; } if (updatedAt && agent.updatedAt.toISOString() !== updatedAt) { throw new conflict_error_1.ConflictError('Agent has been modified'); } agent.description = description; if (agent.schema) { agent.schema = { ...agent.schema, description }; } (0, agent_draft_utils_1.markAgentDraftDirty)(agent); const saved = await this.agentRepository.save(agent); this.logger.debug('Updated SDK agent description', { agentId, projectId }); return saved; } async findByUser(userId) { const projectRelations = await this.projectRelationRepository.findAllByUser(userId); const projectIds = projectRelations.map((pr) => pr.projectId); if (projectIds.length === 0) return []; return await this.agentRepository.find({ where: { projectId: (0, db_1.In)(projectIds) }, order: { updatedAt: 'DESC' }, }); } async findPublishedByUser(userId) { const projectRelations = await this.projectRelationRepository.findAllByUser(userId); const projectIds = projectRelations.map((pr) => pr.projectId); if (projectIds.length === 0) return []; const agents = await this.agentRepository.find({ where: { projectId: (0, db_1.In)(projectIds) }, relations: { activeVersion: true }, order: { updatedAt: 'DESC' }, }); return agents.filter((agent) => agent.activeVersionId !== null); } async publishAgent(agentId, projectId, user, versionId, options = {}) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); } if (!versionId && agent.versionId !== null && agent.versionId === agent.activeVersionId) { return agent; } if (versionId !== undefined && versionId === agent.activeVersionId) { return agent; } await this.agentRepository.manager.transaction(async (trx) => { if (versionId) { const existing = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agentId, trx); if (!existing) { throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found for agent "${agentId}"`); } agent.activeVersionId = existing.versionId; agent.activeVersion = existing; agent.versionId = (0, uuid_1.v4)(); } else { agent.versionId ??= (0, uuid_1.v4)(); agent.activeVersion = await this.agentHistoryRepository.saveVersion({ versionId: agent.versionId, agentId: agent.id, schema: agent.schema, tools: this.snapshotConfiguredTools(agent.schema, agent.tools ?? {}), skills: this.agentSkillsService.snapshotConfiguredSkills(agent.schema, agent.skills ?? {}), publishedBy: user, }, trx); await this.snapshotConfiguredTasks(trx, agent.versionId, agent.id, agent.schema); agent.activeVersionId = agent.versionId; } await trx.save(agent); }); this.clearRuntimes(agentId); const credentialIntegrations = agent.integrations ?? []; if (credentialIntegrations.length > 0 && options.syncIntegrations !== false) { const { ChatIntegrationService } = await Promise.resolve().then(() => __importStar(require('./integrations/chat-integration.service'))); await di_1.Container.get(ChatIntegrationService) .syncToConfig(agent, [], credentialIntegrations) .catch((error) => this.logger.warn('Failed to connect integrations on publish', { agentId, error, })); } const { AgentTaskService } = await Promise.resolve().then(() => __importStar(require('./agent-task.service'))); await di_1.Container.get(AgentTaskService) .requestReconcile(agentId) .catch((error) => this.logger.warn('Failed to register agent tasks on publish', { agentId, error })); this.logger.debug('Published SDK agent', { agentId, projectId, userId: user.id }); return agent; } async unpublishAgent(agentId, projectId) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); } await this.agentRepository.manager.transaction(async (trx) => { agent.activeVersionId = null; agent.activeVersion = null; agent.versionId = (0, uuid_1.v4)(); await trx.save(agent); }); this.clearRuntimes(agentId); const { ChatIntegrationService } = await Promise.resolve().then(() => __importStar(require('./integrations/chat-integration.service'))); await di_1.Container.get(ChatIntegrationService).disconnect(agentId); const { AgentTaskService } = await Promise.resolve().then(() => __importStar(require('./agent-task.service'))); await di_1.Container.get(AgentTaskService) .requestReconcile(agentId) .catch((error) => this.logger.warn('Failed to stop agent tasks on unpublish', { agentId, error })); this.logger.debug('Unpublished SDK agent', { agentId, projectId }); return agent; } async revertToPublishedAgent(agentId, projectId) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); } const activeVersion = agent.activeVersion; if (!activeVersion) { throw new conflict_error_1.ConflictError(`Agent "${agentId}" is not published`); } await this.agentRepository.manager.transaction(async (trx) => { agent.schema = activeVersion.schema ? (0, n8n_workflow_1.deepCopy)(activeVersion.schema) : null; agent.tools = (0, n8n_workflow_1.deepCopy)(activeVersion.tools ?? {}); agent.skills = (0, n8n_workflow_1.deepCopy)(activeVersion.skills ?? {}); agent.versionId = activeVersion.versionId; if (agent.schema) { agent.name = agent.schema.name; agent.description = agent.schema.description ?? null; } await trx.save(agent); await this.restoreTasksFromSnapshot(trx, agentId, activeVersion.versionId); }); this.clearRuntimes(agentId); this.logger.debug('Reverted SDK agent to published version', { agentId, projectId }); return agent; } async revertToVersion(agentId, projectId, versionId) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); } await this.agentRepository.manager.transaction(async (trx) => { const target = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agentId, trx); if (!target) { throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found`); } agent.schema = target.schema ? (0, n8n_workflow_1.deepCopy)(target.schema) : null; agent.tools = (0, n8n_workflow_1.deepCopy)(target.tools ?? {}); agent.skills = (0, n8n_workflow_1.deepCopy)(target.skills ?? {}); agent.versionId = (0, uuid_1.v4)(); if (agent.schema) { agent.name = agent.schema.name; agent.description = agent.schema.description ?? null; } await trx.save(agent); }); this.clearRuntimes(agentId); this.logger.debug('Reverted SDK agent to a specific version', { agentId, projectId, versionId, }); return agent; } async hasPublishHistory(agentId) { return await this.agentHistoryRepository.existsForAgent(agentId); } async listPublishHistory(agentId, projectId, take, skip) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); } const versions = await this.agentHistoryRepository.findByAgentId(agentId, take, skip); return versions.map((v) => ({ versionId: v.versionId, agentId: v.agentId, createdAt: v.createdAt.toISOString(), updatedAt: v.updatedAt.toISOString(), author: v.author, isActive: v.versionId === agent.activeVersionId, })); } async delete(agentId, projectId) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { return false; } try { await this.agentKnowledgeService.deleteAllFilesForAgent(agentId); } catch (error) { this.logger.warn('Failed to delete knowledge files on agent delete', { agentId, error: error instanceof Error ? error.message : error, }); } await this.agentRepository.remove(agent); this.clearRuntimes(agentId); try { const { AgentTaskService } = await Promise.resolve().then(() => __importStar(require('./agent-task.service'))); await di_1.Container.get(AgentTaskService).requestReconcile(agentId); } catch (error) { this.logger.warn('Failed to stop tasks on agent delete', { agentId, error: error instanceof Error ? error.message : error, }); } try { await this.clearAllTestChatMessages(agentId); } catch (error) { this.logger.warn('Failed to clear test chat on agent delete', { agentId, error: error instanceof Error ? error.message : error, }); } this.logger.debug('Deleted SDK agent', { agentId, projectId }); return true; } async getConversationHistory(params) { const { threadId, projectId, agentId } = params; const detail = await this.agentExecutionService.getThreadDetail(threadId, projectId, agentId); if (!detail) return null; return (0, execution_to_message_mapper_1.executionsToMessagesDto)(detail.executions); } getMemoryFactory(agentId) { return (_params) => this.n8nMemory.getImplementation(agentId); } createCredentialProvider(projectId) { return new agents_credential_provider_1.AgentsCredentialProvider(di_1.Container.get(credentials_service_1.CredentialsService), projectId); } async getRuntime(params) { const { agentId, projectId, integrationType, usePublishedVersion } = params; const cacheKey = this.computeRuntimeCacheKey(params); const cached = this.runtimes.get(cacheKey); if (cached) return cached; const agentEntity = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agentEntity) throw new not_found_error_1.NotFoundError(`Agent ${agentId} not found`); let n8nUserId = params.n8nUserId; let agentData = agentEntity; if (usePublishedVersion) { const activeVersionSchema = agentEntity.activeVersion?.schema; if (!activeVersionSchema) { throw new not_found_error_1.NotFoundError(`Agent ${agentId} is not published`); } agentData = { ...agentEntity, schema: activeVersionSchema, tools: agentEntity.activeVersion?.tools ?? agentEntity.tools ?? {}, skills: agentEntity.activeVersion?.skills ?? agentEntity.skills ?? {}, }; n8nUserId ??= agentEntity.activeVersion?.publishedById ?? undefined; } if (!n8nUserId) { throw new n8n_workflow_1.UserError('Agent user owner id is required'); } const credentialProvider = this.createCredentialProvider(projectId); const { agent: agentInstance, toolRegistry } = await this.reconstructFromConfig(agentData, credentialProvider, n8nUserId, integrationType); this.runtimes.set(cacheKey, { agent: agentInstance, agentId, toolRegistry, projectId }); const runtime = this.runtimes.get(cacheKey); if (!runtime) throw new Error(`Agent ${agentId} failed to reconstruct`); return runtime; } makeToolResolver(projectId, userId) { return async (ref) => { if (ref.type === 'workflow') { if (!userId) { throw new n8n_workflow_1.UserError('userId is required when agent uses workflow tools'); } const { resolveWorkflowTool } = await Promise.resolve().then(() => __importStar(require('./tools/workflow-tool-factory'))); return await resolveWorkflowTool(ref, { workflowRepository: this.workflowRepository, workflowRunner: this.workflowRunner, activeExecutions: this.activeExecutions, executionRepository: this.executionRepository, workflowFinderService: this.workflowFinderService, userRepository: this.userRepository, userId, projectId, webhookBaseUrl: this.urlService.getWebhookBaseUrl(), }); } if (ref.type === 'node') { const { resolveNodeTool } = await Promise.resolve().then(() => __importStar(require('./tools/node-tool-factory'))); return await resolveNodeTool(ref, { executor: this.ephemeralNodeExecutor, projectId, }); } return null; }; } async injectRuntimeDependencies(params) { const { agent, agentId, projectId, credentialProvider, nodeToolsEnabled, credentialIntegrations, integrationType, } = params; agent.tool((0, environment_tool_1.createGetEnvironmentTool)()); if (this.isKnowledgeBaseModuleEnabled()) { try { const { createSearchKnowledgeTool } = await Promise.resolve().then(() => __importStar(require('./tools/knowledge/tool'))); agent.tool(createSearchKnowledgeTool({ agentId, projectId, knowledgeService: this.agentKnowledgeService, commandService: this.agentKnowledgeCommandService, })); } catch (toolError) { this.logger.warn('Failed to inject search_knowledge tool', { agentId, error: toolError instanceof Error ? toolError.message : String(toolError), }); } } const integrationRegistry = di_1.Container.get(agent_chat_integration_1.ChatIntegrationRegistry); const integration = integrationType ? integrationRegistry.get(integrationType) : undefined; if (integration?.supportedComponents !== undefined) { agent.tool((0, rich_interaction_tool_1.createRichInteractionTool)(integrationType)); } if (credentialIntegrations.length > 0) { const messageContextStore = di_1.Container.get(integration_message_context_service_1.IntegrationMessageContextService); const actionExecutor = di_1.Container.get(integration_action_executor_1.ChatIntegrationActionExecutor); const queryExecutor = di_1.Container.get(integration_context_query_executor_1.ChatIntegrationContextQueryExecutor); for (const descriptor of (0, integration_tools_1.getIntegrationToolConnectionDescriptors)(credentialIntegrations, agentId, (integrationConfig) => { const integrationDef = integrationRegistry.get(integrationConfig.type); return { contextQueries: integrationDef?.contextQueries, actions: integrationDef?.actions, }; })) { agent.tool((0, integration_tools_1.createIntegrationContextTool)({ descriptor, messageContextStore, queryExecutor })); agent.tool((0, integration_tools_1.createIntegrationActionTool)({ descriptor, messageContextStore, actionExecutor })); } } if (nodeToolsEnabled) { this.attachNodeToolChain(agent, credentialProvider, projectId); } if (!agent.hasCheckpointStorage()) { agent.checkpoint(this.n8nCheckpointStorage); } } attachNodeToolChain(agent, credentialProvider, projectId) { agent.tool(this.agentsToolsService.getRuntimeTools(credentialProvider, projectId)); } async *resumeForChat(config) { const { agentId, projectId, runId, toolCallId, resumeData, integrationType } = config; const checkpointStatus = await this.n8nCheckpointStorage.getStatus(runId); if (checkpointStatus.status === 'expired') { throw new n8n_workflow_1.UserError(`Checkpoint ${runId} is expired and cannot be resumed`); } if (checkpointStatus.status === 'not-found') { throw new n8n_workflow_1.UserError(`Checkpoint ${runId} not found and cannot be resumed`); } const memoryScope = checkpointStatus.checkpoint?.persistence; if (!memoryScope) { throw new n8n_workflow_1.UserError(`Checkpoint ${runId} has no memory data and cannot be resumed`); } const threadId = memoryScope.threadId; const runtime = await this.getRuntime({ agentId, projectId, usePublishedVersion: true, integrationType, }); const { agent: agentInstance, toolRegistry } = runtime; const recorder = new execution_recorder_1.ExecutionRecorder(toolRegistry); const resultStream = await agentInstance.resume('stream', resumeData, { runId, toolCallId, executionCounter: this.createAgentExecutionCounter({ agentId }), }); const reader = resultStream.stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; recorder.record(value); yield value; } } finally { reader.releaseLock(); } const messageRecord = recorder.getMessageRecord(); void this.agentExecutionService .recordMessage({ threadId, agentId, agentName: agentInstance.name, projectId, userMessage: '', record: messageRecord, hitlStatus: 'resumed', }) .catch((error) => { this.logger.warn('Failed to record resumed agent execution', { agentId, threadId, error: error instanceof Error ? error.message : String(error), }); }); } async validateAgentIsRunnable(agentId, projectId, credentialProvider) { const agentEntity = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agentEntity) { return { missing: ['agent'] }; } const config = agentEntity.schema; const missing = []; if (!config) { return { missing: ['instructions', 'model', 'credential'] }; } if (!config.instructions?.trim()) { missing.push('instructions'); } if (!config.model?.trim() || !api_types_1.AgentModelSchema.safeParse(config.model).success) { missing.push('model'); } let credentialList; const findCredential = async (credentialId) => { credentialList ??= await credentialProvider.list(); return credentialList.find((credential) => credential.id === credentialId); }; const credentialExists = async (credentialId) => { return (await findCredential(credentialId)) !== undefined; }; if (!config.credential?.trim()) { missing.push('credential'); } else { try { const credentialId = config.credential.trim(); if (!(await credentialExists(credentialId))) missing.push('credential'); } catch { } } const episodicMemory = config.memory?.episodicMemory; if (config.memory?.enabled) { try { await this.validateMemoryWorkerModel(config.memory.observationalMemory?.observerModel, 'memory.observationalMemory.observerModel', findCredential, missing); await this.validateMemoryWorkerModel(config.memory.observationalMemory?.reflectorModel, 'memory.observationalMemory.reflectorModel', findCredential, missing); if (episodicMemory?.enabled === true) { if (!(await credentialExists(episodicMemory.credential.trim()))) { missing.push('episodicMemory.credential'); } await this.validateMemoryWorkerModel(episodicMemory.extractorModel, 'memory.episodicMemory.extractorModel', findCredential, missing); await this.validateMemoryWorkerModel(episodicMemory.reflectorModel, 'memory.episodicMemory.reflectorModel', findCredential, missing); } } catch { } } const webSearch = config.config?.webSearch; if (webSearch?.enabled && (webSearch.provider === 'brave' || webSearch.provider === 'searxng')) { const webSearchCredentialId = webSearch.credential?.trim(); if (!webSearchCredentialId) { missing.push('webSearch.credential'); } else { try { if (!(await credentialExists(webSearchCredentialId))) { missing.push('webSearch.credential'); } } catch { } } } missing.push(...this.agentSkillsService .getMissingSkillIds(config, agentEntity.skills ?? {}) .map((skillId) => `skill:${skillId}`)); return { missing }; } async validateMemoryWorkerModel(modelConfig, path, findCredential, missing) { if (modelConfig === undefined || modelConfig === null) return; if (typeof modelConfig === 'string') { missing.push(`${path}.credential`); return; } if (!modelConfig.model?.trim() || !api_types_1.AgentModelSchema.safeParse(modelConfig.model).success) { missing.push(`${path}.model`); } const credentialId = modelConfig.credential?.trim(); if (!credentialId) { missing.push(`${path}.credential`); return; } const credential = await findCredential(credentialId); if (!credential || !this.workerCredentialSupportsModel(credential.type, modelConfig.model ?? '')) { missing.push(`${path}.credential`); } } workerCredentialSupportsModel(credentialType, model) { return llm_provider_defaults_1.LLM_PROVIDER_DEFAULTS[credentialType]?.provider === getProviderPrefix(model); } async *executeForChat(config) { const { agentId, projectId, message, userId, memory } = config; const runtime = await this.getRuntime({ agentId, projectId, n8nUserId: userId }); yield* this.streamChatResponse({ agentInstance: runtime.agent, toolRegistry: runtime.toolRegistry, agentId, userId, message, memory, projectId: runtime.projectId, }); } async getTestChatMessages(agentId, userId) { return await this.n8nMemory .getImplementation(agentId) .getMessages(chatThreadId(agentId, userId), { resourceId: (0, agent_memory_scope_1.draftChatMemoryResourceId)(userId), }); } async clearTestChatMessages(agentId, userId) { await this.n8nMemory.getImplementation(agentId).deleteThread(chatThreadId(agentId, userId)); } async clearAllTestChatMessages(agentId) { const threadId = chatThreadId(agentId); const memory = this.n8nMemory.getImplementation(agentId); await memory.deleteThreadsByPrefix(threadId); await memory.deleteMessagesByThread(threadId); await memory.deleteThread(threadId); } async *executeForChatPublished(config) { const { agentId, projectId, message, memory, integrationType } = config; const runtime = await this.getRuntime({ agentId, projectId, integrationType, usePublishedVersion: true, }); yield* this.streamChatResponse({ agentInstance: runtime.agent, toolRegistry: runtime.toolRegistry, agentId, message, memory, projectId: runtime.projectId, source: integrationType, }); } async *executeForTaskPublished(config) { const { agentId, projectId, message, memory, taskId, taskVersionId } = config; const runtime = await this.getRuntime({ agentId, projectId, integrationType: 'task', usePublishedVersion: true, }); yield* this.streamChatResponse({ agentInstance: runtime.agent, toolRegistry: runtime.toolRegistry, agentId, message, memory, projectId: runtime.projectId, source: 'task', taskId, taskVersionId, }); } async *executeForTaskNow(config) { const { agentId, projectId, userId, message, memory, taskId } = config; const runtime = await this.getRuntime({ agentId, projectId, n8nUserId: userId }); yield* this.streamChatResponse({ agentInstance: runtime.agent, toolRegistry: runtime.toolRegistry, agentId, userId, message, memory, projectId: runtime.projectId, source: 'task', taskId, }); } async *streamChatResponse(config) { const { agentInstance, toolRegistry, agentId, userId, message, memory, projectId, source, taskId, taskVersionId, } = config; const { threadId, resourceId } = memory; const recorder = new execution_recorder_1.ExecutionRecorder(toolRegistry); const resultStream = await agentInstance.stream(message, { persistence: { threadId, resourceId }, executionCounter: this.createAgentExecutionCounter({ agentId, userId }), }); const reader = resultStream.stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; recorder.record(value); if (value.type === 'tool-call-suspended') { this.logger.info('Chat: tool-call-suspended chunk received', { agentId, toolCallId: value.toolCallId, toolName: value.toolName, }); } if (value.type === 'finish' && value.finishReason === 'max-iterations') { for (const chunk of getMaxIterationsChunks()) { yield chunk; } } yield value; } } finally { reader.releaseLock(); } const messageRecord = recorder.getMessageRecord(); void this.agentExecutionService .recordMessage({ threadId, agentId, agentName: agentInstance.name, projectId, userMessage: message, record: messageRecord, hitlStatus: recorder.suspended ? 'suspended' : undefined, source, taskId, taskVersionId, }) .catch((error) => { this.logger.warn('Failed to record agent execution', { agentId, threadId, error: error instanceof Error ? error.message : String(error), }); }); } async compileIsolated(agentEntity, credentialProvider, userId) { if (!agentEntity.schema) { return { ok: false, error: 'Agent has no JSON config. Create a config first.' }; } try { const { agent: reconstructed } = await this.reconstructFromConfig(agentEntity, credentialProvider, userId); return { ok: true, agent: reconstructed }; } catch (e) { return { ok: false, error: e instanceof Error ? e.message : 'Unknown compilation error', }; } } async executeForWorkflow(agentId, message, executionId, threadId, userId, projectId, telemetryUserId) { const agentEntity = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agentEntity) { throw new n8n_workflow_1.OperationalError('Agent not found or not accessible.'); } if (!agentEntity.activeVersionId) { throw new n8n_workflow_1.OperationalError('Agent is not published. Publish the agent before using it in a workflow.'); } const credentialProvider = new agents_credential_provider_1.AgentsCredentialProvider(di_1.Container.get(credentials_service_1.CredentialsService), projectId); const compiled = await this.compileIsolated(agentEntity, credentialProvider, userId); if (!compiled.ok || !compiled.agent) { throw new n8n_workflow_1.OperationalError(`Failed to compile agent: ${compiled.error ?? 'unknown error'}`); } const agentInstance = compiled.agent; const recorder = new execution_recorder_1.ExecutionRecorder(); let structuredOutput = null; const toolCalls = []; const toolInputs = new Map(); const resultStream = await agentInstance.stream(message, { persistence: { resourceId: executionId, threadId }, executionCounter: this.createAgentExecutionCounter({ agentId, userId: telemetryUserId }), }); const reader = resultStream.stream.getReader(); try { while (true) { const { done, value } = await reader.read(); if (done) break; recorder.record(value); if (value.type === 'tool-call') { toolInputs.set(value.toolCallId, { toolName: value.toolName, input: value.input }); } else if (value.type === 'tool-result') { const pending = toolInputs.get(value.toolCallId); toolCalls.push({ toolName: value.toolName, input: pending?.input ?? null, result: value.output, }); toolInputs.delete(value.toolCallId); } else if (value.type === 'finish' && value.structuredOutput !== undefined) { structuredOutput = value.structuredOutput; } } } finally { reader.releaseLock(); } const messageRecord = recorder.getMessageRecord(); void this.agentExecutionService .recordMessage({ threadId, agentId, agentName: agentInstance.name, projectId, userMessage: message, record: messageRecord, source: api_types_1.AGENT_WORKFLOW_TRIGGER_TYPE, }) .catch((error) => { this.logger.warn('Failed to record agent execution from workflow', { agentId, threadId, error: error instanceof Error ? error.message : String(error), }); }); if (recorder.suspended) { throw new n8n_workflow_1.OperationalError('Agent execution suspended waiting for tool approval. ' + 'Suspend/resume is not supported in workflow execution context.'); } if (messageRecord.error) { throw new n8n_workflow_1.OperationalError(`Agent execution failed: ${messageRecord.error}`); } if (messageRecord.finishReason === 'error') { throw new n8n_workflow_1.OperationalError('Agent execution finished with an error.'); } return { response: messageRecord.assistantResponse, structuredOutput: structuredOutput ?? null, usage: messageRecord.usage ? { promptTokens: messageRecord.usage.promptTokens, completionTokens: messageRecord.usage.completionTokens, totalTokens: messageRecord.usage.totalTokens, } : null, toolCalls, finishReason: messageRecord.finishReason, session: { agentId, projectId, sessionId: threadId, }, }; } async getConfig(agentId, projectId) { const entity = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!entity) throw new not_found_error_1.NotFoundError('Agent not found'); const config = (0, agent_config_composition_1.composeJsonConfig)(entity); if (!config) { throw new n8n_workflow_1.UserError('Agent has no JSON config yet.'); } return config; } async validateConfig(raw) { const parsed = api_types_1.AgentJsonConfigSchema.safeParse(raw); if (!parsed.success) { return { valid: false, error: parsed.error.message }; } const config = parsed.data; if ((0, api_types_1.isNodeToolsEnabled)(config.config) && !this.isNodeToolsModuleEnabled()) { return { valid: false, error: 'config.nodeTools.enabled requires the node-tools-searcher agents module to be enabled.', }; } const mcpServers = config.mcpServers ?? []; for (const server of mcpServers) { if (server.authentication !== 'none' && !server.credential) { return { valid: false, error: `MCP server "${server.name}" requires a credential when authentication is not "none".`, }; } } try { this.validateNodeToolExpressions(config); } catch (error) { const message = error instanceof Error ? error.message : String(error); return { valid: false, error: `Invalid $fromAI expression in node tool config: ${message}`, }; } const nodeError = await this.validateNodeToolConfigs(confi