n8n
Version:
n8n Workflow Automation Tool
300 lines • 14.1 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);
};
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