@n8n-plus/n8n-plus
Version:
n8n Workflow Automation Tool (plus edition)
2,244 lines • 108 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
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 __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.InstanceAiAdapterService = void 0;
exports.resolveDataTableByIdOrName = resolveDataTableByIdOrName;
exports.truncateResultData = truncateResultData;
exports.extractExecutionResult = extractExecutionResult;
exports.formatExecutionError = formatExecutionError;
exports.truncateNodeOutput = truncateNodeOutput;
exports.extractNodeOutput = extractNodeOutput;
exports.extractExecutionDebugInfo = extractExecutionDebugInfo;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const node_path_1 = __importDefault(require("node:path"));
const instance_ai_1 = require("@n8n/instance-ai");
const config_1 = require("@n8n/config");
const constants_1 = require("@n8n/constants");
const instance_ai_settings_service_1 = require("./instance-ai-settings.service");
const node_definition_resolver_1 = require("./node-definition-resolver");
const web_research_1 = require("./web-research");
const db_1 = require("@n8n/db");
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const permissions_1 = require("@n8n/permissions");
const typeorm_1 = require("@n8n/typeorm");
const n8n_workflow_1 = require("n8n-workflow");
const n8n_core_1 = require("n8n-core");
const active_executions_1 = require("../../active-executions");
const credentials_finder_service_1 = require("../../credentials/credentials-finder.service");
const credentials_service_1 = require("../../credentials/credentials.service");
const event_service_1 = require("../../events/event.service");
const execution_persistence_1 = require("../../executions/execution-persistence");
const license_1 = require("../../license");
const load_nodes_and_credentials_1 = require("../../load-nodes-and-credentials");
const node_types_1 = require("../../node-types");
const data_table_repository_1 = require("../../modules/data-table/data-table.repository");
const data_table_service_1 = require("../../modules/data-table/data-table.service");
const source_control_preferences_service_ee_1 = require("../../modules/source-control.ee/source-control-preferences.service.ee");
const check_access_1 = require("../../permissions.ee/check-access");
const dynamic_node_parameters_service_1 = require("../../services/dynamic-node-parameters.service");
const folder_service_1 = require("../../services/folder.service");
const project_service_ee_1 = require("../../services/project.service.ee");
const role_service_1 = require("../../services/role.service");
const ssrf_protection_service_1 = require("../../services/ssrf/ssrf-protection.service");
const tag_service_1 = require("../../services/tag.service");
const workflow_finder_service_1 = require("../../workflows/workflow-finder.service");
const workflow_history_service_1 = require("../../workflows/workflow-history/workflow-history.service");
const workflow_service_1 = require("../../workflows/workflow.service");
const utils_1 = require("../../workflows/utils");
const workflow_service_ee_1 = require("../../workflows/workflow.service.ee");
const telemetry_1 = require("../../telemetry");
const workflow_runner_1 = require("../../workflow-runner");
const workflow_execute_additional_data_1 = require("../../workflow-execute-additional-data");
function resolveDisplayedDefaults(nodeProperties, parameters, nodeType, typeVersion, desc) {
const stubNode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: parameters,
position: [0, 0],
};
const resolved = n8n_workflow_1.NodeHelpers.getNodeParameters(nodeProperties, parameters, true, false, stubNode, desc);
return resolved ?? parameters;
}
let InstanceAiAdapterService = class InstanceAiAdapterService {
async getNodesFromCache() {
if (this.nodesCache && Date.now() < this.nodesCache.expiresAt) {
return await this.nodesCache.promise;
}
const filePath = node_path_1.default.join(this.instanceSettings.staticCacheDir, 'types/nodes.json');
const promise = (0, promises_1.readFile)(filePath, 'utf-8').then((json) => (0, n8n_workflow_1.jsonParse)(json));
this.nodesCache = { promise, expiresAt: Date.now() + this.NODES_CACHE_TTL_MS };
promise.catch(() => {
this.nodesCache = null;
});
return await promise;
}
constructor(logger, globalConfig, workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, projectRepository, executionRepository, credentialsService, credentialsFinderService, activeExecutions, workflowRunner, loadNodesAndCredentials, nodeTypes, instanceSettings, dataTableService, dataTableRepository, dynamicNodeParametersService, folderService, projectService, tagService, sourceControlPreferencesService, settingsService, workflowHistoryService, enterpriseWorkflowService, license, executionPersistence, eventService, roleService, telemetry, aiBuilderTemporaryWorkflowRepository, ssrfProtectionService) {
this.workflowService = workflowService;
this.workflowFinderService = workflowFinderService;
this.workflowRepository = workflowRepository;
this.sharedWorkflowRepository = sharedWorkflowRepository;
this.projectRepository = projectRepository;
this.executionRepository = executionRepository;
this.credentialsService = credentialsService;
this.credentialsFinderService = credentialsFinderService;
this.activeExecutions = activeExecutions;
this.workflowRunner = workflowRunner;
this.loadNodesAndCredentials = loadNodesAndCredentials;
this.nodeTypes = nodeTypes;
this.instanceSettings = instanceSettings;
this.dataTableService = dataTableService;
this.dataTableRepository = dataTableRepository;
this.dynamicNodeParametersService = dynamicNodeParametersService;
this.folderService = folderService;
this.projectService = projectService;
this.tagService = tagService;
this.sourceControlPreferencesService = sourceControlPreferencesService;
this.settingsService = settingsService;
this.workflowHistoryService = workflowHistoryService;
this.enterpriseWorkflowService = enterpriseWorkflowService;
this.license = license;
this.executionPersistence = executionPersistence;
this.eventService = eventService;
this.roleService = roleService;
this.telemetry = telemetry;
this.aiBuilderTemporaryWorkflowRepository = aiBuilderTemporaryWorkflowRepository;
this.ssrfProtectionService = ssrfProtectionService;
this.nodesCache = null;
this.NODES_CACHE_TTL_MS = 5 * 60 * 1000;
this.webResearchCache = new web_research_1.LRUCache({
maxEntries: 100,
ttlMs: 15 * 60 * 1000,
});
this.searchCache = new web_research_1.LRUCache({
maxEntries: 100,
ttlMs: 15 * 60 * 1000,
});
this.logger = logger.scoped('instance-ai');
this.allowSendingParameterValues = globalConfig.ai.allowSendingParameterValues;
}
createContext(user, options) {
const { searchProxyConfig, pushRef, threadId } = options ?? {};
return {
userId: user.id,
workflowService: this.createWorkflowAdapter(user, threadId),
executionService: this.createExecutionAdapter(user, pushRef, threadId),
credentialService: this.createCredentialAdapter(user),
nodeService: this.createNodeAdapter(user),
dataTableService: this.createDataTableAdapter(user),
webResearchService: this.createWebResearchAdapter(user, searchProxyConfig),
workspaceService: this.createWorkspaceAdapter(user),
templatesService: this.getTemplatesService(),
licenseHints: this.buildLicenseHints(),
logger: this.logger,
nodeTypesProvider: this.nodeTypes,
allowSendingParameterValues: this.allowSendingParameterValues,
};
}
getTemplatesService() {
if (!this.templatesService) {
this.templatesService = new instance_ai_1.BuilderTemplatesService({
...(0, instance_ai_1.builderTemplatesOptionsFromEnv)({ logger: this.logger }),
cacheDir: node_path_1.default.join(this.instanceSettings.n8nFolder, 'n8n-sdk-templates'),
logger: this.logger,
});
}
return this.templatesService;
}
buildLicenseHints() {
const hints = [];
if (!this.license.isLicensed('feat:namedVersions')) {
hints.push('**Named workflow versions** — naming and describing workflow versions (update-workflow-version) is available on the Pro plan and above.');
}
if (!this.license.isLicensed('feat:folders')) {
hints.push('**Folders** — organizing workflows into folders (list-folders, create-folder, delete-folder, move-workflow-to-folder) is available on registered Community Edition or paid plans.');
}
return hints;
}
assertInstanceNotReadOnly(resourceType) {
if (this.sourceControlPreferencesService.getPreferences().branchReadOnly) {
throw new Error(`Cannot modify ${resourceType} on a protected instance. This instance is in read-only mode.`);
}
}
createProjectScopeHelpers(user) {
const { projectRepository } = this;
let personalProjectIdPromise = null;
const getPersonalProjectId = async () => {
personalProjectIdPromise ??= projectRepository
.getPersonalProjectForUserOrFail(user.id)
.then((p) => p.id);
return await personalProjectIdPromise;
};
const assertProjectScope = async (scopes, projectId) => {
const allowed = await (0, check_access_1.userHasScopes)(user, scopes, false, { projectId });
if (!allowed) {
throw new Error('User does not have the required permissions in this project');
}
};
const resolveProjectId = async (scopes, providedProjectId) => {
const projectId = providedProjectId ?? (await getPersonalProjectId());
await assertProjectScope(scopes, projectId);
return projectId;
};
return { getPersonalProjectId, assertProjectScope, resolveProjectId };
}
createWorkflowAdapter(user, threadId) {
const { workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, aiBuilderTemporaryWorkflowRepository, workflowHistoryService, enterpriseWorkflowService, executionRepository, license, allowSendingParameterValues, telemetry, } = this;
const logger = this.logger;
const assertNotReadOnly = () => this.assertInstanceNotReadOnly('workflows');
const { resolveProjectId } = this.createProjectScopeHelpers(user);
const redactParameters = !allowSendingParameterValues;
return {
async list(options) {
const filter = {
...(options?.status === 'all' ? {} : { isArchived: options?.status === 'archived' }),
...(options?.query ? { query: options.query } : {}),
};
const { workflows } = await workflowService.getMany(user, {
take: options?.limit ?? 50,
filter,
});
return workflows
.filter((wf) => 'versionId' in wf)
.map((wf) => ({
id: wf.id,
name: wf.name,
versionId: wf.versionId,
activeVersionId: wf.activeVersionId ?? null,
isArchived: wf.isArchived,
createdAt: wf.createdAt.toISOString(),
updatedAt: wf.updatedAt.toISOString(),
}));
},
async get(workflowId) {
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
return toWorkflowDetail(workflow, { redactParameters });
},
async archive(workflowId) {
assertNotReadOnly();
const result = await workflowService.archive(user, workflowId, { skipArchived: true });
if (!result) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
},
async unarchive(workflowId) {
assertNotReadOnly();
const result = await workflowService.unarchive(user, workflowId);
if (!result) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
},
async clearAiTemporary(workflowId) {
assertNotReadOnly();
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:update',
]);
if (!workflow)
return;
if (!(await aiBuilderTemporaryWorkflowRepository.existsForWorkflow(workflowId)))
return;
await aiBuilderTemporaryWorkflowRepository.unmark(workflowId);
},
async archiveIfAiTemporary(workflowId) {
assertNotReadOnly();
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:update',
]);
if (!workflow)
return false;
if (!(await aiBuilderTemporaryWorkflowRepository.existsForWorkflow(workflowId))) {
return false;
}
if (workflow.isArchived) {
await aiBuilderTemporaryWorkflowRepository.unmark(workflowId);
return false;
}
await workflowService.archive(user, workflowId, { skipArchived: true });
await aiBuilderTemporaryWorkflowRepository.unmark(workflowId);
return true;
},
async publish(workflowId, options) {
const wf = await workflowService.activateWorkflow(user, workflowId, {
versionId: options?.versionId,
name: options?.name,
description: options?.description,
source: 'n8n-ai',
});
if (!wf.activeVersionId) {
throw new Error(`Workflow ${workflowId} was not activated — no active version set`);
}
if (threadId) {
telemetry.track('Builder published workflow', {
thread_id: threadId,
workflow_id: workflowId,
executed_by: 'ai',
});
}
return { activeVersionId: wf.activeVersionId };
},
async unpublish(workflowId) {
await workflowService.deactivateWorkflow(user, workflowId, {
source: 'n8n-ai',
});
},
async getAsWorkflowJSON(workflowId) {
const wf = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
if (!wf)
throw new Error(`Workflow ${workflowId} not found or not accessible`);
return toWorkflowJSON(wf, { redactParameters });
},
async getLatestRunData(workflowId) {
const accessible = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
if (!accessible)
return null;
const [latest] = await executionRepository.find({
select: ['id'],
where: { workflowId },
order: { startedAt: 'DESC' },
take: 1,
});
if (!latest)
return null;
const execution = await executionRepository.findSingleExecution(latest.id, {
includeData: true,
unflattenData: true,
});
return execution?.data?.resultData?.runData ?? null;
},
async createFromWorkflowJSON(json, options) {
assertNotReadOnly();
const projectId = await resolveProjectId(['workflow:create'], options?.projectId);
const settings = (json.settings ?? {});
if (settings.redactionPolicy !== undefined && settings.redactionPolicy !== 'none') {
const canUpdateRedaction = await (0, check_access_1.userHasScopes)(user, ['workflow:enableRedaction'], false, { projectId });
if (!canUpdateRedaction) {
delete settings.redactionPolicy;
}
}
const newWorkflow = workflowRepository.create({
name: json.name,
nodes: [],
connections: {},
settings,
active: false,
versionId: (0, node_crypto_1.randomUUID)(),
});
const saved = await workflowRepository.manager.transaction(async (transactionManager) => {
const workflow = await transactionManager.save(db_1.WorkflowEntity, newWorkflow);
await sharedWorkflowRepository.makeOwner([workflow.id], projectId, transactionManager);
if (options?.markAsAiTemporary) {
if (!threadId) {
throw new n8n_workflow_1.UnexpectedError('Cannot mark AI-builder temporary workflow without a thread ID');
}
await aiBuilderTemporaryWorkflowRepository.mark(workflow.id, threadId, transactionManager);
}
return workflow;
});
let updateData = workflowRepository.create({
name: json.name,
nodes: json.nodes,
connections: json.connections,
settings,
pinData: sdkPinDataToRuntime(json.pinData),
});
let updated;
try {
if (license.isSharingEnabled()) {
updateData = await enterpriseWorkflowService.preventTampering(updateData, saved.id, user);
}
updated = await workflowService.update(user, updateData, saved.id, {
source: 'n8n-ai',
});
}
catch (error) {
logger.warn('AI-builder workflow save failed', {
threadId,
workflowId: saved.id,
error: error instanceof Error ? error.message : String(error),
});
try {
const archived = await workflowService.archive(user, saved.id, { skipArchived: true });
if (archived && options?.markAsAiTemporary) {
await aiBuilderTemporaryWorkflowRepository.unmark(saved.id);
}
}
catch (cleanupError) {
logger.warn('Failed to clean up AI-builder workflow shell after create failure', {
threadId,
workflowId: saved.id,
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
});
}
throw error;
}
if (threadId) {
telemetry.track('Builder created workflow', {
thread_id: threadId,
workflow_id: updated.id,
});
}
return toWorkflowDetail(updated, { redactParameters });
},
async updateFromWorkflowJSON(workflowId, json, _options) {
assertNotReadOnly();
const settings = (json.settings ?? {});
if (settings.redactionPolicy !== undefined) {
const [existingWorkflow, ownerProject] = await Promise.all([
workflowRepository.findOne({ where: { id: workflowId } }),
sharedWorkflowRepository.getWorkflowOwningProject(workflowId),
]);
const currentPolicy = existingWorkflow?.settings?.redactionPolicy;
if (settings.redactionPolicy !== currentPolicy) {
const requiredScopes = (0, utils_1.getRequiredRedactionScopes)(currentPolicy, settings.redactionPolicy);
const canUpdateRedaction = ownerProject &&
(await (0, check_access_1.userHasScopes)(user, requiredScopes, false, { projectId: ownerProject.id }));
if (!canUpdateRedaction) {
delete settings.redactionPolicy;
}
}
}
let updateData = workflowRepository.create({
name: json.name,
nodes: json.nodes,
connections: json.connections,
settings,
pinData: sdkPinDataToRuntime(json.pinData),
});
let updated;
try {
if (license.isSharingEnabled()) {
updateData = await enterpriseWorkflowService.preventTampering(updateData, workflowId, user);
}
updated = await workflowService.update(user, updateData, workflowId, {
source: 'n8n-ai',
});
}
catch (error) {
logger.warn('AI-builder workflow save failed', {
threadId,
workflowId,
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
if (threadId) {
telemetry.track('Builder modified workflow', {
thread_id: threadId,
workflow_id: workflowId,
});
}
return toWorkflowDetail(updated, { redactParameters });
},
async listVersions(workflowId, options) {
const take = options?.limit ?? 20;
const skip = options?.skip ?? 0;
const versions = await workflowHistoryService.getList(user, workflowId, take, skip);
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
const activeVersionId = workflow?.activeVersionId ?? null;
const currentDraftVersionId = workflow?.versionId ?? null;
return versions.map((v) => ({
versionId: v.versionId,
name: v.name ?? null,
description: v.description ?? null,
authors: v.authors,
createdAt: v.createdAt.toISOString(),
autosaved: v.autosaved ?? false,
isActive: v.versionId === activeVersionId,
isCurrentDraft: v.versionId === currentDraftVersionId,
}));
},
async getVersion(workflowId, versionId) {
const version = await workflowHistoryService.getVersion(user, workflowId, versionId);
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:read',
]);
const activeVersionId = workflow?.activeVersionId ?? null;
const currentDraftVersionId = workflow?.versionId ?? null;
return {
versionId: version.versionId,
name: version.name ?? null,
description: version.description ?? null,
authors: version.authors,
createdAt: version.createdAt.toISOString(),
autosaved: version.autosaved ?? false,
isActive: version.versionId === activeVersionId,
isCurrentDraft: version.versionId === currentDraftVersionId,
nodes: (version.nodes ?? []).map((n) => ({
name: n.name,
type: n.type,
parameters: redactParameters ? undefined : n.parameters,
position: n.position,
})),
connections: version.connections,
};
},
async restoreVersion(workflowId, versionId) {
const version = await workflowHistoryService.getVersion(user, workflowId, versionId);
const updateData = workflowRepository.create({
nodes: version.nodes,
connections: version.connections,
});
await workflowService.update(user, updateData, workflowId, {
source: 'n8n-ai',
});
},
...(this.license.isLicensed('feat:namedVersions')
? {
async updateVersion(workflowId, versionId, data) {
await workflowHistoryService.updateVersionForUser(user, workflowId, versionId, data);
},
}
: {}),
};
}
createExecutionAdapter(user, pushRef, threadId) {
const { workflowFinderService, workflowRunner, activeExecutions, executionRepository, allowSendingParameterValues, license, roleService, telemetry, } = this;
const assertNotReadOnly = () => this.assertInstanceNotReadOnly('executions');
const DEFAULT_TIMEOUT_MS = 5 * constants_1.Time.minutes.toMilliseconds;
const MAX_TIMEOUT_MS = 10 * constants_1.Time.minutes.toMilliseconds;
const assertExecutionAccess = async (executionId, scopes = ['workflow:read']) => {
const execution = await executionRepository.findSingleExecution(executionId, {
includeData: false,
});
if (!execution) {
throw new Error(`Execution ${executionId} not found`);
}
const workflow = await workflowFinderService.findWorkflowForUser(execution.workflowId, user, scopes);
if (!workflow) {
throw new Error(`Execution ${executionId} not found`);
}
return execution;
};
return {
async list(options) {
const scope = 'workflow:read';
let sharingOptions;
if (license.isSharingEnabled()) {
const projectRoles = await roleService.rolesWithScope('project', [scope]);
const workflowRoles = await roleService.rolesWithScope('workflow', [scope]);
sharingOptions = { scopes: [scope], projectRoles, workflowRoles };
}
else {
sharingOptions = {
workflowRoles: ['workflow:owner'],
projectRoles: [permissions_1.PROJECT_OWNER_ROLE_SLUG],
};
}
const query = {
kind: 'range',
range: { limit: options?.limit ?? 20, lastId: undefined, firstId: undefined },
order: { startedAt: 'DESC' },
user,
sharingOptions,
...(options?.workflowId ? { workflowId: options.workflowId } : {}),
...(options?.status
? {
status: [options.status],
}
: {}),
};
const executions = await executionRepository.findManyByRangeQuery(query);
return executions.map((e) => ({
id: e.id,
workflowId: e.workflowId,
workflowName: e.workflowName ?? '',
status: e.status,
startedAt: String(e.startedAt ?? ''),
finishedAt: e.stoppedAt ? String(e.stoppedAt) : undefined,
mode: e.mode,
}));
},
async run(workflowId, inputData, options) {
assertNotReadOnly();
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:execute',
]);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
const nodes = workflow.nodes ?? [];
const triggerNode = options?.triggerNodeName
? (nodes.find((n) => n.name === options.triggerNodeName) ?? findTriggerNode(nodes))
: findTriggerNode(nodes);
const runData = {
executionMode: triggerNode
? getExecutionModeForTrigger(triggerNode)
: 'manual',
workflowData: {
...workflow,
settings: {
...workflow.settings,
saveManualExecutions: true,
saveDataSuccessExecution: 'all',
saveDataErrorExecution: 'all',
},
},
userId: user.id,
pushRef,
};
const workflowPinData = workflow.pinData ?? {};
const overridePinData = options?.pinData
? (sdkPinDataToRuntime(options.pinData) ?? {})
: {};
const basePinData = { ...workflowPinData, ...overridePinData };
if (inputData && triggerNode) {
const triggerPinData = getPinDataForTrigger(triggerNode, inputData);
const mergedPinData = { ...basePinData, ...triggerPinData };
runData.startNodes = [{ name: triggerNode.name, sourceData: null }];
runData.pinData = mergedPinData;
runData.executionData = (0, n8n_workflow_1.createRunExecutionData)({
startData: {},
resultData: { pinData: mergedPinData, runData: {} },
executionData: {
contextData: {},
metadata: {},
nodeExecutionStack: [
{
node: triggerNode,
data: { main: [triggerPinData[triggerNode.name]] },
source: null,
},
],
waitingExecution: {},
waitingExecutionSource: {},
},
});
}
else if (triggerNode) {
runData.triggerToStartFrom = { name: triggerNode.name };
if (Object.keys(basePinData).length > 0) {
runData.pinData = basePinData;
}
}
else if (Object.keys(basePinData).length > 0) {
runData.pinData = basePinData;
}
const trackBuilderExecutedWorkflow = (status) => {
if (!threadId)
return;
telemetry.track('Builder executed workflow', {
thread_id: threadId,
workflow_id: workflowId,
executed_by: 'ai',
pinned_node_count: Object.keys(runData.pinData ?? {}).length,
exec_type: runData.executionMode,
status,
});
};
const executionId = await workflowRunner.run(runData);
const timeoutMs = Math.min(options?.timeout ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
if (activeExecutions.has(executionId)) {
let timeoutId;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new Error(`Execution timed out after ${timeoutMs}ms`));
}, timeoutMs);
});
try {
await Promise.race([
activeExecutions.getPostExecutePromise(executionId),
timeoutPromise,
]);
clearTimeout(timeoutId);
}
catch (error) {
clearTimeout(timeoutId);
if (error instanceof Error && error.message.includes('timed out')) {
try {
activeExecutions.stopExecution(executionId, new n8n_workflow_1.TimeoutExecutionCancelledError(executionId));
}
catch {
}
const result = {
executionId,
status: 'error',
error: `Execution timed out after ${timeoutMs}ms and was cancelled`,
};
trackBuilderExecutedWorkflow(result.status);
return result;
}
throw error;
}
}
const result = await extractExecutionResult(executionRepository, executionId, allowSendingParameterValues);
trackBuilderExecutedWorkflow(result.status);
return result;
},
async getStatus(executionId) {
await assertExecutionAccess(executionId);
const isRunning = activeExecutions.has(executionId);
if (isRunning) {
return { executionId, status: 'running' };
}
return await extractExecutionResult(executionRepository, executionId, allowSendingParameterValues);
},
async getResult(executionId) {
await assertExecutionAccess(executionId);
if (activeExecutions.has(executionId)) {
await activeExecutions.getPostExecutePromise(executionId);
}
return await extractExecutionResult(executionRepository, executionId, allowSendingParameterValues);
},
async stop(executionId) {
assertNotReadOnly();
await assertExecutionAccess(executionId, ['workflow:execute']);
if (!activeExecutions.has(executionId)) {
return {
success: false,
message: `Execution ${executionId} is not currently running`,
};
}
try {
activeExecutions.stopExecution(executionId, new n8n_workflow_1.TimeoutExecutionCancelledError(executionId));
return { success: true, message: `Execution ${executionId} cancelled` };
}
catch {
return {
success: false,
message: `Failed to cancel execution ${executionId}`,
};
}
},
async getDebugInfo(executionId) {
await assertExecutionAccess(executionId);
return await extractExecutionDebugInfo(executionRepository, executionId, allowSendingParameterValues);
},
async getNodeOutput(executionId, nodeName, options) {
await assertExecutionAccess(executionId);
if (!allowSendingParameterValues) {
return {
nodeName,
items: [],
totalItems: 0,
returned: { from: 0, to: 0 },
};
}
return await extractNodeOutput(executionRepository, executionId, nodeName, options);
},
};
}
createCredentialAdapter(user) {
const { credentialsService, credentialsFinderService, loadNodesAndCredentials } = this;
return {
async list(options) {
if (options?.workflowId || options?.projectId) {
const scoped = options.workflowId
? await credentialsService.getCredentialsAUserCanUseInAWorkflow(user, {
workflowId: options.workflowId,
})
: await credentialsService.getCredentialsAUserCanUseInAWorkflow(user, {
projectId: options.projectId,
});
const filtered = options.type ? scoped.filter((c) => c.type === options.type) : scoped;
return filtered.map((c) => ({
id: c.id,
name: c.name,
type: c.type,
}));
}
const credentials = await credentialsService.getMany(user, {
listQueryOptions: {
filter: options?.type ? { type: options.type } : undefined,
},
includeGlobal: true,
});
return credentials.map((c) => ({
id: c.id,
name: c.name,
type: c.type,
}));
},
async get(credentialId) {
const credential = await credentialsService.getOne(user, credentialId, false);
return {
id: credential.id,
name: credential.name,
type: credential.type,
};
},
async delete(credentialId) {
await credentialsService.delete(user, credentialId);
},
async test(credentialId) {
const credential = await credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
throw new Error(`Credential ${credentialId} not found or not accessible`);
}
const credentialsToTest = {
id: credential.id,
name: credential.name,
type: credential.type,
data: await credentialsService.decrypt(credential, true),
};
const result = await credentialsService.test(user.id, credentialsToTest);
return {
success: result.status === 'OK',
message: result.message,
};
},
async isTestable(credentialType) {
try {
const credClass = loadNodesAndCredentials.getCredential(credentialType);
if (credClass.type.test)
return true;
const known = loadNodesAndCredentials.knownCredentials;
const supportedNodes = known[credentialType]?.supportedNodes ?? [];
for (const nodeName of supportedNodes) {
try {
const loaded = loadNodesAndCredentials.getNode(nodeName);
const nodeInstance = loaded.type;
const nodeDesc = 'nodeVersions' in nodeInstance
? Object.values(nodeInstance.nodeVersions).pop()?.description
: nodeInstance.description;
const hasTestedBy = nodeDesc?.credentials?.some((cred) => cred.name === credentialType && cred.testedBy);
if (hasTestedBy)
return true;
}
catch {
continue;
}
}
return false;
}
catch {
return false;
}
},
async getDocumentationUrl(credentialType) {
try {
const credClass = loadNodesAndCredentials.getCredential(credentialType);
const slug = credClass.type.documentationUrl;
if (!slug)
return null;
if (slug.startsWith('http'))
return slug;
return `https://docs.n8n.io/integrations/builtin/credentials/${slug}/`;
}
catch {
return null;
}
},
getCredentialFields(credentialType) {
try {
const allTypes = [credentialType];
const known = loadNodesAndCredentials.knownCredentials;
for (const typeName of allTypes) {
const extendsArr = known[typeName]?.extends ?? [];
allTypes.push(...extendsArr);
}
const fields = [];
const seen = new Set();
for (const typeName of allTypes) {
try {
const credClass = loadNodesAndCredentials.getCredential(typeName);
for (const prop of credClass.type.properties) {
if (prop.type === 'hidden' || seen.has(prop.name))
continue;
seen.add(prop.name);
fields.push({
name: prop.name,
displayName: prop.displayName,
type: prop.type,
required: prop.required ?? false,
description: prop.description,
});
}
}
catch {
}
}
return fields;
}
catch {
return [];
}
},
async searchCredentialTypes(query) {
const q = query.toLowerCase().trim();
if (!q)
return [];
const known = loadNodesAndCredentials.knownCredentials;
const results = [];
for (const typeName of Object.keys(known)) {
if (typeName.toLowerCase().includes(q)) {
try {
const credClass = loadNodesAndCredentials.getCredential(typeName);
results.push({
type: typeName,
displayName: credClass.type.displayName,
});
}
catch {
results.push({ type: typeName, displayName: typeName });
}
continue;
}
try {
const credClass = loadNodesAndCredentials.getCredential(typeName);
if (credClass.type.displayName.toLowerCase().includes(q)) {
results.push({
type: typeName,
displayName: credClass.type.displayName,
});
}
}
catch {
}
}
return results;
},
async getAccountContext(credentialId) {
const credential = await credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']);
if (!credential) {
return { accountIdentifier: undefined };
}
const mask = (id) => {
const atIdx = id.indexOf('@');
if (atIdx > 0) {
const local = id.slice(0, atIdx);
const domain = id.slice(atIdx);
const keep = Math.min(2, local.length);
return local.slice(0, keep) + '***' + domain;
}
if (id.length <= 3)
return id;
return id.slice(0, 2) + '***' + id.slice(-1);
};
try {
const redacted = await credentialsService.decrypt(credential, false);
if (typeof redacted.accountIdentifier === 'string' && redacted.accountIdentifier) {
return { accountIdentifier: mask(redacted.accountIdentifier) };
}
for (const key of ['email', 'user', 'username', 'account', 'serviceAccountEmail']) {
const value = redacted[key];
if (typeof value === 'string' && value) {
return { accountIdentifier: mask(value) };
}
}
const raw = await credentialsService.decrypt(credential, true);
const tokenData = raw.oauthTokenData;
if (tokenData && typeof tokenData === 'object') {
const { OauthService } = await Promise.resolve().then(() => __importStar(require('../../oauth/oauth.service')));
const identifier = OauthService.extractAccountIdentifier(tokenData);
if (identifier) {
return { accountIdentifier: mask(identifier) };
}
}
return { accountIdentifier: undefined };
}
catch {
return { accountIdentifier: undefined };
}
},
};
}
createDataTableAdapter(user) {
const { dataTableService, dataTableRepository } = this;
const assertNotReadOnly = () => this.assertInstanceNotReadOnly('data tables');
const { resolveProjectId } = this.createProjectScopeHelpers(user);
const logger = this.logger;
const resolveAccessibleTable = async (scopes, dataTableId, disambiguator) => {
const projectIdFilter = disambiguator?.projectId;
const result = await resolveDataTableByIdOrName(dataTableRepository, logger, dataTableId, {
projectIdFilter,
accessFilter: async (id) => await (0, check_access_1.userHasScopes)(user, scopes, false, { dataTableId: id }),
});
if (result.kind === 'miss') {
throw new Error(`Data table "${dataTableId}" not found`);
}
if (result.kind === 'ambiguous') {
const projectIds = result.candidates.map((c) => c.projectId).join(', ');
throw new Error(`Data table name "${dataTableId}" is ambiguous across accessible projects ` +
`(${projectIds}); pass the UUID or include a \`projectId\` to disambiguate.`);
}
if (projectIdFilter && result.table.projectId !== projectIdFilter) {
throw new Error(`Data table "${dataTableId}" does not belong to project "${projectIdFilter}".`);
}
return result.table;
};
const resolveProjectIdForTable = async (scopes, dataTableId, disambiguator) => {
const table = await resolveAccessibleTable(scopes, dataTableId, disambiguator);
return { projectId: table.projectId, resolvedId: table.id };
};
const resolveTableMeta = async (scopes, dataTableId, disambiguator) => {
const table = await resolveAccessibleTable(scopes, dataTableId, disambiguator);
return { projectId: table.projectId, tableName: table.name, resolvedId: table.id };
};
const referenceScopes = {
read: ['dataTable:read'],
readRow: ['dataTable:readRow'],
writeRow: ['dataTable:writeRow'],
update: ['dataTable:update'],
delete: ['dataTable:delete'],
};
return {
async list(options) {
const projectId = await resolveProjectId(['dataTable:listProject'], options?.projectId);
const { data: tables } = await dataTableService.getManyAndCount({
filter: { projectId },
});
return tables.map((t) => ({
id: t.id,
name: t.name,
projectId,
columns: t.columns.map((c) => ({ id: c.id, name: c.name, type: c.type })),
createdAt: t.createdAt.toISOString(),
updatedAt: t.updatedAt.toISOString(),
}));
},
async create(name, columns, options) {
assertNotReadOnly();
const projectId = await resolveProjectId(['dataTable:create'], options?.projectId);
const result = await dataTableService.createDataTable(projectId, { name, columns });
return {
id: result.id,
name: result.name,
projectId,
columns: result.columns.map((c) => ({ id: c.id, name: c.name, type: c.type })),
createdAt: result.createdAt.toISOString(),
updatedAt: result.updatedAt.toISOString(),
};
},
async delete(dataTableId, options) {
assertNotReadOnly();
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:delete'], dataTableId, options);
await dataTableService.deleteDataTable(resolvedId, projectId);
},
async resolveTableReference(dataTableId, options) {
const { projectId, tableName, resolvedId } = await resolveTableMeta(referenceScopes[options?.permission ?? 'read'], dataTableId, options);
return { id: resolvedId, name: tableName, projectId };
},
async getSchema(dataTableId, options) {
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:read'], dataTableId, options);
const columns = await dataTableService.getColumns(resolvedId, projectId);
return columns.map((c, index) => ({
id: c.id,
name: c.name,
type: c.type,
index,
}));
},
async addColumn(dataTableId, column, options) {
assertNotReadOnly();
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:update'], dataTableId, options);
const result = await dataTableService.addColumn(resolvedId, projectId, column);
return {
id: result.id,
name: result.name,
type: result.type,
index: result.index,
};
},
async deleteColumn(dataTableId, columnId, options) {
assertNotReadOnly();
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:update'], dataTableId, options);
await dataTableService.deleteColumn(resolvedId, projectId, columnId);
},
async renameColumn(dataTableId, columnId, newName, options) {
assertNotReadOnly();
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:update'], dataTableId, options);
await dataTableService.renameColumn(resolvedId, projectId, columnId, {
name: newName,
});
},
async queryRows(dataTableId, options) {
const { projectId, resolvedId } = await resolveProjectIdForTable(['dataTable:readRow'], dataTableId, options);
return await dataTableService.getManyRowsAndCount(resolvedId, projectId, {
take: options?.limit ?? 50,
skip: options?.offset ?? 0,
filter: options?.filter,
});
},
async insertRows(dataTableId, rows, options) {
assertNotReadOnly();
const { projectId, tableName, resolvedId } = await resolveTableMeta(['dataTable:writeRow'], dataTableId, options);
const result = await dataTableService.insertRows(resolvedId, projectId, rows, 'count');
return {
insertedCount: typeof result === 'number' ? result : rows.length,
dataTableId: resolvedId,
tableName,
projectId,
};
},
async updateRows(dataTableId, filter, data, options) {
assertNotReadOnly();
const { projectId, tableName, resolvedId } = await resolveTableMeta(['dataTable:writeRow'], dataTableId, options);
const result = await dataTableService.updateRows(resolvedId, projectId, { filter: filter, data: data }, true);
return {
updatedCount: Array.isArray(result) ? result.length : 0,
dataTableId: resolvedId,
tableName,
projectId,
};
},
async deleteRows(dataTableId, filter, options) {
assertNotReadOnly();
const { projectId, tableName, resolvedId } = await resolveTableMeta(['dataTable:writeRow'], dataTableId, options);
const result = await dataTableService.deleteRows(resolvedId, projectId, { filter: filter }, true);
return {
deletedCount: Array.isArray(result) ? result.length : 0,
dataTableId: resolvedId,
tableName,
projectId,
};
},
};
}
createWebResearchAdapter(user, searchProxyConfig) {
const fetchCache = this.webResearchCache;
const searchCacheRef = this.searchCache;
const settingsService = this.settingsService;
const ssrf = this.ssrfProtectionService;
const userId = user.id;
let resolvedSearchMethod;
let searchResolved = false;
const lazySearch = async (query, options) => {
if (!searchResolved) {
const config = await settingsService.resolveSearchConfig(user);
resolvedSearchMethod = this.buildSearchMethod(config.braveApiKey ?? '', config.searxngUrl ?? '', searchCacheRef, searchProxyConfig, userId);
searchResolved = true;
}
if (!resolvedSearchMethod)
return { query, results: [] };
return await resolvedSearchMethod(query, options);
};
return {
search: lazySearch,
async fetchUrl(url, options) {
const cacheKey = `${userId}:${url}`;
const cached = fetchCache.get(cacheKey);
if (cached) {
if (options?.authorizeUrl && cached.finalUrl) {
const origHost = new URL(url).hostname;
const finalHost = new URL(cached.finalUrl).hostname;
if (origHost !== finalHost) {
await options.authorizeUrl(cached.finalUrl);
}
}
return cached;
}
const page = await (0, web_research_1.fetchAndExtract)(url, {
maxContentLength: options?.maxContentLength,
maxResponseBytes: options?.maxResponseBytes,
timeoutMs: options?.timeoutMs,
authorizeUrl: options?.authorizeUrl,
ssrf,
});
const result = await (0, web_research_1.maybeSummarize)(page);
fetchCache.set(cacheKey, result);
return result;
},
};
}
buildSearchMethod(apiKey, searxngUrl, cache, searchProxyConfig, userId) {
const keyPrefix = userId ? `${userId}:` : '';
if (searchProxyConfig) {
return async (query, options) => {
const cacheKey = `${keyPrefix}${JSON.stringify([query, options ?? {}])}`;
const cached = cache.get(cacheKey);
if (cached)
return cached;
const result = await (0, web_research_1.braveSearch)('', query, {
...options,
proxyConfig: searchProxyConfig,
});
cache.set(cacheKey, result);
return result;
};
}
if (apiKey) {
return async (query, options) => {
const cacheKey = `${keyPrefix}${JSON.stringify([query, options ?? {}])}`;
const cached = cache.get(cacheKey);
if (cached)
return cached;
const result = await (0, web_research_1.braveSearch)(apiKey, query, options ?? {});
cache.set(cacheKey, result);
return result;
};
}
if (searxngUrl) {
return async (query, options) => {
const cacheKey = `${keyPrefix}${JSON.stringify([query, options ?? {}])}`;
const cached = cache.get(cacheKey);
if (cached)
return cached;
const result = await (0, web_research_1.searxngSearch)(searxngUrl, query, options ?? {});
cache.set(cacheKey, result);
return result;
};
}
return undefined;
}
getNodeDefinitionDirs() {
if (!this._nodeDefinitionDirs) {
this._nodeDefinitionDirs = (0, node_definition_resolver_1.resolveBuiltinNodeDefinitionDirs)();
}
return this._nodeDefinitionDirs;
}
createNodeAdapter(user) {
const { dynamicNodeParametersService, projectRepository, credentialsFinderService } = this;
const getNodes = async () => await this.getNodesFromCache();
const findNodeByVersion = (nodes, nodeType, version) => {
if (version !== undefined) {
const exact = nodes.find((n) => {
if (n.name !== nodeType)
return false;
if (Array.isArray(n.version))
return n.version.includes(version);
return n.version === version;
});
if (exact)
return exact;
}
return nodes.find((n) => n.name === nodeType);
};
const normalizeNodeVersion = (version) => {
if (!version)
return undefined;
const normalized = version.replace(/^v/i, '');
if (!/^\d+$/.test(normalized))
return Number(normalized);
if (normalized.length === 2) {
return Number(`${normalized[0]}.${normalized[1]}`);
}
return Number(normalized);
};
return {
async listAvailable(options) {
const nodes = await getNodes();
let filtered = nodes;
if (options?.query) {
const q = options.query.toLowerCase();
filtered = nodes.filter((n) => n.displayName.toLowerCase().includes(q) ||
n.name.toLowerCase().includes(q) ||
n.description?.toLowerCase().includes(q));
}
return filtered.map((n) => ({
name: n.name,
displayName: n.displayName,
description: n.description ?? '',
group: n.group ?? [],
version: Array.isArray(n.version) ? n.version[n.version.length - 1] : n.version,
}));
},
async listSearchable() {
const nodes = await getNodes();
const toStringArray = (value) => {
if (typeof value === 'string')
return value;
return value.map((v) => (typeof v === 'string' ? v : v.type));
};
return nodes.map((n) => {
const result = {
name: n.name,
displayName: n.displayName,
description: n.description ?? '',
version: n.version,
inputs: toStringArray(n.inputs),
outputs: toStringArray(n.outputs),
};
if (n.codex?.alias) {
result.codex = { alias: n.codex.alias };
}
if (n.builderHint) {
result.builderHint = {};
if (n.builderHint.searchHint) {
result.builderHint.message = n.builderHint.searchHint;
}
if (n.builderHint.inputs) {
const inputs = {};
for (const [key, config] of Object.entries(n.builderHint.inputs)) {
inputs[key] = {
required: config.required,
...(config.displayOptions
? { displayOptions: config.displayOptions }
: {}),
};
}
result.builderHint.inputs = inputs;
}
if (n.builderHint.outputs) {
const outputs = {};
for (const [key, config] of Object.entries(n.builderHint.outputs)) {
outputs[key] = {
...(config.required !== undefined ? { required: config.required } : {}),
...(config.displayOptions
? { displayOptions: config.displayOptions }
: {}),
};
}
result.builderHint.outputs = outputs;
}
}
return result;
});
},
async getDescription(nodeType, version) {
const nodes = await getNodes();
let desc = version !== undefined
? nodes.find((n) => {
if (n.name !== nodeType)
return false;
if (Array.isArray(n.version))
return n.version.includes(version);
return n.version === version;
})
: undefined;
if (!desc) {
desc = nodes.find((n) => n.name === nodeType);
}
if (!desc) {
throw new Error(`Node type ${nodeType} not found`);
}
return {
name: desc.name,
displayName: desc.displayName,
description: desc.description ?? '',
group: desc.group ?? [],
version: Array.isArray(desc.version)
? desc.version[desc.version.length - 1]
: desc.version,
properties: desc.properties.map((p) => ({
displayName: p.displayName,
name: p.name,
type: p.type,
required: p.required,
description: p.description,
default: p.default,
options: p.options
?.filter((o) => typeof o === 'object' && o !== null && 'name' in o && 'value' in o)
.map((o) => ({
name: String(o.name),
value: o.value,
})),
})),
credentials: desc.credentials?.map((c) => ({
name: c.name,
required: c.required,
...(c.displayOptions
? { displayOptions: c.displayOptions }
: {}),
})),
inputs: Array.isArray(desc.inputs) ? desc.inputs.map(String) : [],
outputs: Array.isArray(desc.outputs) ? desc.outputs.map(String) : [],
...(desc.webhooks ? { webhooks: desc.webhooks } : {}),
...(desc.polling ? { polling: desc.polling } : {}),
...(desc.triggerPanel !== undefined ? { triggerPanel: desc.triggerPanel } : {}),
};
},
getNodeTypeDefinition: async (nodeType, options) => {
const result = (0, node_definition_resolver_1.resolveNodeTypeDefinition)(nodeType, this.getNodeDefinitionDirs(), options);
if (result.error) {
return { content: '', error: result.error };
}
const nodes = await getNodes();
const nodeDesc = findNodeByVersion(nodes, nodeType, normalizeNodeVersion(result.version ?? options?.version));
const builderHint = nodeDesc?.builderHint?.searchHint;
return {
content: result.content,
version: result.version,
...(builderHint ? { builderHint } : {}),
};
},
listDiscriminators: async (nodeType) => {
return (0, node_definition_resolver_1.listNodeDiscriminators)(nodeType, this.getNodeDefinitionDirs());
},
getParameterIssues: async (nodeType, typeVersion, parameters) => {
const nodes = await getNodes();
const desc = findNodeByVersion(nodes, nodeType, typeVersion);
if (!desc)
return {};
const nodeProperties = desc.properties;
const paramsWithDefaults = resolveDisplayedDefaults(nodeProperties, parameters, nodeType, typeVersion, desc);
const minimalNode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: paramsWithDefaults,
position: [0, 0],
};
const issues = n8n_workflow_1.NodeHelpers.getNodeParametersIssues(nodeProperties, minimalNode, desc);
const allIssues = issues?.parameters ?? {};
const topLevelPropsByName = new Map();
for (const prop of nodeProperties) {
const existing = topLevelPropsByName.get(prop.name);
if (existing) {
existing.push(prop);
}
else {
topLevelPropsByName.set(prop.name, [prop]);
}
}
const filteredIssues = {};
for (const [key, value] of Object.entries(allIssues)) {
const props = topLevelPropsByName.get(key);
if (!props)
continue;
const isDisplayed = props.some((prop) => {
if (prop.type === 'hidden')
return false;
if (prop.displayOptions &&
!n8n_workflow_1.NodeHelpers.displayParameter(paramsWithDefaults, prop, minimalNode, desc)) {
return false;
}
return true;
});
if (!isDisplayed)
continue;
filteredIssues[key] = value;
}
return filteredIssues;
},
getNodeCredentialTypes: async (nodeType, typeVersion, parameters, _existingCredentials) => {
const nodes = await getNodes();
const desc = findNodeByVersion(nodes, nodeType, typeVersion);
if (!desc)
return [];
const credentialTypes = new Set();
const paramsWithDefaults = resolveDisplayedDefaults(desc.properties, parameters, nodeType, typeVersion, desc);
const minimalNode = {
id: '',
name: '',
type: nodeType,
typeVersion,
parameters: paramsWithDefaults,
position: [0, 0],
};
const nodeCredentials = desc.credentials ?? [];
for (const cred of nodeCredentials) {
if (cred.displayOptions) {
if (!n8n_workflow_1.NodeHelpers.displayParameter(paramsWithDefaults, cred, minimalNode, desc)) {
continue;
}
}
credentialTypes.add(cred.name);
}
const issues = n8n_workflow_1.NodeHelpers.getNodeParametersIssues(desc.properties, minimalNode, desc);
const credentialIssues = issues?.credentials ?? {};
for (const credType of Object.keys(credentialIssues)) {
credentialTypes.add(credType);
}
if (parameters.authentication === 'genericCredentialType' && parameters.genericAuthType) {
credentialTypes.add(parameters.genericAuthType);
}
else if (parameters.authentication === 'predefinedCredentialType' &&
parameters.nodeCredentialType) {
credentialTypes.add(parameters.nodeCredentialType);
}
return Array.from(credentialTypes);
},
getResolvedNodeInputs: async (workflowJson, nodeName) => {
const nodeJson = workflowJson.nodes.find((n) => n.name === nodeName);
if (!nodeJson)
return [];
const nodeType = this.nodeTypes.getByNameAndVersion(nodeJson.type, nodeJson.typeVersion ?? 1);
if (!nodeType)
return [];
const workflow = new n8n_workflow_1.Workflow({
nodes: workflowJson.nodes,
connections: workflowJson.connections,
active: false,
nodeTypes: this.nodeTypes,
});
const workflowNode = workflow.getNode(nodeName);
if (!workflowNode)
return [];
return n8n_workflow_1.NodeHelpers.getNodeInputs(workflow, workflowNode, nodeType.description);
},
exploreResources: async (params) => {
const credential = await credentialsFinderService.findCredentialForUser(params.credentialId, user, ['credential:read']);
if (!credential || credential.type !== params.credentialType) {
throw new Error(`Credential ${params.credentialId} not found or not accessible`);
}
const nodeTypeAndVersion = {
name: params.nodeType,
version: params.version,
};
const currentNodeParameters = (params.currentNodeParameters ?? {});
const credentials = {
[credential.type]: { id: credential.id, name: credential.name },
};
if (!currentNodeParameters.authentication) {
const nodes = await getNodes();
const nodeDesc = nodes.find((n) => n.name === params.nodeType);
if (nodeDesc) {
const authProp = nodeDesc.properties.find((p) => p.name === 'authentication');
if (authProp?.options) {
for (const opt of authProp.options) {
if (typeof opt === 'object' && 'value' in opt && typeof opt.value === 'string') {
const credTypes = nodeDesc.credentials
?.filter((c) => {
const show = c.displayOptions?.show?.authentication;
return Array.isArray(show) && show.includes(opt.value);
})
.map((c) => c.name);
if (credTypes?.includes(params.credentialType)) {
currentNodeParameters.authentication = opt.value;
break;
}
}
}
}
}
}
const personalProject = await projectRepository.getPersonalProjectForUserOrFail(user.id);
const additionalData = await (0, workflow_execute_additional_data_1.getBase)({
userId: user.id,
projectId: personalProject.id,
currentNodeParameters,
});
let builderHint;
{
const nodes = await getNodes();
const nodeDesc = nodes.find((n) => n.name === params.nodeType);
if (nodeDesc) {
builderHint = findBuilderHintForMethod(nodeDesc, params.methodName, params.methodType);
}
}
try {
if (params.methodType === 'listSearch') {
const result = await dynamicNodeParametersService.getResourceLocatorResults(params.methodName, '', additionalData, nodeTypeAndVersion, currentNodeParameters, credentials, params.filter, params.paginationToken);
return {
results: (result.results ?? []).map((r) => ({
name: String(r.name),
value: r.value,
url: r.url,
})),
paginationToken: result.paginationToken,
...(builderHint ? { builderHint } : {}),
};
}
const options = await dynamicNodeParametersService.getOptionsViaMethodName(params.methodName, '', additionalData, nodeTypeAndVersion, currentNodeParameters, credentials);
return {
results: options.map((o) => ({
name: String(o.name),
value: o.value,
description: o.description,
})),
...(builderHint ? { builderHint } : {}),
};
}
catch (error) {
this.logger.error('Failed to load options for explore-resources', {
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
},
};
}
createWorkspaceAdapter(user) {
const { projectService, folderService, tagService, workflowFinderService, workflowService, executionRepository, executionPersistence, eventService, } = this;
const assertNotReadOnly = (resource) => this.assertInstanceNotReadOnly(resource);
const { assertProjectScope } = this.createProjectScopeHelpers(user);
const adapter = {
async getProject(projectId) {
const project = await projectService.getProjectWithScope(user, projectId, ['project:read']);
if (!project)
return null;
return { id: project.id, name: project.name, type: project.type };
},
async listProjects() {
const projects = await projectService.getAccessibleProjects(user);
return projects.map((p) => ({
id: p.id,
name: p.name,
type: p.type,
}));
},
...(this.license.isLicensed('feat:folders')
? {
async listFolders(projectId) {
await assertProjectScope(['folder:list'], projectId);
const [folders] = await folderService.getManyAndCount(projectId, { take: 100 });
return folders.map((f) => ({
id: f.id,
name: f.name,
parentFolderId: f.parentFolderId,
}));
},
async createFolder(name, projectId, parentFolderId) {
assertNotReadOnly('folders');
await assertProjectScope(['folder:create'], projectId);
const folder = await folderService.createFolder({ name, parentFolderId: parentFolderId ?? undefined }, projectId);
return {
id: folder.id,
name: folder.name,
parentFolderId: folder.parentFolderId ?? null,
};
},
async deleteFolder(folderId, projectId, transferToFolderId) {
assertNotReadOnly('folders');
await assertProjectScope(['folder:delete'], projectId);
await folderService.deleteFolder(user, folderId, projectId, {
transferToFolderId: transferToFolderId ?? undefined,
});
},
async moveWorkflowToFolder(workflowId, folderId) {
assertNotReadOnly('workflows');
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:update',
]);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
await workflowService.update(user, workflow, workflowId, {
parentFolderId: folderId,
source: 'n8n-ai',
});
},
}
: {}),
async tagWorkflow(workflowId, tagNames) {
assertNotReadOnly('workflows');
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:update',
]);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
if (!(0, permissions_1.hasGlobalScope)(user, 'tag:list')) {
throw new Error('User does not have permission to list tags');
}
const existingTags = await tagService.getAll();
const tagMap = new Map(existingTags.map((t) => [t.name.toLowerCase(), t]));
const tagIds = [];
for (const tagName of tagNames) {
const existing = tagMap.get(tagName.toLowerCase());
if (existing) {
tagIds.push(existing.id);
}
else {
if (!(0, permissions_1.hasGlobalScope)(user, 'tag:create')) {
throw new Error('User does not have permission to create tags');
}
const entity = tagService.toEntity({ name: tagName });
const saved = await tagService.save(entity, 'create');
tagIds.push(saved.id);
}
}
await workflowService.update(user, workflow, workflowId, { tagIds, source: 'n8n-ai' });
return tagNames;
},
async listTags() {
if (!(0, permissions_1.hasGlobalScope)(user, 'tag:list')) {
throw new Error('User does not have permission to list tags');
}
const tags = await tagService.getAll();
return tags.map((t) => ({ id: t.id, name: t.name }));
},
async createTag(name) {
if (!(0, permissions_1.hasGlobalScope)(user, 'tag:create')) {
throw new Error('User does not have permission to create tags');
}
const entity = tagService.toEntity({ name });
const saved = await tagService.save(entity, 'create');
return { id: saved.id, name: saved.name };
},
async cleanupTestExecutions(workflowId, options) {
assertNotReadOnly('executions');
const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [
'workflow:execute',
]);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found or not accessible`);
}
const olderThanHours = options?.olderThanHours ?? 1;
const cutoff = new Date(Date.now() - olderThanHours * 60 * 60 * 1000);
const executions = await executionRepository.find({
select: ['id'],
where: {
workflowId,
mode: 'manual',
startedAt: (0, typeorm_1.LessThan)(cutoff),
},
});
if (executions.length === 0) {
return { deletedCount: 0 };
}
const ids = executions.map((e) => e.id);
await executionPersistence.hardDeleteBy({
filters: { workflowId, mode: 'manual' },
accessibleWorkflowIds: [workflowId],
deleteConditions: { deleteBefore: cutoff },
});
eventService.emit('execution-deleted', {
user: {
id: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
role: user.role,
},
executionIds: ids,
deleteBefore: cutoff,
});
return { deletedCount: ids.length };
},
};
return adapter;
}
};
exports.InstanceAiAdapterService = InstanceAiAdapterService;
exports.InstanceAiAdapterService = InstanceAiAdapterService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [backend_common_1.Logger,
config_1.GlobalConfig,
workflow_service_1.WorkflowService,
workflow_finder_service_1.WorkflowFinderService,
db_1.WorkflowRepository,
db_1.SharedWorkflowRepository,
db_1.ProjectRepository,
db_1.ExecutionRepository,
credentials_service_1.CredentialsService,
credentials_finder_service_1.CredentialsFinderService,
active_executions_1.ActiveExecutions,
workflow_runner_1.WorkflowRunner,
load_nodes_and_credentials_1.LoadNodesAndCredentials,
node_types_1.NodeTypes,
n8n_core_1.InstanceSettings,
data_table_service_1.DataTableService,
data_table_repository_1.DataTableRepository,
dynamic_node_parameters_service_1.DynamicNodeParametersService,
folder_service_1.FolderService,
project_service_ee_1.ProjectService,
tag_service_1.TagService,
source_control_preferences_service_ee_1.SourceControlPreferencesService,
instance_ai_settings_service_1.InstanceAiSettingsService,
workflow_history_service_1.WorkflowHistoryService,
workflow_service_ee_1.EnterpriseWorkflowService,
license_1.License,
execution_persistence_1.ExecutionPersistence,
event_service_1.EventService,
role_service_1.RoleService,
telemetry_1.Telemetry,
db_1.AiBuilderTemporaryWorkflowRepository,
ssrf_protection_service_1.SsrfProtectionService])
], InstanceAiAdapterService);
const MAX_RESULT_CHARS = 20_000;
const MAX_NODE_OUTPUT_CHARS = 1_000;
async function resolveDataTableByIdOrName(repository, logger, idOrName, options) {
const byId = await repository.findOneBy({ id: idOrName });
if (byId) {
if (options?.accessFilter && !(await options.accessFilter(byId.id))) {
return { kind: 'miss' };
}
return { kind: 'hit', table: byId };
}
const candidates = await repository.findBy({
name: idOrName,
...(options?.projectIdFilter ? { projectId: options.projectIdFilter } : {}),
});
let filtered = candidates;
if (options?.accessFilter) {
filtered = [];
for (const c of candidates) {
if (await options.accessFilter(c.id))
filtered.push(c);
}
}
if (filtered.length === 0)
return { kind: 'miss' };
if (filtered.length > 1)
return { kind: 'ambiguous', candidates: filtered };
const hit = filtered[0];
logger.warn('data-tables tool called with table name instead of id — resolved by name fallback', {
passedValue: idOrName,
resolvedId: hit.id,
projectId: hit.projectId,
});
return { kind: 'hit', table: hit };
}
function findBuilderHintForMethod(nodeDesc, methodName, methodType) {
const referencesMethod = (prop) => {
switch (methodType) {
case 'loadOptions':
return prop.typeOptions?.loadOptionsMethod === methodName;
case 'listSearch': {
const modes = prop.modes ?? [];
return modes.some((mode) => mode.typeOptions?.searchListMethod === methodName);
}
}
};
const isCollection = (item) => 'values' in item;
const isProperty = (item) => 'type' in item;
const searchProps = (items) => {
for (const item of items ?? []) {
if (isCollection(item)) {
const nested = searchProps(item.values);
if (nested)
return nested;
continue;
}
if (!isProperty(item))
continue;
if (referencesMethod(item) && item.builderHint?.propertyHint) {
return item.builderHint.propertyHint;
}
const nested = searchProps(item.options);
if (nested)
return nested;
}
return undefined;
};
return searchProps(nodeDesc.properties);
}
function truncateResultData(resultData) {
const serialized = JSON.stringify(resultData);
if (serialized.length <= MAX_RESULT_CHARS)
return resultData;
const truncated = {};
for (const [nodeName, items] of Object.entries(resultData)) {
if (!Array.isArray(items) || items.length === 0) {
truncated[nodeName] = items;
continue;
}
const itemStr = JSON.stringify(items[0]);
const preview = itemStr.length > MAX_NODE_OUTPUT_CHARS
? `${itemStr.slice(0, MAX_NODE_OUTPUT_CHARS)}…`
: items[0];
truncated[nodeName] = {
_itemCount: items.length,
_truncated: true,
_firstItemPreview: preview,
};
}
return truncated;
}
function wrapResultDataEntries(data) {
const wrapped = {};
for (const [nodeName, value] of Object.entries(data)) {
wrapped[nodeName] = (0, instance_ai_1.wrapUntrustedData)(JSON.stringify(value, null, 2), 'execution-output', `node:${nodeName}`);
}
return wrapped;
}
async function extractExecutionResult(executionRepository, executionId, includeOutputData = true) {
const execution = await executionRepository.findSingleExecution(executionId, {
includeData: true,
unflattenData: true,
});
if (!execution) {
return { executionId, status: 'unknown' };
}
const status = execution.status === 'error' || execution.status === 'crashed'
? 'error'
: execution.status === 'running' || execution.status === 'new'
? 'running'
: execution.status === 'waiting'
? 'waiting'
: 'success';
const resultData = {};
if (includeOutputData) {
const runData = execution.data?.resultData?.runData;
if (runData) {
for (const [nodeName, nodeRuns] of Object.entries(runData)) {
const lastRun = nodeRuns[nodeRuns.length - 1];
if (lastRun?.data?.main) {
const outputItems = lastRun.data.main
.flat()
.filter((item) => item !== null && item !== undefined)
.map((item) => item.json);
if (outputItems.length > 0) {
resultData[nodeName] = truncateNodeOutput(outputItems);
}
}
}
}
}
const error = execution.data?.resultData?.error;
const errorMessage = error ? formatExecutionError(error, includeOutputData) : undefined;
return {
executionId,
status,
data: Object.keys(resultData).length > 0
? wrapResultDataEntries(truncateResultData(resultData))
: undefined,
error: errorMessage,
startedAt: execution.startedAt?.toISOString(),
finishedAt: execution.stoppedAt?.toISOString(),
};
}
const MAX_ERROR_CHARS = 4_000;
function formatExecutionError(error, includeUpstreamDetails) {
const parts = [];
if (error.message)
parts.push(error.message);
if (includeUpstreamDetails) {
if (error.description && error.description !== error.message) {
parts.push(error.description);
}
if ('messages' in error && error.messages.length > 0) {
parts.push(`Details: ${error.messages.join(' | ')}`);
}
}
else {
const hasDescription = !!error.description && error.description !== error.message;
const hasMessages = 'messages' in error && error.messages.length > 0;
if (hasDescription || hasMessages) {
parts.push('(upstream error details suppressed by the instance AI privacy setting; ask the user to share the node error from the UI)');
}
}
const combined = parts.join(' — ') || 'Unknown error';
return combined.length > MAX_ERROR_CHARS ? `${combined.slice(0, MAX_ERROR_CHARS)}…` : combined;
}
const MAX_NODE_OUTPUT_BYTES = 5_000;
function truncateNodeOutput(items) {
const serialized = JSON.stringify(items);
if (serialized.length <= MAX_NODE_OUTPUT_BYTES)
return items;
const truncated = [];
let size = 2;
for (const item of items) {
const itemStr = JSON.stringify(item);
if (size + itemStr.length + 2 > MAX_NODE_OUTPUT_BYTES)
break;
truncated.push(item);
size += itemStr.length + 1;
}
return {
items: truncated,
truncated: true,
totalItems: items.length,
shownItems: truncated.length,
message: `Output truncated: showing ${truncated.length} of ${items.length} items. Use get-node-output to retrieve full data for this node.`,
};
}
const MAX_ITEM_CHARS = 50_000;
async function extractNodeOutput(executionRepository, executionId, nodeName, options) {
const execution = await executionRepository.findSingleExecution(executionId, {
includeData: true,
unflattenData: true,
});
if (!execution) {
throw new Error(`Execution ${executionId} not found`);
}
const runData = execution.data?.resultData?.runData;
if (!runData?.[nodeName]) {
throw new Error(`Node "${nodeName}" not found in execution ${executionId}`);
}
const nodeRuns = runData[nodeName];
const lastRun = nodeRuns[nodeRuns.length - 1];
const startIndex = options?.startIndex ?? 0;
const maxItems = Math.min(options?.maxItems ?? 10, 50);
let index = 0;
let totalItems = 0;
const collected = [];
for (const output of lastRun?.data?.main ?? []) {
for (const item of output ?? []) {
totalItems++;
if (index >= startIndex && collected.length < maxItems) {
collected.push(item.json);
}
index++;
}
}
const capped = collected.map((item) => {
const str = JSON.stringify(item);
if (str.length > MAX_ITEM_CHARS) {
return {
_truncatedItem: true,
preview: str.slice(0, MAX_ITEM_CHARS),
originalLength: str.length,
};
}
return item;
});
return {
nodeName,
items: capped.map((item, i) => (0, instance_ai_1.wrapUntrustedData)(JSON.stringify(item, null, 2), 'execution-output', `node:${nodeName}[${startIndex + i}]`)),
totalItems,
returned: { from: startIndex, to: startIndex + capped.length },
};
}
const KNOWN_TRIGGER_TYPES = new Set([
n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE,
n8n_workflow_1.FORM_TRIGGER_NODE_TYPE,
n8n_workflow_1.WEBHOOK_NODE_TYPE,
n8n_workflow_1.SCHEDULE_TRIGGER_NODE_TYPE,
]);
function findTriggerNode(nodes) {
const known = nodes.find((n) => KNOWN_TRIGGER_TYPES.has(n.type));
if (known)
return known;
return nodes.find((n) => n.type.includes('Trigger') || n.type.includes('trigger') || n.type.includes('webhook'));
}
function getExecutionModeForTrigger(node) {
switch (node.type) {
case n8n_workflow_1.WEBHOOK_NODE_TYPE:
return 'webhook';
case n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE:
return 'chat';
case n8n_workflow_1.FORM_TRIGGER_NODE_TYPE:
case n8n_workflow_1.SCHEDULE_TRIGGER_NODE_TYPE:
return 'trigger';
default:
return 'manual';
}
}
function validateInputDataShape(node, inputData) {
if (node.type === n8n_workflow_1.FORM_TRIGGER_NODE_TYPE) {
const formFieldsValue = inputData.formFields;
const looksWrapped = typeof formFieldsValue === 'object' && formFieldsValue !== null;
if (looksWrapped) {
throw new Error('verify-built-workflow: inputData for a Form Trigger must be a flat field map ' +
'(e.g. {name: "Alice", email: "a@b.c"}), NOT wrapped in `formFields`. ' +
'The production Form Trigger emits fields directly on $json, so downstream ' +
'expressions like $json.name are correct. Re-run with the flat shape.');
}
}
}
function getPinDataForTrigger(node, inputData) {
validateInputDataShape(node, inputData);
switch (node.type) {
case n8n_workflow_1.CHAT_TRIGGER_NODE_TYPE:
return {
[node.name]: [
{
json: {
sessionId: `instance-ai-${Date.now()}`,
action: 'sendMessage',
chatInput: typeof inputData.chatInput === 'string'
? inputData.chatInput
: JSON.stringify(inputData),
},
},
],
};
case n8n_workflow_1.FORM_TRIGGER_NODE_TYPE:
return {
[node.name]: [
{
json: {
submittedAt: new Date().toISOString(),
formMode: 'instanceAi',
...inputData,
},
},
],
};
case n8n_workflow_1.WEBHOOK_NODE_TYPE: {
const envelopeKeys = new Set(['body', 'headers', 'query']);
const inputKeys = Object.keys(inputData);
const looksLikeEnvelope = inputKeys.length > 0 &&
inputKeys.every((k) => envelopeKeys.has(k)) &&
typeof inputData.body === 'object' &&
inputData.body !== null;
const body = looksLikeEnvelope ? inputData.body : inputData;
const headers = looksLikeEnvelope && typeof inputData.headers === 'object' && inputData.headers !== null
? inputData.headers
: {};
const query = looksLikeEnvelope && typeof inputData.query === 'object' && inputData.query !== null
? inputData.query
: {};
return {
[node.name]: [
{
json: { headers, query, body },
},
],
};
}
case n8n_workflow_1.SCHEDULE_TRIGGER_NODE_TYPE: {
const now = new Date();
return {
[node.name]: [
{
json: {
timestamp: now.toISOString(),
'Readable date': now.toLocaleString(),
'Day of week': now.toLocaleDateString('en-US', { weekday: 'long' }),
Year: String(now.getFullYear()),
Month: now.toLocaleDateString('en-US', { month: 'long' }),
'Day of month': String(now.getDate()).padStart(2, '0'),
Hour: String(now.getHours()).padStart(2, '0'),
Minute: String(now.getMinutes()).padStart(2, '0'),
Second: String(now.getSeconds()).padStart(2, '0'),
},
},
],
};
}
default:
return {
[node.name]: [{ json: inputData }],
};
}
}
async function extractExecutionDebugInfo(executionRepository, executionId, includeOutputData = true) {
const execution = await executionRepository.findSingleExecution(executionId, {
includeData: true,
unflattenData: true,
});
if (!execution) {
return {
executionId,
status: 'unknown',
nodeTrace: [],
};
}
const baseResult = await extractExecutionResult(executionRepository, executionId, includeOutputData);
const runData = execution.data?.resultData?.runData;
const nodeTrace = [];
let failedNode;
if (runData) {
const workflowNodes = execution.workflowData?.nodes ?? [];
const nodeTypeMap = new Map(workflowNodes.map((n) => [n.name, n.type]));
for (const [nodeName, nodeRuns] of Object.entries(runData)) {
const lastRun = nodeRuns[nodeRuns.length - 1];
if (!lastRun)
continue;
const nodeType = nodeTypeMap.get(nodeName) ?? 'unknown';
nodeTrace.push({
name: nodeName,
type: nodeType,
status: lastRun.error !== undefined ? 'error' : 'success',
startedAt: lastRun.startTime !== undefined ? new Date(lastRun.startTime).toISOString() : undefined,
finishedAt: lastRun.startTime !== undefined && lastRun.executionTime !== undefined
? new Date(lastRun.startTime + lastRun.executionTime).toISOString()
: undefined,
});
if (lastRun.error !== undefined && !failedNode) {
failedNode = {
name: nodeName,
type: nodeType,
error: formatExecutionError(lastRun.error, includeOutputData),
inputData: includeOutputData
? (() => {
const inputItems = lastRun.data?.main
?.flat()
.filter((item) => item !== null && item !== undefined)
.map((item) => item.json);
if (inputItems && inputItems.length > 0) {
const raw = inputItems[0];
return (0, instance_ai_1.wrapUntrustedData)(JSON.stringify(raw, null, 2), 'execution-output', `failed-node-input:${nodeName}`);
}
return undefined;
})()
: undefined,
};
}
}
}
return {
...baseResult,
failedNode,
nodeTrace,
};
}
function sdkPinDataToRuntime(pinData) {
const result = {};
if (!pinData)
return result;
for (const [nodeName, items] of Object.entries(pinData)) {
result[nodeName] = items.map((item) => ({ json: (item ?? {}) }));
}
return result;
}
function toWorkflowJSON(workflow, options) {
const redact = options?.redactParameters ?? false;
return {
id: workflow.id,
name: workflow.name,
nodes: (workflow.nodes ?? []).map((n) => ({
id: n.id ?? '',
name: n.name,
type: n.type,
typeVersion: n.typeVersion,
position: n.position,
parameters: redact ? {} : n.parameters,
credentials: n.credentials,
webhookId: n.webhookId,
disabled: n.disabled,
notes: n.notes,
notesInFlow: n.notesInFlow,
executeOnce: n.executeOnce,
retryOnFail: n.retryOnFail,
alwaysOutputData: n.alwaysOutputData,
onError: n.onError,
})),
connections: workflow.connections,
settings: workflow.settings,
};
}
function toWorkflowDetail(workflow, options) {
const redact = options?.redactParameters ?? false;
return {
id: workflow.id,
name: workflow.name,
versionId: workflow.versionId,
activeVersionId: workflow.activeVersionId ?? null,
isArchived: workflow.isArchived,
createdAt: workflow.createdAt.toISOString(),
updatedAt: workflow.updatedAt.toISOString(),
nodes: (workflow.nodes ?? []).map((n) => ({
name: n.name,
type: n.type,
parameters: redact ? undefined : n.parameters,
position: n.position,
webhookId: n.webhookId,
})),
connections: workflow.connections,
settings: workflow.settings,
};
}
//# sourceMappingURL=instance-ai.adapter.service.js.map