UNPKG

@n8n-plus/n8n-plus

Version:

n8n Workflow Automation Tool (plus edition)

1,197 lines 55.3 kB
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentsService = void 0;
exports.chatThreadId = chatThreadId;
const api_types_1 = require("@n8n/api-types");
const fromai_helpers_1 = require("@n8n/ai-utilities/fromai-helpers");
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const constants_1 = require("@n8n/constants");
const db_1 = require("@n8n/db");
const decorators_1 = require("@n8n/decorators");
const di_1 = require("@n8n/di");
const n8n_workflow_1 = require("n8n-workflow");
const uuid_1 = require("uuid");
const active_executions_1 = require("../../active-executions");
const credentials_service_1 = require("../../credentials/credentials.service");
const conflict_error_1 = require("../../errors/response-errors/conflict.error");
const not_found_error_1 = require("../../errors/response-errors/not-found.error");
const node_definition_resolver_1 = require("../../modules/instance-ai/node-definition-resolver");
const node_execution_1 = require("../../node-execution");
const publisher_service_1 = require("../../scaling/pubsub/publisher.service");
const url_service_1 = require("../../services/url.service");
const telemetry_1 = require("../../telemetry");
const ttl_map_1 = require("../../utils/ttl-map");
const workflow_runner_1 = require("../../workflow-runner");
const workflow_finder_service_1 = require("../../workflows/workflow-finder.service");
const agents_credential_provider_1 = require("./adapters/agents-credential-provider");
const agent_draft_utils_1 = require("./utils/agent-draft.utils");
const agent_memory_scope_1 = require("./utils/agent-memory-scope");
const execution_to_message_mapper_1 = require("./utils/execution-to-message-mapper");
const agent_resource_id_1 = require("./utils/agent-resource-id");
const agent_execution_service_1 = require("./agent-execution.service");
const agent_skills_service_1 = require("./agent-skills.service");
const agents_tools_service_1 = require("./agents-tools.service");
const builder_tool_names_1 = require("./builder/builder-tool-names");
const execution_recorder_1 = require("./execution-recorder");
const agent_chat_integration_1 = require("./integrations/agent-chat-integration");
const integrations_sync_1 = require("./integrations/integrations-sync");
const n8n_checkpoint_storage_1 = require("./integrations/n8n-checkpoint-storage");
const n8n_memory_1 = require("./integrations/n8n-memory");
const agent_config_composition_1 = require("./json-config/agent-config-composition");
const from_json_config_1 = require("./json-config/from-json-config");
const agent_history_repository_1 = require("./repositories/agent-history.repository");
const agent_repository_1 = require("./repositories/agent.repository");
const agent_secure_runtime_1 = require("./runtime/agent-secure-runtime");
const tool_registry_1 = require("./tool-registry");
const chat_integration_service_1 = require("./integrations/chat-integration.service");
function chatThreadId(agentId, userId) {
    const baseThreadId = `${builder_tool_names_1.AGENT_THREAD_PREFIX.TEST}${agentId}`;
    return userId ? `${baseThreadId}:${userId}` : baseThreadId;
}
function getMaxIterationsChunks() {
    const id = crypto.randomUUID();
    return [
        { type: 'text-start', id },
        {
            type: 'text-delta',
            id,
            delta: 'The agent has reached the maximum number of iterations and has stopped.',
        },
        { type: 'text-end', id },
    ];
}
let AgentsService = class AgentsService {
    computeRuntimeCacheKey(params) {
        if (params.usePublishedVersion) {
            const parts = [params.agentId, 'published'];
            if (params.integrationType)
                parts.push(params.integrationType);
            return parts.join(':');
        }
        const parts = [params.agentId, 'draft'];
        if (params.n8nUserId)
            parts.push(params.n8nUserId);
        return parts.join(':');
    }
    clearRuntimes(agentId, options = {}) {
        for (const key of this.runtimes.keys()) {
            if (key === agentId || key.startsWith(`${agentId}:`)) {
                this.runtimes.delete(key);
            }
        }
        if (options.skipBroadcast)
            return;
        if (!this.globalConfig.multiMainSetup.enabled)
            return;
        void this.publisher
            .publishCommand({
            command: 'agent-config-changed',
            payload: { agentId },
        })
            .catch((error) => {
            this.logger.warn(`[AgentsService] Failed to publish agent-config-changed for ${agentId}`, {
                error: error instanceof Error ? error.message : String(error),
            });
        });
    }
    handleAgentConfigChanged(payload) {
        this.clearRuntimes(payload.agentId, { skipBroadcast: true });
    }
    constructor(logger, agentRepository, projectRelationRepository, workflowRunner, activeExecutions, executionRepository, workflowRepository, userRepository, workflowFinderService, urlService, n8nCheckpointStorage, secureRuntime, ephemeralNodeExecutor, agentsToolsService, n8nMemory, agentExecutionService, agentHistoryRepository, agentSkillsService, publisher, agentsConfig, globalConfig, telemetry, chatIntegrationService) {
        this.logger = logger;
        this.agentRepository = agentRepository;
        this.projectRelationRepository = projectRelationRepository;
        this.workflowRunner = workflowRunner;
        this.activeExecutions = activeExecutions;
        this.executionRepository = executionRepository;
        this.workflowRepository = workflowRepository;
        this.userRepository = userRepository;
        this.workflowFinderService = workflowFinderService;
        this.urlService = urlService;
        this.n8nCheckpointStorage = n8nCheckpointStorage;
        this.secureRuntime = secureRuntime;
        this.ephemeralNodeExecutor = ephemeralNodeExecutor;
        this.agentsToolsService = agentsToolsService;
        this.n8nMemory = n8nMemory;
        this.agentExecutionService = agentExecutionService;
        this.agentHistoryRepository = agentHistoryRepository;
        this.agentSkillsService = agentSkillsService;
        this.publisher = publisher;
        this.agentsConfig = agentsConfig;
        this.globalConfig = globalConfig;
        this.telemetry = telemetry;
        this.chatIntegrationService = chatIntegrationService;
        this.runtimes = new ttl_map_1.TtlMap(30 * constants_1.Time.minutes.toMilliseconds);
    }
    isNodeToolsModuleEnabled() {
        return this.agentsConfig.modules.includes('node-tools-searcher');
    }
    createAgentExecutionCounter({ agentId, userId, }) {
        const attribution = userId ? { user_id: userId } : {};
        return {
            incrementMessageCount: () => this.telemetry.trackAgentExecution({
                agent_id: agentId,
                ...attribution,
                message_count: 1,
            }),
            incrementTokenCount: (tokenCount) => this.telemetry.trackAgentExecution({
                agent_id: agentId,
                ...attribution,
                token_count: tokenCount,
            }),
            incrementToolCallCount: () => this.telemetry.trackAgentExecution({
                agent_id: agentId,
                ...attribution,
                tool_call_count: 1,
            }),
        };
    }
    shouldAttachNodeTools(config) {
        return this.isNodeToolsModuleEnabled() && (0, api_types_1.isNodeToolsEnabled)(config);
    }
    listChatIntegrations() {
        return di_1.Container.get(agent_chat_integration_1.ChatIntegrationRegistry)
            .list()
            .map((i) => ({
            type: i.type,
            label: i.displayLabel,
            icon: i.displayIcon,
            credentialTypes: i.credentialTypes,
        }));
    }
    async create(projectId, name) {
        const defaultConfig = {
            name,
            model: '',
            instructions: '',
            tools: [],
            skills: [],
        };
        const agent = this.agentRepository.create({
            name,
            projectId,
            schema: defaultConfig,
            versionId: (0, uuid_1.v4)(),
        });
        const saved = await this.agentRepository.save(agent);
        this.logger.debug('Created SDK agent', { agentId: saved.id, projectId });
        return saved;
    }
    async findByProjectId(projectId) {
        return await this.agentRepository.findByProjectId(projectId);
    }
    async findById(agentId, projectId) {
        return await this.agentRepository.findByIdAndProjectId(agentId, projectId);
    }
    async updateName(agentId, projectId, name) {
        const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agent) {
            return null;
        }
        agent.name = name;
        if (agent.schema) {
            agent.schema = { ...agent.schema, name };
        }
        (0, agent_draft_utils_1.markAgentDraftDirty)(agent);
        const saved = await this.agentRepository.save(agent);
        this.logger.debug('Updated SDK agent name', { agentId, projectId, name });
        return saved;
    }
    async updateDescription(agentId, projectId, description, updatedAt) {
        const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agent) {
            return null;
        }
        if (updatedAt && agent.updatedAt.toISOString() !== updatedAt) {
            throw new conflict_error_1.ConflictError('Agent has been modified');
        }
        agent.description = description;
        if (agent.schema) {
            agent.schema = { ...agent.schema, description };
        }
        (0, agent_draft_utils_1.markAgentDraftDirty)(agent);
        const saved = await this.agentRepository.save(agent);
        this.logger.debug('Updated SDK agent description', { agentId, projectId });
        return saved;
    }
    async findByUser(userId) {
        const projectRelations = await this.projectRelationRepository.findAllByUser(userId);
        const projectIds = projectRelations.map((pr) => pr.projectId);
        if (projectIds.length === 0)
            return [];
        return await this.agentRepository.find({
            where: { projectId: (0, db_1.In)(projectIds) },
            order: { updatedAt: 'DESC' },
        });
    }
    async findPublishedByUser(userId) {
        const projectRelations = await this.projectRelationRepository.findAllByUser(userId);
        const projectIds = projectRelations.map((pr) => pr.projectId);
        if (projectIds.length === 0)
            return [];
        const agents = await this.agentRepository.find({
            where: { projectId: (0, db_1.In)(projectIds) },
            relations: { activeVersion: true },
            order: { updatedAt: 'DESC' },
        });
        return agents.filter((agent) => agent.activeVersionId !== null);
    }
    async publishAgent(agentId, projectId, user, versionId) {
        const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agent) {
            throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
        }
        if (!versionId && agent.versionId !== null && agent.versionId === agent.activeVersionId) {
            return agent;
        }
        await this.agentRepository.manager.transaction(async (trx) => {
            if (versionId) {
                const existing = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agentId, trx);
                if (!existing) {
                    throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found for agent "${agentId}"`);
                }
                agent.activeVersionId = existing.versionId;
                agent.activeVersion = existing;
            }
            else {
                agent.versionId ??= (0, uuid_1.v4)();
                agent.activeVersion = await this.agentHistoryRepository.saveVersion({
                    versionId: agent.versionId,
                    agentId: agent.id,
                    schema: agent.schema,
                    tools: this.snapshotConfiguredTools(agent.schema, agent.tools ?? {}),
                    skills: this.agentSkillsService.snapshotConfiguredSkills(agent.schema, agent.skills ?? {}),
                    publishedBy: user,
                }, trx);
                agent.activeVersionId = agent.versionId;
            }
            await trx.save(agent);
        });
        this.clearRuntimes(agentId);
        const credentialIntegrations = (agent.integrations ?? []).filter(api_types_1.isAgentCredentialIntegration);
        if (credentialIntegrations.length > 0) {
            const { ChatIntegrationService } = await Promise.resolve().then(() => __importStar(require('./integrations/chat-integration.service')));
            await di_1.Container.get(ChatIntegrationService)
                .syncToConfig(agent, [], credentialIntegrations)
                .catch((error) => this.logger.warn('Failed to connect integrations on publish', {
                agentId,
                error,
            }));
        }
        this.logger.debug('Published SDK agent', { agentId, projectId, userId: user.id });
        return agent;
    }
    async unpublishAgent(agentId, projectId) {
        const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agent) {
            throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
        }
        await this.agentRepository.manager.transaction(async (trx) => {
            agent.activeVersionId = null;
            agent.activeVersion = null;
            agent.versionId = (0, uuid_1.v4)();
            const hasActiveSchedule = (agent.integrations ?? []).some((integration) => (0, api_types_1.isAgentScheduleIntegration)(integration) && integration.active);
            if (hasActiveSchedule) {
                agent.integrations = (agent.integrations ?? []).map((integration) => (0, api_types_1.isAgentScheduleIntegration)(integration) ? { ...integration, active: false } : integration);
            }
            await trx.save(agent);
        });
        this.clearRuntimes(agentId);
        const { ChatIntegrationService } = await Promise.resolve().then(() => __importStar(require('./integrations/chat-integration.service')));
        await di_1.Container.get(ChatIntegrationService).disconnect(agentId);
        const { AgentScheduleService } = await Promise.resolve().then(() => __importStar(require('./integrations/agent-schedule.service')));
        di_1.Container.get(AgentScheduleService).deregister(agentId);
        this.logger.debug('Unpublished SDK agent', { agentId, projectId });
        return agent;
    }
    async revertToPublishedAgent(agentId, projectId) {
        const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agent) {
            throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
        }
        const activeVersion = agent.activeVersion;
        if (!activeVersion) {
            throw new conflict_error_1.ConflictError(`Agent "${agentId}" is not published`);
        }
        await this.agentRepository.manager.transaction(async (trx) => {
            agent.schema = activeVersion.schema ? (0, n8n_workflow_1.deepCopy)(activeVersion.schema) : null;
            agent.tools = (0, n8n_workflow_1.deepCopy)(activeVersion.tools ?? {});
            agent.skills = (0, n8n_workflow_1.deepCopy)(activeVersion.skills ?? {});
            agent.versionId = activeVersion.versionId;
            if (agent.schema) {
                agent.name = agent.schema.name;
                agent.description = agent.schema.description ?? null;
            }
            await trx.save(agent);
        });
        this.clearRuntimes(agentId);
        this.logger.debug('Reverted SDK agent to published version', { agentId, projectId });
        return agent;
    }
    async delete(agentId, projectId) {
        const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agent) {
            return false;
        }
        await this.agentRepository.remove(agent);
        this.clearRuntimes(agentId);
        try {
            const { AgentScheduleService } = await Promise.resolve().then(() => __importStar(require('./integrations/agent-schedule.service')));
            di_1.Container.get(AgentScheduleService).deregister(agentId);
        }
        catch (error) {
            this.logger.warn('Failed to stop schedule on agent delete', {
                agentId,
                error: error instanceof Error ? error.message : error,
            });
        }
        try {
            await this.clearAllTestChatMessages(agentId);
        }
        catch (error) {
            this.logger.warn('Failed to clear test chat on agent delete', {
                agentId,
                error: error instanceof Error ? error.message : error,
            });
        }
        this.logger.debug('Deleted SDK agent', { agentId, projectId });
        return true;
    }
    async getConversationHistory(params) {
        const { threadId, projectId, agentId } = params;
        const detail = await this.agentExecutionService.getThreadDetail(threadId, projectId, agentId);
        if (!detail)
            return null;
        return (0, execution_to_message_mapper_1.executionsToMessagesDto)(detail.executions);
    }
    getMemoryFactory(agentId) {
        return (_params) => this.n8nMemory.getImplementation(agentId);
    }
    createCredentialProvider(projectId) {
        return new agents_credential_provider_1.AgentsCredentialProvider(di_1.Container.get(credentials_service_1.CredentialsService), projectId);
    }
    async getRuntime(params) {
        const { agentId, projectId, integrationType, usePublishedVersion } = params;
        const cacheKey = this.computeRuntimeCacheKey(params);
        const cached = this.runtimes.get(cacheKey);
        if (cached)
            return cached;
        const agentEntity = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agentEntity)
            throw new not_found_error_1.NotFoundError(`Agent ${agentId} not found`);
        let n8nUserId = params.n8nUserId;
        let agentData = agentEntity;
        if (usePublishedVersion) {
            const activeVersionSchema = agentEntity.activeVersion?.schema;
            if (!activeVersionSchema) {
                throw new not_found_error_1.NotFoundError(`Agent ${agentId} is not published`);
            }
            agentData = {
                ...agentEntity,
                schema: activeVersionSchema,
                tools: agentEntity.activeVersion?.tools ?? agentEntity.tools ?? {},
                skills: agentEntity.activeVersion?.skills ?? agentEntity.skills ?? {},
            };
            n8nUserId ??= agentEntity.activeVersion?.publishedById ?? undefined;
        }
        if (!n8nUserId) {
            throw new n8n_workflow_1.UserError('Agent user owner id is required');
        }
        const credentialProvider = this.createCredentialProvider(projectId);
        const { agent: agentInstance, toolRegistry } = await this.reconstructFromConfig(agentData, credentialProvider, n8nUserId, integrationType);
        this.runtimes.set(cacheKey, { agent: agentInstance, agentId, toolRegistry, projectId });
        const runtime = this.runtimes.get(cacheKey);
        if (!runtime)
            throw new Error(`Agent ${agentId} failed to reconstruct`);
        return runtime;
    }
    makeToolResolver(projectId, userId) {
        return async (ref) => {
            if (ref.type === 'workflow') {
                if (!userId) {
                    throw new n8n_workflow_1.UserError('userId is required when agent uses workflow tools');
                }
                const { resolveWorkflowTool } = await Promise.resolve().then(() => __importStar(require('./tools/workflow-tool-factory')));
                return await resolveWorkflowTool(ref, {
                    workflowRepository: this.workflowRepository,
                    workflowRunner: this.workflowRunner,
                    activeExecutions: this.activeExecutions,
                    executionRepository: this.executionRepository,
                    workflowFinderService: this.workflowFinderService,
                    userRepository: this.userRepository,
                    userId,
                    projectId,
                    webhookBaseUrl: this.urlService.getWebhookBaseUrl(),
                });
            }
            if (ref.type === 'node') {
                const { resolveNodeTool } = await Promise.resolve().then(() => __importStar(require('./tools/node-tool-factory')));
                return await resolveNodeTool(ref, {
                    executor: this.ephemeralNodeExecutor,
                    projectId,
                });
            }
            return null;
        };
    }
    async injectRuntimeDependencies(params) {
        const { agent, agentId, projectId, credentialProvider, nodeToolsEnabled, integrationType } = params;
        try {
            const { createGetEnvironmentTool } = await Promise.resolve().then(() => __importStar(require('./tools/environment-tool')));
            agent.tool(createGetEnvironmentTool());
        }
        catch (toolError) {
            this.logger.warn('Failed to inject get_environment tool', {
                agentId,
                error: toolError instanceof Error ? toolError.message : String(toolError),
            });
        }
        const integration = integrationType
            ? di_1.Container.get(agent_chat_integration_1.ChatIntegrationRegistry).get(integrationType)
            : undefined;
        if (integration?.supportedComponents !== undefined) {
            try {
                const { createRichInteractionTool } = await Promise.resolve().then(() => __importStar(require('./integrations/rich-interaction-tool')));
                agent.tool(createRichInteractionTool(integrationType));
            }
            catch (toolError) {
                this.logger.warn('Failed to inject rich_interaction tool', {
                    agentId,
                    error: toolError instanceof Error ? toolError.message : String(toolError),
                });
            }
        }
        if (nodeToolsEnabled) {
            this.attachNodeToolChain(agent, credentialProvider, projectId);
        }
        if (!agent.hasCheckpointStorage()) {
            agent.checkpoint(this.n8nCheckpointStorage);
        }
    }
    attachNodeToolChain(agent, credentialProvider, projectId) {
        agent.tool(this.agentsToolsService.getRuntimeTools(credentialProvider, projectId));
    }
    async *resumeForChat(config) {
        const { agentId, projectId, runId, toolCallId, resumeData, integrationType } = config;
        const checkpointStatus = await this.n8nCheckpointStorage.getStatus(runId);
        if (checkpointStatus.status === 'expired') {
            throw new n8n_workflow_1.UserError(`Checkpoint ${runId} is expired and cannot be resumed`);
        }
        if (checkpointStatus.status === 'not-found') {
            throw new n8n_workflow_1.UserError(`Checkpoint ${runId} not found and cannot be resumed`);
        }
        const memoryScope = checkpointStatus.checkpoint?.persistence;
        if (!memoryScope) {
            throw new n8n_workflow_1.UserError(`Checkpoint ${runId} has no memory data and cannot be resumed`);
        }
        const threadId = memoryScope.threadId;
        const runtime = await this.getRuntime({
            agentId,
            projectId,
            usePublishedVersion: true,
            integrationType,
        });
        const { agent: agentInstance, toolRegistry } = runtime;
        const recorder = new execution_recorder_1.ExecutionRecorder(toolRegistry);
        const resultStream = await agentInstance.resume('stream', resumeData, {
            runId,
            toolCallId,
            executionCounter: this.createAgentExecutionCounter({ agentId }),
        });
        const reader = resultStream.stream.getReader();
        try {
            while (true) {
                const { done, value } = await reader.read();
                if (done)
                    break;
                recorder.record(value);
                yield value;
            }
        }
        finally {
            reader.releaseLock();
        }
        const messageRecord = recorder.getMessageRecord();
        void this.agentExecutionService
            .recordMessage({
            threadId,
            agentId,
            agentName: agentInstance.name,
            projectId,
            userMessage: '',
            record: messageRecord,
            hitlStatus: 'resumed',
        })
            .catch((error) => {
            this.logger.warn('Failed to record resumed agent execution', {
                agentId,
                threadId,
                error: error instanceof Error ? error.message : String(error),
            });
        });
    }
    async validateAgentIsRunnable(agentId, projectId, credentialProvider) {
        const agentEntity = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agentEntity) {
            return { missing: ['agent'] };
        }
        const config = agentEntity.schema;
        const missing = [];
        if (!config) {
            return { missing: ['instructions', 'model', 'credential'] };
        }
        if (!config.instructions?.trim()) {
            missing.push('instructions');
        }
        if (!config.model?.trim() || !api_types_1.AgentModelSchema.safeParse(config.model).success) {
            missing.push('model');
        }
        let credentialList;
        const credentialExists = async (credentialId) => {
            credentialList ??= await credentialProvider.list();
            return credentialList.some((credential) => credential.id === credentialId);
        };
        if (!config.credential?.trim()) {
            missing.push('credential');
        }
        else {
            try {
                const credentialId = config.credential.trim();
                if (!(await credentialExists(credentialId)))
                    missing.push('credential');
            }
            catch {
            }
        }
        const episodicMemory = config.memory?.episodicMemory;
        if (config.memory?.enabled && episodicMemory?.enabled === true) {
            try {
                if (!(await credentialExists(episodicMemory.credential.trim()))) {
                    missing.push('episodicMemory.credential');
                }
            }
            catch {
            }
        }
        missing.push(...this.agentSkillsService
            .getMissingSkillIds(config, agentEntity.skills ?? {})
            .map((skillId) => `skill:${skillId}`));
        return { missing };
    }
    async *executeForChat(config) {
        const { agentId, projectId, message, userId, memory } = config;
        const runtime = await this.getRuntime({ agentId, projectId, n8nUserId: userId });
        yield* this.streamChatResponse({
            agentInstance: runtime.agent,
            toolRegistry: runtime.toolRegistry,
            agentId,
            userId,
            message,
            memory,
            projectId: runtime.projectId,
        });
    }
    async getTestChatMessages(agentId, userId) {
        return await this.n8nMemory
            .getImplementation(agentId)
            .getMessages(chatThreadId(agentId, userId), {
            resourceId: (0, agent_memory_scope_1.draftChatMemoryResourceId)(userId),
        });
    }
    async clearTestChatMessages(agentId, userId) {
        await this.n8nMemory.getImplementation(agentId).deleteThread(chatThreadId(agentId, userId));
    }
    async clearAllTestChatMessages(agentId) {
        const threadId = chatThreadId(agentId);
        const memory = this.n8nMemory.getImplementation(agentId);
        await memory.deleteThreadsByPrefix(threadId);
        await memory.deleteMessagesByThread(threadId);
        await memory.deleteThread(threadId);
    }
    async *executeForChatPublished(config) {
        const { agentId, projectId, message, memory, integrationType } = config;
        const runtime = await this.getRuntime({
            agentId,
            projectId,
            integrationType,
            usePublishedVersion: true,
        });
        yield* this.streamChatResponse({
            agentInstance: runtime.agent,
            toolRegistry: runtime.toolRegistry,
            agentId,
            message,
            memory,
            projectId: runtime.projectId,
            source: integrationType,
        });
    }
    async *executeForSchedulePublished(config) {
        const { agentId, projectId, message, memory } = config;
        const runtime = await this.getRuntime({
            agentId,
            projectId,
            integrationType: api_types_1.AGENT_SCHEDULE_TRIGGER_TYPE,
            usePublishedVersion: true,
        });
        yield* this.streamChatResponse({
            agentInstance: runtime.agent,
            toolRegistry: runtime.toolRegistry,
            agentId,
            message,
            memory,
            projectId: runtime.projectId,
            source: api_types_1.AGENT_SCHEDULE_TRIGGER_TYPE,
        });
    }
    async *streamChatResponse(config) {
        const { agentInstance, toolRegistry, agentId, userId, message, memory, projectId, source } = config;
        const { threadId, resourceId } = memory;
        const recorder = new execution_recorder_1.ExecutionRecorder(toolRegistry);
        const resultStream = await agentInstance.stream(message, {
            persistence: { threadId, resourceId },
            executionCounter: this.createAgentExecutionCounter({ agentId, userId }),
        });
        const reader = resultStream.stream.getReader();
        try {
            while (true) {
                const { done, value } = await reader.read();
                if (done)
                    break;
                recorder.record(value);
                if (value.type === 'tool-call-suspended') {
                    this.logger.info('Chat: tool-call-suspended chunk received', {
                        agentId,
                        toolCallId: value.toolCallId,
                        toolName: value.toolName,
                    });
                }
                if (value.type === 'finish' && value.finishReason === 'max-iterations') {
                    for (const chunk of getMaxIterationsChunks()) {
                        yield chunk;
                    }
                }
                yield value;
            }
        }
        finally {
            reader.releaseLock();
        }
        const messageRecord = recorder.getMessageRecord();
        void this.agentExecutionService
            .recordMessage({
            threadId,
            agentId,
            agentName: agentInstance.name,
            projectId,
            userMessage: message,
            record: messageRecord,
            hitlStatus: recorder.suspended ? 'suspended' : undefined,
            source,
        })
            .catch((error) => {
            this.logger.warn('Failed to record agent execution', {
                agentId,
                threadId,
                error: error instanceof Error ? error.message : String(error),
            });
        });
    }
    async compileIsolated(agentEntity, credentialProvider, userId) {
        if (!agentEntity.schema) {
            return { ok: false, error: 'Agent has no JSON config. Create a config first.' };
        }
        try {
            const { agent: reconstructed } = await this.reconstructFromConfig(agentEntity, credentialProvider, userId);
            return { ok: true, agent: reconstructed };
        }
        catch (e) {
            return {
                ok: false,
                error: e instanceof Error ? e.message : 'Unknown compilation error',
            };
        }
    }
    async executeForWorkflow(agentId, message, executionId, threadId, userId, projectId, telemetryUserId) {
        const agentEntity = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!agentEntity) {
            throw new n8n_workflow_1.OperationalError('Agent not found or not accessible.');
        }
        if (!agentEntity.activeVersionId) {
            throw new n8n_workflow_1.OperationalError('Agent is not published. Publish the agent before using it in a workflow.');
        }
        const credentialProvider = new agents_credential_provider_1.AgentsCredentialProvider(di_1.Container.get(credentials_service_1.CredentialsService), projectId);
        const compiled = await this.compileIsolated(agentEntity, credentialProvider, userId);
        if (!compiled.ok || !compiled.agent) {
            throw new n8n_workflow_1.OperationalError(`Failed to compile agent: ${compiled.error ?? 'unknown error'}`);
        }
        const agentInstance = compiled.agent;
        const recorder = new execution_recorder_1.ExecutionRecorder();
        let structuredOutput = null;
        const toolCalls = [];
        const toolInputs = new Map();
        const resultStream = await agentInstance.stream(message, {
            persistence: { resourceId: executionId, threadId },
            executionCounter: this.createAgentExecutionCounter({ agentId, userId: telemetryUserId }),
        });
        const reader = resultStream.stream.getReader();
        try {
            while (true) {
                const { done, value } = await reader.read();
                if (done)
                    break;
                recorder.record(value);
                if (value.type === 'tool-call') {
                    toolInputs.set(value.toolCallId, { toolName: value.toolName, input: value.input });
                }
                else if (value.type === 'tool-result') {
                    const pending = toolInputs.get(value.toolCallId);
                    toolCalls.push({
                        toolName: value.toolName,
                        input: pending?.input ?? null,
                        result: value.output,
                    });
                    toolInputs.delete(value.toolCallId);
                }
                else if (value.type === 'finish' && value.structuredOutput !== undefined) {
                    structuredOutput = value.structuredOutput;
                }
            }
        }
        finally {
            reader.releaseLock();
        }
        const messageRecord = recorder.getMessageRecord();
        void this.agentExecutionService
            .recordMessage({
            threadId,
            agentId,
            agentName: agentInstance.name,
            projectId,
            userMessage: message,
            record: messageRecord,
            source: api_types_1.AGENT_WORKFLOW_TRIGGER_TYPE,
        })
            .catch((error) => {
            this.logger.warn('Failed to record agent execution from workflow', {
                agentId,
                threadId,
                error: error instanceof Error ? error.message : String(error),
            });
        });
        if (recorder.suspended) {
            throw new n8n_workflow_1.OperationalError('Agent execution suspended waiting for tool approval. ' +
                'Suspend/resume is not supported in workflow execution context.');
        }
        if (messageRecord.error) {
            throw new n8n_workflow_1.OperationalError(`Agent execution failed: ${messageRecord.error}`);
        }
        if (messageRecord.finishReason === 'error') {
            throw new n8n_workflow_1.OperationalError('Agent execution finished with an error.');
        }
        return {
            response: messageRecord.assistantResponse,
            structuredOutput: structuredOutput ?? null,
            usage: messageRecord.usage
                ? {
                    promptTokens: messageRecord.usage.promptTokens,
                    completionTokens: messageRecord.usage.completionTokens,
                    totalTokens: messageRecord.usage.totalTokens,
                }
                : null,
            toolCalls,
            finishReason: messageRecord.finishReason,
            session: {
                agentId,
                projectId,
                sessionId: threadId,
            },
        };
    }
    async getConfig(agentId, projectId) {
        const entity = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!entity)
            throw new not_found_error_1.NotFoundError('Agent not found');
        const config = (0, agent_config_composition_1.composeJsonConfig)(entity);
        if (!config) {
            throw new n8n_workflow_1.UserError('Agent has no JSON config yet.');
        }
        return config;
    }
    async validateConfig(raw) {
        const parsed = api_types_1.AgentJsonConfigSchema.safeParse(raw);
        if (!parsed.success) {
            return { valid: false, error: parsed.error.message };
        }
        const config = parsed.data;
        if ((0, api_types_1.isNodeToolsEnabled)(config.config) && !this.isNodeToolsModuleEnabled()) {
            return {
                valid: false,
                error: 'config.nodeTools.enabled requires the node-tools-searcher agents module to be enabled.',
            };
        }
        try {
            this.validateNodeToolExpressions(config);
        }
        catch (error) {
            const message = error instanceof Error ? error.message : String(error);
            return {
                valid: false,
                error: `Invalid $fromAI expression in node tool config: ${message}`,
            };
        }
        const nodeError = await this.validateNodeToolConfigs(config);
        if (nodeError) {
            return { valid: false, error: nodeError };
        }
        return { valid: true, config };
    }
    validateNodeToolExpressions(config) {
        for (const tool of config.tools ?? []) {
            if (tool.type !== 'node')
                continue;
            (0, fromai_helpers_1.extractFromAIParameters)((tool.node.nodeParameters ?? {}));
        }
    }
    async updateConfig(agentId, projectId, config) {
        const entity = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!entity)
            throw new not_found_error_1.NotFoundError('Agent not found');
        const result = await this.validateConfig(config);
        if (!result.valid) {
            throw new n8n_workflow_1.UserError(`Invalid agent config: ${result.error}`);
        }
        this.validateConfigRefs(result.config, entity);
        const previousIntegrations = entity.integrations ?? [];
        const previousSchema = entity.schema ?? null;
        const integrationsProvided = result.config.integrations !== undefined;
        const toolsProvided = result.config.tools !== undefined;
        const skillsProvided = result.config.skills !== undefined;
        const descriptionProvided = result.config.description !== undefined;
        const credentialProvided = result.config.credential !== undefined;
        const memoryProvided = result.config.memory !== undefined;
        const providerToolsProvided = result.config.providerTools !== undefined;
        const configBlockProvided = result.config.config !== undefined;
        const { schemaConfig: decomposedSchema, integrations: decomposedIntegrations } = (0, agent_config_composition_1.decomposeJsonConfig)(result.config);
        const nextIntegrations = integrationsProvided ? decomposedIntegrations : previousIntegrations;
        const nextSchema = {
            ...(previousSchema ?? {}),
            name: decomposedSchema.name,
            model: decomposedSchema.model,
            instructions: decomposedSchema.instructions,
            ...(descriptionProvided ? { description: decomposedSchema.description } : {}),
            ...(credentialProvided ? { credential: decomposedSchema.credential } : {}),
            ...(memoryProvided ? { memory: decomposedSchema.memory } : {}),
            ...(toolsProvided ? { tools: decomposedSchema.tools } : {}),
            ...(skillsProvided ? { skills: decomposedSchema.skills } : {}),
            ...(providerToolsProvided ? { providerTools: decomposedSchema.providerTools } : {}),
            ...(configBlockProvided ? { config: decomposedSchema.config } : {}),
        };
        entity.schema = nextSchema;
        entity.name = result.config.name;
        if (descriptionProvided)
            entity.description = result.config.description ?? null;
        entity.integrations = nextIntegrations;
        (0, agent_draft_utils_1.markAgentDraftDirty)(entity);
        if (toolsProvided) {
            const referencedIds = new Set((result.config.tools ?? [])
                .filter((t) => t.type === 'custom')
                .map((t) => t.id));
            const orphanIds = Object.keys(entity.tools).filter((id) => !referencedIds.has(id));
            if (orphanIds.length > 0) {
                const tools = { ...entity.tools };
                for (const id of orphanIds) {
                    delete tools[id];
                }
                entity.tools = tools;
            }
        }
        if (skillsProvided) {
            this.agentSkillsService.removeUnreferencedSkills(entity, result.config);
        }
        this.clearRuntimes(agentId);
        const saved = await this.agentRepository.save(entity);
        this.logger.debug('Updated agent JSON config', { agentId, projectId });
        if (integrationsProvided) {
            await (0, integrations_sync_1.syncAgentIntegrations)(saved, previousIntegrations, nextIntegrations, this.logger);
        }
        return {
            config: (0, agent_config_composition_1.composeJsonConfig)(saved) ?? result.config,
            updatedAt: saved.updatedAt.toISOString(),
            versionId: saved.versionId,
        };
    }
    async saveCredentialIntegration(agent, integration) {
        const parseResult = api_types_1.AgentCredentialIntegrationSchema.safeParse(integration);
        if (!parseResult.success) {
            throw new n8n_workflow_1.UserError(`Invalid credential integration: ${parseResult.error.message}`);
        }
        const validated = parseResult.data;
        const { type, credentialId } = validated;
        const existing = agent.integrations ?? [];
        const alreadyExists = existing.some((i) => (0, api_types_1.isAgentCredentialIntegration)(i) && i.type === type && i.credentialId === credentialId);
        agent.integrations = alreadyExists
            ? existing.map((existingIntegration) => (0, api_types_1.isAgentCredentialIntegration)(existingIntegration) &&
                existingIntegration.type === type &&
                existingIntegration.credentialId === credentialId
                ? validated
                : existingIntegration)
            : [...existing, validated];
        const result = await this.agentRepository.save(agent);
        await this.chatIntegrationService.broadcastIntegrationChange(agent.id, integration, 'connect');
        return result;
    }
    async removeCredentialIntegration(agent, type, credentialId) {
        if (!agent.integrations?.length)
            return agent;
        const integration = agent.integrations.find((i) => (0, api_types_1.isAgentCredentialIntegration)(i) && i.type === type && i.credentialId === credentialId);
        if (!integration)
            return agent;
        agent.integrations = agent.integrations.filter((i) => i !== integration);
        const result = await this.agentRepository.save(agent);
        await this.chatIntegrationService.broadcastIntegrationChange(agent.id, integration, 'disconnect');
        return result;
    }
    validateIntegrationRefs(integrations, agent) {
        const activeUnpublishedSchedule = integrations.some((integration) => (0, api_types_1.isAgentScheduleIntegration)(integration) && integration.active);
        if (activeUnpublishedSchedule && !agent.activeVersionId) {
            throw new n8n_workflow_1.UserError('Invalid agent config: schedule integration cannot be active until the agent is published');
        }
    }
    async buildCustomTool(agentId, projectId, code, descriptor) {
        const entity = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!entity)
            throw new not_found_error_1.NotFoundError('Agent not found');
        const toolId = (0, agent_resource_id_1.generateAgentResourceId)('tool', Object.keys(entity.tools ?? {}));
        entity.tools = {
            ...entity.tools,
            [toolId]: { code, descriptor },
        };
        (0, agent_draft_utils_1.markAgentDraftDirty)(entity);
        this.clearRuntimes(agentId);
        await this.agentRepository.save(entity);
        this.logger.debug('Built custom tool', { agentId, projectId, toolId });
        return { ok: true, id: toolId, descriptor };
    }
    async listSkills(agentId, projectId) {
        return await this.agentSkillsService.listSkills(agentId, projectId);
    }
    async getSkill(agentId, projectId, skillId) {
        return await this.agentSkillsService.getSkill(agentId, projectId, skillId);
    }
    async createSkill(agentId, projectId, skill) {
        const result = await this.agentSkillsService.createSkill(agentId, projectId, skill);
        this.clearRuntimes(agentId);
        return result;
    }
    async createAndAttachSkill(agentId, projectId, skill) {
        const result = await this.agentSkillsService.createAndAttachSkill(agentId, projectId, skill);
        this.clearRuntimes(agentId);
        return result;
    }
    async updateSkill(agentId, projectId, skillId, updates) {
        const result = await this.agentSkillsService.updateSkill(agentId, projectId, skillId, updates);
        this.clearRuntimes(agentId);
        return result;
    }
    async deleteCustomTool(agentId, projectId, toolId) {
        const entity = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
        if (!entity)
            throw new not_found_error_1.NotFoundError('Agent not found');
        const tools = { ...entity.tools };
        delete tools[toolId];
        entity.tools = tools;
        if (entity.schema?.tools) {
            entity.schema.tools = entity.schema.tools.filter((t) => !(t.type === 'custom' && 'id' in t && t.id === toolId));
        }
        (0, agent_draft_utils_1.markAgentDraftDirty)(entity);
        this.clearRuntimes(agentId);
        await this.agentRepository.save(entity);
        this.logger.debug('Deleted custom tool', { agentId, projectId, toolId });
    }
    async deleteSkill(agentId, projectId, skillId) {
        await this.agentSkillsService.deleteSkill(agentId, projectId, skillId);
        this.clearRuntimes(agentId);
    }
    async validateNodeToolConfigs(config) {
        const nodeTools = (config.tools ?? []).filter((t) => t.type === 'node');
        if (nodeTools.length === 0)
            return null;
        const { setSchemaBaseDirs, validateNodeConfig } = await Promise.resolve().then(() => __importStar(require('@n8n/workflow-sdk')));
        const dirs = (0, node_definition_resolver_1.resolveBuiltinNodeDefinitionDirs)();
        if (dirs.length > 0) {
            setSchemaBaseDirs(dirs);
        }
        const errors = [];
        for (const tool of nodeTools) {
            const nodeType = tool.node.nodeType;
            const nodeTypeVersion = tool.node.nodeTypeVersion;
            const nodeParameters = tool.node.nodeParameters ?? {};
            const result = validateNodeConfig(nodeType, nodeTypeVersion, { parameters: nodeParameters }, { isToolNode: true });
            if (!result.valid) {
                const messages = result.errors
                    .map((e) => e.message)
                    .join('; ');
                errors.push(`Node tool "${tool.name}" (${nodeType}@${nodeTypeVersion}): ${messages}`);
            }
        }
        return errors.length > 0 ? errors.join('\n') : null;
    }
    validateConfigRefs(config, entity) {
        const missingSkillIds = this.agentSkillsService.getMissingSkillIds(config, entity.skills ?? {});
        if (missingSkillIds.length > 0) {
            throw new n8n_workflow_1.UserError(`Invalid agent config: Missing skill bodies: ${missingSkillIds.join(', ')}`);
        }
        const missingToolIds = this.getMissingCustomToolIds(config, entity.tools ?? {});
        if (missingToolIds.length > 0) {
            throw new n8n_workflow_1.UserError(`Invalid agent config: Missing custom tool definitions: ${missingToolIds.join(', ')}`);
        }
        this.validateIntegrationRefs(config.integrations ?? [], entity);
    }
    getMissingCustomToolIds(config, tools) {
        const refs = (config?.tools ?? []).filter((ref) => ref.type === 'custom');
        const seen = new Set();
        const missing = [];
        for (const ref of refs) {
            if (seen.has(ref.id))
                continue;
            seen.add(ref.id);
            if (!tools[ref.id])
                missing.push(ref.id);
        }
        return missing;
    }
    snapshotConfiguredTools(config, tools) {
        if (!config)
            return null;
        const missing = this.getMissingCustomToolIds(config, tools);
        if (missing.length > 0) {
            throw new n8n_workflow_1.UserError(`Cannot publish agent with missing custom tools: ${missing.join(', ')}`);
        }
        const snapshot = {};
        for (const ref of config.tools ?? []) {
            if (ref.type !== 'custom')
                continue;
            const tool = tools[ref.id];
            if (tool)
                snapshot[ref.id] = tool;
        }
        return snapshot;
    }
    async reconstructFromConfig(agentEntity, credentialProvider, userId, integrationType) {
        const config = agentEntity.schema;
        if (!config) {
            throw new n8n_workflow_1.UserError('Agent has no JSON config.');
        }
        const toolsByName = {};
        for (const [_toolId, toolEntry] of Object.entries(agentEntity.tools ?? {})) {
            toolsByName[toolEntry.descriptor.name] = toolEntry.code;
        }
        const toolDescriptors = {};
        for (const [toolId, toolEntry] of Object.entries(agentEntity.tools ?? {})) {
            toolDescriptors[toolId] = toolEntry.descriptor;
        }
        const toolExecutor = this.secureRuntime.createToolExecutor(toolsByName);
        const toolResolver = this.makeToolResolver(agentEntity.projectId, userId);
        const resolvedTools = [];
        const reconstructed = await (0, from_json_config_1.buildFromJson)(config, toolDescriptors, {
            toolExecutor,
            credentialProvider,
            resolveTool: async (ref) => {
                const resolved = await toolResolver(ref);
                if (resolved)
                    resolvedTools.push(resolved);
                return resolved;
            },
            skills: agentEntity.skills ?? {},
            memoryFactory: this.getMemoryFactory(agentEntity.id),
        });
        await this.injectRuntimeDependencies({
            agent: reconstructed,
            agentId: agentEntity.id,
            projectId: agentEntity.projectId,
            credentialProvider,
            nodeToolsEnabled: this.shouldAttachNodeTools(config.config),
            integrationType,
        });
        const toolRegistry = (0, tool_registry_1.buildToolRegistry)(resolvedTools);
        return { agent: reconstructed, toolRegistry };
    }
};
exports.AgentsService = AgentsService;
__decorate([
    (0, decorators_1.OnPubSubEvent)('agent-config-changed', { instanceType: 'main' }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", [Object]),
    __metadata("design:returntype", void 0)
], AgentsService.prototype, "handleAgentConfigChanged", null);
exports.AgentsService = AgentsService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [backend_common_1.Logger,
        agent_repository_1.AgentRepository,
        db_1.ProjectRelationRepository,
        workflow_runner_1.WorkflowRunner,
        active_executions_1.ActiveExecutions,
        db_1.ExecutionRepository,
        db_1.WorkflowRepository,
        db_1.UserRepository,
        workflow_finder_service_1.WorkflowFinderService,
        url_service_1.UrlService,
        n8n_checkpoint_storage_1.N8NCheckpointStorage,
        agent_secure_runtime_1.AgentSecureRuntime,
        node_execution_1.EphemeralNodeExecutor,
        agents_tools_service_1.AgentsToolsService,
        n8n_memory_1.N8nMemory,
        agent_execution_service_1.AgentExecutionService,
        agent_history_repository_1.AgentHistoryRepository,
        agent_skills_service_1.AgentSkillsService,
        publisher_service_1.Publisher,
        config_1.AgentsConfig,
        config_1.GlobalConfig,
        telemetry_1.Telemetry,
        chat_integration_service_1.ChatIntegrationService])
], AgentsService);
//# sourceMappingURL=agents.service.js.map