n8n
Version:
n8n Workflow Automation Tool
397 lines • 18.7 kB
JavaScript
;
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