n8n
Version:
n8n Workflow Automation Tool
684 lines • 31.8 kB
JavaScript
"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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgentKnowledgeSandboxService = exports.AGENT_KNOWLEDGE_SANDBOX_NAME_PREFIX = void 0;
const agents_1 = require("@n8n/agents");
const sandbox_1 = require("@n8n/agents/sandbox");
const backend_common_1 = require("@n8n/backend-common");
const config_1 = require("@n8n/config");
const constants_1 = require("@n8n/constants");
const di_1 = require("@n8n/di");
const n8n_core_1 = require("n8n-core");
const n8n_workflow_1 = require("n8n-workflow");
const nanoid_1 = require("nanoid");
const node_crypto_1 = require("node:crypto");
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 ai_service_1 = require("../../services/ai.service");
const ai_service_retry_1 = require("../../utils/ai-service-retry");
const ttl_map_1 = require("../../utils/ttl-map");
const agent_knowledge_commands_1 = require("./agent-knowledge-commands");
const agent_knowledge_gate_1 = require("./agent-knowledge-gate");
const agent_knowledge_retrieval_1 = require("./agent-knowledge-retrieval");
const agent_knowledge_storage_1 = require("./agent-knowledge-storage");
const agent_file_repository_1 = require("./repositories/agent-file.repository");
const agent_repository_1 = require("./repositories/agent.repository");
exports.AGENT_KNOWLEDGE_SANDBOX_NAME_PREFIX = 'agent-';
const MAX_SANDBOX_ERROR_DETAIL_CHARS = 2_000;
const LABEL_KNOWLEDGE_BASE = 'n8n-agents-knowledgebase';
const LABEL_PROJECT_ID = 'n8n-project-id';
const LABEL_AGENT_ID = 'n8n-agent-id';
const SANDBOX_STATE_STARTED = 'started';
const DEAD_SANDBOX_STATES = new Set([
'destroyed',
'destroying',
'error',
'build_failed',
]);
const DEFAULT_SANDBOX_IMAGE = 'daytonaio/sandbox:0.5.0';
const AUTO_STOP_INTERVAL_MINUTES = 5;
const MIRROR_MANIFEST_HASH_TTL_MS = 30 * constants_1.Time.minutes.toMilliseconds;
const MIRROR_DISK_FIT_WARNING_BYTES = 2 * 1024 * 1024 * 1024;
const MIRROR_UPLOAD_BATCH_BYTES = 64 * 1024 * 1024;
function emptySearchKnowledgeResult(outputMode, limit) {
if (outputMode === 'files_with_matches') {
return { outputMode, files: [], limit, hasMore: false, truncated: false };
}
if (outputMode === 'count') {
return { outputMode, counts: [], limit, hasMore: false, truncated: false };
}
return { outputMode, matches: [], limit, hasMore: false, truncated: false };
}
function buildSandboxScopeKey(projectId, agentId) {
return `${projectId}:${agentId}`;
}
function buildSandboxName(scope) {
return `${exports.AGENT_KNOWLEDGE_SANDBOX_NAME_PREFIX}${scope.instanceId}-${scope.projectId}-${scope.agentId}`.toLowerCase();
}
function extractCommandOutput(result) {
return result.artifacts?.stdout ?? result.result ?? '';
}
function parseManifestNames(output) {
return output
.split(/\r?\n/)
.map((line) => line.trim())
.filter(Boolean);
}
function hashManifestNames(names) {
return (0, node_crypto_1.createHash)('sha256')
.update(JSON.stringify([...names].sort()))
.digest('hex');
}
function isDaytonaNotFoundError(error) {
const { DaytonaNotFoundError } = (0, sandbox_1.loadDaytona)();
return error instanceof DaytonaNotFoundError;
}
function buildScopeLabels(projectId, agentId) {
return {
[LABEL_KNOWLEDGE_BASE]: 'true',
[LABEL_PROJECT_ID]: projectId,
[LABEL_AGENT_ID]: agentId,
};
}
function isUsableSandbox(sandbox) {
const state = sandbox.state;
if (!state)
return true;
return !DEAD_SANDBOX_STATES.has(state);
}
function truncateSandboxErrorDetail(value) {
if (value.length <= MAX_SANDBOX_ERROR_DETAIL_CHARS)
return value;
return `${value.slice(0, MAX_SANDBOX_ERROR_DETAIL_CHARS)}...[truncated]`;
}
function sanitizeSandboxErrorDetail(value) {
return truncateSandboxErrorDetail((0, agents_1.redactText)(value).text.trimEnd());
}
function formatSandboxCommandFailure(operation, result) {
const stderrText = sanitizeSandboxErrorDetail(result.stderr);
const stdoutText = sanitizeSandboxErrorDetail(result.stdout);
const parts = [`Agent knowledge ${operation} failed`, `exitCode=${result.exitCode}`];
parts.push(stderrText ? `stderr=${stderrText}` : 'stderr=<empty>');
parts.push(stdoutText ? `stdout=${stdoutText}` : 'stdout=<empty>');
return parts.join('; ');
}
function assertKnowledgeFilesDirectoryAvailable(operation, result) {
if (result.exitCode !== agent_knowledge_commands_1.KNOWLEDGE_FILES_DIR_UNAVAILABLE_EXIT_CODE)
return;
throw new n8n_workflow_1.OperationalError(`Agent knowledge ${operation} failed because the uploaded knowledge files directory is unavailable in the sandbox`);
}
let AgentKnowledgeSandboxService = class AgentKnowledgeSandboxService {
constructor(agentsConfig, logger, aiService, instanceSettings, agentFileRepository, agentRepository, binaryDataService) {
this.agentsConfig = agentsConfig;
this.logger = logger;
this.aiService = aiService;
this.instanceSettings = instanceSettings;
this.agentFileRepository = agentFileRepository;
this.agentRepository = agentRepository;
this.binaryDataService = binaryDataService;
this.pendingSandboxAcquisitions = new Map();
this.mirrorManifestHashes = new ttl_map_1.TtlMap(MIRROR_MANIFEST_HASH_TTL_MS);
this.pendingMirrorSyncs = new Map();
}
async warmSandbox(projectId, agentId) {
this.assertKnowledgeConfiguration(projectId, agentId);
await this.acquireSandbox(projectId, agentId);
}
async destroySandbox(projectId, agentId) {
if (!this.isKnowledgeBaseEnabled())
return;
try {
const { Daytona } = (0, sandbox_1.loadDaytona)();
const connection = await this.resolveDaytonaConnection(projectId);
const daytona = new Daytona({
apiUrl: connection.apiUrl,
apiKey: connection.apiKey,
});
const name = buildSandboxName({
instanceId: this.instanceSettings.instanceId,
projectId,
agentId,
});
const timeoutSeconds = Math.ceil(this.agentsConfig.sandboxTimeout / 1000);
const sandbox = await daytona.get(name);
await sandbox.delete(timeoutSeconds);
}
catch (error) {
this.logger.warn('Failed to destroy agent knowledge sandbox', {
projectId,
agentId,
error: error instanceof Error ? error.message : String(error),
});
}
}
async searchKnowledge(projectId, agentId, request) {
const validatedRequest = (0, agent_knowledge_retrieval_1.parseSearchKnowledgeRequest)(request);
const references = await this.loadKnowledgeReferenceLookup(projectId, agentId);
const outputMode = validatedRequest.output_mode ?? 'content';
const limit = validatedRequest.head_limit ?? agent_knowledge_retrieval_1.DEFAULT_SEARCH_TEXT_LIMIT;
if (references.files.length === 0) {
return emptySearchKnowledgeResult(outputMode, limit);
}
const scopedFilesByPath = new Map();
for (const path of validatedRequest.path ?? []) {
const file = this.resolveOptionalFile({ file: path }, references);
if (!file) {
throw new bad_request_error_1.BadRequestError('Knowledge file not found');
}
scopedFilesByPath.set(file.file, file);
}
const scopedFiles = [...scopedFilesByPath.values()];
const command = (0, agent_knowledge_commands_1.buildSearchKnowledgeCommand)(validatedRequest, scopedFiles.map((file) => file.file));
const result = await this.executeKnowledgeOperation(projectId, agentId, command, references.files);
assertKnowledgeFilesDirectoryAvailable('search', result);
if (result.exitCode === 1) {
return emptySearchKnowledgeResult(outputMode, limit);
}
if (result.exitCode !== 0) {
throw new n8n_workflow_1.OperationalError(formatSandboxCommandFailure('search', result));
}
if (outputMode === 'files_with_matches') {
const parsed = (0, agent_knowledge_commands_1.parseRipgrepFilesOutput)(result.stdout, references.byFile);
const files = parsed.files.slice(0, limit);
return {
outputMode,
files,
limit,
hasMore: parsed.files.length > limit,
truncated: parsed.incomplete,
};
}
if (outputMode === 'count') {
const parsed = (0, agent_knowledge_commands_1.parseRipgrepCountOutput)(result.stdout, references.byFile);
const counts = parsed.counts.slice(0, limit);
return {
outputMode,
counts,
limit,
hasMore: parsed.counts.length > limit,
truncated: parsed.incomplete,
};
}
const parsed = (0, agent_knowledge_commands_1.parseRipgrepOutput)(result.stdout, references.byFile, (0, agent_knowledge_commands_1.getSearchContextWindow)(validatedRequest));
const matches = parsed.matches.slice(0, limit);
return {
outputMode,
matches,
limit,
hasMore: parsed.matches.length > limit,
truncated: parsed.incomplete,
};
}
async globKnowledgeFiles(projectId, agentId, request) {
const validatedRequest = (0, agent_knowledge_retrieval_1.parseGlobKnowledgeFilesRequest)(request);
const references = await this.loadKnowledgeReferenceLookup(projectId, agentId);
const limit = validatedRequest.limit ?? agent_knowledge_retrieval_1.DEFAULT_GLOB_FILES_LIMIT;
const offset = validatedRequest.offset ?? 0;
if (references.files.length === 0) {
return { files: [], limit, offset, hasMore: false };
}
const matches = matchKnowledgeFilesByGlob(references.files, validatedRequest);
return {
files: matches.slice(offset, offset + limit),
limit,
offset,
hasMore: matches.length > offset + limit,
};
}
async readKnowledge(projectId, agentId, request) {
const validatedRequest = (0, agent_knowledge_retrieval_1.parseReadKnowledgeRequest)(request);
const references = await this.loadKnowledgeReferenceLookup(projectId, agentId);
const file = this.resolveRequiredFile(validatedRequest, references);
const command = (0, agent_knowledge_commands_1.buildReadKnowledgeCommand)(file.file, validatedRequest);
const result = await this.executeKnowledgeOperation(projectId, agentId, command, references.files);
assertKnowledgeFilesDirectoryAvailable('read', result);
if (result.exitCode !== 0) {
throw new n8n_workflow_1.OperationalError(formatSandboxCommandFailure('read', result));
}
const parsed = (0, agent_knowledge_commands_1.parseReadKnowledgeOutput)(result.stdout, file, validatedRequest);
return {
file: file.file,
fileId: file.fileId,
displayName: file.displayName,
ranges: parsed.ranges,
truncated: parsed.truncated,
};
}
async executeKnowledgeOperation(projectId, agentId, command, files) {
const timeoutSeconds = Math.ceil(this.agentsConfig.sandboxTimeout / 1000);
const scopedCommand = (0, agent_knowledge_commands_1.buildScopedKnowledgeShellCommand)(command);
const sandbox = await this.acquireSandbox(projectId, agentId);
const sandboxName = buildSandboxName({
instanceId: this.instanceSettings.instanceId,
projectId,
agentId,
});
await this.ensureMirrorSynced(sandbox, files, sandboxName);
const result = await sandbox.process.executeCommand(scopedCommand, undefined, undefined, timeoutSeconds);
return {
exitCode: result.exitCode,
stdout: result.artifacts?.stdout ?? result.result ?? '',
stderr: result.artifacts &&
'stderr' in result.artifacts &&
typeof result.artifacts.stderr === 'string'
? result.artifacts.stderr
: '',
};
}
async ensureMirrorSynced(sandbox, files, sandboxName) {
const expectedHash = hashManifestNames(files.map((file) => file.file));
if (this.mirrorManifestHashes.get(sandboxName) === expectedHash) {
return;
}
let pending = this.pendingMirrorSyncs.get(sandboxName);
if (!pending) {
pending = this.syncMirror(sandbox, files, expectedHash, sandboxName).finally(() => {
this.pendingMirrorSyncs.delete(sandboxName);
});
this.pendingMirrorSyncs.set(sandboxName, pending);
}
await pending;
}
async syncMirror(sandbox, files, expectedHash, sandboxName) {
if (this.mirrorManifestHashes.get(sandboxName) === expectedHash)
return;
const manifestResult = await sandbox.process.executeCommand((0, agent_knowledge_commands_1.buildReadMirrorManifestCommand)(), undefined, undefined, agent_knowledge_commands_1.MIRROR_SYNC_TIMEOUT_SECONDS);
const present = parseManifestNames(extractCommandOutput(manifestResult));
const expectedNames = files.map((file) => file.file);
const expectedSet = new Set(expectedNames);
const presentSet = new Set(present);
const toCopy = expectedNames.filter((name) => !presentSet.has(name));
const toDelete = present.filter((name) => !expectedSet.has(name));
if (toCopy.length === 0 && toDelete.length === 0) {
this.mirrorManifestHashes.set(sandboxName, expectedHash);
return;
}
for (const name of [...expectedNames, ...toDelete]) {
(0, agent_knowledge_storage_1.assertKnowledgePathSegment)(name, 'knowledge mirror file name');
}
if (present.length === 0 && toCopy.length > 0) {
const totalBytes = files.reduce((total, file) => total + file.fileSizeBytes, 0);
if (totalBytes > MIRROR_DISK_FIT_WARNING_BYTES) {
this.logger.warn('Agent knowledge mirror copy exceeds the disk-fit guard threshold', {
sandboxName,
totalBytes,
});
}
}
const filesByName = new Map(files.map((file) => [file.file, file]));
const copiedNames = await this.uploadMirrorFiles(sandbox, toCopy, filesByName, sandboxName);
const finalManifestNames = expectedNames.filter((name) => copiedNames.has(name) || presentSet.has(name));
const syncResult = await sandbox.process.executeCommand((0, agent_knowledge_commands_1.buildMirrorFinalizeCommand)([...copiedNames], toDelete, finalManifestNames), undefined, undefined, agent_knowledge_commands_1.MIRROR_SYNC_TIMEOUT_SECONDS);
if (syncResult.exitCode !== 0) {
throw new n8n_workflow_1.OperationalError(`Agent knowledge mirror sync failed: exitCode=${syncResult.exitCode}; output=${sanitizeSandboxErrorDetail(extractCommandOutput(syncResult))}`);
}
this.mirrorManifestHashes.set(sandboxName, hashManifestNames(finalManifestNames));
}
async uploadMirrorFiles(sandbox, names, filesByName, sandboxName) {
const copiedNames = new Set();
if (names.length === 0)
return copiedNames;
await sandbox.fs.createFolder(agent_knowledge_storage_1.KNOWLEDGE_MIRROR_FILES_DIR, '755');
let batch = [];
let batchNames = [];
let batchBytes = 0;
const flushBatch = async () => {
if (batch.length === 0)
return;
await sandbox.fs.uploadFiles(batch);
for (const name of batchNames)
copiedNames.add(name);
batch = [];
batchNames = [];
batchBytes = 0;
};
for (const name of names) {
const file = filesByName.get(name);
if (!file)
continue;
try {
const buffer = await this.binaryDataService.getAsBuffer({
id: file.binaryDataId,
data: '',
mimeType: file.mimeType,
});
batch.push({ source: buffer, destination: `${agent_knowledge_storage_1.KNOWLEDGE_MIRROR_FILES_DIR}/.tmp-${name}` });
batchNames.push(name);
batchBytes += buffer.length;
}
catch (error) {
this.logger.warn('Failed to load agent knowledge file for mirror sync', {
sandboxName,
file: name,
error: error instanceof Error ? error.message : String(error),
});
continue;
}
if (batchBytes >= MIRROR_UPLOAD_BATCH_BYTES) {
await flushBatch();
}
}
await flushBatch();
return copiedNames;
}
prewarmMirrorInBackground(projectId, agentId) {
void (async () => {
const references = await this.loadKnowledgeReferenceLookup(projectId, agentId);
const sandbox = await this.acquireSandbox(projectId, agentId);
const sandboxName = buildSandboxName({
instanceId: this.instanceSettings.instanceId,
projectId,
agentId,
});
await this.ensureMirrorSynced(sandbox, references.files, sandboxName);
})().catch((error) => {
this.logger.warn('Agent knowledge mirror pre-warm failed', {
projectId,
agentId,
error: error instanceof Error ? error.message : String(error),
});
});
}
invalidateMirror(projectId, agentId) {
const sandboxName = buildSandboxName({
instanceId: this.instanceSettings.instanceId,
projectId,
agentId,
});
this.mirrorManifestHashes.delete(sandboxName);
}
async loadKnowledgeReferenceLookup(projectId, agentId) {
await this.assertKnowledgeAccess(projectId, agentId);
const files = await this.loadKnowledgeFileReferences(agentId);
return {
files,
byFile: new Map(files.map((file) => [file.file, file])),
byId: new Map(files.map((file) => [file.fileId, file])),
};
}
async loadKnowledgeFileReferences(agentId) {
const files = await this.agentFileRepository.findByAgentId(agentId);
return files.map((file) => ({
file: (0, agent_knowledge_storage_1.storageFileNameForOriginalFileName)(file.fileName),
fileId: file.id,
binaryDataId: file.binaryDataId,
displayName: file.fileName,
mimeType: file.mimeType,
fileSizeBytes: file.fileSizeBytes,
createdAt: file.createdAt.toISOString(),
}));
}
resolveRequiredFile(request, references) {
const file = this.resolveOptionalFile(request, references);
if (!file) {
throw new bad_request_error_1.BadRequestError('Knowledge file not found');
}
return file;
}
resolveOptionalFile(request, references) {
if (!request.file && !request.fileId)
return undefined;
if (request.file && request.fileId) {
const normalized = (0, agent_knowledge_retrieval_1.assertValidKnowledgeFilePath)(request.file);
const fileByPath = references.byFile.get(normalized);
const fileById = references.byId.get(request.fileId);
if (!fileByPath || !fileById || fileByPath.fileId !== fileById.fileId) {
throw new bad_request_error_1.BadRequestError('Knowledge file not found');
}
return fileByPath;
}
if (request.file) {
const normalized = (0, agent_knowledge_retrieval_1.assertValidKnowledgeFilePath)(request.file);
const file = references.byFile.get(normalized);
if (!file) {
throw new bad_request_error_1.BadRequestError('Knowledge file not found');
}
return file;
}
const file = references.byId.get(request.fileId ?? '');
if (!file) {
throw new bad_request_error_1.BadRequestError('Knowledge file not found');
}
return file;
}
async acquireSandbox(projectId, agentId) {
const cacheKey = buildSandboxScopeKey(projectId, agentId);
let pending = this.pendingSandboxAcquisitions.get(cacheKey);
if (!pending) {
pending = this.acquireSandboxFresh(projectId, agentId).finally(() => {
this.pendingSandboxAcquisitions.delete(cacheKey);
});
this.pendingSandboxAcquisitions.set(cacheKey, pending);
}
return await pending;
}
async acquireSandboxFresh(projectId, agentId) {
const agent = await this.agentRepository.findByIdAndProjectId(agentId, projectId);
if (!agent) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
const { Daytona } = (0, sandbox_1.loadDaytona)();
const connection = await this.resolveDaytonaConnection(projectId);
const daytona = new Daytona({
apiUrl: connection.apiUrl,
apiKey: connection.apiKey,
});
const labels = buildScopeLabels(projectId, agentId);
const timeoutSeconds = Math.ceil(this.agentsConfig.sandboxTimeout / 1000);
const name = buildSandboxName({
instanceId: this.instanceSettings.instanceId,
projectId,
agentId,
});
const sandboxByName = await this.resolveSandboxByName(daytona, name, timeoutSeconds, connection);
if (sandboxByName) {
this.logger.debug('Reused agent knowledge sandbox', { projectId, agentId, name });
return sandboxByName;
}
const image = connection.image;
const baseCreateParams = {
name,
labels,
language: 'typescript',
ephemeral: this.agentsConfig.sandboxEphemeral,
autoStopInterval: AUTO_STOP_INTERVAL_MINUTES,
};
let sandbox;
if (connection.snapshot) {
try {
sandbox = await daytona.create({ ...baseCreateParams, snapshot: connection.snapshot }, { timeout: timeoutSeconds });
}
catch (error) {
if (connection.mode === 'proxy')
throw error;
this.logger.warn('Agent knowledge sandbox create from snapshot failed; falling back to image', {
projectId,
agentId,
snapshotName: connection.snapshot,
error: error instanceof Error ? error.message : String(error),
});
sandbox = await daytona.create({ ...baseCreateParams, image }, { timeout: timeoutSeconds });
}
}
else {
if (connection.mode === 'proxy') {
throw new n8n_workflow_1.OperationalError('Agent knowledge sandbox requires a snapshot when Daytona is reached through the AI service proxy. Set N8N_AGENTS_AI_SANDBOX_SNAPSHOT to a snapshot available to the instance.');
}
sandbox = await daytona.create({ ...baseCreateParams, image }, { timeout: timeoutSeconds });
}
this.logger.debug('Created agent knowledge sandbox', { projectId, agentId, name });
return sandbox;
}
async resolveDaytonaConnection(projectId) {
const directImage = this.agentsConfig.sandboxImage || DEFAULT_SANDBOX_IMAGE;
const snapshot = this.agentsConfig.sandboxSnapshot.trim() || undefined;
if (!this.aiService.isProxyEnabled()) {
return {
mode: 'direct',
apiUrl: this.agentsConfig.daytonaApiUrl || undefined,
apiKey: this.agentsConfig.daytonaApiKey || undefined,
image: directImage,
snapshot,
};
}
const client = await this.aiService.getClient();
const proxyConfig = await (0, ai_service_retry_1.callAiServiceWithRetry)('Agent knowledge sandbox proxy config fetch', async () => await client.getSandboxProxyConfig(), this.logger);
const token = await (0, ai_service_retry_1.callAiServiceWithRetry)('Agent knowledge sandbox proxy token mint', async () => await client.getBuilderApiProxyToken({ id: projectId }, { userMessageId: (0, nanoid_1.nanoid)() }), this.logger);
return {
mode: 'proxy',
apiUrl: client.getSandboxProxyBaseUrl(),
apiKey: token.accessToken,
image: proxyConfig.image || directImage,
snapshot,
};
}
async resolveSandboxByName(daytona, name, timeoutSeconds, connection) {
let sandbox;
try {
sandbox = await daytona.get(name);
}
catch (error) {
if (isDaytonaNotFoundError(error)) {
return undefined;
}
throw error;
}
if (!isUsableSandbox(sandbox)) {
await sandbox.delete(timeoutSeconds);
return undefined;
}
if (sandbox.state !== SANDBOX_STATE_STARTED) {
await sandbox.start(timeoutSeconds);
}
return await this.resolveReusableSandbox(daytona, sandbox, connection);
}
async resolveReusableSandbox(daytona, sandbox, connection) {
if (connection.mode !== 'proxy') {
return sandbox;
}
return await daytona.get(sandbox.name);
}
async assertKnowledgeAccess(projectId, agentId) {
this.assertKnowledgeConfiguration(projectId, agentId);
const agentExists = await this.agentRepository.existsBy({ id: agentId, projectId });
if (!agentExists) {
throw new not_found_error_1.NotFoundError(`Agent "${agentId}" not found`);
}
}
assertKnowledgeConfiguration(projectId, agentId) {
this.assertKnowledgeBaseEnabled();
this.assertValidPathSegments(projectId, agentId);
}
assertValidPathSegments(projectId, agentId) {
try {
(0, agent_knowledge_storage_1.assertKnowledgePathSegment)(this.instanceSettings.instanceId, 'instance id');
(0, agent_knowledge_storage_1.assertKnowledgePathSegment)(projectId, 'project id');
(0, agent_knowledge_storage_1.assertKnowledgePathSegment)(agentId, 'agent id');
}
catch (error) {
throw new n8n_workflow_1.OperationalError(error instanceof Error ? error.message : 'Invalid agent knowledge storage scope');
}
}
isKnowledgeBaseEnabled() {
return (0, agent_knowledge_gate_1.isAgentKnowledgeBaseEnabled)(this.agentsConfig, this.aiService.isProxyEnabled());
}
assertKnowledgeBaseEnabled() {
if (this.isKnowledgeBaseEnabled()) {
return;
}
throw new n8n_workflow_1.OperationalError('Agent knowledge sandbox is not enabled');
}
};
exports.AgentKnowledgeSandboxService = AgentKnowledgeSandboxService;
exports.AgentKnowledgeSandboxService = AgentKnowledgeSandboxService = __decorate([
(0, di_1.Service)(),
__metadata("design:paramtypes", [config_1.AgentsConfig, backend_common_1.Logger, ai_service_1.AiService, n8n_core_1.InstanceSettings, agent_file_repository_1.AgentFileRepository, agent_repository_1.AgentRepository, n8n_core_1.BinaryDataService])
], AgentKnowledgeSandboxService);
function matchKnowledgeFilesByGlob(files, request) {
const caseSensitive = request.caseSensitive === true;
const regex = globPatternToRegExp(request.pattern, caseSensitive);
const patternTokens = tokenizeKnowledgeFilePattern(request.pattern, caseSensitive);
return files
.filter((file) => regex.test(file.file) || regex.test(file.displayName))
.map((file) => ({
file,
bucket: getKnowledgeFileMatchBucket(file, patternTokens, caseSensitive),
}))
.sort((left, right) => left.bucket - right.bucket || left.file.displayName.localeCompare(right.file.displayName))
.map(({ file }) => file);
}
function getKnowledgeFileMatchBucket(file, patternTokens, caseSensitive) {
const fileNames = [file.file, file.displayName];
if (fileNames.some((fileName) => hasExactTokenMatch(tokenizeKnowledgeFileName(fileName, caseSensitive), patternTokens))) {
return 0;
}
if (fileNames.some((fileName) => containsTokenSequence(tokenizeKnowledgeFileName(fileName, caseSensitive), patternTokens))) {
return 1;
}
const compactPattern = patternTokens.join('');
if (compactPattern &&
fileNames.some((fileName) => compactKnowledgeFileName(fileName, caseSensitive).includes(compactPattern))) {
return 2;
}
return 3;
}
function tokenizeKnowledgeFilePattern(pattern, caseSensitive) {
return tokenizeKnowledgeFileName(pattern.replace(/[*?]/g, ' '), caseSensitive);
}
function tokenizeKnowledgeFileName(fileName, caseSensitive) {
const normalized = caseSensitive ? fileName : fileName.toLowerCase();
const baseName = normalized
.split(/[\\/]/)
.at(-1)
?.replace(/\.[^.]*$/, '') ?? normalized;
return baseName.split(/[^a-z0-9]+/i).filter(Boolean);
}
function compactKnowledgeFileName(fileName, caseSensitive) {
return tokenizeKnowledgeFileName(fileName, caseSensitive).join('');
}
function hasExactTokenMatch(fileTokens, patternTokens) {
return (patternTokens.length > 0 &&
fileTokens.length === patternTokens.length &&
fileTokens.every((fileToken, index) => fileToken === patternTokens[index]));
}
function containsTokenSequence(fileTokens, patternTokens) {
if (patternTokens.length === 0)
return false;
let patternIndex = 0;
for (const fileToken of fileTokens) {
if (fileToken === patternTokens[patternIndex]) {
patternIndex++;
if (patternIndex === patternTokens.length)
return true;
}
}
return false;
}
function globPatternToRegExp(pattern, caseSensitive) {
let source = '^';
for (const character of pattern) {
if (character === '*') {
source += '.*';
continue;
}
if (character === '?') {
source += '.';
continue;
}
source += escapeRegExp(character);
}
source += '$';
return new RegExp(source, caseSensitive ? undefined : 'i');
}
function escapeRegExp(value) {
return value.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
}
//# sourceMappingURL=agent-knowledge-sandbox.service.js.map