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