UNPKG

n8n

Version:

n8n Workflow Automation Tool

312 lines • 16.8 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); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentPublishService = void 0; const api_types_1 = require("@n8n/api-types"); const backend_common_1 = require("@n8n/backend-common"); const di_1 = require("@n8n/di"); const telemetry_1 = require("@n8n/telemetry"); const n8n_workflow_1 = require("n8n-workflow"); const uuid_1 = require("uuid"); 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 agent_missing_skill_ids_1 = require("../../modules/agents/utils/agent-missing-skill-ids"); const telemetry_2 = require("../../telemetry"); const agents_credential_provider_1 = require("./adapters/agents-credential-provider"); const agent_custom_tools_service_1 = require("./agent-custom-tools.service"); const agent_runtime_cache_service_1 = require("./agent-runtime-cache.service"); const agent_validation_service_1 = require("./agent-validation.service"); const agent_task_entity_1 = require("./entities/agent-task.entity"); const chat_integration_service_1 = require("./integrations/chat-integration.service"); const agent_history_repository_1 = require("./repositories/agent-history.repository"); const agent_task_snapshot_repository_1 = require("./repositories/agent-task-snapshot.repository"); const agent_task_repository_1 = require("./repositories/agent-task.repository"); const agent_repository_1 = require("./repositories/agent.repository"); const sub_agent_cleanup_service_1 = require("./sub-agents/sub-agent-cleanup.service"); function requireValidValidation(validation) { if (validation.status !== 'valid') { throw new n8n_workflow_1.UserError('Agent configuration has errors that must be resolved before publishing'); } } let AgentPublishService = class AgentPublishService { constructor(logger, agentRepository, agentHistoryRepository, agentTaskSnapshotRepository, agentTaskRepository, customToolsService, runtimeCacheService, subAgentCleanupService, agentValidationService, credentialsService, telemetry) { this.logger = logger; this.agentRepository = agentRepository; this.agentHistoryRepository = agentHistoryRepository; this.agentTaskSnapshotRepository = agentTaskSnapshotRepository; this.agentTaskRepository = agentTaskRepository; this.customToolsService = customToolsService; this.runtimeCacheService = runtimeCacheService; this.subAgentCleanupService = subAgentCleanupService; this.agentValidationService = agentValidationService; this.credentialsService = credentialsService; this.telemetry = telemetry; } async publishAgent(agentId, projectId, user, source, versionId, options = {}) { 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 }; } if (versionId !== undefined && versionId === agent.activeVersionId) { return { agent }; } let targetHistory; if (versionId) { const target = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agent.id); if (!target) { throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found for agent "${agent.id}"`); } targetHistory = target; } const tasks = versionId ? new Map() : new Map((await this.agentTaskRepository.findByAgentId(agentId)).map((task) => [task.id, task])); const validation = await this.assertPublishable(agent, projectId, user, tasks, targetHistory, options.ignoreDraftIntegrations); await this.agentRepository.manager.transaction(async (trx) => { if (targetHistory) { agent.activeVersionId = targetHistory.versionId; agent.activeVersion = targetHistory; agent.versionId = (0, uuid_1.v4)(); } else { agent.versionId ??= (0, uuid_1.v4)(); agent.activeVersion = await this.agentHistoryRepository.saveVersion({ versionId: agent.versionId, agentId: agent.id, schema: agent.schema, tools: this.customToolsService.snapshotConfiguredTools(agent.schema, agent.tools ?? {}), skills: this.pickConfiguredSkillBodies(agent.schema, agent.skills ?? {}), publishedBy: user, }, trx); await this.snapshotConfiguredTasks(trx, agent.versionId, agent.schema, tasks); agent.activeVersionId = agent.versionId; } await trx.save(agent); }); this.runtimeCacheService.clearRuntimes(agentId); this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.AGENT_PUBLISHED, { agent_id: agentId, project_id: projectId, user_id: user.id, source, version_id: agent.activeVersionId, }); const credentialIntegrations = agent.integrations ?? []; if (credentialIntegrations.length > 0 && options.syncIntegrations !== false) { await di_1.Container.get(chat_integration_service_1.ChatIntegrationService) .syncToConfig(agent, [], credentialIntegrations) .catch((error) => this.logger.warn('Failed to connect integrations on publish', { agentId, error, })); } const { AgentTaskService } = await import('./agent-task.service.js'); await di_1.Container.get(AgentTaskService) .requestReconcile(agentId) .catch((error) => this.logger.warn('Failed to register agent tasks on publish', { agentId, error })); this.logger.debug('Published SDK agent', { agentId, projectId, userId: user.id }); return versionId ? { agent } : { agent, draftValidation: validation }; } async assertPublishable(agent, projectId, user, tasks, targetHistory, ignoreDraftIntegrations) { const credentialProvider = new agents_credential_provider_1.AgentsCredentialProvider(this.credentialsService, projectId, user); const baseIntegrations = agent.integrations ?? []; const integrations = ignoreDraftIntegrations ? baseIntegrations.filter((integration) => !(0, api_types_1.isDraftIntegration)(integration)) : baseIntegrations; const validation = targetHistory ? await this.agentValidationService.validateAgentHistoryConfiguration(agent.id, projectId, targetHistory, integrations, credentialProvider) : ignoreDraftIntegrations ? await this.agentValidationService.validateAgentEntityConfiguration(agent, projectId, tasks, credentialProvider, 'publish', integrations) : await this.agentValidationService.validateAgentEntityConfiguration(agent, projectId, tasks, credentialProvider); requireValidValidation(validation); return validation; } async unpublishAgent(agentId, projectId, user, source) { 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)(); await trx.save(agent); }); this.runtimeCacheService.clearRuntimes(agentId); this.telemetry.track(telemetry_1.TELEMETRY_EVENT.AGENTS.AGENT_UNPUBLISHED, { agent_id: agentId, project_id: projectId, user_id: user.id, source, }); await this.subAgentCleanupService.removeSubAgentFromParents(agentId, projectId); const chatIntegrationService = di_1.Container.get(chat_integration_service_1.ChatIntegrationService); for (const integration of agent.integrations ?? []) { await chatIntegrationService.disconnectChannel(agentId, integration, { deleteSubscriptions: false, }); } const { AgentTaskService } = await import('./agent-task.service.js'); await di_1.Container.get(AgentTaskService) .requestReconcile(agentId) .catch((error) => this.logger.warn('Failed to stop agent tasks on unpublish', { agentId, error })); 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; } await trx.save(agent); await this.restoreTasksFromSnapshot(trx, agentId, activeVersion.versionId); }); this.runtimeCacheService.clearRuntimes(agentId); this.logger.debug('Reverted SDK agent to published version', { agentId, projectId }); return agent; } async revertToVersion(agentId, projectId, versionId) { 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) => { const target = await this.agentHistoryRepository.findByVersionAndAgentId(versionId, agentId, trx); if (!target) { throw new not_found_error_1.NotFoundError(`Version "${versionId}" not found`); } agent.schema = target.schema ? (0, n8n_workflow_1.deepCopy)(target.schema) : null; agent.tools = (0, n8n_workflow_1.deepCopy)(target.tools ?? {}); agent.skills = (0, n8n_workflow_1.deepCopy)(target.skills ?? {}); agent.versionId = (0, uuid_1.v4)(); if (agent.schema) { agent.name = agent.schema.name; } await trx.save(agent); await this.restoreTasksFromSnapshot(trx, agentId, target.versionId); }); this.runtimeCacheService.clearRuntimes(agentId); this.logger.debug('Reverted SDK agent to a specific version', { agentId, projectId, versionId, }); return agent; } async hasPublishHistory(agentId) { return await this.agentHistoryRepository.existsForAgent(agentId); } async listPublishHistory(agentId, projectId, take, skip) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); } const versions = await this.agentHistoryRepository.findByAgentId(agentId, take, skip); return versions.map((v) => ({ versionId: v.versionId, agentId: v.agentId, createdAt: v.createdAt.toISOString(), updatedAt: v.updatedAt.toISOString(), author: v.author, isActive: v.versionId === agent.activeVersionId, })); } async snapshotConfiguredTasks(trx, versionId, config, tasks) { if (!config) return; const refs = config.tasks ?? []; if (refs.length === 0) return; const missing = refs.filter((ref) => !tasks.has(ref.id)).map((ref) => ref.id); if (missing.length > 0) { throw new n8n_workflow_1.UserError(`Cannot publish agent with missing task bodies: ${missing.join(', ')}`); } await this.agentTaskSnapshotRepository.saveForVersion(refs.map((ref) => { const body = tasks.get(ref.id); if (!body) { throw new n8n_workflow_1.UserError(`Cannot publish agent with missing task body: ${ref.id}`); } return { versionId, taskId: ref.id, enabled: ref.enabled, name: body.name, objective: body.objective, cronExpression: body.cronExpression, }; }), trx); } pickConfiguredSkillBodies(config, skills) { if (!config) return null; const missing = (0, agent_missing_skill_ids_1.getMissingSkillIds)(config, skills); if (missing.length > 0) { throw new n8n_workflow_1.UserError(`Cannot publish agent with missing skill bodies: ${missing.join(', ')}`); } const snapshot = {}; for (const ref of config.skills ?? []) { const skill = skills[ref.id]; if (skill) snapshot[ref.id] = (0, n8n_workflow_1.deepCopy)(skill); } return snapshot; } async restoreTasksFromSnapshot(trx, agentId, versionId) { const repo = trx.getRepository(agent_task_entity_1.AgentTask); const existing = await repo.findBy({ agentId }); const snapshots = await this.agentTaskSnapshotRepository.findByVersionId(versionId, trx); const snapshotIds = new Set(snapshots.map((snapshot) => snapshot.taskId)); const orphanIds = existing.filter((row) => !snapshotIds.has(row.id)).map((row) => row.id); if (orphanIds.length > 0) await repo.delete(orphanIds); const existingIds = new Set(existing.map((row) => row.id)); for (const snapshot of snapshots) { if (existingIds.has(snapshot.taskId)) { await repo.update(snapshot.taskId, { name: snapshot.name, objective: snapshot.objective, cronExpression: snapshot.cronExpression, }); } else { await repo.insert({ id: snapshot.taskId, agentId, name: snapshot.name, objective: snapshot.objective, cronExpression: snapshot.cronExpression, }); } } } }; exports.AgentPublishService = AgentPublishService; exports.AgentPublishService = AgentPublishService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, agent_repository_1.AgentRepository, agent_history_repository_1.AgentHistoryRepository, agent_task_snapshot_repository_1.AgentTaskSnapshotRepository, agent_task_repository_1.AgentTaskRepository, agent_custom_tools_service_1.AgentCustomToolsService, agent_runtime_cache_service_1.AgentRuntimeCacheService, sub_agent_cleanup_service_1.SubAgentCleanupService, agent_validation_service_1.AgentValidationService, credentials_service_1.CredentialsService, telemetry_2.Telemetry]) ], AgentPublishService); //# sourceMappingURL=agent-publish.service.js.map