UNPKG

n8n

Version:

n8n Workflow Automation Tool

361 lines • 18.3 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.AgentWorkflowExecutionService = void 0; const api_types_1 = require("@n8n/api-types"); const backend_common_1 = require("@n8n/backend-common"); const di_1 = require("@n8n/di"); const n8n_workflow_1 = require("n8n-workflow"); const credentials_service_1 = require("../../credentials/credentials.service"); const telemetry_1 = require("../../telemetry"); const agent_telemetry_1 = require("./agent-telemetry"); const agent_execution_service_1 = require("./agent-execution.service"); const agent_run_tracing_service_1 = require("./agent-run-tracing.service"); const agent_runtime_reconstruction_service_1 = require("./agent-runtime-reconstruction.service"); const execution_recorder_1 = require("./execution-recorder"); const agent_repository_1 = require("./repositories/agent.repository"); const input_data_tool_1 = require("./tools/input-data-tool"); const workflow_context_tool_1 = require("./tools/workflow-context-tool"); const agent_credential_provider_1 = require("./utils/agent-credential-provider"); const agent_execution_counter_1 = require("./utils/agent-execution-counter"); const agent_stream_1 = require("./utils/agent-stream"); const node_tool_validation_1 = require("./utils/node-tool-validation"); const agent_published_snapshot_1 = require("./utils/agent-published-snapshot"); const structured_output_error_1 = require("./utils/structured-output-error"); let AgentWorkflowExecutionService = class AgentWorkflowExecutionService { constructor(logger, agentRepository, agentExecutionService, telemetry, credentialsService, agentRuntimeReconstructionService, agentRunTracingService) { this.logger = logger; this.agentRepository = agentRepository; this.agentExecutionService = agentExecutionService; this.telemetry = telemetry; this.credentialsService = credentialsService; this.agentRuntimeReconstructionService = agentRuntimeReconstructionService; this.agentRunTracingService = agentRunTracingService; } normalizeWorkflowStreamError(error, outputSchema) { const normalizedError = error instanceof Error ? error : new Error(String(error)); if (!outputSchema || normalizedError instanceof n8n_workflow_1.OperationalError) return normalizedError; const structuredOutputError = (0, structured_output_error_1.describeStructuredOutputError)(normalizedError.message); if (!structuredOutputError) return normalizedError; return new n8n_workflow_1.OperationalError(structuredOutputError, { cause: normalizedError }); } applyPerCallAgentExtras(reconstructed, outputSchema, extraTools) { if (outputSchema) { reconstructed.structuredOutput(outputSchema); } if (extraTools?.length) { const declared = new Set(reconstructed.declaredTools.map((t) => t.name)); const collisions = extraTools.filter((t) => declared.has(t.name)).map((t) => t.name); if (collisions.length) { const names = collisions.map((n) => `"${n}"`).join(', '); const plural = collisions.length > 1; return { ok: false, error: `Agent declares ${plural ? 'tools' : 'a tool'} named ${names}, ` + `which ${plural ? 'are' : 'is'} reserved by n8n for workflow data access. ` + `Rename the agent ${plural ? 'tools' : 'tool'} to avoid the collision.`, }; } reconstructed.tool(extraTools); } return { ok: true, agent: reconstructed }; } async compileIsolated(agentEntity, credentialProvider, outputSchema, extraTools) { if (!agentEntity.schema) { return { ok: false, error: 'Agent has no JSON config. Create a config first.' }; } try { const { agent: reconstructed } = await this.agentRuntimeReconstructionService.reconstructFromAgentEntity(agentEntity, credentialProvider); return this.applyPerCallAgentExtras(reconstructed, outputSchema, extraTools); } catch (e) { return { ok: false, error: e instanceof Error ? e.message : 'Unknown compilation error', }; } } async compileIsolatedFromSource(config, skills, syntheticAgentId, projectId, credentialProvider, outputSchema, extraTools) { try { const { agent: reconstructed } = await this.agentRuntimeReconstructionService.reconstructFromResolvedSource({ config, memoryOwnerAgentId: syntheticAgentId, projectId, credentialProvider, toolDescriptors: {}, toolCodeByName: {}, skills, runtimeProfile: 'inline', }); return this.applyPerCallAgentExtras(reconstructed, outputSchema, extraTools); } catch (e) { return { ok: false, error: e instanceof Error ? e.message : 'Unknown compilation error', }; } } buildWorkflowExtraTools(workflowContext) { if (!workflowContext) return undefined; const extraTools = [(0, input_data_tool_1.createInputDataTool)(workflowContext)]; if (workflowContext.exposeWorkflowData) { extraTools.push((0, workflow_context_tool_1.createWorkflowContextTool)(workflowContext)); } return extraTools; } async streamWorkflowAgent(params) { const { agentInstance, message, threadId, telemetryAgentId, telemetryUserId, outputSchema, tracing, } = params; const recorder = new execution_recorder_1.ExecutionRecorder(); let structuredOutput = null; const toolCalls = []; const toolInputs = new Map(); let streamError; try { const telemetry = await this.agentRunTracingService.build({ agentId: telemetryAgentId, projectId: tracing.projectId, threadId, userId: telemetryUserId, source: 'workflow', executionId: tracing.executionId, workflowId: tracing.workflowId, nodeId: tracing.nodeId, }); const resultStream = await agentInstance.stream(message, { persistence: { resourceId: threadId, threadId }, executionCounter: (0, agent_execution_counter_1.createAgentExecutionCounter)(this.telemetry, { agentId: telemetryAgentId, userId: telemetryUserId, }), ...(telemetry ? { telemetry } : {}), }); for await (const value of (0, agent_stream_1.streamAgentChunks)(resultStream.stream)) { 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; } } } catch (error) { const normalizedError = this.normalizeWorkflowStreamError(error, outputSchema); recorder.record({ type: 'error', error: normalizedError }); recorder.record({ type: 'finish', finishReason: 'error' }); streamError = normalizedError; } return { recorder, messageRecord: recorder.getMessageRecord(), structuredOutput, toolCalls, streamError, }; } buildWorkflowResult(params) { const { run, session, outputSchema } = params; const { recorder, messageRecord, structuredOutput, toolCalls, streamError } = run; if (streamError !== undefined) { throw streamError; } 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) { if (outputSchema) { const structuredOutputError = (0, structured_output_error_1.describeStructuredOutputError)(messageRecord.error); if (structuredOutputError) { throw new n8n_workflow_1.OperationalError(structuredOutputError); } } throw new n8n_workflow_1.OperationalError(`Agent execution failed: ${messageRecord.error}`); } if (messageRecord.finishReason === 'error') { throw new n8n_workflow_1.OperationalError(outputSchema ? 'Agent execution finished with an error while producing structured output. ' + "The agent's model or provider may not support JSON Schema structured output." : '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, }; } async executeForWorkflow(agentId, message, executionId, threadId, projectId, telemetryUserId, useDraftVersion, outputSchema, workflowContext) { const agentEntity = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agentEntity) { throw new n8n_workflow_1.OperationalError('Agent not found or not accessible.'); } const credentialProvider = (0, agent_credential_provider_1.createAgentCredentialProvider)(this.credentialsService, projectId); let agentData = agentEntity; if (!useDraftVersion) { agentData = (0, agent_published_snapshot_1.getPublishedAgentSnapshot)(agentEntity); } const telemetryConfiguration = (0, agent_telemetry_1.buildAgentConfigurationTelemetry)(agentData); const extraTools = this.buildWorkflowExtraTools(workflowContext); const compiled = await this.compileIsolated(agentData, credentialProvider, outputSchema, extraTools?.length ? extraTools : undefined); if (!compiled.ok || !compiled.agent) { throw new n8n_workflow_1.OperationalError(`Failed to compile agent: ${compiled.error ?? 'unknown error'}`); } const agentInstance = compiled.agent; const run = await this.streamWorkflowAgent({ agentInstance, message, threadId, telemetryAgentId: agentId, telemetryUserId, outputSchema, tracing: { projectId, executionId, workflowId: workflowContext?.workflowId, nodeId: workflowContext?.callingNodeId, }, }); void this.agentExecutionService .recordMessage({ threadId, agentId, agentName: agentInstance.name, projectId, userMessage: message, record: run.messageRecord, source: api_types_1.AGENT_WORKFLOW_TRIGGER_TYPE, telemetry: { runType: useDraftVersion ? 'test' : 'production', configuration: telemetryConfiguration, }, }) .catch((error) => { this.logger.warn('Failed to record agent execution from workflow', { agentId, threadId, error: error instanceof Error ? error.message : String(error), }); }); return this.buildWorkflowResult({ run, session: { agentId, projectId, sessionId: threadId, threadId }, outputSchema, }); } async executeInlineForWorkflow(inlineAgent, message, executionId, threadId, projectId, telemetryUserId, runType = 'production', outputSchema, workflowContext) { const { config, skills } = await this.validateInlineAgentConfig(inlineAgent); const persistMemory = workflowContext?.hasCallerSessionId === true; const runtimeConfig = persistMemory ? { ...config, memory: { enabled: true, storage: 'n8n', observationalMemory: { enabled: false }, episodicMemory: { enabled: false }, }, } : config; const credentialProvider = (0, agent_credential_provider_1.createAgentCredentialProvider)(this.credentialsService, projectId); const syntheticAgentId = `inline:${workflowContext?.workflowId ?? 'unknown'}:${workflowContext?.callingNodeName ?? 'unknown'}`; const extraTools = this.buildWorkflowExtraTools(workflowContext); const compiled = await this.compileIsolatedFromSource(runtimeConfig, skills, syntheticAgentId, projectId, credentialProvider, outputSchema, extraTools?.length ? extraTools : undefined); if (!compiled.ok || !compiled.agent) { throw new n8n_workflow_1.OperationalError(`Failed to compile agent: ${compiled.error ?? 'unknown error'}`); } const run = await this.streamWorkflowAgent({ agentInstance: compiled.agent, message, threadId, telemetryAgentId: syntheticAgentId, telemetryUserId, outputSchema, tracing: { projectId, executionId, workflowId: workflowContext?.workflowId, nodeId: workflowContext?.callingNodeId, }, }); try { this.telemetry.trackAgentTurnFinished({ agent_id: syntheticAgentId, thread_id: threadId, run_type: runType, agent_type: 'inline', turn_status: run.messageRecord.error !== null || run.messageRecord.finishReason === 'error' ? 'failed' : 'succeeded', configuration: (0, agent_telemetry_1.buildAgentConfigurationTelemetryFromConfig)(runtimeConfig), latency_ms: run.messageRecord.duration, cost: run.messageRecord.totalCost ?? 0, tool_call_count: run.messageRecord.timeline.filter((t) => t.type === 'tool-call').length, }); } catch (error) { this.logger.warn('Failed to track inline agent execution telemetry', { threadId, error: error instanceof Error ? error.message : String(error), }); } return this.buildWorkflowResult({ run, session: null, outputSchema }); } async validateInlineAgentConfig(payload) { const parsed = api_types_1.RunnableInlineAgentConfigSchema.safeParse({ config: (0, api_types_1.sanitizeAgentJsonConfig)(payload.config), ...(payload.skills !== undefined ? { skills: (0, api_types_1.sanitizeAgentSkillBodies)(payload.skills) } : {}), }); if (!parsed.success) { throw new n8n_workflow_1.UserError(`Invalid inline agent configuration: ${(0, api_types_1.formatAgentConfigZodError)(parsed.error)}`); } const config = parsed.data.config; try { (0, node_tool_validation_1.validateNodeToolExpressions)(config.tools); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new n8n_workflow_1.UserError(`Invalid $fromAI expression in node tool config: ${message}`); } const nodeError = await (0, node_tool_validation_1.validateNodeToolConfigs)(config.tools); if (nodeError) { throw new n8n_workflow_1.UserError(`Invalid inline agent configuration: ${nodeError}`); } return { config, skills: parsed.data.skills ?? {} }; } }; exports.AgentWorkflowExecutionService = AgentWorkflowExecutionService; exports.AgentWorkflowExecutionService = AgentWorkflowExecutionService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, agent_repository_1.AgentRepository, agent_execution_service_1.AgentExecutionService, telemetry_1.Telemetry, credentials_service_1.CredentialsService, agent_runtime_reconstruction_service_1.AgentRuntimeReconstructionService, agent_run_tracing_service_1.AgentRunTracingService]) ], AgentWorkflowExecutionService); //# sourceMappingURL=agent-workflow-execution.service.js.map