n8n
Version:
n8n Workflow Automation Tool
470 lines • 21 kB
JavaScript
"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