n8n
Version:
n8n Workflow Automation Tool
440 lines • 22.3 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.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 api_1 = require("@opentelemetry/api");
const n8n_workflow_1 = require("n8n-workflow");
const credentials_service_1 = require("../../credentials/credentials.service");
const execution_level_tracer_1 = require("../../modules/otel/execution-level-tracer");
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_reconstruction_service_1 = require("./agent-runtime-reconstruction.service");
const agent_telemetry_1 = require("./agent-telemetry");
const execution_recorder_1 = require("./execution-recorder");
const node_tool_ai_gateway_service_1 = require("./json-config/node-tool-ai-gateway.service");
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_published_snapshot_1 = require("./utils/agent-published-snapshot");
const agent_stream_1 = require("./utils/agent-stream");
const node_tool_validation_1 = require("./utils/node-tool-validation");
const structured_output_error_1 = require("./utils/structured-output-error");
let AgentWorkflowExecutionService = class AgentWorkflowExecutionService {
constructor(logger, agentRepository, agentExecutionService, telemetry, credentialsService, agentRuntimeReconstructionService, agentRunTracingService, executionLevelTracer, nodeToolAiGatewayService) {
this.logger = logger;
this.agentRepository = agentRepository;
this.agentExecutionService = agentExecutionService;
this.telemetry = telemetry;
this.credentialsService = credentialsService;
this.agentRuntimeReconstructionService = agentRuntimeReconstructionService;
this.agentRunTracingService = agentRunTracingService;
this.executionLevelTracer = executionLevelTracer;
this.nodeToolAiGatewayService = nodeToolAiGatewayService;
}
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, runType, 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, runType);
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, runType, outputSchema, extraTools) {
try {
const { agent: reconstructed } = await this.agentRuntimeReconstructionService.reconstructFromResolvedSource({
config,
memoryOwnerAgentId: syntheticAgentId,
projectId,
credentialProvider,
toolDescriptors: {},
toolCodeByName: {},
skills,
runtimeProfile: 'inline',
runType,
});
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, runType, outputSchema, tracing, recordingParams, } = params;
let agentExecutionId;
const recorder = new execution_recorder_1.ExecutionRecorder(undefined, (timeline) => {
if (agentExecutionId && recordingParams) {
this.agentExecutionService.recordTimelineSnapshot({
projectId: recordingParams.projectId,
agentId: recordingParams.agentId,
threadId: recordingParams.threadId,
executionId: agentExecutionId,
timeline,
});
}
});
const startedAt = recorder.startedAt;
let structuredOutput = null;
const toolCalls = [];
const toolInputs = new Map();
let streamError;
const parentCtx = tracing.executionId
? this.executionLevelTracer.getActiveContext(tracing.executionId, tracing.nodeName)
: undefined;
const run = async () => {
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,
hasParentContext: parentCtx !== undefined,
});
const resultStream = await agentInstance.stream(message, {
persistence: { resourceId: threadId, threadId },
executionCounter: (0, agent_execution_counter_1.createAgentExecutionCounter)(this.telemetry, {
agentId: telemetryAgentId,
userId: telemetryUserId,
runType,
}),
...(telemetry ? { telemetry } : {}),
});
if (recordingParams) {
try {
agentExecutionId = await this.agentExecutionService.startExecutionRecording(recordingParams, startedAt);
}
catch (error) {
this.logger.warn('Failed to start agent execution recording from workflow', {
agentId: recordingParams.agentId,
threadId,
error: error instanceof Error ? error.message : String(error),
});
}
}
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;
}
};
if (parentCtx) {
await api_1.context.with(parentCtx, run);
}
else {
await run();
}
if (streamError && recordingParams && !agentExecutionId) {
try {
agentExecutionId = await this.agentExecutionService.startExecutionRecording(recordingParams, startedAt);
}
catch (error) {
this.logger.warn('Failed to start agent execution recording from workflow', {
agentId: recordingParams.agentId,
threadId,
error: error instanceof Error ? error.message : String(error),
});
}
}
return {
recorder,
messageRecord: recorder.getMessageRecord(),
structuredOutput,
toolCalls,
streamError,
agentExecutionId,
};
}
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 runType = useDraftVersion ? 'test' : 'production';
const extraTools = this.buildWorkflowExtraTools(workflowContext);
const compiled = await this.compileIsolated(agentData, credentialProvider, runType, 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,
runType,
outputSchema,
tracing: {
projectId,
executionId,
workflowId: workflowContext?.workflowId,
nodeId: workflowContext?.callingNodeId,
nodeName: workflowContext?.callingNodeName,
},
recordingParams: {
threadId,
agentId,
agentName: agentInstance.name,
projectId,
userMessage: message,
source: api_types_1.AGENT_WORKFLOW_TRIGGER_TYPE,
telemetry: {
runType,
configuration: telemetryConfiguration,
},
},
});
if (run.agentExecutionId) {
try {
await this.agentExecutionService.finalizeExecution(run.agentExecutionId, {
threadId,
agentId,
agentName: agentInstance.name,
projectId,
userMessage: message,
record: run.messageRecord,
source: api_types_1.AGENT_WORKFLOW_TRIGGER_TYPE,
telemetry: {
runType,
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 accessibleCredentials = await credentialProvider.list();
await this.nodeToolAiGatewayService.assignManagedCredentials(runtimeConfig.tools, new Set(accessibleCredentials.map((credential) => credential.type)));
const syntheticAgentId = `inline:${workflowContext?.workflowId ?? 'unknown'}:${workflowContext?.callingNodeName ?? 'unknown'}`;
const extraTools = this.buildWorkflowExtraTools(workflowContext);
const compiled = await this.compileIsolatedFromSource(runtimeConfig, skills, syntheticAgentId, projectId, credentialProvider, runType, 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,
runType,
outputSchema,
tracing: {
projectId,
executionId,
workflowId: workflowContext?.workflowId,
nodeId: workflowContext?.callingNodeId,
nodeName: workflowContext?.callingNodeName,
},
});
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,
token_count: run.messageRecord.usage?.totalTokens ?? 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, execution_level_tracer_1.ExecutionLevelTracer, node_tool_ai_gateway_service_1.NodeToolAiGatewayService])
], AgentWorkflowExecutionService);
//# sourceMappingURL=agent-workflow-execution.service.js.map