UNPKG

n8n

Version:

n8n Workflow Automation Tool

470 lines • 21 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.AgentExecutionOrchestratorService = void 0; const agents_1 = require("@n8n/agents"); const api_types_1 = require("@n8n/api-types"); const backend_common_1 = require("@n8n/backend-common"); const di_1 = require("@n8n/di"); const is_record_1 = require("@n8n/utils/is-record"); const n8n_workflow_1 = require("n8n-workflow"); const external_hooks_1 = require("../../external-hooks"); const telemetry_1 = require("../../telemetry"); const agent_execution_service_1 = require("./agent-execution.service"); const agent_run_tracing_service_1 = require("./agent-run-tracing.service"); const agent_runtime_cache_service_1 = require("./agent-runtime-cache.service"); const execution_recorder_1 = require("./execution-recorder"); const integration_message_context_service_1 = require("./integrations/integration-message-context.service"); const n8n_checkpoint_storage_1 = require("./integrations/n8n-checkpoint-storage"); const agent_execution_counter_1 = require("./utils/agent-execution-counter"); const inbound_attachments_1 = require("./utils/inbound-attachments"); const agent_stream_1 = require("./utils/agent-stream"); const execution_to_message_mapper_1 = require("./utils/execution-to-message-mapper"); 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 }, ]; } function normalizeAbortedMessageRecord(record, abortSignal) { if (!abortSignal?.aborted) return record; return { ...record, finishReason: 'cancelled', error: null }; } function getDelegatedChildCheckpoints(checkpoint, parentAgentId) { const childCheckpoints = []; const seen = new Set(); for (const pendingToolCall of Object.values(checkpoint.pendingToolCalls)) { if (!pendingToolCall.suspended) continue; const childCheckpoint = (0, agents_1.parseDelegateSubAgentContinuation)(pendingToolCall.continuation); if (!childCheckpoint) continue; let ownerAgentId; if (childCheckpoint.subAgentId === agents_1.INLINE_SUB_AGENT_ID) { if (childCheckpoint.resumeContext !== undefined) continue; ownerAgentId = parentAgentId; } else { if (!(0, is_record_1.isRecord)(childCheckpoint.resumeContext) || childCheckpoint.resumeContext.agentId !== childCheckpoint.subAgentId || typeof childCheckpoint.resumeContext.versionId !== 'string' || childCheckpoint.resumeContext.versionId.length === 0) { continue; } ownerAgentId = childCheckpoint.subAgentId; } const identity = `${ownerAgentId}\0${childCheckpoint.runId}`; if (seen.has(identity)) continue; seen.add(identity); childCheckpoints.push({ runId: childCheckpoint.runId, agentId: ownerAgentId }); } return childCheckpoints; } let AgentExecutionOrchestratorService = class AgentExecutionOrchestratorService { constructor(logger, n8nCheckpointStorage, agentExecutionService, telemetry, runtimeCacheService, integrationMessageContextService, agentRunTracingService, externalHooks) { this.logger = logger; this.n8nCheckpointStorage = n8nCheckpointStorage; this.agentExecutionService = agentExecutionService; this.telemetry = telemetry; this.runtimeCacheService = runtimeCacheService; this.integrationMessageContextService = integrationMessageContextService; this.agentRunTracingService = agentRunTracingService; this.externalHooks = externalHooks; } 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); } async cancelChatRun(params) { const checkpointStatus = await this.n8nCheckpointStorage.getStatus(params.runId, params.agentId); if (checkpointStatus.status === 'not-found' || checkpointStatus.checkpoint === undefined) { return false; } const { checkpoint } = checkpointStatus; if (checkpoint.status !== 'suspended' || checkpoint.persistence?.delegated === true || checkpoint.persistence?.resourceId !== params.resourceId) { return false; } const childCheckpoints = getDelegatedChildCheckpoints(checkpoint, params.agentId); if (checkpointStatus.status === 'active') { const cancelled = await this.n8nCheckpointStorage.cancelSuspended(params.runId, checkpoint, params.agentId); if (!cancelled) return false; } await Promise.all(childCheckpoints.map(async ({ runId, agentId }) => await this.n8nCheckpointStorage.delete(runId, agentId))); await this.n8nCheckpointStorage.delete(params.runId, params.agentId); return true; } async *resumeForChat(config) { const { agentId, projectId, runId, toolCallId, resumeData, expectedMemory, source, integrationType, user, usePublishedVersion = true, onExecutionRecorded, abortSignal, } = config; const checkpointStatus = await this.n8nCheckpointStorage.getStatus(runId, agentId); 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`); } if (memoryScope.delegated === true) { throw new n8n_workflow_1.UserError('Delegated actions must be resumed through their parent agent'); } if ((expectedMemory?.threadId !== undefined && memoryScope.threadId !== expectedMemory.threadId) || (expectedMemory?.resourceId !== undefined && memoryScope.resourceId !== expectedMemory.resourceId)) { throw new n8n_workflow_1.UserError(`Checkpoint ${runId} does not belong to this chat`); } const threadId = memoryScope.threadId; const runtime = await this.runtimeCacheService.getRuntime({ agentId, projectId, usePublishedVersion, integrationType, user: usePublishedVersion ? undefined : user, }); const { agent: agentInstance, toolRegistry } = runtime; let executionId; const recorder = this.createRecorder(toolRegistry, () => executionId, { projectId, agentId, threadId, }); const startedAt = recorder.startedAt; const runType = usePublishedVersion ? 'production' : 'test'; let executionSource = source; try { const suspendedExecution = this.agentRunTracingService.enabled && source === undefined ? await this.agentExecutionService.findLatestSuspendedRun(threadId) : undefined; executionSource ??= suspendedExecution?.source ?? undefined; const tracing = await this.agentRunTracingService.build({ agentId, projectId, threadId, userId: user?.id, source: executionSource ?? 'unknown', modelId: (0, agent_run_tracing_service_1.modelIdFromSnapshot)(agentInstance.snapshot.model), }); const resultStream = await agentInstance.resume('stream', resumeData, { runId, toolCallId, executionCounter: (0, agent_execution_counter_1.createAgentExecutionCounter)(this.telemetry, { agentId, userId: user?.id, runType, }), ...(tracing ? { telemetry: tracing } : {}), ...(abortSignal ? { abortSignal } : {}), }); const startParams = { threadId, agentId, agentName: agentInstance.name, projectId, userMessage: null, ...(executionSource !== undefined ? { source: executionSource } : {}), telemetry: { runType, configuration: runtime.telemetryConfiguration, }, }; executionId = await this.tryStartExecution(startParams, startedAt, 'Failed to start resumed agent execution recording'); for await (const value of (0, agent_stream_1.streamAgentChunks)(resultStream.stream)) { recorder.record(value); yield value; } } catch (error) { recorder.record({ type: 'error', error }); recorder.record({ type: 'finish', finishReason: 'error' }); throw error; } finally { const messageRecord = normalizeAbortedMessageRecord(recorder.getMessageRecord(), abortSignal); await this.persistRecordedExecution({ executionId, onExecutionRecorded, failureMessage: 'Failed to record resumed agent execution', params: { threadId, agentId, agentName: agentInstance.name, projectId, userMessage: null, ...(executionSource !== undefined ? { source: executionSource } : {}), record: messageRecord, hitlStatus: recorder.suspended ? 'suspended' : 'resumed', telemetry: { runType, configuration: runtime.telemetryConfiguration, }, }, }); } } async *executeForChat(config) { const { agentId, projectId, message, user, memory, attachments, source, onExecutionRecorded, abortSignal, } = config; const runtime = await this.runtimeCacheService.getRuntime({ agentId, projectId, integrationType: api_types_1.N8N_CHAT_INTEGRATION_TYPE, user, }); await this.integrationMessageContextService.setLatest(memory.threadId, memory.resourceId, { integrationConnectionId: api_types_1.N8N_CHAT_INTEGRATION_TYPE, platform: api_types_1.N8N_CHAT_INTEGRATION_TYPE, target: { type: 'dm', userId: user.id, threadId: memory.threadId }, interactingUserId: user.id, updatedAt: new Date().toISOString(), }); yield* this.streamChatResponse({ agentInstance: runtime.agent, toolRegistry: runtime.toolRegistry, agentId, userId: user.id, message, attachments, memory, projectId: runtime.projectId, source, telemetry: { runType: 'test', configuration: runtime.telemetryConfiguration, }, onExecutionRecorded, abortSignal, }); } async *executeForChatPublished(config) { const { agentId, projectId, message, memory, integrationType, attachments } = config; await this.externalHooks.run('agent.preExecute', [agentId]); const runtime = await this.runtimeCacheService.getRuntime({ agentId, projectId, integrationType, usePublishedVersion: true, }); yield* this.streamChatResponse({ agentInstance: runtime.agent, toolRegistry: runtime.toolRegistry, agentId, message, attachments, memory, projectId: runtime.projectId, source: integrationType, telemetry: { runType: 'production', configuration: runtime.telemetryConfiguration, }, }); } async *executeForTaskPublished(config) { const { agentId, projectId, message, memory, taskId, taskVersionId } = config; await this.externalHooks.run('agent.preExecute', [agentId]); const runtime = await this.runtimeCacheService.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, telemetry: { runType: 'production', configuration: runtime.telemetryConfiguration, }, }); } async *executeForTaskNow(config) { const { agentId, projectId, user, message, memory, taskId } = config; const runtime = await this.runtimeCacheService.getRuntime({ agentId, projectId, user, }); yield* this.streamChatResponse({ agentInstance: runtime.agent, toolRegistry: runtime.toolRegistry, agentId, userId: user.id, message, memory, projectId: runtime.projectId, source: 'task', taskId, telemetry: { runType: 'test', configuration: runtime.telemetryConfiguration, }, }); } async *streamChatResponse(config) { const { agentInstance, toolRegistry, agentId, userId, message, attachments, memory, projectId, source, taskId, taskVersionId, telemetry, onExecutionRecorded, abortSignal, } = config; const { threadId, resourceId } = memory; let executionId; const recorder = this.createRecorder(toolRegistry, () => executionId, { projectId, agentId, threadId, }); const startedAt = recorder.startedAt; try { const tracing = await this.agentRunTracingService.build({ agentId, projectId, threadId, userId, source: source ?? 'test', modelId: (0, agent_run_tracing_service_1.modelIdFromSnapshot)(agentInstance.snapshot.model), }); const input = attachments?.length ? (0, inbound_attachments_1.buildInboundUserMessage)(message, attachments) : message; const resultStream = await agentInstance.stream(input, { persistence: { threadId, resourceId }, executionCounter: (0, agent_execution_counter_1.createAgentExecutionCounter)(this.telemetry, { agentId, userId, runType: telemetry.runType, }), ...(tracing ? { telemetry: tracing } : {}), ...(abortSignal ? { abortSignal } : {}), }); const startParams = { threadId, agentId, agentName: agentInstance.name, projectId, userMessage: message, attachments, source, taskId, taskVersionId, telemetry, }; executionId = await this.tryStartExecution(startParams, startedAt, 'Failed to start agent execution recording'); for await (const value of (0, agent_stream_1.streamAgentChunks)(resultStream.stream)) { 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()) { recorder.record(chunk); yield chunk; } } yield value; } } catch (error) { recorder.record({ type: 'error', error }); recorder.record({ type: 'finish', finishReason: 'error' }); throw error; } finally { const messageRecord = normalizeAbortedMessageRecord(recorder.getMessageRecord(), abortSignal); await this.persistRecordedExecution({ executionId, onExecutionRecorded, failureMessage: 'Failed to record agent execution', params: { threadId, agentId, agentName: agentInstance.name, projectId, userMessage: message, attachments, record: messageRecord, hitlStatus: recorder.suspended ? 'suspended' : undefined, source, taskId, taskVersionId, telemetry, }, }); } } createRecorder(toolRegistry, getExecutionId, context) { return new execution_recorder_1.ExecutionRecorder(toolRegistry, (timeline) => { const executionId = getExecutionId(); if (executionId) { this.agentExecutionService.recordTimelineSnapshot({ ...context, executionId, timeline, }); } }); } async tryStartExecution(params, startedAt, failureMessage) { try { return await this.agentExecutionService.startExecutionRecording(params, startedAt); } catch (error) { this.logger.warn(failureMessage, { agentId: params.agentId, threadId: params.threadId, error: error instanceof Error ? error.message : String(error), }); return undefined; } } async persistRecordedExecution(args) { const { executionId, onExecutionRecorded, params, failureMessage } = args; if (!executionId) return; try { const recordedId = await this.agentExecutionService.finalizeExecution(executionId, params); onExecutionRecorded?.(recordedId); } catch (error) { this.logger.warn(failureMessage, { agentId: params.agentId, threadId: params.threadId, error: error instanceof Error ? error.message : String(error), }); } } }; exports.AgentExecutionOrchestratorService = AgentExecutionOrchestratorService; exports.AgentExecutionOrchestratorService = AgentExecutionOrchestratorService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, n8n_checkpoint_storage_1.N8NCheckpointStorage, agent_execution_service_1.AgentExecutionService, telemetry_1.Telemetry, agent_runtime_cache_service_1.AgentRuntimeCacheService, integration_message_context_service_1.IntegrationMessageContextService, agent_run_tracing_service_1.AgentRunTracingService, external_hooks_1.ExternalHooks]) ], AgentExecutionOrchestratorService); //# sourceMappingURL=agent-execution-orchestrator.service.js.map