UNPKG

n8n

Version:

n8n Workflow Automation Tool

345 lines • 16.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); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentChatController = void 0; const api_types_1 = require("@n8n/api-types"); const decorators_1 = require("@n8n/decorators"); const sanitize_filename_1 = require("@n8n/utils/files/sanitize-filename"); const n8n_core_1 = require("n8n-core"); const promises_1 = require("node:stream/promises"); const credentials_service_1 = require("../../credentials/credentials.service"); const bad_request_error_1 = require("../../errors/response-errors/bad-request.error"); const not_found_error_1 = require("../../errors/response-errors/not-found.error"); const agents_credential_provider_1 = require("./adapters/agents-credential-provider"); const agent_chat_attachment_service_1 = require("./agent-chat-attachment.service"); const agent_execution_orchestrator_service_1 = require("./agent-execution-orchestrator.service"); const agent_message_mapper_1 = require("./agent-message-mapper"); const agent_sse_stream_1 = require("./agent-sse-stream"); const agent_test_chat_service_1 = require("./agent-test-chat.service"); const agent_test_run_service_1 = require("./agent-test-run.service"); const agents_service_1 = require("./agents.service"); const agents_builder_service_1 = require("./builder/agents-builder.service"); const agent_memory_scope_1 = require("./utils/agent-memory-scope"); const inbound_attachments_1 = require("./utils/inbound-attachments"); const messages_envelope_1 = require("./utils/messages-envelope"); let AgentChatController = class AgentChatController { constructor(agentExecutionOrchestratorService, agentTestRunService, agentTestChatService, agentsBuilderService, credentialsService, agentsService, agentChatAttachmentService) { this.agentExecutionOrchestratorService = agentExecutionOrchestratorService; this.agentTestRunService = agentTestRunService; this.agentTestChatService = agentTestChatService; this.agentsBuilderService = agentsBuilderService; this.credentialsService = credentialsService; this.agentsService = agentsService; this.agentChatAttachmentService = agentChatAttachmentService; } async storeChatAttachments(params) { const { attachments, agentId, projectId, threadId, resourceId } = params; if (!attachments?.length) return undefined; const stored = []; try { for (const attachment of attachments) { const data = Buffer.from(attachment.data, 'base64'); if (data.byteLength === 0) { throw new bad_request_error_1.BadRequestError(`Attachment "${attachment.fileName}" is empty`); } if (data.byteLength > api_types_1.MAX_AGENT_CHAT_ATTACHMENT_SIZE_BYTES) { throw new bad_request_error_1.BadRequestError(`Attachment "${attachment.fileName}" exceeds the ${api_types_1.MAX_AGENT_CHAT_ATTACHMENT_SIZE_MB} MB limit`); } const mimeType = await (0, inbound_attachments_1.resolveInboundMimeType)(attachment.mimeType, data); const row = await this.agentChatAttachmentService.storeInbound({ agentId, projectId, threadId, resourceId, source: 'chat', fileName: attachment.fileName, mimeType, data, }); stored.push({ id: row.id, fileName: row.fileName, mimeType: row.mimeType, sizeBytes: row.fileSizeBytes, }); } } catch (error) { await this.agentChatAttachmentService.deleteByIds(stored.map((ref) => ref.id)); throw error; } return stored; } async chat(req, res, agentId, payload) { const { projectId } = req.params; const { message, sessionId, attachments } = payload; const credentialProvider = new agents_credential_provider_1.AgentsCredentialProvider(this.credentialsService, projectId, req.user); const { send } = (0, agent_sse_stream_1.initSseStream)(res); const abortController = new AbortController(); const abortOnClose = () => abortController.abort(); res.once('close', abortOnClose); let executionId; let storedAttachments; try { const prepared = await this.agentTestRunService.prepareDraftRun({ agentId, projectId, sessionId, credentialProvider, }); if (abortController.signal.aborted) return; if (prepared.status === 'session_not_found') { send({ type: 'error', message: 'Session not found' }); return; } if (prepared.status === 'agent_misconfigured') { send({ type: 'error', message: 'This agent is not ready to run yet.', errorCode: 'agent_misconfigured', missing: prepared.missing, }); return; } const threadId = prepared.sessionId; storedAttachments = await this.storeChatAttachments({ attachments, agentId, projectId, threadId, resourceId: (0, agent_memory_scope_1.draftChatMemoryResourceId)(req.user.id), }); const suspended = await (0, agent_sse_stream_1.pumpChunks)(this.agentTestRunService.streamDraftRun({ agentId, projectId, message, attachments: storedAttachments, user: req.user, sessionId: threadId, onExecutionRecorded: (id) => { executionId = id; }, abortSignal: abortController.signal, }), send); if (!suspended) { send({ type: 'done', sessionId: threadId, ...(executionId ? { executionId } : {}) }); } } catch (error) { if (!executionId && storedAttachments?.length) { await this.agentChatAttachmentService .deleteByIds(storedAttachments.map((ref) => ref.id)) .catch(() => { }); } if (!abortController.signal.aborted) { const errorMessage = error instanceof Error ? error.message : 'Chat failed'; send({ type: 'error', message: errorMessage }); } } finally { res.off('close', abortOnClose); res.end(); } } async chatResume(req, res, agentId, payload) { const { projectId } = req.params; const { runId, toolCallId, resumeData } = payload; const { send } = (0, agent_sse_stream_1.initSseStream)(res); const abortController = new AbortController(); const abortOnClose = () => abortController.abort(); res.once('close', abortOnClose); try { let executionId; const suspended = await (0, agent_sse_stream_1.pumpChunks)(this.agentExecutionOrchestratorService.resumeForChat({ agentId, projectId, runId, toolCallId, resumeData, user: req.user, usePublishedVersion: false, integrationType: api_types_1.N8N_CHAT_INTEGRATION_TYPE, onExecutionRecorded: (id) => { executionId = id; }, abortSignal: abortController.signal, }), send); if (!suspended) { send({ type: 'done', ...(executionId ? { executionId } : {}) }); } } catch (error) { if (!abortController.signal.aborted) { const errorMessage = error instanceof Error ? error.message : 'Resume failed'; send({ type: 'error', message: errorMessage }); } } finally { res.off('close', abortOnClose); res.end(); } } async cancelChatRun(req, _res, agentId, runId) { const { projectId } = req.params; const agent = await this.agentsService.findById(agentId, projectId); if (!agent) throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); const cancelled = await this.agentExecutionOrchestratorService.cancelChatRun({ agentId, runId, resourceId: (0, agent_memory_scope_1.draftChatMemoryResourceId)(req.user.id), }); return { cancelled }; } async getChatMessages(req) { const { projectId, agentId, threadId } = req.params; const agent = await this.agentsService.findById(agentId, projectId); if (!agent) throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); const history = await this.agentExecutionOrchestratorService.getConversationHistory({ threadId, projectId, agentId, }); const checkpoint = await this.agentsBuilderService.findOpenCheckpointForThread(agentId, threadId); if (!history) { if (checkpoint) return (0, messages_envelope_1.withOpenSuspensions)([], checkpoint); throw new not_found_error_1.NotFoundError(`Thread "${threadId}" not found`); } return (0, messages_envelope_1.withOpenSuspensions)(history, checkpoint, { appendInactiveCheckpointMessages: false, }); } async getTestChatMessages(req) { const { projectId, agentId } = req.params; const agent = await this.agentsService.findById(agentId, projectId); if (!agent) throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); const messages = await this.agentTestChatService.getTestChatMessages(agentId, req.user.id); const checkpoint = await this.agentsBuilderService.findOpenCheckpointForThread(agentId, (0, agent_test_chat_service_1.chatThreadId)(agentId, req.user.id)); return (0, messages_envelope_1.withOpenSuspensions)((0, agent_message_mapper_1.messagesToDto)(messages), checkpoint); } async getChatAttachment(req, res) { const { projectId, agentId, attachmentId } = req.params; const agent = await this.agentsService.findById(agentId, projectId); if (!agent) throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); const attachment = await this.agentChatAttachmentService.getForAgent(attachmentId, { agentId, projectId, }); if (!attachment) throw new not_found_error_1.NotFoundError(`Attachment "${attachmentId}" not found`); let stream; try { stream = await this.agentChatAttachmentService.getStream(attachment); } catch (error) { if (error instanceof n8n_core_1.FileNotFoundError) { throw new not_found_error_1.NotFoundError(`Attachment "${attachmentId}" is no longer available`); } throw error; } res.setHeader('Content-Type', attachment.mimeType); res.setHeader('Content-Length', attachment.fileSizeBytes); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Content-Security-Policy', (0, n8n_core_1.getHtmlSandboxCSP)()); if (!api_types_1.ViewableMimeTypes.includes(attachment.mimeType.toLowerCase())) { res.setHeader('Content-Disposition', `attachment; filename="${(0, sanitize_filename_1.sanitizeFilename)(attachment.fileName)}"`); } try { await (0, promises_1.pipeline)(stream, res); } catch (error) { if (error instanceof Error && 'code' in error && error.code === 'ERR_STREAM_PREMATURE_CLOSE') { return; } throw error; } } async clearTestChatMessages(req) { const { projectId, agentId } = req.params; const agent = await this.agentsService.findById(agentId, projectId); if (!agent) throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); await this.agentTestChatService.clearTestChatMessages(agentId, req.user.id); return { ok: true }; } }; exports.AgentChatController = AgentChatController; __decorate([ (0, decorators_1.Post)('/:agentId/chat', { usesTemplates: true }), (0, decorators_1.ProjectScope)('agent:execute'), __param(2, (0, decorators_1.Param)('agentId')), __param(3, decorators_1.Body), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object, String, api_types_1.AgentChatMessageDto]), __metadata("design:returntype", Promise) ], AgentChatController.prototype, "chat", null); __decorate([ (0, decorators_1.Post)('/:agentId/chat/resume', { usesTemplates: true }), (0, decorators_1.ProjectScope)('agent:execute'), __param(2, (0, decorators_1.Param)('agentId')), __param(3, decorators_1.Body), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object, String, api_types_1.AgentChatResumeDto]), __metadata("design:returntype", Promise) ], AgentChatController.prototype, "chatResume", null); __decorate([ (0, decorators_1.Delete)('/:agentId/chat/runs/:runId'), (0, decorators_1.ProjectScope)('agent:execute'), __param(2, (0, decorators_1.Param)('agentId')), __param(3, (0, decorators_1.Param)('runId')), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object, String, String]), __metadata("design:returntype", Promise) ], AgentChatController.prototype, "cancelChatRun", null); __decorate([ (0, decorators_1.Get)('/:agentId/chat/:threadId/messages'), (0, decorators_1.ProjectScope)('agent:read'), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], AgentChatController.prototype, "getChatMessages", null); __decorate([ (0, decorators_1.Get)('/:agentId/chat/messages'), (0, decorators_1.ProjectScope)('agent:read'), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], AgentChatController.prototype, "getTestChatMessages", null); __decorate([ (0, decorators_1.Get)('/:agentId/chat/attachments/:attachmentId'), (0, decorators_1.ProjectScope)('agent:read'), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], AgentChatController.prototype, "getChatAttachment", null); __decorate([ (0, decorators_1.Delete)('/:agentId/chat/messages'), (0, decorators_1.ProjectScope)('agent:update'), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], AgentChatController.prototype, "clearTestChatMessages", null); exports.AgentChatController = AgentChatController = __decorate([ (0, decorators_1.RestController)('/projects/:projectId/agents/v2'), __metadata("design:paramtypes", [agent_execution_orchestrator_service_1.AgentExecutionOrchestratorService, agent_test_run_service_1.AgentTestRunService, agent_test_chat_service_1.AgentTestChatService, agents_builder_service_1.AgentsBuilderService, credentials_service_1.CredentialsService, agents_service_1.AgentsService, agent_chat_attachment_service_1.AgentChatAttachmentService]) ], AgentChatController); //# sourceMappingURL=agent-chat.controller.js.map