n8n
Version:
n8n Workflow Automation Tool
312 lines • 16.8 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.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