UNPKG

n8n

Version:

n8n Workflow Automation Tool

255 lines • 14 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.AgentConfigService = void 0; const agent_config_1 = require("@n8n/ai-utilities/agent-config"); const api_types_1 = require("@n8n/api-types"); const backend_common_1 = require("@n8n/backend-common"); const db_1 = require("@n8n/db"); const di_1 = require("@n8n/di"); const n8n_workflow_1 = require("n8n-workflow"); const credentials_service_1 = require("../../credentials/credentials.service"); const not_found_error_1 = require("../../errors/response-errors/not-found.error"); const agent_runtime_cache_service_1 = require("./agent-runtime-cache.service"); const agent_skills_service_1 = require("./agent-skills.service"); const integrations_sync_1 = require("./integrations/integrations-sync"); const agent_config_composition_1 = require("./json-config/agent-config-composition"); const sanitize_unknown_agent_credentials_1 = require("./json-config/sanitize-unknown-agent-credentials"); const agent_task_repository_1 = require("./repositories/agent-task.repository"); const agent_repository_1 = require("./repositories/agent.repository"); const workflow_tool_workflow_resolver_1 = require("./tools/workflow-tool-workflow-resolver"); const agent_credential_provider_1 = require("./utils/agent-credential-provider"); const agent_draft_utils_1 = require("./utils/agent-draft.utils"); const node_tool_validation_1 = require("./utils/node-tool-validation"); const sub_agent_resolver_1 = require("./utils/sub-agent-resolver"); let AgentConfigService = class AgentConfigService { constructor(logger, agentRepository, agentTaskRepository, agentSkillsService, runtimeCacheService, credentialsService, workflowRepository) { this.logger = logger; this.agentRepository = agentRepository; this.agentTaskRepository = agentTaskRepository; this.agentSkillsService = agentSkillsService; this.runtimeCacheService = runtimeCacheService; this.credentialsService = credentialsService; this.workflowRepository = workflowRepository; } 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) { if (hasNodeToolInputSchema(raw)) { return { valid: false, error: 'Node tool configs must not include inputSchema.' }; } const parsed = api_types_1.AgentJsonConfigSchema.safeParse((0, api_types_1.sanitizeAgentJsonConfig)(raw)); if (!parsed.success) { return { valid: false, error: (0, api_types_1.formatAgentConfigZodError)(parsed.error) }; } const config = parsed.data; const toolNameCollisions = (0, api_types_1.findVectorStoreToolNameCollisions)(config); if (toolNameCollisions.length > 0) { return { valid: false, error: `Vector store tool name collides with an existing tool: ${toolNameCollisions.join(', ')}`, }; } try { (0, node_tool_validation_1.validateNodeToolExpressions)(config.tools); } 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 (0, node_tool_validation_1.validateNodeToolConfigs)(config.tools); if (nodeError) { return { valid: false, error: nodeError }; } return { valid: true, config }; } 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 credentialProvider = (0, agent_credential_provider_1.createAgentCredentialProvider)(this.credentialsService, projectId); const accessibleCredentialIds = new Set((await credentialProvider.list()).map((credential) => credential.id)); const sanitizedBaseConfig = (0, api_types_1.sanitizeAgentJsonConfig)(config); const sanitizedConfig = (0, sanitize_unknown_agent_credentials_1.sanitizeUnknownAgentCredentials)(sanitizedBaseConfig, accessibleCredentialIds); const result = await this.validateConfig(sanitizedConfig); if (!result.valid) { throw new n8n_workflow_1.UserError(`Invalid agent config: ${result.error}`); } const validatedConfig = (0, agent_config_1.reconcileNativeWebSearch)(result.config); if (validatedConfig.tools !== undefined) { await (0, workflow_tool_workflow_resolver_1.normalizeWorkflowToolRefs)(this.workflowRepository, validatedConfig.tools, projectId); } const tasksProvided = validatedConfig.tasks !== undefined; const existingTaskIds = tasksProvided ? (await this.agentTaskRepository.findByAgentId(agentId)).map((task) => task.id) : []; const resolvedSubAgents = await this.removeMissingConfigRefs(validatedConfig, entity, new Set(existingTaskIds)); this.validateSubAgentRefs(resolvedSubAgents, entity); const previousIntegrations = entity.integrations ?? []; const previousSchema = entity.schema ?? null; const integrationsProvided = validatedConfig.integrations !== undefined; const toolsProvided = validatedConfig.tools !== undefined; const skillsProvided = validatedConfig.skills !== undefined; const credentialProvided = validatedConfig.credential !== undefined; const personalisationProvided = validatedConfig.personalisation !== undefined; const memoryProvided = validatedConfig.memory !== undefined; const subAgentsProvided = validatedConfig.subAgents !== undefined; const providerToolsProvided = validatedConfig.providerTools !== undefined; const configBlockProvided = validatedConfig.config !== undefined; const mcpServersProvided = validatedConfig.mcpServers !== undefined; const vectorStoresProvided = validatedConfig.vectorStores !== undefined; const { schemaConfig: decomposedSchema, integrations: decomposedIntegrations } = (0, agent_config_composition_1.decomposeJsonConfig)(validatedConfig); const nextIntegrations = integrationsProvided ? decomposedIntegrations : previousIntegrations; const nextPersonalisation = personalisationProvided ? mergePersonalisationWithPreviousGradient(decomposedSchema.personalisation, previousSchema, config) : undefined; const nextSchema = { ...omitLegacyAgentDescription(previousSchema), name: decomposedSchema.name, model: decomposedSchema.model, instructions: decomposedSchema.instructions, ...(credentialProvided ? { credential: decomposedSchema.credential } : {}), ...(personalisationProvided ? { personalisation: nextPersonalisation } : {}), ...(memoryProvided ? { memory: decomposedSchema.memory } : {}), ...(subAgentsProvided ? { subAgents: decomposedSchema.subAgents } : {}), ...(toolsProvided ? { tools: decomposedSchema.tools } : {}), ...(skillsProvided ? { skills: decomposedSchema.skills } : {}), ...(tasksProvided ? { tasks: decomposedSchema.tasks } : {}), ...(providerToolsProvided ? { providerTools: decomposedSchema.providerTools } : {}), ...(configBlockProvided ? { config: decomposedSchema.config } : {}), ...(mcpServersProvided ? { mcpServers: decomposedSchema.mcpServers } : {}), ...(vectorStoresProvided ? { vectorStores: decomposedSchema.vectorStores } : {}), }; entity.schema = nextSchema; entity.name = validatedConfig.name; entity.integrations = nextIntegrations; (0, agent_draft_utils_1.markAgentDraftDirty)(entity); if (toolsProvided) { const referencedIds = new Set((validatedConfig.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, validatedConfig); } this.runtimeCacheService.clearRuntimes(agentId); const saved = await this.agentRepository.save(entity); this.logger.debug('Updated agent JSON config', { agentId, projectId }); if (tasksProvided) { const referencedTaskIds = new Set((validatedConfig.tasks ?? []).map((ref) => ref.id)); const orphanTaskIds = existingTaskIds.filter((id) => !referencedTaskIds.has(id)); if (orphanTaskIds.length > 0) { await this.agentTaskRepository.delete(orphanTaskIds); } } if (integrationsProvided) { await (0, integrations_sync_1.syncAgentIntegrations)(saved, previousIntegrations, nextIntegrations, this.logger); } return { config: (0, agent_config_composition_1.composeJsonConfig)(saved) ?? validatedConfig, updatedAt: saved.updatedAt.toISOString(), versionId: saved.versionId, }; } async removeMissingConfigRefs(config, entity, existingTaskIds) { if (config.skills !== undefined) { const skills = entity.skills ?? {}; config.skills = config.skills.filter((ref) => Boolean(skills[ref.id])); } if (config.tools !== undefined) { const tools = entity.tools ?? {}; config.tools = config.tools.filter((ref) => ref.type !== 'custom' || Boolean(tools[ref.id])); } if (config.tasks !== undefined) { config.tasks = config.tasks.filter((ref) => existingTaskIds.has(ref.id)); } if (config.subAgents?.agents !== undefined) { const resolvedSubAgents = await (0, sub_agent_resolver_1.resolveUniqueSubAgents)({ refs: config.subAgents.agents, projectId: entity.projectId, agentRepository: this.agentRepository, }); config.subAgents.agents = resolvedSubAgents .filter(({ agent }) => agent !== null) .map(({ agentId, useWhen }) => ({ agentId, ...(useWhen ? { useWhen } : {}), })); return resolvedSubAgents; } return []; } validateSubAgentRefs(resolvedSubAgents, entity) { for (const { agentId, agent } of resolvedSubAgents) { if (!agent) continue; if (agentId === entity.id) { throw new n8n_workflow_1.UserError('Invalid agent config: An agent cannot use itself as a subagent'); } if (!agent.activeVersionId) { throw new n8n_workflow_1.UserError(`Invalid agent config: Subagent "${agentId}" must be published`); } } } }; exports.AgentConfigService = AgentConfigService; exports.AgentConfigService = AgentConfigService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, agent_repository_1.AgentRepository, agent_task_repository_1.AgentTaskRepository, agent_skills_service_1.AgentSkillsService, agent_runtime_cache_service_1.AgentRuntimeCacheService, credentials_service_1.CredentialsService, db_1.WorkflowRepository]) ], AgentConfigService); function isRecord(value) { return typeof value === 'object' && value !== null && !Array.isArray(value); } function mergePersonalisationWithPreviousGradient(personalisation, previousSchema, rawConfig) { if (!personalisation || !isRecord(rawConfig) || !isRecord(rawConfig.personalisation)) { return personalisation; } if (rawConfig.personalisation.gradient !== undefined) return personalisation; const previousGradient = previousSchema?.personalisation?.gradient; if (!previousGradient) return personalisation; return { ...personalisation, gradient: previousGradient, }; } function hasNodeToolInputSchema(raw) { if (!isRecord(raw) || !Array.isArray(raw.tools)) return false; return raw.tools.some((tool) => isRecord(tool) && tool.type === 'node' && 'inputSchema' in tool); } function omitLegacyAgentDescription(config) { if (!config) return {}; const { description: _description, ...rest } = config; return rest; } //# sourceMappingURL=agent-config.service.js.map