n8n
Version:
n8n Workflow Automation Tool
817 lines • 37.7 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.EvalExecutionService = void 0;
const api_types_1 = require("@n8n/api-types");
const backend_common_1 = require("@n8n/backend-common");
const proxy_1 = require("@n8n/backend-network/proxy");
const config_1 = require("@n8n/config");
const di_1 = require("@n8n/di");
const workflow_sdk_1 = require("@n8n/workflow-sdk");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const node_crypto_1 = require("node:crypto");
const active_executions_1 = require("../../../active-executions");
const load_nodes_and_credentials_1 = require("../../../load-nodes-and-credentials");
const node_types_1 = require("../../../node-types");
const posthog_1 = require("../../../posthog");
const workflow_runner_1 = require("../../../workflow-runner");
const workflow_finder_service_1 = require("../../../workflows/workflow-finder.service");
const workflow_static_data_service_1 = require("../../../workflows/workflow-static-data.service");
const llm_completion_mock_1 = require("./llm-completion-mock");
const eval_mocked_credentials_helper_1 = require("./eval-mocked-credentials-helper");
const eval_timings_1 = require("./eval-timings");
const llm_wire_server_1 = require("./llm-wire-server");
const mock_handler_1 = require("./mock-handler");
const openai_responses_envelope_1 = require("./openai-responses-envelope");
const pin_data_generator_1 = require("./pin-data-generator");
const workflow_analysis_1 = require("./workflow-analysis");
const MAX_OUTPUT_ITEMS_PER_BRANCH = 10;
let EvalExecutionService = class EvalExecutionService {
constructor(workflowFinderService, nodeTypes, logger, postHogClient, workflowRunner, activeExecutions, executionsConfig, binaryDataService, workflowStaticDataService, loadNodesAndCredentials) {
this.workflowFinderService = workflowFinderService;
this.nodeTypes = nodeTypes;
this.logger = logger;
this.postHogClient = postHogClient;
this.workflowRunner = workflowRunner;
this.activeExecutions = activeExecutions;
this.executionsConfig = executionsConfig;
this.binaryDataService = binaryDataService;
this.workflowStaticDataService = workflowStaticDataService;
this.loadNodesAndCredentials = loadNodesAndCredentials;
}
async executeWithLlmMock(workflowId, user, options = {}) {
if (this.executionsConfig.mode === 'queue') {
return this.errorResult((0, node_crypto_1.randomUUID)(), 'Eval execution requires main process mode — queue mode is not supported.');
}
let workflowEntity = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:execute',
]);
if (!workflowEntity) {
for (const delayMs of [200, 500, 1000]) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
workflowEntity = await this.workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:execute',
]);
if (workflowEntity)
break;
}
}
if (!workflowEntity) {
return this.errorResult((0, node_crypto_1.randomUUID)(), `Workflow ${workflowId} not found or not accessible`);
}
let partitioned;
try {
partitioned = (0, workflow_analysis_1.partitionAiRoots)(workflowEntity, options.pinNodes ?? []);
}
catch (error) {
if (error instanceof n8n_workflow_1.UserError) {
return this.errorResult((0, node_crypto_1.randomUUID)(), error.message);
}
throw error;
}
for (const entry of partitioned.autoPinned) {
this.logger.debug(`[EvalMock] Auto-pinning AI root "${entry.root}" — sub-node "${entry.subNode}" (${entry.subNodeType}) is ${entry.reason}`);
}
let interceptionEnabled = false;
let unpinNodes = partitioned.unpinNodes;
if (unpinNodes.length > 0) {
interceptionEnabled = await this.isInterceptionEnabled(user);
if (!interceptionEnabled) {
this.logger.warn('[EvalMock] Vendor SDK interception disabled by kill-switch — pinning all AI roots');
unpinNodes = [];
}
}
const unpinSet = unpinNodes.length > 0 ? new Set(unpinNodes) : undefined;
const timings = new eval_timings_1.EvalTimings();
let hints;
try {
hints = await this.analyzeWorkflow(workflowEntity, timings, options.scenarioHints, unpinSet);
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
return this.errorResult((0, node_crypto_1.randomUUID)(), message.startsWith('FRAMEWORK ISSUE:') ? message : `FRAMEWORK ISSUE: ${message}`);
}
const vendorLlmRouting = interceptionEnabled
? (0, workflow_analysis_1.buildVendorLlmRouting)(workflowEntity, unpinNodes)
: undefined;
return await this.execute(workflowEntity, user, hints, timings, options.scenarioHints, interceptionEnabled, vendorLlmRouting);
}
async isInterceptionEnabled(user) {
try {
const flags = await this.postHogClient.getFeatureFlags(user);
return flags?.[api_types_1.EVAL_VENDOR_SDK_INTERCEPTION_FLAG] !== false;
}
catch (error) {
this.logger.warn('[EvalMock] Failed to resolve vendor-SDK interception flag', {
error: error instanceof Error ? error.message : String(error),
});
return false;
}
}
async analyzeWorkflow(workflowEntity, timings, scenarioHints, unpinSet) {
const hintNodes = (0, workflow_analysis_1.identifyNodesForHints)(workflowEntity);
const nodeNames = hintNodes.map((n) => n.name);
this.logger.debug(`[EvalMock] Generating hints for ${nodeNames.length} nodes: ${nodeNames.join(', ')}`);
const hints = await timings.time('hints', undefined, async () => await (0, workflow_analysis_1.generateMockHints)({
workflow: workflowEntity,
nodeNames,
scenarioHints,
}));
if (!hints.globalContext && nodeNames.length > 0) {
this.logger.warn('[EvalMock] Phase 1 hint generation returned empty — mock responses will lack cross-node consistency');
}
this.logger.debug(`[EvalMock] Phase 1 result — globalContext: ${hints.globalContext ? 'present' : 'EMPTY'}, triggerContent keys: ${JSON.stringify(Object.keys(hints.triggerContent))}, nodeHints: ${Object.keys(hints.nodeHints).join(', ')}`);
const bypassNodes = (0, workflow_analysis_1.identifyNodesForPinData)(workflowEntity, unpinSet);
const bypassNodeNames = bypassNodes.map((n) => n.name);
if (bypassNodeNames.length > 0) {
this.logger.debug(`[EvalMock] Generating pin data for ${bypassNodeNames.length} bypass nodes: ${bypassNodeNames.join(', ')}`);
hints.bypassPinData = await this.generateBypassPinData(workflowEntity, bypassNodeNames, hints.globalContext, timings, scenarioHints);
this.logger.debug(`[EvalMock] Phase 1.5 result — pinned nodes: ${Object.keys(hints.bypassPinData).join(', ') || 'none'}`);
}
return hints;
}
async generateBypassPinData(workflowEntity, bypassNodeNames, globalContext, timings, scenarioHints) {
if (bypassNodeNames.length === 0)
return {};
try {
const result = await timings.time('bypass-pin', undefined, async () => await (0, pin_data_generator_1.generatePinData)({
workflow: workflowEntity,
nodeNames: bypassNodeNames,
instructions: globalContext || scenarioHints
? { dataDescription: globalContext, testScenario: scenarioHints }
: undefined,
outputSchemaLookup: this.loadNodesAndCredentials.createOutputSchemaLookup(),
}));
const normalized = (0, workflow_sdk_1.normalizePinData)(result);
for (const nodeName of bypassNodeNames) {
if (!normalized[nodeName]) {
this.logger.warn(`[EvalMock] Phase 1.5 produced no pin data for bypass node "${nodeName}" — pinning empty to prevent real execution`);
normalized[nodeName] = [];
}
}
return normalized;
}
catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
this.logger.error(`[EvalMock] Phase 1.5 pin data generation failed: ${errorMsg}`);
throw new Error(`FRAMEWORK ISSUE: Phase 1.5 pin data generation failed: ${errorMsg}`);
}
}
async execute(workflowEntity, user, hints, timings, scenarioHints, interceptionEnabled = false, vendorLlmRouting) {
const nodeResults = {};
for (const node of workflowEntity.nodes) {
if (node.disabled || !node.parameters)
continue;
fillSetupPendingResourceLocators(node.parameters);
}
for (const node of workflowEntity.nodes) {
if (node.disabled || node.type !== 'n8n-nodes-base.wait')
continue;
const resume = node.parameters?.resume;
if (resume === 'webhook' || resume === 'form')
continue;
node.parameters = { ...node.parameters, resume: 'timeInterval', amount: 0, unit: 'seconds' };
}
const workflow = this.buildWorkflow(workflowEntity);
const hintedStart = hints.startNodeName
? this.asTriggerNode(workflow.nodes[hints.startNodeName])
: undefined;
const startNode = hintedStart ?? this.findStartNode(workflow);
if (!startNode) {
return this.errorResult((0, node_crypto_1.randomUUID)(), 'No trigger or start node found in the workflow');
}
const mockHandler = (0, mock_handler_1.createLlmMockHandler)({
scenarioHints,
globalContext: hints.globalContext,
nodeHints: hints.nodeHints,
pinnedOutputs: summarizePinnedOutputs(hints.bypassPinData),
});
const binaryRequirement = (0, workflow_analysis_1.detectBinaryDependencies)(workflowEntity);
const triggerPinData = this.buildTriggerPinData(startNode, hints.triggerContent, binaryRequirement);
const pinData = { ...triggerPinData, ...hints.bypassPinData };
const pinDataNodeNames = Object.keys(pinData);
this.patchParameterIssuesForEval(workflow, pinDataNodeNames);
this.checkNodeConfig(workflow, nodeResults, pinDataNodeNames);
const executionData = this.buildExecutionData(startNode, pinData);
if (Object.keys(triggerPinData).length > 0) {
this.markNodeAsPinned(startNode.name, nodeResults);
}
for (const nodeName of Object.keys(hints.bypassPinData)) {
this.markNodeAsPinned(nodeName, nodeResults);
}
let wireServer;
let restoreNoProxy;
let credentialsHelper;
let dbExecutionId;
try {
let serverUrl;
if (interceptionEnabled) {
const llmCompletionMockHandler = (0, llm_completion_mock_1.createLlmCompletionMockHandler)({
scenarioHints,
globalContext: hints.globalContext,
nodeHints: hints.nodeHints,
});
const timedAiTurnHandler = async (request, node) => await timings.time('ai-turn', node.type, async () => await llmCompletionMockHandler(request, node));
wireServer = new llm_wire_server_1.LlmWireServer({
mockHandler: timedAiTurnHandler,
rootToSubNode: vendorLlmRouting?.rootToSubNode,
onIntercept: (turn) => this.recordWireServerTurn(turn, nodeResults),
logger: this.logger,
});
serverUrl = await wireServer.start();
restoreNoProxy = (0, proxy_1.ensureHostsBypassProxy)(['127.0.0.1', 'localhost']);
this.logger.debug(`[EvalMock] Wire server listening at ${serverUrl}`);
}
const runData = {
executionMode: 'evaluation',
workflowData: { ...workflowEntity, staticData: undefined },
userId: user.id,
executionData,
pinData,
configureAdditionalData: (additionalData) => {
credentialsHelper = new eval_mocked_credentials_helper_1.EvalMockedCredentialsHelper(additionalData.credentialsHelper, serverUrl, this.logger, vendorLlmRouting?.subNodeToRoot);
additionalData.credentialsHelper = credentialsHelper;
additionalData.evalLlmMockHandler = this.createInterceptingHandler(mockHandler, nodeResults, timings);
},
};
dbExecutionId = await this.workflowRunner.run(runData);
const runResult = await this.activeExecutions.getPostExecutePromise(dbExecutionId);
if (!runResult) {
return this.buildPartialFailureResult(dbExecutionId, new Error('Execution finished with no run data'), nodeResults, hints, credentialsHelper);
}
return await this.buildResult(dbExecutionId, runResult, nodeResults, hints, credentialsHelper);
}
catch (error) {
return this.buildPartialFailureResult(dbExecutionId ?? (0, node_crypto_1.randomUUID)(), error, nodeResults, hints, credentialsHelper);
}
finally {
if (restoreNoProxy)
restoreNoProxy();
if (wireServer) {
try {
await wireServer.stop();
}
catch (error) {
this.logger.warn('[EvalMock] Wire server teardown failed', {
error: error instanceof Error ? error.message : String(error),
});
}
}
await this.blankPersistedStaticData(workflowEntity.id);
timings.summary(this.logger);
}
}
async blankPersistedStaticData(workflowId) {
try {
await this.workflowStaticDataService.saveStaticDataById(workflowId, {});
}
catch (error) {
this.logger.warn('[EvalMock] Failed to blank workflow staticData after run', {
workflowId,
error: error instanceof Error ? error.message : String(error),
});
}
}
buildWorkflow(workflowEntity) {
return new n8n_workflow_1.Workflow({
id: workflowEntity.id,
name: workflowEntity.name,
nodes: workflowEntity.nodes,
connections: workflowEntity.connections,
active: false,
nodeTypes: this.nodeTypes,
staticData: workflowEntity.staticData,
settings: workflowEntity.settings ?? {},
});
}
findStartNode(workflow) {
return workflow.getStartNode() ?? this.findWebhookNode(workflow);
}
asTriggerNode(node) {
if (!node || node.disabled)
return undefined;
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
if (!nodeType)
return undefined;
const isTriggerCapable = 'trigger' in nodeType || 'poll' in nodeType || 'webhook' in nodeType;
return isTriggerCapable ? node : undefined;
}
findWebhookNode(workflow) {
return Object.values(workflow.nodes).find((node) => {
if (node.disabled)
return false;
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
return nodeType !== undefined && 'webhook' in nodeType;
});
}
checkNodeConfig(workflow, nodeResults, pinDataNodeNames) {
for (const node of Object.values(workflow.nodes)) {
if (node.disabled)
continue;
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
if (!nodeType)
continue;
const issues = n8n_workflow_1.NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node, nodeType.description, pinDataNodeNames);
const parameterIssues = { ...(issues?.parameters ?? {}) };
for (const [paramName, value] of Object.entries(node.parameters ?? {})) {
if (value === '__evalMockValue') {
parameterIssues[paramName] ??= [`Parameter "${paramName}" is required.`];
}
}
if (Object.keys(parameterIssues).length > 0) {
const entry = (nodeResults[node.name] ??= {
outputs: {},
outputCount: 0,
iterationCount: 0,
interceptedRequests: [],
executionMode: 'real',
});
entry.configIssues = parameterIssues;
}
}
}
patchParameterIssuesForEval(workflow, pinDataNodeNames) {
for (const node of Object.values(workflow.nodes)) {
if (node.disabled)
continue;
if (pinDataNodeNames.includes(node.name))
continue;
if (node.parameters) {
node.parameters = scrubPlaceholderValues(node.parameters);
for (const change of patchSetupPendingResourceMappers(node.parameters)) {
this.logger.info(`[EvalMock] resourceMapper patch on "${node.name}": ${change}`);
}
}
const nodeType = this.nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
if (!nodeType)
continue;
const issues = n8n_workflow_1.NodeHelpers.getNodeParametersIssues(nodeType.description.properties, node, nodeType.description, pinDataNodeNames);
const paramIssues = issues?.parameters;
if (!paramIssues || Object.keys(paramIssues).length === 0)
continue;
const params = node.parameters ?? {};
for (const paramName of Object.keys(paramIssues)) {
params[paramName] = synthesizeMissingParamValue(params[paramName], paramName);
}
node.parameters = params;
}
}
buildTriggerPinData(startNode, triggerContent, binaryRequirement) {
if (Object.keys(triggerContent).length === 0 && !binaryRequirement)
return {};
const embedded = readEmbeddedBinaryMeta(triggerContent);
const item = { json: triggerContent };
const binary = {};
if (binaryRequirement) {
const embeddedMeta = embedded[binaryRequirement.propertyName];
const isGenericFallback = binaryRequirement.contentType === 'application/octet-stream' &&
binaryRequirement.filename === 'input.bin';
binary[binaryRequirement.propertyName] = synthesizeBinaryEntry((isGenericFallback && embeddedMeta?.mimeType) || binaryRequirement.contentType, (isGenericFallback && embeddedMeta?.fileName) || binaryRequirement.filename);
}
for (const [key, meta] of Object.entries(embedded)) {
if (binary[key])
continue;
binary[key] = synthesizeBinaryEntry(meta.mimeType ?? 'application/octet-stream', meta.fileName ?? 'input.bin');
}
if (Object.keys(binary).length > 0)
item.binary = binary;
return { [startNode.name]: [item] };
}
buildExecutionData(startNode, pinData) {
return (0, n8n_workflow_1.createRunExecutionData)({
startData: {},
resultData: { pinData, runData: {} },
executionData: {
contextData: {},
metadata: {},
nodeExecutionStack: [
{
node: startNode,
data: { main: [[{ json: {} }]] },
source: null,
},
],
waitingExecution: {},
waitingExecutionSource: {},
},
});
}
recordWireServerTurn(turn, nodeResults) {
const entry = (nodeResults[turn.rootName] ??= {
outputs: {},
outputCount: 0,
iterationCount: 0,
interceptedRequests: [],
executionMode: 'mocked',
});
if (entry.executionMode !== 'pinned') {
entry.executionMode = 'mocked';
}
entry.interceptedRequests.push({
url: turn.url,
method: turn.method,
nodeType: turn.nodeType,
requestBody: turn.requestBody,
mockResponse: turn.mockResponse,
});
this.logger.debug(`[EvalMock] Wire server intercepted ${turn.method} ${turn.url} attributed to root "${turn.rootName}"`);
}
createInterceptingHandler(mockHandler, nodeResults, timings) {
return async (requestOptions, node) => {
const entry = (nodeResults[node.name] ??= {
outputs: {},
outputCount: 0,
iterationCount: 0,
interceptedRequests: [],
executionMode: 'mocked',
});
entry.executionMode = 'mocked';
let response = await timings.time('http-mock', node.name, async () => await mockHandler(requestOptions, node));
if (response && response.statusCode < 400 && (0, openai_responses_envelope_1.isOpenAiResponsesUrl)(requestOptions.url)) {
const normalized = (0, openai_responses_envelope_1.normalizeOpenAiResponsesMockResponse)(response, (0, openai_responses_envelope_1.extractResponsesRequestModel)(requestOptions.body));
if (normalized !== response) {
this.logger.debug(`[EvalMock] Applied Responses-envelope normalization for "${node.name}"`);
}
response = normalized;
}
entry.interceptedRequests.push({
url: requestOptions.url ?? '(no URL)',
method: requestOptions.method ?? 'GET',
nodeType: node.type,
requestBody: requestOptions.body,
mockResponse: response?.body,
});
this.logger.debug(`[EvalMock] Intercepted ${requestOptions.method ?? 'GET'} ${requestOptions.url} from "${node.name}" (${node.type})`);
return response;
};
}
markNodeAsPinned(nodeName, nodeResults) {
const existing = nodeResults[nodeName];
nodeResults[nodeName] = {
outputs: {},
outputCount: 0,
iterationCount: 0,
interceptedRequests: [],
executionMode: 'pinned',
...(existing?.configIssues ? { configIssues: existing.configIssues } : {}),
};
}
buildPartialFailureResult(executionId, error, nodeResults, hints, credentialsHelper) {
const message = error instanceof Error ? error.message : String(error);
this.logger.error(`[EvalMock] Workflow execution failed: ${message}`);
return {
executionId,
success: false,
nodeResults,
errors: [`Execution failed: ${message}`],
hints,
mockedCredentials: credentialsHelper?.mockedCredentials ?? [],
rewrittenCredentials: credentialsHelper?.rewrittenCredentials ?? [],
};
}
async hydrateBinaryData(items) {
return await Promise.all(items.map(async (item) => {
if (!item.binary)
return item;
const hydratedBinary = {};
for (const [key, entry] of Object.entries(item.binary)) {
if (entry.id) {
try {
const buffer = await this.binaryDataService.getAsBuffer(entry);
hydratedBinary[key] = { ...entry, data: buffer.toString('base64') };
continue;
}
catch (error) {
this.logger.warn(`[EvalMock] Failed to hydrate binary "${key}" (${entry.id}): ${error instanceof Error ? error.message : String(error)}`);
}
}
hydratedBinary[key] = entry;
}
return { ...item, binary: hydratedBinary };
}));
}
async buildResult(executionId, result, nodeResults, hints, credentialsHelper) {
const errors = [];
const runData = result.data?.resultData?.runData ?? {};
for (const [nodeName, nodeRuns] of Object.entries(runData)) {
const entry = (nodeResults[nodeName] ??= {
outputs: {},
outputCount: 0,
iterationCount: 0,
interceptedRequests: [],
executionMode: 'real',
});
entry.iterationCount = nodeRuns.length;
const firstErrorIdx = nodeRuns.findIndex((run) => run?.error !== undefined);
if (firstErrorIdx !== -1) {
entry.firstErrorIteration = firstErrorIdx;
}
const lastRun = nodeRuns[nodeRuns.length - 1];
if (lastRun?.startTime) {
entry.startTime = lastRun.startTime;
}
if (lastRun?.data) {
let totalCount = 0;
let truncated = false;
const outputs = {};
for (const [connectionType, branches] of Object.entries(lastRun.data)) {
if (!Array.isArray(branches))
continue;
outputs[connectionType] = await Promise.all(branches.map(async (branch) => {
if (!Array.isArray(branch))
return [];
totalCount += branch.length;
let kept = branch;
if (branch.length > MAX_OUTPUT_ITEMS_PER_BRANCH) {
truncated = true;
kept = branch.slice(0, MAX_OUTPUT_ITEMS_PER_BRANCH);
}
return await this.hydrateBinaryData(kept);
}));
}
entry.outputs = outputs;
entry.outputCount = totalCount;
if (truncated)
entry.truncated = true;
}
if (lastRun?.error) {
errors.push(`Node "${nodeName}": ${lastRun.error.message}`);
}
}
const executionError = result.data?.resultData?.error;
if (executionError) {
errors.push(`Workflow error: ${executionError.message}`);
}
const configIssueErrors = collectConfigIssueErrors(nodeResults);
const allErrors = [...errors, ...configIssueErrors];
return {
executionId,
success: allErrors.length === 0,
nodeResults,
errors: allErrors,
hints,
mockedCredentials: credentialsHelper?.mockedCredentials ?? [],
rewrittenCredentials: credentialsHelper?.rewrittenCredentials ?? [],
};
}
errorResult(executionId, message) {
return {
executionId,
success: false,
nodeResults: {},
errors: [message],
hints: {
globalContext: '',
triggerContent: {},
nodeHints: {},
warnings: [],
bypassPinData: {},
},
mockedCredentials: [],
rewrittenCredentials: [],
};
}
};
exports.EvalExecutionService = EvalExecutionService;
exports.EvalExecutionService = EvalExecutionService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [workflow_finder_service_1.WorkflowFinderService, node_types_1.NodeTypes, backend_common_1.Logger, posthog_1.PostHogClient, workflow_runner_1.WorkflowRunner, active_executions_1.ActiveExecutions, config_1.ExecutionsConfig, n8n_core_1.BinaryDataService, workflow_static_data_service_1.WorkflowStaticDataService, load_nodes_and_credentials_1.LoadNodesAndCredentials])
], EvalExecutionService);
function synthesizeBinaryEntry(contentType, filename) {
const bytes = (0, n8n_core_1.synthesizeBinaryFixture)(contentType, filename);
const extension = filename.includes('.') ? filename.slice(filename.lastIndexOf('.') + 1) : 'bin';
return {
mimeType: contentType,
fileName: filename,
fileExtension: extension,
fileType: (0, n8n_workflow_1.fileTypeFromMimeType)(contentType.toLowerCase()),
data: bytes.toString('base64'),
};
}
function nonEmptyString(value) {
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
}
function readEmbeddedBinaryMeta(triggerContent) {
const candidate = triggerContent.binary;
if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate)) {
return {};
}
const entries = Object.entries(candidate);
if (entries.length === 0)
return {};
const embedded = {};
for (const [key, value] of entries) {
if (value === null || typeof value !== 'object' || Array.isArray(value))
return {};
const v = value;
const mimeType = nonEmptyString(v.mimeType) ?? nonEmptyString(v.mimetype) ?? nonEmptyString(v.contentType);
const strictFileName = nonEmptyString(v.fileName) ?? nonEmptyString(v.filename);
if (!mimeType && !strictFileName)
return {};
embedded[key] = {
mimeType,
fileName: strictFileName ?? nonEmptyString(v.name),
};
}
return embedded;
}
const PLACEHOLDER_PREFIX = '<__PLACEHOLDER_VALUE__';
const PLACEHOLDER_SUFFIX = '__>';
function synthesizePlaceholderValue(hint) {
const h = hint.toLowerCase();
if (h.includes('email'))
return 'eval-mock@example.com';
if (h.includes('url') || h.includes('endpoint') || h.includes('webhook')) {
return 'https://eval-mock.invalid/';
}
if (h.includes('phone'))
return '+10000000000';
if (h.includes('slack channel') || h.includes('channel'))
return 'C00000000EVAL';
if (h.includes('chat') && h.includes('id'))
return '100000000';
if (h.includes('telegram'))
return '100000000';
const selectedResourceValue = synthesizeSelectedResourcePlaceholderValue(h);
if (selectedResourceValue)
return selectedResourceValue;
return '__evalMockValue';
}
function synthesizeSelectedResourcePlaceholderValue(hint) {
if (!hint.includes('select'))
return undefined;
if (hint.includes('spreadsheet') || hint.includes('document'))
return 'eval-spreadsheet-id';
if (hint.includes('sheet'))
return '0';
if (hint.includes('calendar'))
return 'eval-calendar-id';
if (hint.includes('folder'))
return 'eval-folder-id';
if (hint.includes('file'))
return 'eval-file-id';
if (hint.includes('drive'))
return 'eval-drive-id';
return undefined;
}
function scrubPlaceholderValues(value) {
if (typeof value === 'string') {
if (!value.startsWith(PLACEHOLDER_PREFIX) || !value.endsWith(PLACEHOLDER_SUFFIX)) {
return value;
}
const hint = value.slice(PLACEHOLDER_PREFIX.length, -PLACEHOLDER_SUFFIX.length);
return synthesizePlaceholderValue(hint);
}
if (Array.isArray(value))
return value.map(scrubPlaceholderValues);
if (value !== null && typeof value === 'object') {
const out = {};
for (const [key, child] of Object.entries(value)) {
out[key] = scrubPlaceholderValues(child);
}
return out;
}
return value;
}
function synthesizeResourceLocatorValue(paramName) {
const h = paramName.toLowerCase();
if (h.includes('spreadsheet') || h.includes('document'))
return 'eval-spreadsheet-id';
if (h.includes('sheet'))
return '0';
if (h.includes('calendar'))
return 'eval-calendar-id';
if (h.includes('folder'))
return 'eval-folder-id';
if (h.includes('file'))
return 'eval-file-id';
if (h.includes('drive'))
return 'eval-drive-id';
if (h.includes('channel'))
return 'C00000000EVAL';
return '__evalMockResource';
}
function fillSetupPendingResourceLocators(parameters) {
for (const [key, raw] of Object.entries(parameters)) {
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
continue;
const rl = raw;
if (!('__rl' in rl))
continue;
const value = rl.value;
const isEmpty = value === undefined || value === null || value === '';
if (!isEmpty)
continue;
parameters[key] = {
...rl,
value: synthesizeResourceLocatorValue(key),
};
}
}
function synthesizeMissingParamValue(current, paramName = '') {
if (current !== null &&
typeof current === 'object' &&
!Array.isArray(current) &&
'__rl' in current) {
const rl = current;
const mode = typeof rl.mode === 'string' && rl.mode.length > 0 ? rl.mode : 'id';
const rawValue = rl.value;
const hasValue = (typeof rawValue === 'string' && rawValue.length > 0) ||
(typeof rawValue === 'number' && Number.isFinite(rawValue));
return {
...rl,
mode,
value: hasValue ? rawValue : synthesizeResourceLocatorValue(paramName),
};
}
if (typeof current === 'string' && current.length === 0)
return '__evalMockValue';
if (current === null || current === undefined)
return '__evalMockValue';
return current;
}
function patchSetupPendingResourceMappers(parameters) {
const changes = [];
for (const [key, raw] of Object.entries(parameters)) {
if (raw === null || typeof raw !== 'object' || Array.isArray(raw))
continue;
const mapper = raw;
if (!('mappingMode' in mapper) || mapper.mappingMode !== 'defineBelow')
continue;
const value = mapper.value;
const mappingKeys = value !== null && typeof value === 'object' && !Array.isArray(value)
? Object.keys(value)
: [];
if (mappingKeys.length === 0) {
parameters[key] = {
...mapper,
mappingMode: 'autoMapInputData',
value: null,
schema: Array.isArray(mapper.schema) ? mapper.schema : [],
};
changes.push(`${key}: defineBelow without mappings → autoMapInputData`);
continue;
}
const schema = mapper.schema;
if (Array.isArray(schema) && schema.length > 0)
continue;
changes.push(`${key}: synthesized schema from ${String(mappingKeys.length)} mapping keys`);
parameters[key] = {
...mapper,
schema: mappingKeys.map((id) => ({
id,
displayName: id,
required: false,
defaultMatch: false,
display: true,
type: 'string',
canBeUsedToMatch: true,
})),
};
}
return changes;
}
function summarizePinnedOutputs(pinData) {
if (!pinData)
return undefined;
const lines = [];
for (const [nodeName, items] of Object.entries(pinData)) {
const meaningful = items.filter((item) => Object.keys(item.json ?? {}).length > 0 || item.binary !== undefined);
if (meaningful.length === 0)
continue;
let json = '';
try {
json = JSON.stringify(meaningful);
}
catch {
continue;
}
if (json.length > 1500)
json = json.slice(0, 1500) + '…';
lines.push(`- ${nodeName}: ${json}`);
}
return lines.length > 0 ? lines.join('\n') : undefined;
}
function collectConfigIssueErrors(nodeResults) {
const errors = [];
for (const [nodeName, result] of Object.entries(nodeResults)) {
const issues = result.configIssues;
if (!issues || Object.keys(issues).length === 0)
continue;
const issueMessages = Object.values(issues).flat();
if (issueMessages.length === 0)
continue;
errors.push(`Node "${nodeName}" has missing or invalid configuration: ${issueMessages.join('; ')}`);
}
return errors;
}
//# sourceMappingURL=execution.service.js.map