UNPKG

n8n

Version:

n8n Workflow Automation Tool

300 lines • 14.1 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); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentKnowledgeService = void 0; const di_1 = require("@n8n/di"); const utils_1 = require("@n8n/utils"); const n8n_core_1 = require("n8n-core"); const n8n_workflow_1 = require("n8n-workflow"); const node_fs_1 = require("node:fs"); const promises_1 = require("node:fs/promises"); const node_path_1 = __importDefault(require("node:path")); const promises_2 = require("node:stream/promises"); 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 agent_file_repository_1 = require("./repositories/agent-file.repository"); const agent_repository_1 = require("./repositories/agent.repository"); const MAX_AGENT_FILE_METADATA_LENGTH = 255; const MAX_WORKSPACE_FILES = 2_000; const MAX_WORKSPACE_BYTES = 2 * 1024 * 1024 * 1024; let AgentKnowledgeService = class AgentKnowledgeService { constructor(agentRepository, agentFileRepository, binaryDataService) { this.agentRepository = agentRepository; this.agentFileRepository = agentFileRepository; this.binaryDataService = binaryDataService; } async uploadFiles(agentId, projectId, files) { await this.ensureAgentBelongsToProject(agentId, projectId); const storedFiles = []; try { for (const file of files) { storedFiles.push(await this.storeFile(agentId, file)); } } catch (error) { await this.cleanupStoredFiles(storedFiles).catch(() => { }); throw error; } finally { await this.cleanupUploadTempFiles(files); } return storedFiles.map((file) => this.toDto(file)); } async listFiles(agentId, projectId) { await this.ensureAgentBelongsToProject(agentId, projectId); const files = await this.agentFileRepository.findByAgentId(agentId); return files.map((file) => this.toDto(file)); } async listWorkspaceFiles(agentId, projectId) { await this.ensureAgentBelongsToProject(agentId, projectId); const files = await this.agentFileRepository.findByAgentId(agentId); return files.map((file) => this.toWorkspaceFile(file)); } async deleteFile(agentId, projectId, fileId) { await this.ensureAgentBelongsToProject(agentId, projectId); const file = await this.agentFileRepository.findByIdAndAgentId(fileId, agentId); if (!file) { throw new not_found_error_1.NotFoundError(`Agent file "${fileId}" not found`); } await this.binaryDataService.deleteManyByBinaryDataId([file.binaryDataId]); await this.agentFileRepository.delete({ id: fileId, agentId }); } async deleteAllFilesForAgent(agentId) { const files = await this.agentFileRepository.findByAgentId(agentId); if (files.length === 0) return; await this.binaryDataService.deleteManyByBinaryDataId(files.map((file) => file.binaryDataId)); await this.agentFileRepository.delete({ agentId }); } async resolveWorkspaceFiles(agentId, projectId, fileReferences) { await this.ensureAgentBelongsToProject(agentId, projectId); const files = this.filterFilesForWorkspace(await this.agentFileRepository.findByAgentId(agentId), fileReferences); this.assertWorkspaceWithinLimits(files); return files.map((file) => this.toWorkspaceFile(file)); } async materializeWorkspace(agentId, projectId, workspaceRoot, options = {}) { await this.ensureAgentBelongsToProject(agentId, projectId); await (0, promises_1.mkdir)(workspaceRoot, { recursive: true }); const files = this.filterFilesForWorkspace(await this.agentFileRepository.findByAgentId(agentId), options.fileReferences); this.assertWorkspaceWithinLimits(files); const materializedFiles = []; for (const file of files) { const relativePath = this.getWorkspaceRelativePath(file); const targetPath = node_path_1.default.join(workspaceRoot, relativePath); const contentStream = await this.binaryDataService.getAsStream(file.binaryDataId); await (0, promises_2.pipeline)(contentStream, (0, node_fs_1.createWriteStream)(targetPath)); materializedFiles.push(this.toWorkspaceFile(file)); } return materializedFiles; } async ensureAgentBelongsToProject(agentId, projectId) { const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId); if (!agent) { throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`); } } async storeFile(agentId, file) { let storedBinaryDataId; try { const fileId = (0, utils_1.generateNanoId)(); const fileName = (0, utils_1.sanitizeFilename)(Buffer.from(file.originalname, 'latin1').toString('utf8'), MAX_AGENT_FILE_METADATA_LENGTH + 1); this.validateMetadataLength('File name', fileName); const buffer = file.buffer ?? (await (0, promises_1.readFile)(file.path)); const storedContent = await this.prepareStoredContent(fileName, file.mimetype, buffer); this.validateMetadataLength('MIME type', storedContent.mimeType); const binaryData = { data: '', mimeType: storedContent.mimeType, fileName: storedContent.fileName, fileSize: `${storedContent.buffer.length}`, bytes: storedContent.buffer.length, fileExtension: storedContent.fileExtension, }; const storedBinaryData = await this.binaryDataService.store(n8n_core_1.FileLocation.ofCustom({ sourceType: 'agent_file', sourceId: fileId, pathSegments: ['agents', agentId, 'files', fileId], }), storedContent.buffer, binaryData); if (!storedBinaryData.id) { throw new n8n_workflow_1.UnexpectedError('Agent file upload requires persisted binary data'); } storedBinaryDataId = storedBinaryData.id; const agentFile = this.agentFileRepository.create({ id: fileId, agentId, binaryDataId: storedBinaryDataId, fileName, mimeType: storedContent.mimeType, fileSizeBytes: buffer.length, }); return await this.agentFileRepository.save(agentFile); } catch (error) { if (storedBinaryDataId) { await this.binaryDataService.deleteManyByBinaryDataId([storedBinaryDataId]); } throw error; } finally { if (file.path) { await (0, promises_1.unlink)(file.path).catch(() => { }); } } } toDto(file) { return { id: file.id, agentId: file.agentId, fileName: file.fileName, mimeType: file.mimeType, fileSizeBytes: file.fileSizeBytes, createdAt: file.createdAt.toISOString(), }; } toWorkspaceFile(file) { return { id: file.id, fileName: file.fileName, mimeType: file.mimeType, fileSizeBytes: file.fileSizeBytes, relativePath: this.getWorkspaceRelativePath(file), }; } assertWorkspaceWithinLimits(files) { if (files.length > MAX_WORKSPACE_FILES) { throw new bad_request_error_1.BadRequestError(`Cannot materialize ${files.length} knowledge files at once (limit ${MAX_WORKSPACE_FILES}). Pass file references to narrow the operation.`); } const totalBytes = files.reduce((total, file) => total + file.fileSizeBytes, 0); if (totalBytes > MAX_WORKSPACE_BYTES) { throw new bad_request_error_1.BadRequestError(`Cannot materialize ${totalBytes} bytes of knowledge files at once (limit ${MAX_WORKSPACE_BYTES}). Pass file references to narrow the operation.`); } } filterFilesForWorkspace(files, fileReferences) { if (!fileReferences) return files; const requested = new Set(fileReferences); return files.filter((file) => requested.has(file.id) || requested.has(this.getWorkspaceRelativePath(file)) || requested.has(file.fileName)); } getWorkspaceRelativePath(file) { const extension = node_path_1.default.extname(file.fileName).toLowerCase(); if (extension === '.pdf' && file.mimeType === 'text/plain') { return `${file.id}.pdf.txt`; } return `${file.id}${node_path_1.default.extname(file.fileName)}`; } async prepareStoredContent(fileName, mimeType, buffer) { if (!this.isPdf(fileName, mimeType)) { return { buffer, mimeType: mimeType || 'application/octet-stream', fileName, fileExtension: fileName.split('.').pop(), }; } const extractedText = await this.extractPdfText(fileName, buffer); const extractedBuffer = Buffer.from(extractedText, 'utf8'); return { buffer: extractedBuffer, mimeType: 'text/plain', fileName: `${fileName}.txt`, fileExtension: 'txt', }; } isPdf(fileName, mimeType) { return node_path_1.default.extname(fileName).toLowerCase() === '.pdf' || mimeType === 'application/pdf'; } async extractPdfText(fileName, buffer) { const { PDFParse } = await Promise.resolve().then(() => __importStar(require('pdf-parse'))); const parser = new PDFParse({ data: buffer }); try { const result = await parser.getText(); const text = result.text.trim(); if (!text) { throw new bad_request_error_1.BadRequestError(`PDF "${fileName}" contains no extractable text and cannot be added to knowledge`); } return text; } catch (error) { if (error instanceof bad_request_error_1.BadRequestError) throw error; const message = error instanceof Error ? error.message : 'unknown error'; throw new bad_request_error_1.BadRequestError(`Failed to extract text from PDF "${fileName}": ${message}`); } finally { await parser.destroy(); } } validateMetadataLength(label, value) { if (value.length <= MAX_AGENT_FILE_METADATA_LENGTH) return; throw new bad_request_error_1.BadRequestError(`${label} must be ${MAX_AGENT_FILE_METADATA_LENGTH} characters or less`); } async cleanupStoredFiles(files) { if (files.length === 0) return; await this.agentFileRepository.delete(files.map((file) => file.id)); await this.binaryDataService.deleteManyByBinaryDataId(files.map((file) => file.binaryDataId)); } async cleanupUploadTempFiles(files) { await Promise.all(files.map(async (file) => await this.cleanupUploadTempFile(file))); } async cleanupUploadTempFile(file) { if (!file.path) return; await (0, promises_1.unlink)(file.path).catch(() => { }); } }; exports.AgentKnowledgeService = AgentKnowledgeService; exports.AgentKnowledgeService = AgentKnowledgeService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [agent_repository_1.AgentRepository, agent_file_repository_1.AgentFileRepository, n8n_core_1.BinaryDataService]) ], AgentKnowledgeService); //# sourceMappingURL=agent-knowledge.service.js.map