UNPKG

n8n

Version:

n8n Workflow Automation Tool

397 lines 18.7 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.ChatIntegrationService = void 0; const api_types_1 = require("@n8n/api-types"); const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const db_1 = require("@n8n/db"); const decorators_1 = require("@n8n/decorators"); const di_1 = require("@n8n/di"); const n8n_core_1 = require("n8n-core"); const credentials_finder_service_1 = require("../../../credentials/credentials-finder.service"); const credentials_service_1 = require("../../../credentials/credentials.service"); const publisher_service_1 = require("../../../scaling/pubsub/publisher.service"); const url_service_1 = require("../../../services/url.service"); const agent_chat_bridge_1 = require("./agent-chat-bridge"); const agent_chat_integration_1 = require("./agent-chat-integration"); const component_mapper_1 = require("./component-mapper"); const esm_loader_1 = require("./esm-loader"); const agent_repository_1 = require("../repositories/agent.repository"); let ChatIntegrationService = class ChatIntegrationService { constructor(logger, agentRepository, credentialsService, credentialsFinderService, urlService, integrationRegistry, instanceSettings, publisher, globalConfig) { this.logger = logger; this.agentRepository = agentRepository; this.credentialsService = credentialsService; this.credentialsFinderService = credentialsFinderService; this.urlService = urlService; this.integrationRegistry = integrationRegistry; this.instanceSettings = instanceSettings; this.publisher = publisher; this.globalConfig = globalConfig; this.connections = new Map(); } async broadcastIntegrationChange(agentId, type, credentialId, action) { if (!this.globalConfig.multiMainSetup.enabled) return; try { await this.publisher.publishCommand({ command: 'agent-chat-integration-changed', payload: { agentId, type, credentialId, action }, }); } catch (error) { this.logger.warn(`[ChatIntegrationService] Failed to publish ${action} for ${type} on agent ${agentId}: ${error instanceof Error ? error.message : String(error)}`); } } connectionKey(agentId, type, credentialId) { return `${agentId}:${type}:${credentialId}`; } connectionTypeFromKey(key) { const parts = key.split(':'); return parts.length >= 3 ? parts[1] : undefined; } async connect(agentId, credentialId, integrationType, userId, projectId, options = {}) { const key = this.connectionKey(agentId, integrationType, credentialId); if (this.connections.has(key)) { await this.disconnectOne(key); } const integration = this.integrationRegistry.require(integrationType); const user = await this.resolveUser(userId); const decryptedData = await this.decryptCredential(credentialId, user); const ctx = { agentId, projectId, credentialId, credential: decryptedData, webhookUrlFor: (platform) => this.buildWebhookUrl(agentId, projectId, platform), }; if (integration.onBeforeConnect && !options.skipExternalHooks) { await integration.onBeforeConnect(ctx); } const adapter = await integration.createAdapter(ctx); const { Chat } = await (0, esm_loader_1.loadChatSdk)(); const { createMemoryState } = await (0, esm_loader_1.loadMemoryState)(); const chat = new Chat({ userName: `n8n-agent-${agentId}`, adapters: { [integrationType]: adapter }, state: createMemoryState(), }); const componentMapper = new component_mapper_1.ComponentMapper(); const { AgentsService } = await Promise.resolve().then(() => __importStar(require('../agents.service'))); const agentService = di_1.Container.get(AgentsService); const bridge = agent_chat_bridge_1.AgentChatBridge.create(chat, agentId, agentService, componentMapper, this.logger, projectId, integrationType); await chat.initialize(); if (integration.onAfterConnect && !options.skipExternalHooks) { try { await integration.onAfterConnect(ctx); } catch (error) { await chat.shutdown().catch((shutdownError) => { this.logger.warn(`[ChatIntegrationService] Shutdown after failed onAfterConnect threw: ${shutdownError instanceof Error ? shutdownError.message : String(shutdownError)}`); }); bridge.dispose(); throw error; } } const chatInstance = chat; this.connections.set(key, { chat: chatInstance, bridge, }); this.logger.info(`[ChatIntegrationService] Connected: ${key}`); } async disconnect(agentId, type, credentialId) { if (type && credentialId) { await this.disconnectOne(this.connectionKey(agentId, type, credentialId)); } else { const keysToRemove = [...this.connections.keys()].filter((k) => k.startsWith(`${agentId}:`)); for (const k of keysToRemove) { await this.disconnectOne(k); } } } async disconnectAll() { const keys = [...this.connections.keys()]; for (const key of keys) { await this.disconnectOne(key); } } async disconnectLeaderOnlyIntegrations() { for (const key of [...this.connections.keys()]) { const type = this.connectionTypeFromKey(key); if (!type) continue; const integration = this.integrationRegistry.get(type); if (integration?.requiresLeader()) { await this.disconnectOne(key); } } } async syncToConfig(agent, previous, next) { const key = (i) => `${i.type}:${i.credentialId}`; const previousKeys = new Set(previous.map(key)); const nextKeys = new Set(next.map(key)); for (const integration of previous) { if (!nextKeys.has(key(integration))) { try { await this.disconnect(agent.id, integration.type, integration.credentialId); await this.broadcastIntegrationChange(agent.id, integration.type, integration.credentialId, 'disconnect'); } catch (error) { this.logger.warn('[ChatIntegrationService] Disconnect during sync failed', { agentId: agent.id, type: integration.type, error, }); } } } const additions = next.filter((i) => !previousKeys.has(key(i))); if (additions.length > 0 && !agent.publishedVersion) { this.logger.debug('[ChatIntegrationService] Skipping connect for unpublished agent — entry persisted, will connect on publish', { agentId: agent.id, pendingTypes: additions.map((i) => i.type) }); return; } const userIds = additions.length ? await di_1.Container.get(db_1.ProjectRelationRepository).findUserIdsByProjectId(agent.projectId) : []; for (const integration of additions) { let connected = false; for (const userId of userIds) { try { await this.connect(agent.id, integration.credentialId, integration.type, userId, agent.projectId); connected = true; break; } catch (error) { this.logger.debug('[ChatIntegrationService] Connect attempt failed during sync', { agentId: agent.id, userId, type: integration.type, error, }); } } if (connected) { await this.broadcastIntegrationChange(agent.id, integration.type, integration.credentialId, 'connect'); } else { this.logger.warn('[ChatIntegrationService] Could not connect integration during sync — no project member had credential access', { agentId: agent.id, type: integration.type, credentialId: integration.credentialId }); } } } getStatus(agentId) { const integrations = []; for (const k of this.connections.keys()) { if (k.startsWith(`${agentId}:`)) { const parts = k.split(':'); if (parts.length >= 3) { integrations.push({ type: parts[1], credentialId: parts.slice(2).join(':') }); } } } return { status: integrations.length > 0 ? 'connected' : 'disconnected', connections: integrations.length, integrations, }; } getChatInstance(agentId) { for (const [k, conn] of this.connections) { if (k.startsWith(`${agentId}:`)) return conn.chat; } return undefined; } getWebhookHandler(agentId, platform) { for (const [key, conn] of this.connections) { if (key.startsWith(`${agentId}:${platform}:`)) { return conn.chat.webhooks[platform]; } } return undefined; } async reconnectAll() { const agents = await this.agentRepository.findPublished(); for (const agent of agents) { if (!agent.integrations || agent.integrations.length === 0) continue; for (const integration of agent.integrations) { if (!(0, api_types_1.isAgentCredentialIntegration)(integration)) { continue; } const definition = this.integrationRegistry.get(integration.type); if (definition?.requiresLeader() && !this.instanceSettings.isLeader) { this.logger.debug(`[ChatIntegrationService] Skipping ${integration.type} for agent ${agent.id} — leader-only and this main is a follower`); continue; } const key = this.connectionKey(agent.id, integration.type, integration.credentialId); if (this.connections.has(key)) continue; const userIds = await di_1.Container.get(db_1.ProjectRelationRepository).findUserIdsByProjectId(agent.projectId); if (userIds.length === 0) { this.logger.warn(`[ChatIntegrationService] No users found for project ${agent.projectId} — skipping reconnect for agent ${agent.id}`); continue; } const skipExternalHooks = !this.instanceSettings.isLeader; let connected = false; for (const userId of userIds) { try { await this.connect(agent.id, integration.credentialId, integration.type, userId, agent.projectId, { skipExternalHooks }); connected = true; break; } catch (error) { this.logger.debug(`[ChatIntegrationService] User ${userId} could not reconnect ${integration.type} for agent ${agent.id}: ${error instanceof Error ? error.message : String(error)}`); } } if (!connected) { this.logger.error(`[ChatIntegrationService] Failed to reconnect ${integration.type} for agent ${agent.id} — no project member could access the credential`); } } } } async handleIntegrationChanged(payload) { const { agentId, type, credentialId, action } = payload; if (action === 'disconnect') { await this.disconnect(agentId, type, credentialId); return; } const definition = this.integrationRegistry.get(type); if (definition?.requiresLeader() && !this.instanceSettings.isLeader) { this.logger.debug(`[ChatIntegrationService] Ignoring connect for ${type} on agent ${agentId} — leader-only integration on follower`); return; } const key = this.connectionKey(agentId, type, credentialId); if (this.connections.has(key)) return; const agent = await this.agentRepository.findOne({ where: { id: agentId } }); if (!agent) { this.logger.warn(`[ChatIntegrationService] Cannot connect ${type} — agent ${agentId} not found`); return; } const userIds = await di_1.Container.get(db_1.ProjectRelationRepository).findUserIdsByProjectId(agent.projectId); for (const userId of userIds) { try { await this.connect(agentId, credentialId, type, userId, agent.projectId, { skipExternalHooks: true, }); return; } catch (error) { this.logger.debug(`[ChatIntegrationService] User ${userId} could not connect ${type} for agent ${agentId}: ${error instanceof Error ? error.message : String(error)}`); } } this.logger.error(`[ChatIntegrationService] Failed to connect ${type} for agent ${agentId} — no project member could access the credential`); } async disconnectOne(key) { const conn = this.connections.get(key); if (!conn) return; try { await conn.chat.shutdown(); } catch (error) { this.logger.warn(`[ChatIntegrationService] Error during shutdown for ${key}: ${error instanceof Error ? error.message : String(error)}`); } conn.bridge.dispose(); this.connections.delete(key); this.logger.info(`[ChatIntegrationService] Disconnected: ${key}`); } async resolveUser(userId) { const user = await di_1.Container.get(db_1.UserRepository).findOne({ where: { id: userId }, relations: ['role'], }); if (!user) { throw new Error(`User ${userId} not found`); } return user; } async decryptCredential(credentialId, user) { const credential = await this.credentialsFinderService.findCredentialForUser(credentialId, user, ['credential:read']); if (!credential) { throw new Error(`Credential ${credentialId} not found or not accessible`); } const decrypted = await this.credentialsService.decrypt(credential, true); return decrypted; } buildWebhookUrl(agentId, projectId, platform) { const base = this.urlService.getWebhookBaseUrl(); return `${base}rest/projects/${projectId}/agents/v2/${agentId}/webhooks/${platform}`; } }; exports.ChatIntegrationService = ChatIntegrationService; __decorate([ (0, decorators_1.OnLeaderStepdown)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], ChatIntegrationService.prototype, "disconnectLeaderOnlyIntegrations", null); __decorate([ (0, decorators_1.OnLeaderTakeover)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], ChatIntegrationService.prototype, "reconnectAll", null); __decorate([ (0, decorators_1.OnPubSubEvent)('agent-chat-integration-changed', { instanceType: 'main' }), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], ChatIntegrationService.prototype, "handleIntegrationChanged", null); exports.ChatIntegrationService = ChatIntegrationService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, agent_repository_1.AgentRepository, credentials_service_1.CredentialsService, credentials_finder_service_1.CredentialsFinderService, url_service_1.UrlService, agent_chat_integration_1.ChatIntegrationRegistry, n8n_core_1.InstanceSettings, publisher_service_1.Publisher, config_1.GlobalConfig]) ], ChatIntegrationService); //# sourceMappingURL=chat-integration.service.js.map