UNPKG

n8n

Version:

n8n Workflow Automation Tool

339 lines • 16 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.SubAgentForegroundRunner = void 0; const agents_1 = require("@n8n/agents"); 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 uuid_1 = require("uuid"); const agent_execution_service_1 = require("../agent-execution.service"); const agent_telemetry_1 = require("../agent-telemetry"); const execution_recorder_1 = require("../execution-recorder"); const n8n_checkpoint_storage_1 = require("../integrations/n8n-checkpoint-storage"); const agent_stream_1 = require("../utils/agent-stream"); const sub_agent_source_resolver_1 = require("./sub-agent-source-resolver"); let SubAgentForegroundRunner = class SubAgentForegroundRunner { constructor(sourceResolver, agentExecutionService, checkpointStorage, logger) { this.sourceResolver = sourceResolver; this.agentExecutionService = agentExecutionService; this.checkpointStorage = checkpointStorage; this.logger = logger; } async runForeground(request, context) { if (request.executionMode !== undefined && request.executionMode !== 'foreground') { throw new n8n_workflow_1.UserError('Foreground sub-agent runner only supports foreground execution mode'); } const taskPath = request.taskPath; (0, agents_1.assertSubAgentTaskPath)(taskPath); return await this.executeForeground({ type: 'run', request, taskPath }, context); } async resumeForeground(request, context) { (0, agents_1.assertSubAgentTaskPath)(request.taskPath); if (request.childThreadId === undefined || request.resumeContext === undefined) { throw new n8n_workflow_1.UserError('Configured sub-agent checkpoint metadata is missing or invalid'); } const pinnedSource = parseResumeContext(request.resumeContext, request.subAgentId); return await this.executeForeground({ type: 'resume', request, taskPath: request.taskPath, source: pinnedSource, threadId: request.childThreadId, }, context); } async cancelForeground(request) { (0, agents_1.assertSubAgentTaskPath)(request.taskPath); if (request.resumeContext === undefined) { throw new n8n_workflow_1.UserError('Configured sub-agent checkpoint metadata is missing or invalid'); } const pinnedSource = parseResumeContext(request.resumeContext, request.subAgentId); await this.checkpointStorage.delete(request.childRunId, pinnedSource.agentId); } async executeForeground(operation, context) { const runtimeSource = await this.sourceResolver.resolveForRuntime(operation.type === 'run' ? operation.request.source : operation.source, { projectId: context.projectId }); const threadId = operation.type === 'run' ? (0, uuid_1.v4)() : operation.threadId; const resourceId = operation.type === 'run' ? (operation.request.parentResourceId ?? threadId) : threadId; const reconstructionService = await getReconstructionService(); const childConfig = context.instrumentation?.transformDelegatedAgentConfig?.(runtimeSource.source.config, { subAgentId: runtimeSource.source.sourceId, }) ?? runtimeSource.source.config; const { agent } = await reconstructionService.reconstructFromResolvedSource({ config: childConfig, memoryOwnerAgentId: runtimeSource.source.sourceId, projectId: context.projectId, credentialProvider: context.credentialProvider, toolDescriptors: runtimeSource.toolDescriptors, toolCodeByName: runtimeSource.toolCodeByName, skills: runtimeSource.skills, runtimeProfile: 'sub-agent', runType: context.runType, workflowToolExecutionMode: context.workflowToolExecutionMode, parentAgentIdForDelegation: context.parentAgentId, user: context.user, instrumentation: context.instrumentation, }); const telemetry = (0, agents_1.deriveSubAgentTelemetry)(context.telemetry); const userMessage = operation.type === 'run' ? (0, agents_1.renderDelegateSubAgentPrompt)(operation.request) : null; let executionId; const recorder = new execution_recorder_1.ExecutionRecorder(undefined, (timeline) => { if (executionId) { this.agentExecutionService.recordTimelineSnapshot({ projectId: context.projectId, agentId: runtimeSource.source.sourceId, threadId, executionId, timeline, }); } }); const startedAt = recorder.startedAt; let recorded = false; try { const executionOptions = { ...(context.abortSignal !== undefined ? { abortSignal: context.abortSignal } : {}), ...(telemetry !== undefined ? { telemetry } : {}), executionCounter: context.executionCounter, }; const resultStream = operation.type === 'run' ? await agent.stream(userMessage ?? '', { ...executionOptions, persistence: { resourceId, threadId, delegated: true, }, }) : await agent.resume('stream', operation.request.resumeData, { ...executionOptions, runId: operation.request.childRunId, toolCallId: operation.request.childToolCallId, }); try { const currentExecutionId = await this.agentExecutionService.startExecutionRecording({ threadId, agentId: runtimeSource.source.sourceId, agentName: runtimeSource.source.config.name, projectId: context.projectId, userMessage, source: 'subagent', threadMetadata: { parentThreadId: operation.request.parentThreadId, parentAgentId: context.parentAgentId, }, telemetry: { runType: context.runType, configuration: (0, agent_telemetry_1.buildAgentConfigurationTelemetryFromConfig)(runtimeSource.source.config), }, }, startedAt); executionId = currentExecutionId; } catch (error) { this.logger.warn('Failed to start subagent execution recording', { agentId: runtimeSource.source.sourceId, taskPath: operation.taskPath, error: error instanceof Error ? error.message : String(error), }); } const { messageRecord, result } = await consumeAgentStream(resultStream, recorder, context.onChunk); const suspended = result.pendingSuspend !== undefined && result.pendingSuspend.length > 0; const hitlStatus = suspended ? 'suspended' : operation.type === 'resume' ? 'resumed' : undefined; await this.recordSubAgentExecution({ runtimeSource: runtimeSource.source, projectId: context.projectId, threadId, parentThreadId: operation.request.parentThreadId, parentAgentId: context.parentAgentId, runType: context.runType, taskPath: operation.taskPath, userMessage, record: messageRecord, executionId, ...(hitlStatus !== undefined ? { hitlStatus } : {}), }); recorded = true; return { taskPath: operation.taskPath, threadId, status: suspended ? 'suspended' : result.finishReason === 'error' || result.error !== undefined ? 'failed' : 'completed', result, ...(suspended ? { resumeContext: createResumeContext(runtimeSource.source) } : {}), }; } catch (error) { if (!recorded) { recorder.record({ type: 'error', error }); recorder.record({ type: 'finish', finishReason: 'error' }); await this.recordSubAgentExecution({ runtimeSource: runtimeSource.source, projectId: context.projectId, threadId, parentThreadId: operation.request.parentThreadId, parentAgentId: context.parentAgentId, runType: context.runType, taskPath: operation.taskPath, userMessage, record: recorder.getMessageRecord(), executionId, ...(operation.type === 'resume' ? { hitlStatus: 'resumed' } : {}), }); } throw error; } finally { await agent.close().catch((error) => { this.logger.warn(`Failed to close subagent after ${operation.type}`, { taskPath: operation.taskPath, error: error instanceof Error ? error.message : String(error), }); }); } } async recordSubAgentExecution(params) { const { runtimeSource, projectId, threadId, parentThreadId, parentAgentId, runType, taskPath, userMessage, record, executionId, hitlStatus, } = params; if (!executionId) return; try { await this.agentExecutionService.finalizeExecution(executionId, { threadId, agentId: runtimeSource.sourceId, agentName: runtimeSource.config.name, projectId, userMessage, record, ...(hitlStatus !== undefined ? { hitlStatus } : {}), source: 'subagent', threadMetadata: { ...(parentThreadId !== undefined ? { parentThreadId } : {}), ...(parentAgentId !== undefined ? { parentAgentId } : {}), }, telemetry: { runType, configuration: (0, agent_telemetry_1.buildAgentConfigurationTelemetryFromConfig)(runtimeSource.config), }, }); } catch (error) { this.logger.warn('Failed to record subagent execution', { agentId: runtimeSource.sourceId, taskPath, error: error instanceof Error ? error.message : String(error), }); } } }; exports.SubAgentForegroundRunner = SubAgentForegroundRunner; exports.SubAgentForegroundRunner = SubAgentForegroundRunner = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [sub_agent_source_resolver_1.SubAgentSourceResolver, agent_execution_service_1.AgentExecutionService, n8n_checkpoint_storage_1.N8NCheckpointStorage, backend_common_1.Logger]) ], SubAgentForegroundRunner); async function getReconstructionService() { const { AgentRuntimeReconstructionService } = await import('../agent-runtime-reconstruction.service.js'); return di_1.Container.get(AgentRuntimeReconstructionService); } async function consumeAgentStream(resultStream, recorder, onChunk) { const pendingSuspend = []; let structuredOutput; for await (const value of (0, agent_stream_1.streamAgentChunks)(resultStream.stream)) { recorder.record(value); onChunk?.(value); if (value.type === 'tool-call-suspended') { pendingSuspend.push({ runId: value.runId, toolCallId: value.toolCallId, toolName: value.toolName, input: value.input, suspendPayload: value.suspendPayload, ...(value.resumeSchema !== undefined ? { resumeSchema: value.resumeSchema } : {}), }); } if (value.type === 'finish' && value.structuredOutput !== undefined) { structuredOutput = value.structuredOutput; } } const messageRecord = recorder.getMessageRecord(); return { messageRecord, result: buildGenerateResultFromRecord(resultStream.runId, messageRecord, structuredOutput, () => resultStream.getState(), pendingSuspend), }; } function createResumeContext(runtimeSource) { if (runtimeSource.versionId === undefined) { throw new n8n_workflow_1.UnexpectedError('Resolved sub-agent source is missing its published version'); } return { agentId: runtimeSource.sourceId, versionId: runtimeSource.versionId }; } function parseResumeContext(resumeContext, subAgentId) { if (!(0, is_record_1.isRecord)(resumeContext) || typeof resumeContext.agentId !== 'string' || resumeContext.agentId.length === 0 || resumeContext.agentId !== subAgentId || typeof resumeContext.versionId !== 'string' || resumeContext.versionId.length === 0) { throw new n8n_workflow_1.UserError('Configured sub-agent resume context is missing or invalid'); } return { agentId: resumeContext.agentId, versionId: resumeContext.versionId }; } function buildGenerateResultFromRecord(runId, record, structuredOutput, getState, pendingSuspend = []) { const messages = createAssistantMessages(record.assistantResponse); const finishReason = toKnownFinishReason(record.finishReason); const result = { runId, messages, ...(record.model !== null ? { model: record.model } : {}), ...(finishReason !== undefined ? { finishReason } : {}), ...(record.usage !== null ? { usage: { ...record.usage, ...(record.totalCost !== null ? { cost: record.totalCost } : {}), }, } : {}), ...(structuredOutput !== undefined ? { structuredOutput } : {}), ...(record.error !== null ? { error: record.error } : {}), ...(pendingSuspend.length > 0 ? { pendingSuspend } : {}), getState, }; return result; } function createAssistantMessages(text) { if (!text.trim()) return []; return [ { role: 'assistant', content: [{ type: 'text', text }], }, ]; } function toKnownFinishReason(value) { if (value === 'stop' || value === 'length' || value === 'content-filter' || value === 'tool-calls' || value === 'error' || value === 'other' || value === 'max-iterations') { return value; } return undefined; } //# sourceMappingURL=sub-agent-foreground-runner.js.map