UNPKG

n8n

Version:

n8n Workflow Automation Tool

2,531 lines 124 kB
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
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.resolveMetricProviders = resolveMetricProviders;
exports.buildEvaluationConfigDto = buildEvaluationConfigDto;
exports.evaluationConfigToSummary = evaluationConfigToSummary;
exports.evaluationConfigToDetail = evaluationConfigToDetail;
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 ai_utilities_1 = require("@n8n/ai-utilities");
const instance_ai_1 = require("@n8n/instance-ai");
const api_types_1 = require("@n8n/api-types");
const config_1 = require("@n8n/config");
const constants_1 = require("@n8n/constants");
const nanoid_1 = require("nanoid");
const extract_resolved_node_parameters_1 = require("./extract-resolved-node-parameters");
const instance_ai_settings_service_1 = require("./instance-ai-settings.service");
const workflow_templates_service_1 = require("./workflow-templates.service");
const instance_ai_run_pin_data_1 = require("./instance-ai-run-pin-data");
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 backend_network_1 = require("@n8n/backend-network");
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 active_executions_1 = require("../../active-executions");
const conflict_error_1 = require("../../errors/response-errors/conflict.error");
const credentials_finder_service_1 = require("../../credentials/credentials-finder.service");
const credentials_service_1 = require("../../credentials/credentials.service");
const evaluation_config_service_1 = require("../../evaluation.ee/evaluation-config.service");
const llm_judge_provider_registry_1 = require("../../evaluation.ee/llm-judge-provider-registry");
const event_service_1 = require("../../events/event.service");
const execution_persistence_1 = require("../../executions/execution-persistence");
const license_1 = require("../../license");
const posthog_1 = require("../../posthog");
const load_nodes_and_credentials_1 = require("../../load-nodes-and-credentials");
const node_types_1 = require("../../node-types");
const agents_credential_provider_1 = require("../../modules/agents/adapters/agents-credential-provider");
const instance_ai_builder_delegate_adapter_1 = require("../../modules/agents/instance-ai-builder-delegate.adapter");
const node_catalog_1 = require("../../node-catalog");
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 node_description_transform_1 = require("../../modules/mcp-registry/node-description-transform");
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 ai_gateway_service_1 = require("../../services/ai-gateway.service");
const folder_service_1 = require("../../services/folder.service");
const node_resource_explorer_service_1 = require("../../services/node-resource-explorer.service");
const project_service_ee_1 = require("../../services/project.service.ee");
const role_service_1 = require("../../services/role.service");
const n8n_core_1 = require("n8n-core");
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");
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 httpCredentialHostsCache;
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, nodeResourceExplorerService, folderService, projectService, tagService, sourceControlPreferencesService, settingsService, workflowHistoryService, enterpriseWorkflowService, license, executionPersistence, eventService, roleService, telemetry, aiBuilderTemporaryWorkflowRepository, ssrfProtectionService, outboundHttp, aiGatewayService, workflowTemplatesService, nodeCatalogService, evaluationConfigService, llmJudgeProviderRegistry) {
        this.globalConfig = globalConfig;
        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.nodeResourceExplorerService = nodeResourceExplorerService;
        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.outboundHttp = outboundHttp;
        this.aiGatewayService = aiGatewayService;
        this.workflowTemplatesService = workflowTemplatesService;
        this.nodeCatalogService = nodeCatalogService;
        this.evaluationConfigService = evaluationConfigService;
        this.llmJudgeProviderRegistry = llmJudgeProviderRegistry;
        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;
        this.loadNodesAndCredentials.addPostProcessor?.(async () => {
            this.nodesCache = null;
        });
    }
    createContext(user, options) {
        const { searchProxyConfig, pushRef, threadId, projectId, credentialIdAllowlist, agentId, configEvalsEnabled, modelId, } = options ?? {};
        void this.trackGatewayAvailability();
        const builderDelegateAdapter = this.getBuilderDelegateAdapter();
        return {
            userId: user.id,
            projectId,
            modelId,
            workflowService: this.createWorkflowAdapter(user, threadId, projectId),
            executionService: this.createExecutionAdapter(user, pushRef, threadId),
            credentialService: this.createCredentialAdapter(user, projectId, credentialIdAllowlist),
            nodeService: this.createNodeAdapter(user),
            dataTableService: this.createDataTableAdapter(user, projectId),
            ...(configEvalsEnabled && this.evaluationConfigService
                ? {
                    evaluationConfigService: this.createEvaluationConfigAdapter(this.evaluationConfigService, user),
                }
                : {}),
            webResearchService: this.createWebResearchAdapter(user, searchProxyConfig),
            workspaceService: this.createWorkspaceAdapter(user),
            templatesService: this.getTemplatesService(),
            workflowTemplateService: this.createWorkflowTemplateAdapter(),
            licenseHints: this.buildLicenseHints(),
            logger: this.logger,
            nodeTypesProvider: this.nodeTypes,
            outputSchemaLookup: this.loadNodesAndCredentials.createOutputSchemaLookup?.(),
            allowSendingParameterValues: this.allowSendingParameterValues,
            ...(builderDelegateAdapter && agentId && projectId
                ? { agentBuilderTarget: { agentId, projectId } }
                : {}),
            ...(builderDelegateAdapter && projectId
                ? {
                    builderDelegate: this.withBuilderCreateTelemetry(builderDelegateAdapter.createDelegate(user, projectId, new agents_credential_provider_1.AgentsCredentialProvider(this.credentialsService, projectId, user)), threadId),
                }
                : {}),
        };
    }
    withBuilderCreateTelemetry(delegate, threadId) {
        if (!threadId)
            return delegate;
        return {
            ...delegate,
            createAgent: async (name) => {
                const created = await delegate.createAgent(name);
                this.telemetry.track('Builder created agent', {
                    thread_id: threadId,
                    agent_id: created.agentId,
                    project_id: created.projectId,
                });
                return created;
            },
        };
    }
    getBuilderDelegateAdapter() {
        if (!di_1.Container.get(backend_common_1.ModuleRegistry).isActive('agents'))
            return null;
        try {
            return di_1.Container.get(instance_ai_builder_delegate_adapter_1.InstanceAiBuilderDelegateAdapterService);
        }
        catch (error) {
            this.logger.warn('Failed to resolve builder delegate adapter; agent building disabled', {
                error: error instanceof Error ? error.message : String(error),
            });
            return null;
        }
    }
    async getGatewayConfigOrNull() {
        if (!this.license.isLicensed(constants_1.LICENSE_FEATURES.AI_GATEWAY))
            return null;
        try {
            return await this.aiGatewayService.getGatewayConfig();
        }
        catch {
            return null;
        }
    }
    async isConfigEvalsEnabled(user) {
        const flags = await di_1.Container.get(posthog_1.PostHogClient).getFeatureFlags(user);
        return flags?.[api_types_1.CONFIG_EVALUATIONS_FLAG] === api_types_1.CONFIG_EVALUATIONS_ENABLED_VARIANT;
    }
    buildAiGatewayNodeMeta(config, nodeName) {
        if (!config)
            return undefined;
        if (!config.nodes.includes(nodeName))
            return undefined;
        const meta = { supported: true };
        const operations = config.supportedActions?.[nodeName];
        if (operations && Object.keys(operations).length > 0)
            meta.operations = operations;
        const minVersion = config.minNodeTypeVersion?.[nodeName];
        if (minVersion !== undefined)
            meta.minVersion = minVersion;
        const hiddenProperties = config.hiddenNodeProperties?.[nodeName];
        if (hiddenProperties && hiddenProperties.length > 0)
            meta.hiddenProperties = hiddenProperties;
        return meta;
    }
    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;
    }
    createWorkflowTemplateAdapter() {
        const workflowTemplatesService = this.workflowTemplatesService;
        return {
            async getTemplate(templateId) {
                return await workflowTemplatesService.getTemplate(templateId);
            },
        };
    }
    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;
    }
    async trackGatewayAvailability() {
        const config = await this.getGatewayConfigOrNull();
        if (!config)
            return;
        this.telemetry.track('instance_ai_gateway_available', {
            nodeCount: config.nodes.length,
            credentialTypeCount: config.credentialTypes.length,
        });
    }
    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, boundProjectId) {
        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 ?? boundProjectId ?? (await getPersonalProjectId());
            await assertProjectScope(scopes, projectId);
            return projectId;
        };
        const resolveBoundProjectId = async (scopes) => {
            if (!boundProjectId) {
                throw new n8n_workflow_1.UnexpectedError('Cannot create a resource: this Instance AI run has no bound project.');
            }
            await assertProjectScope(scopes, boundProjectId);
            return boundProjectId;
        };
        return { getPersonalProjectId, assertProjectScope, resolveProjectId, resolveBoundProjectId };
    }
    createWorkflowAdapter(user, threadId, boundProjectId) {
        const { workflowService, workflowFinderService, workflowRepository, sharedWorkflowRepository, aiBuilderTemporaryWorkflowRepository, workflowHistoryService, enterpriseWorkflowService, executionRepository, executionPersistence, license, allowSendingParameterValues, telemetry, } = this;
        const logger = this.logger;
        const assertNotReadOnly = () => this.assertInstanceNotReadOnly('workflows');
        const { resolveBoundProjectId } = this.createProjectScopeHelpers(user, boundProjectId);
        const redactParameters = !allowSendingParameterValues;
        return {
            async list(options) {
                const filter = {
                    ...(options?.status === 'all' ? {} : { isArchived: options?.status === 'archived' }),
                    ...(options?.query ? { query: options.query } : {}),
                    ...(options?.scope !== 'instance' && boundProjectId ? { projectId: boundProjectId } : {}),
                };
                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 await toWorkflowDetailWithChecksum(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, versionId) {
                const wf = await workflowFinderService.findWorkflowForUser(workflowId, user, [
                    'workflow:read',
                ]);
                if (!wf)
                    throw new Error(`Workflow ${workflowId} not found or not accessible`);
                if (!versionId)
                    return toWorkflowJSON(wf, { redactParameters });
                const version = await workflowHistoryService.getVersion(user, workflowId, versionId);
                return toWorkflowJSON(wf, { redactParameters, graph: version });
            },
            async getWorkflowHead(workflowId) {
                const head = await workflowFinderService.findWorkflowHeadForUser(workflowId, user, [
                    'workflow:read',
                ]);
                if (!head)
                    throw new Error(`Workflow ${workflowId} not found or not accessible`);
                return { versionId: head.versionId, updatedAt: head.updatedAt.getTime() };
            },
            async getWorkflowSnapshot(workflowId) {
                const wf = await workflowFinderService.findWorkflowForUser(workflowId, user, [
                    'workflow:read',
                ]);
                if (!wf)
                    throw new Error(`Workflow ${workflowId} not found or not accessible`);
                return {
                    json: toWorkflowJSON(wf, { redactParameters }),
                    versionId: wf.versionId,
                    updatedAt: wf.updatedAt.getTime(),
                };
            },
            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 executionPersistence.findSingleExecution(latest.id, {
                    includeData: true,
                    unflattenData: true,
                });
                return execution?.data?.resultData?.runData ?? null;
            },
            async createFromWorkflowJSON(json, options) {
                assertNotReadOnly();
                const projectId = await resolveBoundProjectId(['workflow:create']);
                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;
                });
                const nodes = sanitizeCredentialReferencesForSave(json.nodes);
                let updateData = workflowRepository.create({
                    name: json.name,
                    nodes: nodes,
                    connections: json.connections,
                    settings,
                    pinData: (0, instance_ai_run_pin_data_1.sdkPinDataToRuntime)(json.pinData),
                    nodeGroups: sdkNodeGroupsToRuntime(json.nodeGroups),
                });
                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 await toWorkflowDetailWithChecksum(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;
                        }
                    }
                }
                const nodes = sanitizeCredentialReferencesForSave(json.nodes);
                let updateData = workflowRepository.create({
                    name: json.name,
                    nodes: nodes,
                    connections: json.connections,
                    settings,
                    pinData: (0, instance_ai_run_pin_data_1.sdkPinDataToRuntime)(json.pinData),
                    nodeGroups: sdkNodeGroupsToRuntime(json.nodeGroups),
                });
                let updated;
                try {
                    if (license.isSharingEnabled()) {
                        updateData = await enterpriseWorkflowService.preventTampering(updateData, workflowId, user);
                    }
                    updated = await workflowService.update(user, updateData, workflowId, {
                        source: 'n8n-ai',
                        ...(options?.expectedChecksum ? { expectedChecksum: options.expectedChecksum } : {}),
                    });
                }
                catch (error) {
                    if (error instanceof conflict_error_1.ConflictError) {
                        throw new instance_ai_1.WorkflowSaveConflictError(workflowId);
                    }
                    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 await toWorkflowDetailWithChecksum(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,
                        typeVersion: n.typeVersion,
                        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,
                    nodeGroups: version.nodeGroups,
                });
                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, nodeTypes, allowSendingParameterValues, roleService, telemetry, logger, globalConfig, } = 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';
                const projectRoles = await roleService.rolesWithScope('project', [scope]);
                const workflowRoles = await roleService.rolesWithScope('workflow', [scope]);
                const sharingOptions = {
                    scopes: [scope],
                    projectRoles,
                    workflowRoles,
                };
                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 timeoutMs = Math.min(options?.timeout ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
                const runData = {
                    executionMode: triggerNode
                        ? getExecutionModeForTrigger(triggerNode)
                        : 'manual',
                    workflowData: {
                        ...workflow,
                        settings: {
                            ...workflow.settings,
                            saveManualExecutions: true,
                            saveDataSuccessExecution: 'all',
                            saveDataErrorExecution: 'all',
                            executionTimeout: Math.ceil(timeoutMs / 1000),
                        },
                    },
                    userId: user.id,
                    pushRef,
                };
                const pinDataPlan = (0, instance_ai_run_pin_data_1.buildInstanceAiRunPinDataPlan)({
                    workflowPinData: workflow.pinData ?? {},
                    verificationPinData: options?.verificationPinData,
                    inputData,
                    triggerNode,
                });
                if (pinDataPlan.startNodeName) {
                    runData.startNodes = [{ name: pinDataPlan.startNodeName, sourceData: null }];
                }
                else if (triggerNode) {
                    runData.triggerToStartFrom = { name: triggerNode.name };
                }
                if (pinDataPlan.runPinData) {
                    runData.pinData = pinDataPlan.runPinData;
                }
                if (pinDataPlan.triggerExecutionData) {
                    runData.executionData = pinDataPlan.triggerExecutionData;
                }
                runData.source = 'instance_ai';
                runData.telemetryMetadata = {
                    mockDataSources: pinDataPlan.mockDataSources,
                };
                if (runData.executionData) {
                    runData.executionData.manualData = {
                        ...runData.executionData.manualData,
                        userId: user.id,
                        source: runData.source,
                    };
                }
                const offloadingManualExecutionsInQueueMode = globalConfig.executions?.mode === 'queue' &&
                    process.env.OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS === 'true';
                if (runData.executionMode === 'manual' &&
                    offloadingManualExecutionsInQueueMode &&
                    !runData.executionData) {
                    runData.executionData = (0, n8n_workflow_1.createRunExecutionData)({
                        startData: { startNodes: runData.startNodes },
                        resultData: { pinData: runData.pinData, runData: null },
                        manualData: {
                            userId: runData.userId,
                            triggerToStartFrom: runData.triggerToStartFrom,
                            source: runData.source,
                        },
                        executionData: null,
                    });
                }
                const trackBuilderExecutedWorkflow = (status, error) => {
                    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,
                        ...(error ? { error } : {}),
                    });
                };
                try {
                    const executionId = await workflowRunner.run(runData);
                    const pruneVerificationPins = async (executedNodeNames) => {
                        try {
                            await (0, instance_ai_run_pin_data_1.pruneUnreachedVerificationPinData)({
                                executionId,
                                verificationPinData: pinDataPlan.verificationPinData,
                                nonVerificationPinData: pinDataPlan.nonVerificationPinData,
                                executedNodeNames,
                            });
                        }
                        catch (error) {
                            logger.warn('Failed to prune verification pin data from execution', {
                                executionId,
                                error: error instanceof Error ? error.message : String(error),
                            });
                        }
                    };
                    const abortSignal = options?.abortSignal;
                    if (activeExecutions.has(executionId)) {
                        let timeoutId;
                        const timeoutPromise = new Promise((_, reject) => {
                            timeoutId = setTimeout(() => {
                                reject(new Error(`Execution timed out after ${timeoutMs}ms`));
                            }, timeoutMs);
                        });
                        let onAbort;
                        const abortPromise = abortSignal === undefined
                            ? undefined
                            : new Promise((_, reject) => {
                                onAbort = () => {
                                    const error = new Error(typeof abortSignal.reason === 'string'
                                        ? abortSignal.reason
                                        : 'This operation was aborted');
                                    error.name = 'AbortError';
                                    reject(error);
                                };
                                if (abortSignal.aborted) {
                                    onAbort();
                                    return;
                                }
                                abortSignal.addEventListener('abort', onAbort, { once: true });
                            });
                        try {
                            await Promise.race([
                                activeExecutions.getPostExecutePromise(executionId),
                                timeoutPromise,
                                ...(abortPromise ? [abortPromise] : []),
                            ]);
                            clearTimeout(timeoutId);
                            if (onAbort)
                                abortSignal?.removeEventListener('abort', onAbort);
                        }
                        catch (error) {
                            clearTimeout(timeoutId);
                            if (onAbort)
                                abortSignal?.removeEventListener('abort', onAbort);
                            const isTimeout = error instanceof Error && error.message.includes('timed out');
                            const isAbort = error instanceof Error &&
                                (error.name === 'AbortError' || abortSignal?.aborted === true);
                            if (isTimeout || isAbort) {
                                try {
                                    activeExecutions.stopExecution(executionId, isAbort
                                        ? new n8n_workflow_1.ManualExecutionCancelledError(executionId)
                                        : new n8n_workflow_1.TimeoutExecutionCancelledError(executionId));
                                }
                                catch {
                                }
                                const result = {
                                    executionId,
                                    status: 'error',
                                    error: isAbort
                                        ? 'Execution was cancelled'
                                        : `Execution timed out after ${timeoutMs}ms and was cancelled`,
                                };
                                await pruneVerificationPins();
                                trackBuilderExecutedWorkflow(result.status, result.error);
                                return result;
                            }
                            throw error;
                        }
                    }
                    const result = await extractExecutionResult(executionId, allowSendingParameterValues);
                    await pruneVerificationPins(result.executedNodeNames);
                    trackBuilderExecutedWorkflow(result.status, result.error);
                    return result;
                }
                catch (error) {
                    trackBuilderExecutedWorkflow('error', error instanceof Error ? error.message : String(error));
                    throw error;
                }
            },
            async getStatus(executionId) {
                await assertExecutionAccess(executionId);
                const isRunning = activeExecutions.has(executionId);
                if (isRunning) {
                    return { executionId, status: 'running' };
                }
                return await extractExecutionResult(executionId, allowSendingParameterValues);
            },
            async getResult(executionId) {
                await assertExecutionAccess(executionId);
                if (activeExecutions.has(executionId)) {
                    await activeExecutions.getPostExecutePromise(executionId);
                }
                return await extractExecutionResult(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(executionId, allowSendingParameterValues, nodeTypes);
            },
            async getNodeOutput(executionId, nodeName, options) {
                await assertExecutionAccess(executionId);
                if (!allowSendingParameterValues) {
                    return {
                        nodeName,
                        items: [],
                        totalItems: 0,
                        returned: { from: 0, to: 0 },
                    };
                }
                return await extractNodeOutput(executionId, nodeName, options);
            },
            getResolvedNodeParameters: async (executionId, nodeName, options) => {
                await assertExecutionAccess(executionId);
                if (!allowSendingParameterValues) {
                    return {
                        nodeName,
                        runIndex: options?.runIndex ?? 0,
                        itemIndex: options?.itemIndex ?? 0,
                        parameters: null,
                        resolved: null,
                        failedExpressions: [],
                        emptyResolutions: [],
                        suppressed: 'parameter-values-disabled',
                    };
                }
                return await (0, extract_resolved_node_parameters_1.extractResolvedNodeParameters)(nodeTypes, executionId, nodeName, options);
            },
        };
    }
    createCredentialAdapter(user, boundProjectId, credentialIdAllowlist) {
        const { credentialsService, credentialsFinderService, loadNodesAndCredentials } = this;
        const getGatewayConfig = async () => await this.getGatewayConfigOrNull();
        const adapter = {
            async list(options) {
                if (boundProjectId) {
                    const scoped = await credentialsService.getCredentialsAUserCanUseInAWorkflow(user, {
                        projectId: boundProjectId,
                    });
                    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 }));
                }
                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 listHttpCredentialHosts() {
                if (httpCredentialHostsCache)
                    return httpCredentialHostsCache;
                const { knownCredentials } = loadNodesAndCredentials;
                const result = [];
                for (const typeName of Object.keys(knownCredentials)) {
                    let credType;
                    try {
                        credType = loadNodesAndCredentials.getCredential(typeName).type;
                    }
                    catch {
                        continue;
                    }
                    const usableInHttpNode = Boolean(credType.authenticate) ||
                        (knownCredentials[typeName]?.extends ?? []).some((parent) => parent === 'oAuth2Api' || parent === 'oAuth1Api');
                    if (!usableInHttpNode)
                        continue;
                    const hosts = (0, instance_ai_1.deriveCredentialHosts)(credType);
                    if (hosts.length === 0)
                        continue;
                    result.push({ type: typeName, displayName: credType.displayName, hosts });
                }
                httpCredentialHostsCache = result;
                return result;
            },
            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 import('../../oauth/oauth.service.js');
                        const identifier = OauthService.extractAccountIdentifier(tokenData);
                        if (identifier) {
                            return { accountIdentifier: mask(identifier) };
                        }
                    }
                    return { accountIdentifier: undefined };
                }
                catch {
                    return { accountIdentifier: undefined };
                }
            },
            async isAiGatewayCredentialType(credType) {
                const config = await getGatewayConfig();
                return config?.credentialTypes.includes(credType) ?? false;
            },
            async listAiGatewayCredentialTypes() {
                const config = await getGatewayConfig();
                return config?.credentialTypes ?? [];
            },
        };
        if (!credentialIdAllowlist)
            return adapter;
        const allowed = new Set(credentialIdAllowlist);
        return {
            ...adapter,
            list: async (options) => allowed.size === 0 ? [] : (await adapter.list(options)).filter((c) => allowed.has(c.id)),
        };
    }
    createEvaluationConfigAdapter(evaluationConfigService, user) {
        const { workflowFinderService, credentialsFinderService, llmJudgeProviderRegistry } = this;
        const assertNotReadOnly = () => this.assertInstanceNotReadOnly('evaluations');
        const findWorkflow = async (workflowId, scope) => {
            const workflow = await workflowFinderService.findWorkflowForUser(workflowId, user, [scope]);
            if (!workflow) {
                throw new Error(`Workflow ${workflowId} not found or not accessible`);
            }
            return workflow;
        };
        const resolveProviders = async (input) => await resolveMetricProviders(input, {
            user,
            credentialsFinderService,
            llmJudgeProviderRegistry,
        });
        return {
            async list(workflowId) {
                await findWorkflow(workflowId, 'workflow:read');
                const configs = await evaluationConfigService.list(workflowId);
                return configs.map(evaluationConfigToSummary);
            },
            async get(workflowId, configId) {
                await findWorkflow(workflowId, 'workflow:read');
                const config = await evaluationConfigService.get(workflowId, configId);
                return config ? evaluationConfigToSummary(config) : null;
            },
            async describe(workflowId, configId) {
                await findWorkflow(workflowId, 'workflow:read');
                const config = await evaluationConfigService.get(workflowId, configId);
                return config ? evaluationConfigToDetail(config) : null;
            },
            async create(workflowId, input) {
                assertNotReadOnly();
                const workflow = await findWorkflow(workflowId, 'workflow:update');
                const resolved = await resolveProviders(input);
                const config = await evaluationConfigService.create(workflowId, workflow, user, buildEvaluationConfigDto(resolved));
                return evaluationConfigToSummary(config);
            },
            async update(workflowId, configId, input) {
                assertNotReadOnly();
                const workflow = await findWorkflow(workflowId, 'workflow:update');
                const resolved = await resolveProviders(input);
                const config = await evaluationConfigService.update(workflowId, configId, workflow, user, buildEvaluationConfigDto(resolved));
                return evaluationConfigToSummary(config);
            },
            async delete(workflowId, configId) {
                assertNotReadOnly();
                await findWorkflow(workflowId, 'workflow:update');
                await evaluationConfigService.delete(workflowId, configId);
            },
        };
    }
    createDataTableAdapter(user, boundProjectId) {
        const { dataTableService, dataTableRepository } = this;
        const assertNotReadOnly = () => this.assertInstanceNotReadOnly('data tables');
        const { resolveProjectId, resolveBoundProjectId } = this.createProjectScopeHelpers(user, boundProjectId);
        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) {
                assertNotReadOnly();
                const projectId = await resolveBoundProjectId(['dataTable:create']);
                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 { outboundHttp, ssrfProtectionService } = this;
        const sharedTransport = outboundHttp.transport({
            ssrf: this.ssrfProtectionService,
        });
        const userId = user.id;
        let resolvedSearchMethod;
        let searchResolved = false;
        const lazySearch = async (query, options) => {
            if (!searchResolved) {
                const config = await settingsService.resolveSearchConfig();
                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 authorizeUrl = options?.authorizeUrl;
                const transport = authorizeUrl
                    ? outboundHttp.transport({
                        ssrf: ssrfProtectionService,
                        authorize: async (target) => await authorizeUrl(target.href),
                    })
                    : sharedTransport;
                const page = await (0, web_research_1.fetchAndExtract)(url, {
                    maxContentLength: options?.maxContentLength,
                    maxResponseBytes: options?.maxResponseBytes,
                    timeoutMs: options?.timeoutMs,
                    abortSignal: options?.abortSignal,
                    transport,
                });
                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}:` : '';
        const searchCacheKey = (query, options) => {
            const { abortSignal: _abortSignal, ...cacheable } = options ?? {};
            return `${keyPrefix}${JSON.stringify([query, cacheable])}`;
        };
        if (searchProxyConfig) {
            return async (query, options) => {
                const cacheKey = searchCacheKey(query, options);
                const cached = cache.get(cacheKey);
                if (cached)
                    return cached;
                const result = await (0, ai_utilities_1.braveSearch)('', query, {
                    ...options,
                    proxyConfig: searchProxyConfig,
                });
                cache.set(cacheKey, result);
                return result;
            };
        }
        if (apiKey) {
            return async (query, options) => {
                const cacheKey = searchCacheKey(query, options);
                const cached = cache.get(cacheKey);
                if (cached)
                    return cached;
                const result = await (0, ai_utilities_1.braveSearch)(apiKey, query, options ?? {});
                cache.set(cacheKey, result);
                return result;
            };
        }
        if (searxngUrl) {
            return async (query, options) => {
                const cacheKey = searchCacheKey(query, options);
                const cached = cache.get(cacheKey);
                if (cached)
                    return cached;
                const result = await (0, ai_utilities_1.searxngSearch)(searxngUrl, query, options ?? {});
                cache.set(cacheKey, result);
                return result;
            };
        }
        return undefined;
    }
    getNodeDefinitionDirs() {
        const catalogDirs = this.nodeCatalogService?.getNodeDefinitionDirs();
        if (catalogDirs?.length)
            return catalogDirs;
        if (!this._nodeDefinitionDirs) {
            this._nodeDefinitionDirs = (0, node_definition_resolver_1.resolveBuiltinNodeDefinitionDirs)();
        }
        return this._nodeDefinitionDirs;
    }
    getNodeCatalogService() {
        return this.nodeCatalogService ?? di_1.Container.get(node_catalog_1.NodeCatalogService);
    }
    createNodeAdapter(user) {
        const getNodes = async () => await this.getNodesFromCache();
        const getGatewayConfig = async () => await this.getGatewayConfigOrNull();
        const buildMeta = (config, nodeName) => this.buildAiGatewayNodeMeta(config, nodeName);
        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);
        };
        return {
            async listAvailable(options) {
                const [nodes, gatewayConfig] = await Promise.all([
                    getNodes(),
                    options?.n8nConnectOnly ? getGatewayConfig() : Promise.resolve(null),
                ]);
                let filtered = nodes;
                if (options?.query) {
                    const q = options.query.toLowerCase();
                    filtered = filtered.filter((n) => n.displayName.toLowerCase().includes(q) ||
                        n.name.toLowerCase().includes(q) ||
                        n.description?.toLowerCase().includes(q));
                }
                const summaries = filtered.map((n) => {
                    const summary = {
                        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,
                    };
                    const meta = buildMeta(gatewayConfig, n.name);
                    if (meta)
                        summary.aiGateway = meta;
                    return summary;
                });
                return options?.n8nConnectOnly ? summaries.filter((s) => s.aiGateway) : summaries;
            },
            async listSearchable() {
                const [nodes, gatewayConfig] = await Promise.all([getNodes(), getGatewayConfig()]);
                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),
                    };
                    const meta = buildMeta(gatewayConfig, n.name);
                    if (meta)
                        result.aiGateway = meta;
                    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, gatewayConfig] = await Promise.all([getNodes(), getGatewayConfig()]);
                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`);
                }
                const meta = buildMeta(gatewayConfig, desc.name);
                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 } : {}),
                    ...(meta ? { aiGateway: meta } : {}),
                };
            },
            getNodeTypeDefinition: async (nodeType, options) => {
                const nodeCatalogService = this.getNodeCatalogService();
                await nodeCatalogService.initialize();
                const { version, resource, operation, mode } = options ?? {};
                const getDefinition = async (nodeId) => await nodeCatalogService.getNodeTypeDefinition({
                    nodeId,
                    ...(version ? { version } : {}),
                    ...(resource ? { resource } : {}),
                    ...(operation ? { operation } : {}),
                    ...(mode ? { mode } : {}),
                });
                const result = await getDefinition(nodeType);
                if (!result.error || nodeType.includes('.'))
                    return result;
                return await getDefinition(`${node_description_transform_1.MCP_REGISTRY_PACKAGE_NAME}.${nodeType}`);
            },
            listDiscriminators: async (nodeType) => {
                const nodeCatalogService = this.getNodeCatalogService();
                await nodeCatalogService.initialize();
                return (0, node_definition_resolver_1.listNodeDiscriminators)(nodeType, nodeCatalogService.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 [];
                await workflow.expression.acquireIsolate();
                try {
                    return n8n_workflow_1.NodeHelpers.getNodeInputs(workflow, workflowNode, nodeType.description);
                }
                finally {
                    await workflow.expression.releaseIsolate();
                }
            },
            exploreResources: async (params) => await this.nodeResourceExplorerService.exploreResources(user, params),
        };
    }
    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, node_resource_explorer_service_1.NodeResourceExplorerService, 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, backend_network_1.SsrfProtectionService, backend_network_1.OutboundHttp, ai_gateway_service_1.AiGatewayService, workflow_templates_service_1.WorkflowTemplatesService, node_catalog_1.NodeCatalogService, evaluation_config_service_1.EvaluationConfigService, llm_judge_provider_registry_1.LlmJudgeProviderRegistry])
], InstanceAiAdapterService);
const MAX_RESULT_CHARS = 20_000;
const MAX_NODE_OUTPUT_CHARS = 1_000;
async function resolveMetricProviders(input, deps) {
    const { user, credentialsFinderService, llmJudgeProviderRegistry } = deps;
    const metrics = await Promise.all(input.metrics.map(async (metric) => {
        if (metric.provider)
            return metric;
        if (!llmJudgeProviderRegistry) {
            throw new n8n_workflow_1.UnexpectedError('Cannot derive the judge provider: provider registry is unavailable.');
        }
        const credential = await credentialsFinderService.findCredentialForUser(metric.credentialId, user, ['credential:read']);
        if (!credential) {
            throw new n8n_workflow_1.UserError(`Credential "${metric.credentialId}" for metric "${metric.name}" was not found or is not accessible.`);
        }
        const provider = llmJudgeProviderRegistry.getByCredentialType(credential.type);
        if (!provider) {
            throw new n8n_workflow_1.UserError(`Credential type "${credential.type}" for metric "${metric.name}" is not a supported LLM judge provider.`);
        }
        return { ...metric, provider: provider.nodeType };
    }));
    return { ...input, metrics };
}
function buildEvaluationConfigDto(input) {
    return api_types_1.upsertEvaluationConfigSchema.parse({
        name: input.name,
        startNodeName: input.startNodeName,
        endNodeName: input.endNodeName,
        datasetSource: 'data_table',
        datasetRef: { dataTableId: input.dataTableId },
        metrics: input.metrics.map((metric) => ({
            id: (0, nanoid_1.nanoid)(),
            name: metric.name,
            type: 'llm_judge',
            config: {
                preset: metric.preset,
                ...(metric.prompt ? { prompt: metric.prompt } : {}),
                provider: metric.provider,
                credentialId: metric.credentialId,
                model: metric.model,
                outputType: metric.outputType,
                inputs: {
                    actualAnswer: metric.actualAnswer,
                    ...(metric.userQuery ? { userQuery: metric.userQuery } : {}),
                    ...(metric.expectedAnswer ? { expectedAnswer: metric.expectedAnswer } : {}),
                },
            },
        })),
    });
}
function evaluationConfigToSummary(config) {
    const dataTableId = config.datasetSource === 'data_table' && 'dataTableId' in config.datasetRef
        ? config.datasetRef.dataTableId
        : undefined;
    return {
        id: config.id,
        workflowId: config.workflowId,
        name: config.name,
        status: config.status,
        invalidReason: config.invalidReason,
        startNodeName: config.startNodeName,
        endNodeName: config.endNodeName,
        metrics: config.metrics.map((metric) => ({
            id: metric.id,
            name: metric.name,
            type: metric.type,
        })),
        datasetSource: config.datasetSource,
        ...(dataTableId !== undefined ? { dataTableId } : {}),
    };
}
function evaluationConfigToDetail(config) {
    const dataTableId = config.datasetSource === 'data_table' && 'dataTableId' in config.datasetRef
        ? config.datasetRef.dataTableId
        : undefined;
    return {
        id: config.id,
        workflowId: config.workflowId,
        name: config.name,
        status: config.status,
        invalidReason: config.invalidReason,
        startNodeName: config.startNodeName,
        endNodeName: config.endNodeName,
        metrics: config.metrics,
        datasetSource: config.datasetSource,
        ...(dataTableId !== undefined ? { dataTableId } : {}),
    };
}
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 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;
}
const MAX_NODE_ERRORS = 10;
function isFailedNodeRun(nodeRun) {
    return (nodeRun.executionStatus === 'error' ||
        nodeRun.error !== undefined ||
        nodeRun.redactedError !== undefined);
}
function nodeContinuesOnError(node) {
    return (node?.continueOnFail === true ||
        node?.onError === 'continueRegularOutput' ||
        node?.onError === 'continueErrorOutput');
}
function extractNodeErrors(runData, includeUpstreamDetails, workflowNodes = []) {
    if (!runData)
        return [];
    const nodesByName = new Map(workflowNodes.map((node) => [node.name, node]));
    const nodeErrors = [];
    for (const [nodeName, nodeRuns] of Object.entries(runData)) {
        if (nodeErrors.length >= MAX_NODE_ERRORS)
            break;
        if (nodeContinuesOnError(nodesByName.get(nodeName)))
            continue;
        const failedRun = nodeRuns.find(isFailedNodeRun);
        if (!failedRun)
            continue;
        const message = failedRun.error
            ? formatExecutionError(failedRun.error, includeUpstreamDetails)
            : failedRun.redactedError
                ? `${failedRun.redactedError.type} error${failedRun.redactedError.httpCode ? ` (${failedRun.redactedError.httpCode})` : ''}`
                : undefined;
        nodeErrors.push({
            nodeName,
            ...(message ? { message } : {}),
        });
    }
    return nodeErrors;
}
async function extractExecutionResult(executionId, includeOutputData = true) {
    const execution = await di_1.Container.get(execution_persistence_1.ExecutionPersistence).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 = {};
    const runData = execution.data?.resultData?.runData;
    const executedNodeNames = Object.keys(runData ?? {});
    if (includeOutputData) {
        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;
    const nodeErrors = extractNodeErrors(runData, includeOutputData, execution.workflowData?.nodes);
    return {
        executionId,
        status,
        data: Object.keys(resultData).length > 0
            ? wrapResultDataEntries(truncateResultData(resultData))
            : undefined,
        executedNodeNames: executedNodeNames.length > 0 ? executedNodeNames : undefined,
        nodeErrors: nodeErrors.length > 0 ? nodeErrors : undefined,
        lastNodeExecuted: execution.data?.resultData?.lastNodeExecuted,
        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(executionId, nodeName, options) {
    const execution = await di_1.Container.get(execution_persistence_1.ExecutionPersistence).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) => (0, n8n_workflow_1.isTriggerNodeType)(n.type));
}
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';
    }
}
async function extractExecutionDebugInfo(executionId, includeOutputData = true, nodeTypes) {
    const execution = await di_1.Container.get(execution_persistence_1.ExecutionPersistence).findSingleExecution(executionId, {
        includeData: true,
        unflattenData: true,
    });
    if (!execution) {
        return {
            executionId,
            status: 'unknown',
            nodeTrace: [],
        };
    }
    const baseResult = await extractExecutionResult(executionId, includeOutputData);
    const runData = execution.data?.resultData?.runData;
    const nodeTrace = [];
    let failedNode;
    let failedItemIndex;
    let failedRunIndex;
    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: isFailedNodeRun(lastRun) ? '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) {
                const errorContext = lastRun.error.context;
                failedItemIndex =
                    typeof errorContext?.itemIndex === 'number' ? errorContext.itemIndex : undefined;
                failedRunIndex =
                    typeof errorContext?.runIndex === 'number' ? errorContext.runIndex : nodeRuns.length - 1;
                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,
                };
            }
        }
    }
    if (failedNode && includeOutputData && nodeTypes) {
        try {
            const { nodeName: _omitName, suppressed: _omitSuppressed, ...bundle } = await (0, extract_resolved_node_parameters_1.extractResolvedNodeParameters)(nodeTypes, executionId, failedNode.name, {
                itemIndex: failedItemIndex,
                runIndex: failedRunIndex,
            });
            failedNode.resolvedParameters = bundle;
        }
        catch {
        }
    }
    return {
        ...baseResult,
        failedNode,
        nodeTrace,
    };
}
function sdkNodeGroupsToRuntime(nodeGroups) {
    return nodeGroups ?? [];
}
function hasCredentialId(value) {
    if (typeof value !== 'object' || value === null)
        return false;
    if (Reflect.get(value, 'id') === null && Reflect.get(value, '__aiGatewayManaged') === true) {
        return true;
    }
    const id = Reflect.get(value, 'id');
    return typeof id === 'string' && id.trim() !== '';
}
function sanitizeCredentialReferencesForSave(nodes) {
    return nodes.map((node) => {
        if (!node.credentials)
            return node;
        const credentials = Object.entries(node.credentials).reduce((acc, [type, value]) => {
            if (hasCredentialId(value)) {
                acc[type] = value;
            }
            return acc;
        }, {});
        if (Object.keys(credentials).length === Object.keys(node.credentials).length)
            return node;
        const sanitized = { ...node };
        if (Object.keys(credentials).length > 0) {
            sanitized.credentials = credentials;
        }
        else {
            delete sanitized.credentials;
        }
        return sanitized;
    });
}
function toWorkflowJSON(workflow, options) {
    const redact = options?.redactParameters ?? false;
    const source = options?.graph ?? workflow;
    return {
        id: workflow.id,
        name: workflow.name,
        nodes: (source.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: source.connections,
        settings: workflow.settings,
        ...(source.nodeGroups ? { nodeGroups: source.nodeGroups } : {}),
    };
}
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,
            typeVersion: n.typeVersion,
            parameters: redact ? undefined : n.parameters,
            position: n.position,
            webhookId: n.webhookId,
        })),
        connections: workflow.connections,
        settings: workflow.settings,
    };
}
async function toWorkflowDetailWithChecksum(workflow, options) {
    const detail = toWorkflowDetail(workflow, options);
    detail.checksum = await (0, n8n_workflow_1.calculateWorkflowChecksum)(workflow);
    return detail;
}
//# sourceMappingURL=instance-ai.adapter.service.js.map