n8n
Version:
n8n Workflow Automation Tool
257 lines • 12.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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentKnowledgeService = void 0;
const api_types_1 = require("@n8n/api-types");
const ai_utilities_1 = require("@n8n/ai-utilities");
const backend_common_1 = require("@n8n/backend-common");
const di_1 = require("@n8n/di");
const typeorm_1 = require("@n8n/typeorm");
const generate_nano_id_1 = require("@n8n/utils/generate-nano-id");
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 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_knowledge_storage_1 = require("./agent-knowledge-storage");
const agent_knowledge_sandbox_service_1 = require("./agent-knowledge-sandbox.service");
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;
function isUniqueConstraintError(error) {
if (!(error instanceof typeorm_1.QueryFailedError))
return false;
const driverError = error.driverError;
if (!driverError || typeof driverError !== 'object')
return false;
const code = 'code' in driverError && typeof driverError.code === 'string' ? driverError.code : undefined;
if (code === '23505')
return true;
if (code === 'SQLITE_CONSTRAINT_UNIQUE')
return true;
if (code === 'SQLITE_CONSTRAINT' && /UNIQUE constraint/i.test(error.message))
return true;
return false;
}
let AgentKnowledgeService = class AgentKnowledgeService {
constructor(agentRepository, agentFileRepository, agentKnowledgeSandboxService, binaryDataService, binaryDataConfig, logger) {
this.agentRepository = agentRepository;
this.agentFileRepository = agentFileRepository;
this.agentKnowledgeSandboxService = agentKnowledgeSandboxService;
this.binaryDataService = binaryDataService;
this.binaryDataConfig = binaryDataConfig;
this.logger = logger;
}
async uploadFiles(agentId, projectId, files) {
try {
await this.ensureAgentBelongsToProject(agentId, projectId);
if (this.binaryDataConfig.mode === 'default') {
throw new n8n_workflow_1.OperationalError('Agent knowledge base requires a persisted binary data storage mode');
}
this.validateUploadMetadata(files);
await this.validateUploadBatch(agentId, files);
const uploadedFiles = [];
try {
for (const file of files) {
uploadedFiles.push(await this.storeAgentFile(agentId, file));
}
}
catch (error) {
await this.cleanupUploadedFiles(uploadedFiles);
throw error;
}
this.agentKnowledgeSandboxService.invalidateMirror(projectId, agentId);
this.agentKnowledgeSandboxService.prewarmMirrorInBackground(projectId, agentId);
return uploadedFiles.map((file) => (0, agent_knowledge_storage_1.toAgentFileDto)(file));
}
finally {
await this.cleanupUploadTempFiles(files);
}
}
async listFiles(agentId, projectId) {
await this.ensureAgentBelongsToProject(agentId, projectId);
const files = await this.agentFileRepository.findByAgentId(agentId);
return files.map((file) => (0, agent_knowledge_storage_1.toAgentFileDto)(file));
}
async warmSandbox(agentId, projectId) {
await this.ensureAgentBelongsToProject(agentId, projectId);
await this.agentKnowledgeSandboxService.warmSandbox(projectId, agentId);
}
async deleteFile(agentId, projectId, fileId) {
await this.ensureAgentBelongsToProject(agentId, projectId);
const file = await this.agentFileRepository.findByIdAndAgentId(fileId, agentId);
if (!file) {
return;
}
await this.agentFileRepository.delete({ id: fileId, agentId });
await this.binaryDataService.deleteManyByBinaryDataId([file.binaryDataId]).catch((error) => {
this.logger.warn('Failed to delete knowledge file binary data', {
agentId,
fileId: file.id,
error: error instanceof Error ? error.message : error,
});
});
this.agentKnowledgeSandboxService.invalidateMirror(projectId, agentId);
this.agentKnowledgeSandboxService.prewarmMirrorInBackground(projectId, agentId);
}
async deleteAllFilesForAgent(projectId, agentId) {
const files = await this.agentFileRepository.findByAgentId(agentId);
await this.agentFileRepository.delete({ agentId });
if (files.length > 0) {
await this.binaryDataService
.deleteManyByBinaryDataId(files.map((file) => file.binaryDataId))
.catch((error) => {
this.logger.warn('Failed to delete knowledge files binary data', {
agentId,
error: error instanceof Error ? error.message : error,
});
});
}
this.agentKnowledgeSandboxService.invalidateMirror(projectId, agentId);
}
async destroySandbox(projectId, agentId) {
await this.agentKnowledgeSandboxService.destroySandbox(projectId, agentId);
}
async storeAgentFile(agentId, file) {
const fileId = (0, generate_nano_id_1.generateNanoId)();
const storageFileName = (0, agent_knowledge_storage_1.storageFileNameForOriginalFileName)(file.originalname);
const content = await this.prepareUploadContent(file);
const binaryData = {
data: '',
mimeType: file.mimetype,
fileName: storageFileName,
};
const stored = await this.binaryDataService.store((0, agent_knowledge_storage_1.buildKnowledgeFileLocation)(agentId, fileId), content, binaryData);
if (!stored.id) {
throw new n8n_workflow_1.OperationalError('Agent knowledge base requires a persisted binary data storage mode');
}
try {
return await this.saveAgentFile(agentId, fileId, file, stored.id);
}
catch (error) {
await this.binaryDataService.deleteManyByBinaryDataId([stored.id]).catch(() => { });
if (isUniqueConstraintError(error)) {
throw this.duplicateFileNameError(file.originalname);
}
throw error;
}
}
async saveAgentFile(agentId, fileId, file, binaryDataId) {
const agentFile = this.agentFileRepository.create({
id: fileId,
agentId,
binaryDataId,
fileName: file.originalname,
mimeType: file.mimetype,
fileSizeBytes: file.size,
});
return await this.agentFileRepository.save(agentFile);
}
async prepareUploadContent(file) {
if (!file.path) {
throw new bad_request_error_1.BadRequestError('Uploaded file path is missing');
}
const extension = node_path_1.default.extname(file.originalname).toLowerCase();
if (extension === '.pdf') {
const extractedText = await this.extractPdfText(file.path);
return Buffer.from(extractedText, 'utf-8');
}
return (0, node_fs_1.createReadStream)(file.path);
}
async cleanupUploadedFiles(files) {
for (const file of files) {
await this.binaryDataService.deleteManyByBinaryDataId([file.binaryDataId]).catch(() => { });
await this.agentFileRepository.delete({ id: file.id, agentId: file.agentId }).catch(() => { });
}
}
async extractPdfText(filePath) {
const loader = new ai_utilities_1.N8nPdfLoader(filePath, { splitPages: false });
const documents = await loader.load();
const extractedText = documents
.map((document) => document.pageContent)
.join('\n\n')
.replaceAll('\u0000', '')
.trim();
if (!extractedText) {
throw new bad_request_error_1.BadRequestError('PDF contains no extractable text and cannot be added to knowledge');
}
return extractedText;
}
validateUploadMetadata(files) {
for (const file of files) {
this.validateMetadataLength('File name', file.originalname);
this.validateMetadataLength('MIME type', file.mimetype);
}
}
async validateUploadBatch(agentId, files) {
const existingFiles = await this.agentFileRepository.findByAgentId(agentId);
const existingTotalSizeBytes = existingFiles.reduce((total, file) => total + file.fileSizeBytes, 0);
const uploadTotalSizeBytes = files.reduce((total, file) => total + file.size, 0);
if (existingTotalSizeBytes + uploadTotalSizeBytes > api_types_1.MAX_AGENT_KNOWLEDGE_BASE_SIZE_BYTES) {
throw new bad_request_error_1.BadRequestError(`Knowledge base limit reached. The total size can't be larger than ${api_types_1.MAX_AGENT_KNOWLEDGE_BASE_SIZE_GB} GB.`);
}
const existingFileNames = new Set(existingFiles.map((file) => file.fileName));
const existingStorageNames = new Set(existingFiles.map((file) => (0, agent_knowledge_storage_1.storageFileNameForOriginalFileName)(file.fileName)));
const batchFileNames = new Set();
const batchStorageNames = new Set();
for (const file of files) {
if (batchFileNames.has(file.originalname)) {
throw this.duplicateFileNameError(file.originalname);
}
batchFileNames.add(file.originalname);
if (existingFileNames.has(file.originalname)) {
throw this.duplicateFileNameError(file.originalname);
}
const storageFileName = (0, agent_knowledge_storage_1.storageFileNameForOriginalFileName)(file.originalname);
if (batchStorageNames.has(storageFileName)) {
throw this.duplicateFileNameError(file.originalname);
}
batchStorageNames.add(storageFileName);
if (existingStorageNames.has(storageFileName)) {
throw this.duplicateFileNameError(file.originalname);
}
}
}
duplicateFileNameError(fileName) {
return new bad_request_error_1.BadRequestError(`A knowledge file named "${fileName}" already exists for this agent`);
}
validateMetadataLength(label, value) {
if (value.length > MAX_AGENT_FILE_METADATA_LENGTH) {
throw new bad_request_error_1.BadRequestError(`${label} must be ${MAX_AGENT_FILE_METADATA_LENGTH} characters or less`);
}
}
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`);
}
return agent;
}
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, agent_knowledge_sandbox_service_1.AgentKnowledgeSandboxService, n8n_core_1.BinaryDataService, n8n_core_1.BinaryDataConfig, backend_common_1.Logger])
], AgentKnowledgeService);
//# sourceMappingURL=agent-knowledge.service.js.map