n8n
Version:
n8n Workflow Automation Tool
211 lines • 10.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createKnowledgeRetrievalTools = createKnowledgeRetrievalTools;
const tool_1 = require("@n8n/agents/tool");
const agent_knowledge_retrieval_1 = require("../../agent-knowledge-retrieval");
const MODEL_OUTPUT_GLOB_FILE_LIMIT = 20;
const MODEL_OUTPUT_SEARCH_MATCH_LIMIT = 8;
const MODEL_OUTPUT_SEARCH_TEXT_CHARS = 240;
const MODEL_OUTPUT_READ_TEXT_BUDGET = 6_000;
const MODEL_OUTPUT_READ_RANGE_MIN_CHARS = 800;
function formatKnowledgeToolError(error) {
if (error instanceof Error) {
return {
error: error.message,
errorType: error.name,
};
}
return {
error: String(error),
errorType: typeof error,
};
}
async function runKnowledgeTool(operation) {
try {
return await operation();
}
catch (error) {
return formatKnowledgeToolError(error);
}
}
function isRecord(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function truncateForModel(text, maxChars) {
if (text.length <= maxChars) {
return { text, truncated: false };
}
return { text: text.slice(0, maxChars), truncated: true };
}
function isKnowledgeToolErrorOutput(output) {
return (isRecord(output) && typeof output.error === 'string' && typeof output.errorType === 'string');
}
function isGlobKnowledgeFilesResult(output) {
return isRecord(output) && Array.isArray(output.files) && typeof output.hasMore === 'boolean';
}
function isSearchKnowledgeResult(output) {
if (!isRecord(output) ||
typeof output.outputMode !== 'string' ||
typeof output.hasMore !== 'boolean') {
return false;
}
if (output.outputMode === 'content')
return Array.isArray(output.matches);
if (output.outputMode === 'files_with_matches')
return Array.isArray(output.files);
if (output.outputMode === 'count')
return Array.isArray(output.counts);
return false;
}
function isReadKnowledgeResult(output) {
return (isRecord(output) &&
typeof output.file === 'string' &&
typeof output.fileId === 'string' &&
typeof output.displayName === 'string' &&
Array.isArray(output.ranges));
}
function toGlobKnowledgeModelOutput(output) {
if (isKnowledgeToolErrorOutput(output) || !isGlobKnowledgeFilesResult(output))
return output;
const files = output.files.map((file) => ({
file: file.file,
fileId: file.fileId,
displayName: file.displayName,
mimeType: file.mimeType,
fileSizeBytes: file.fileSizeBytes,
}));
return {
files,
returnedFiles: files.length,
offset: output.offset,
hasMore: output.hasMore,
};
}
function toSearchKnowledgeModelOutput(output) {
if (isKnowledgeToolErrorOutput(output) || !isSearchKnowledgeResult(output))
return output;
if (output.outputMode === 'files_with_matches') {
const files = output.files.slice(0, MODEL_OUTPUT_GLOB_FILE_LIMIT).map((file) => ({
file: file.file,
fileId: file.fileId,
displayName: file.displayName,
}));
return {
outputMode: output.outputMode,
files,
returnedFiles: output.files.length,
shownFiles: files.length,
limit: output.limit,
hasMore: output.hasMore || output.files.length > MODEL_OUTPUT_GLOB_FILE_LIMIT,
truncated: output.truncated || output.files.length > MODEL_OUTPUT_GLOB_FILE_LIMIT,
};
}
if (output.outputMode === 'count') {
const counts = output.counts.slice(0, MODEL_OUTPUT_GLOB_FILE_LIMIT);
return {
outputMode: output.outputMode,
counts,
returnedCounts: output.counts.length,
shownCounts: counts.length,
limit: output.limit,
hasMore: output.hasMore || output.counts.length > MODEL_OUTPUT_GLOB_FILE_LIMIT,
truncated: output.truncated || output.counts.length > MODEL_OUTPUT_GLOB_FILE_LIMIT,
};
}
let anyLocalTruncation = output.matches.length > MODEL_OUTPUT_SEARCH_MATCH_LIMIT;
const matches = output.matches.slice(0, MODEL_OUTPUT_SEARCH_MATCH_LIMIT).map((match) => {
const truncated = truncateForModel(match.text, MODEL_OUTPUT_SEARCH_TEXT_CHARS);
if (truncated.truncated)
anyLocalTruncation = true;
const modelMatch = {
file: match.file,
fileId: match.fileId,
displayName: match.displayName,
lineNumber: match.lineNumber,
text: truncated.text,
textTruncated: match.textTruncated || truncated.truncated,
};
if (!match.context?.length)
return modelMatch;
const context = match.context.map((line) => {
const truncatedContext = truncateForModel(line.text, MODEL_OUTPUT_SEARCH_TEXT_CHARS);
if (truncatedContext.truncated)
anyLocalTruncation = true;
return {
lineNumber: line.lineNumber,
text: truncatedContext.text,
matched: line.matched,
};
});
return {
...modelMatch,
context,
};
});
return {
outputMode: output.outputMode,
matches,
returnedMatches: output.matches.length,
shownMatches: matches.length,
limit: output.limit,
hasMore: output.hasMore || output.matches.length > MODEL_OUTPUT_SEARCH_MATCH_LIMIT,
truncated: output.truncated || anyLocalTruncation,
};
}
function toReadKnowledgeModelOutput(output) {
if (isKnowledgeToolErrorOutput(output) || !isReadKnowledgeResult(output))
return output;
const perRangeBudget = Math.max(MODEL_OUTPUT_READ_RANGE_MIN_CHARS, Math.floor(MODEL_OUTPUT_READ_TEXT_BUDGET / Math.max(output.ranges.length, 1)));
let remainingBudget = MODEL_OUTPUT_READ_TEXT_BUDGET;
let anyLocalTruncation = false;
const ranges = [];
for (const range of output.ranges) {
if (remainingBudget <= 0) {
anyLocalTruncation = true;
break;
}
const maxChars = Math.min(perRangeBudget, remainingBudget);
const truncated = truncateForModel(range.text, maxChars);
if (truncated.truncated)
anyLocalTruncation = true;
remainingBudget -= truncated.text.length;
ranges.push({
startLine: range.startLine,
endLine: range.endLine,
citation: range.citation,
text: truncated.text,
textTruncated: truncated.truncated,
});
}
if (ranges.length < output.ranges.length) {
anyLocalTruncation = true;
}
return {
file: output.file,
fileId: output.fileId,
displayName: output.displayName,
ranges,
returnedRanges: output.ranges.length,
shownRanges: ranges.length,
truncated: output.truncated || anyLocalTruncation,
};
}
function createKnowledgeRetrievalTools({ projectId, agentId, sandboxService, }) {
const globTool = new tool_1.Tool('find_file')
.description('Find uploaded knowledge files by filename pattern, such as `*knowledge*`, `*agent*tool*`, or `*sandbox*`. Use `*` to list every uploaded file, an extension pattern like `*.pdf` to filter by type, or a name fragment when the user gives title or filename clues. Use first for knowledge lookup to discover plausible candidate files; call until enough candidates are found, then copy returned file values into search_text path or read_file. Results are paged: page through the full list with `limit` and `offset` while `hasMore` is true. Returns file, fileId, mimeType, and fileSizeBytes metadata; does not read file contents.')
.input(agent_knowledge_retrieval_1.globKnowledgeFilesInputSchema)
.handler(async (input) => await runKnowledgeTool(async () => await sandboxService.globKnowledgeFiles(projectId, agentId, input)))
.toModelOutput(toGlobKnowledgeModelOutput);
const searchTool = new tool_1.Tool('search_text')
.description('Search uploaded knowledge file contents with a ripgrep regex `pattern`. Omit `path` to search across every uploaded knowledge file. Pass `path` — one exact file value or an array of exact file values returned by find_file, search_text, or read_file — to scope the search to specific files. Does not accept empty path, `*`, fileId, absolute paths, guessed paths, arrays of non-strings, offsets, or semantic questions. Searching more than 20 files with an explicit `path` requires batching arrays across multiple calls (max 20 paths per call). `output_mode` defaults to content for snippets; use files_with_matches for matching files only or count for per-file match counts. Use `head_limit` to bound results and small `-C` values for nearby context. Answer from snippets when enough; use read_file only for quotes, citations, exact wording, source text, or larger missing context.')
.input(agent_knowledge_retrieval_1.searchKnowledgeInputSchema)
.handler(async (input) => await runKnowledgeTool(async () => await sandboxService.searchKnowledge(projectId, agentId, input)))
.toModelOutput(toSearchKnowledgeModelOutput);
const readTool = new tool_1.Tool('read_file')
.description(`Read one uploaded knowledge file using an exact \`file\` or \`fileId\` returned by a knowledge tool. Use only for exact quotes, citations, source text, or extra surrounding context not covered by search_text snippets or small -C context. Prefer bounded line ranges; each call supports up to ${agent_knowledge_retrieval_1.MAX_READ_RANGES} ranges and output is truncated when too large. Read all needed ranges from the same file in one call when practical, and do not use read_file for broad manual scanning. If using fileId without an exact matching file value, omit file.`)
.input(agent_knowledge_retrieval_1.readKnowledgeInputSchema)
.handler(async (input) => await runKnowledgeTool(async () => await sandboxService.readKnowledge(projectId, agentId, input)))
.toModelOutput(toReadKnowledgeModelOutput);
return [globTool, searchTool, readTool];
}
//# sourceMappingURL=search-knowledge.tool.js.map