n8n
Version:
n8n Workflow Automation Tool
121 lines • 5.64 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSearchKnowledgeTool = createSearchKnowledgeTool;
const tool_1 = require("@n8n/agents/tool");
const node_crypto_1 = require("node:crypto");
const csv_operation_1 = require("./csv.operation");
const file_references_1 = require("./file-references");
const read_operation_1 = require("./read.operation");
const search_operation_1 = require("./search.operation");
const schemas_1 = require("./schemas");
function createSearchKnowledgeTool({ agentId, projectId, knowledgeService, commandService, }) {
return new tool_1.Tool('search_knowledge')
.description('List, read, search, and query files uploaded to this agent knowledge base. ' +
'Use this when the user asks about uploaded documents or facts likely contained in them.')
.systemInstruction('Use search_knowledge to inspect uploaded knowledge files. Do not claim a file says something ' +
'unless you found it via list, search, read, or a CSV operation. Search defaults to output_mode=files_with_matches. ' +
'Use output_mode=count for counts and output_mode=content only after narrowing to a file or exact phrase. ' +
'For conceptual multi-term lookup, use queries with match_mode instead of writing regex by hand. ' +
'Use read for grounded citations. Cite only file names and line ranges from read results. ' +
'Never mention uploaded file ids, relative paths, binary ids, or storage ids to users. ' +
'For unfamiliar CSVs, call csv_profile first. Use csv_query for rows, csv_distinct for possible values, and csv_aggregate for counts or numeric calculations. ' +
'Do not answer from the first CSV row when rowCount is high or truncated; refine filters using ambiguity hints.')
.input(schemas_1.searchKnowledgeInputSchema)
.output(schemas_1.searchKnowledgeOutputSchema)
.handler(async (input) => {
let parsedInput;
try {
parsedInput = (0, schemas_1.parseSearchKnowledgeInput)(input);
}
catch (error) {
return {
operation: (0, schemas_1.getSearchKnowledgeOperation)(input),
files: [],
error: toToolErrorMessage(error),
};
}
if (parsedInput.operation === 'list') {
try {
return {
operation: 'list',
files: await knowledgeService.listWorkspaceFiles(agentId, projectId),
};
}
catch (error) {
return {
operation: 'list',
files: [],
error: toToolErrorMessage(error),
};
}
}
let files = [];
try {
const fileReferences = (0, file_references_1.getRequiredFileReferences)(parsedInput);
files = await knowledgeService.resolveWorkspaceFiles(agentId, projectId, fileReferences);
const cacheKey = buildWorkspaceCacheKey(projectId, agentId, files);
return await commandService.withCachedWorkspace(cacheKey, async (workspaceRoot) => {
await knowledgeService.materializeWorkspace(agentId, projectId, workspaceRoot, {
fileReferences,
});
}, async (workspaceRoot) => await handleKnowledgeOperation(parsedInput, workspaceRoot, files, commandService));
}
catch (error) {
return {
operation: parsedInput.operation,
files,
error: toToolErrorMessage(error),
};
}
})
.build();
}
function buildWorkspaceCacheKey(projectId, agentId, files) {
const signature = files
.map((file) => `${file.relativePath}:${file.fileSizeBytes}`)
.sort()
.join('|');
return `${projectId}:${agentId}:${(0, node_crypto_1.createHash)('sha1').update(signature).digest('hex')}`;
}
function toToolErrorMessage(error) {
const message = error instanceof Error ? error.message : String(error);
return message.replace(/(^|[\s'"(])\/(?:[^\s'"()]+\/)*[^\s'"()]+/g, '$1[path]');
}
async function handleKnowledgeOperation(input, workspaceRoot, files, commandService) {
switch (input.operation) {
case 'list':
return {
operation: 'list',
files,
};
case 'search':
return await (0, search_operation_1.runSearchOperation)(input, workspaceRoot, files, commandService);
case 'read':
return await (0, read_operation_1.runReadOperation)(input, workspaceRoot, files, commandService);
case 'csv_query':
return {
operation: 'csv_query',
files,
csv: await (0, csv_operation_1.queryCsv)(workspaceRoot, files, input),
};
case 'csv_profile':
return {
operation: 'csv_profile',
files,
csvProfile: await (0, csv_operation_1.profileCsv)(workspaceRoot, files, input),
};
case 'csv_distinct':
return {
operation: 'csv_distinct',
files,
csvDistinct: await (0, csv_operation_1.distinctCsv)(workspaceRoot, files, input),
};
case 'csv_aggregate':
return {
operation: 'csv_aggregate',
files,
csvAggregate: await (0, csv_operation_1.aggregateCsv)(workspaceRoot, files, input),
};
}
}
//# sourceMappingURL=tool.js.map