UNPKG

n8n

Version:

n8n Workflow Automation Tool

300 lines 15.9 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.ChatIntegrationActionExecutor = exports.respondInputSchema = void 0; const api_types_1 = require("@n8n/api-types"); const di_1 = require("@n8n/di"); const is_record_1 = require("@n8n/utils/is-record"); const zod_1 = require("zod"); const agent_chat_integration_1 = require("./agent-chat-integration"); const chat_integration_service_1 = require("./chat-integration.service"); const component_mapper_1 = require("./component-mapper"); const integration_error_codes_1 = require("./integration-error-codes"); const integration_helpers_1 = require("./integration-helpers"); const messageSchema = api_types_1.richMessageSchema; exports.respondInputSchema = zod_1.z.object({ message: messageSchema }); const sendDmInputSchema = zod_1.z.object({ userId: zod_1.z.string().min(1), message: messageSchema, }); const sendChannelMessageInputSchema = zod_1.z.object({ channelId: zod_1.z.string().min(1), message: messageSchema, }); const editMessageInputSchema = zod_1.z .object({ messageId: zod_1.z.string().min(1), message: messageSchema, }) .strict(); const addReactionInputSchema = zod_1.z.object({ emoji: zod_1.z.string().min(1), threadId: zod_1.z.string().min(1).optional(), messageId: zod_1.z.string().min(1).optional(), }); let ChatIntegrationActionExecutor = class ChatIntegrationActionExecutor { constructor(chatIntegrationService, integrationRegistry) { this.chatIntegrationService = chatIntegrationService; this.integrationRegistry = integrationRegistry; this.componentMapper = new component_mapper_1.ComponentMapper(); } async execute(params) { if (!params.descriptor.agentId) return (0, integration_helpers_1.connectionUnavailable)(); if (params.action === 'do_not_respond') { return this.doNotRespond(params); } const unsupportedAction = () => (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.UNSUPPORTED_ACTION, `The ${params.descriptor.integration.type} integration does not support ${params.action}.`); const integrationDef = this.integrationRegistry.get(params.descriptor.integration.type); if (integrationDef && !integrationDef.requiresChatInstance) { if (!integrationDef.executeAction) { return unsupportedAction(); } try { const result = await integrationDef.executeAction({ chat: undefined, descriptor: params.descriptor, action: params.action, input: params.input, currentMessageContext: params.currentMessageContext, }); return result ?? unsupportedAction(); } catch (error) { return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.ACTION_FAILED, error instanceof Error ? error.message : String(error)); } } const { credentialId } = params.descriptor.integration; if (!credentialId) return (0, integration_helpers_1.connectionUnavailable)(); let chat = this.chatIntegrationService.getChatInstance(params.descriptor.agentId, { type: params.descriptor.integration.type, credentialId, }); chat ??= await this.chatIntegrationService.getChatInstanceForTools(params.descriptor.agentId, params.descriptor.integration); if (!chat) return (0, integration_helpers_1.connectionUnavailable)(); try { if (params.action === 'respond') { return await this.respondInCurrentThread(chat, params); } if (params.action === 'send_dm') { return await this.sendDirectMessage(chat, params); } if (params.action === 'edit_message') { return await this.editMessageInCurrentThread(chat, params); } if (params.action === 'add_reaction') { return await this.addReactionToMessage(chat, params); } if (integrationDef?.executeAction) { const result = await integrationDef.executeAction({ chat, descriptor: params.descriptor, action: params.action, input: params.input, currentMessageContext: params.currentMessageContext, }); if (result !== undefined) return result; } if (params.action === 'send_channel_message') { return await this.sendChannelMessage(chat, params); } return unsupportedAction(); } catch (error) { return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.ACTION_FAILED, error instanceof Error ? error.message : String(error)); } } doNotRespond(params) { if (params.currentMessageContext?.replyExpectation !== 'optional') { return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.REPLY_REQUIRED, 'A reply is expected here — this is a direct message, a direct mention, or a conversation you were asked to join. Respond normally instead of staying silent.'); } return { ok: true, silent: true, note: 'No reply will be sent. End your turn now without writing any text.', }; } async addReactionToMessage(chat, params) { const adapter = chat.getAdapter(params.descriptor.integration.type); if (!supportsAddReaction(adapter)) { return (0, integration_helpers_1.unsupportedAction)(params.descriptor.integration.type, 'add_reaction'); } const input = addReactionInputSchema.parse(params.input); const currentMessageContext = params.currentMessageContext; const replyTargetForReaction = input.messageId !== undefined && input.messageId === currentMessageContext?.replyMessageId && (input.threadId === undefined || input.threadId === currentMessageContext.replyTarget?.threadId) ? currentMessageContext.replyTarget : undefined; const fallbackTarget = replyTargetForReaction ?? currentMessageContext?.target; const threadId = input.threadId ?? fallbackTarget?.threadId; const messageId = input.messageId ?? currentMessageContext?.messageId; if (!threadId || !messageId) { const platform = params.descriptor.integration.type; const displayLabel = `${platform.charAt(0).toUpperCase()}${platform.slice(1)}`; return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.NO_MESSAGE_CONTEXT, `${displayLabel} reactions require a messageId and threadId or current message context.`); } await adapter.addReaction(threadId, messageId, input.emoji); return { ok: true, reaction: { emoji: input.emoji, threadId, messageId }, messageContext: { integrationConnectionId: params.descriptor.integrationConnectionId, platform: params.descriptor.integration.type, target: replyTargetForReaction && currentMessageContext ? currentMessageContext.target : buildReactionTarget(params.descriptor.integration.type, threadId, fallbackTarget), messageId: replyTargetForReaction && currentMessageContext ? currentMessageContext.messageId : messageId, updatedAt: new Date().toISOString(), }, }; } async respondInCurrentThread(chat, params) { const input = exports.respondInputSchema.parse(params.input); const threadId = params.currentMessageContext?.target.threadId; if (!threadId) { return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.NO_MESSAGE_CONTEXT, 'There is no current message context. Use an explicit send action.'); } const replyTargetThreadId = params.currentMessageContext?.replyTarget?.threadId; const isAutomaticReplyTarget = params.currentMessageContext?.replyExpectation !== undefined && (replyTargetThreadId === undefined || replyTargetThreadId === threadId); if (!input.message.card && isAutomaticReplyTarget) { return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.ACTION_FAILED, 'Plain text is already delivered to this conversation as your normal reply — write the text directly in your reply instead of calling respond. Call respond only with message.card, or use an explicit send action for a different target.'); } const thread = chat.thread(threadId); await this.prepareSentThread(params.descriptor, thread); const sent = await thread.post(await this.toPostable(params.descriptor, input.message, params)); return { ok: true, messageContext: buildMessageContextFromSentMessage({ descriptor: params.descriptor, sent, target: params.currentMessageContext.target, }), }; } async sendDirectMessage(chat, params) { const input = sendDmInputSchema.parse(params.input); const thread = await chat.openDM(input.userId); await this.prepareSentThread(params.descriptor, thread); const sent = await thread.post(await this.toPostable(params.descriptor, input.message, params)); return { ok: true, messageContext: buildMessageContextFromSentMessage({ descriptor: params.descriptor, sent, target: { type: 'dm', userId: input.userId, threadId: thread.id }, }), }; } async editMessageInCurrentThread(chat, params) { const input = editMessageInputSchema.parse(params.input); const currentMessageContext = params.currentMessageContext; const threadId = currentMessageContext?.target.threadId; if (!currentMessageContext || !threadId) { return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.NO_MESSAGE_CONTEXT, 'There is no current conversation to edit. Send a message first, then try again.'); } const adapter = chat.getAdapter(params.descriptor.integration.type); if (!supportsMessageEditing(adapter)) { return (0, integration_helpers_1.integrationError)(integration_error_codes_1.INTEGRATION_ERROR_CODES.UNSUPPORTED_ACTION, `The ${params.descriptor.integration.type} integration can't edit messages. Use a supported action instead.`); } const edited = await adapter.editMessage(threadId, input.messageId, await this.toPostable(params.descriptor, input.message, params)); return { ok: true, messageContext: { ...currentMessageContext, messageId: edited.id, updatedAt: new Date().toISOString(), }, }; } async sendChannelMessage(chat, params) { const input = sendChannelMessageInputSchema.parse(params.input); const channelId = (0, integration_helpers_1.normalizePlatformId)(params.descriptor.integration.type, input.channelId); const channel = chat.channel(channelId); const sent = await channel.post(await this.toPostable(params.descriptor, input.message, params)); if (sent.threadId) { await this.prepareSentThread(params.descriptor, chat.thread(sent.threadId)); } return { ok: true, messageContext: buildMessageContextFromSentMessage({ descriptor: params.descriptor, sent, target: { type: 'channel', channelId, threadId: sent.threadId }, }), }; } async toPostable(descriptor, message, params) { const cardPayload = message.card; if (!cardPayload) return message.text ?? ''; if (params.awaitResponse && (!params.runId || !params.toolCallId)) { throw new Error('Interactive integration actions require runId and toolCallId.'); } const card = await this.componentMapper.toCard({ title: cardPayload.title ?? message.text, message: cardPayload.message, components: cardPayload.components, }, params.runId ?? '', params.toolCallId ?? '', component_mapper_1.INTERACTIVE_CARD_RESUME_JSON_SCHEMA, this.getShortenCallback(descriptor, params.runId && params.toolCallId ? { groupId: JSON.stringify([params.runId, params.toolCallId]) } : undefined), descriptor.integration.type); return { card }; } getShortenCallback(descriptor, metadata) { const { agentId, integration } = descriptor; if (!agentId) return undefined; const { credentialId } = integration; if (!credentialId) return undefined; return this.chatIntegrationService.getShortenCallback(agentId, { type: integration.type, credentialId }, metadata); } async prepareSentThread(descriptor, thread) { await this.integrationRegistry.get(descriptor.integration.type)?.prepareSentThread?.(thread); } }; exports.ChatIntegrationActionExecutor = ChatIntegrationActionExecutor; exports.ChatIntegrationActionExecutor = ChatIntegrationActionExecutor = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [chat_integration_service_1.ChatIntegrationService, agent_chat_integration_1.ChatIntegrationRegistry]) ], ChatIntegrationActionExecutor); function supportsMessageEditing(adapter) { return (0, is_record_1.isRecord)(adapter) && typeof adapter.editMessage === 'function'; } function supportsAddReaction(adapter) { return (0, is_record_1.isRecord)(adapter) && typeof adapter.addReaction === 'function'; } function buildMessageContextFromSentMessage(params) { return { integrationConnectionId: params.descriptor.integrationConnectionId, platform: params.descriptor.integration.type, target: params.target, messageId: params.sent.id, updatedAt: new Date().toISOString(), }; } function buildReactionTarget(platform, threadId, fallbackTarget) { if (fallbackTarget?.threadId === threadId) return fallbackTarget; const [threadPlatform, channel] = threadId.split(':'); const channelId = platform === 'slack' && threadPlatform === 'slack' && channel ? `${threadPlatform}:${channel}` : undefined; return { type: 'thread', threadId, ...(channelId ? { channelId } : {}) }; } //# sourceMappingURL=integration-action-executor.js.map