summaryrefslogtreecommitdiff
path: root/packages/server/src/tools/read-many-files.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/server/src/tools/read-many-files.ts')
-rw-r--r--packages/server/src/tools/read-many-files.ts133
1 files changed, 89 insertions, 44 deletions
diff --git a/packages/server/src/tools/read-many-files.ts b/packages/server/src/tools/read-many-files.ts
index 4d9d35e8..5064ae1b 100644
--- a/packages/server/src/tools/read-many-files.ts
+++ b/packages/server/src/tools/read-many-files.ts
@@ -12,6 +12,8 @@ import * as path from 'path';
import fg from 'fast-glob';
import { GEMINI_MD_FILENAME } from './memoryTool.js';
+import { PartListUnion } from '@google/genai';
+import mime from 'mime-types';
/**
* Parameters for the ReadManyFilesTool.
*/
@@ -82,14 +84,6 @@ const DEFAULT_EXCLUDES: string[] = [
'**/*.bz2',
'**/*.rar',
'**/*.7z',
- '**/*.png',
- '**/*.jpg',
- '**/*.jpeg',
- '**/*.gif',
- '**/*.bmp',
- '**/*.tiff',
- '**/*.ico',
- '**/*.pdf',
'**/*.doc',
'**/*.docx',
'**/*.xls',
@@ -188,6 +182,13 @@ Default excludes apply to common non-text files and large dependency directories
validateParams(params: ReadManyFilesParams): string | null {
if (
+ !params.paths ||
+ !Array.isArray(params.paths) ||
+ params.paths.length === 0
+ ) {
+ return 'The "paths" parameter is required and must be a non-empty array of strings/glob patterns.';
+ }
+ if (
this.schema.parameters &&
!SchemaValidator.validate(
this.schema.parameters as Record<string, unknown>,
@@ -263,7 +264,7 @@ Default excludes apply to common non-text files and large dependency directories
const filesToConsider = new Set<string>();
const skippedFiles: Array<{ path: string; reason: string }> = [];
const processedFilesRelativePaths: string[] = [];
- let concatenatedContent = '';
+ const content: PartListUnion = [];
const effectiveExcludes = useDefaultExcludes
? [...DEFAULT_EXCLUDES, ...exclude]
@@ -319,28 +320,63 @@ Default excludes apply to common non-text files and large dependency directories
.relative(toolBaseDir, filePath)
.replace(/\\/g, '/');
try {
- const contentBuffer = await fs.readFile(filePath);
- // Basic binary detection: check for null bytes in the first 1KB
- const sample = contentBuffer.subarray(
- 0,
- Math.min(contentBuffer.length, 1024),
- );
- if (sample.includes(0)) {
- skippedFiles.push({
- path: relativePathForDisplay,
- reason: 'Skipped (appears to be binary)',
+ const mimeType = mime.lookup(filePath);
+ if (
+ mimeType &&
+ (mimeType.startsWith('image/') || mimeType === 'application/pdf')
+ ) {
+ const fileExtension = path.extname(filePath);
+ const fileNameWithoutExtension = path.basename(
+ filePath,
+ fileExtension,
+ );
+ const requestedExplicitly = inputPatterns.some(
+ (pattern: string) =>
+ pattern.toLowerCase().includes(fileExtension) ||
+ pattern.includes(fileNameWithoutExtension),
+ );
+
+ if (!requestedExplicitly) {
+ skippedFiles.push({
+ path: relativePathForDisplay,
+ reason:
+ 'asset file (image/pdf) was not explicitly requested by name or extension',
+ });
+ continue;
+ }
+ const contentBuffer = await fs.readFile(filePath);
+ const base64Data = contentBuffer.toString('base64');
+ content.push({
+ inlineData: {
+ data: base64Data,
+ mimeType,
+ },
});
- continue;
+ processedFilesRelativePaths.push(relativePathForDisplay);
+ } else {
+ const contentBuffer = await fs.readFile(filePath);
+ // Basic binary detection: check for null bytes in the first 1KB
+ const sample = contentBuffer.subarray(
+ 0,
+ Math.min(contentBuffer.length, 1024),
+ );
+ if (sample.includes(0)) {
+ skippedFiles.push({
+ path: relativePathForDisplay,
+ reason: 'appears to be binary',
+ });
+ continue;
+ }
+ // Using default encoding
+ const fileContent = contentBuffer.toString(DEFAULT_ENCODING);
+ // Using default separator format
+ const separator = DEFAULT_OUTPUT_SEPARATOR_FORMAT.replace(
+ '{filePath}',
+ relativePathForDisplay,
+ );
+ content.push(`${separator}\n\n${fileContent}\n\n`);
+ processedFilesRelativePaths.push(relativePathForDisplay);
}
- // Using default encoding
- const fileContent = contentBuffer.toString(DEFAULT_ENCODING);
- // Using default separator format
- const separator = DEFAULT_OUTPUT_SEPARATOR_FORMAT.replace(
- '{filePath}',
- relativePathForDisplay,
- );
- concatenatedContent += `${separator}\n\n${fileContent}\n\n`;
- processedFilesRelativePaths.push(relativePathForDisplay);
} catch (error) {
skippedFiles.push({
path: relativePathForDisplay,
@@ -352,18 +388,24 @@ Default excludes apply to common non-text files and large dependency directories
let displayMessage = `### ReadManyFiles Result (Target Dir: \`${this.targetDir}\`)\n\n`;
if (processedFilesRelativePaths.length > 0) {
displayMessage += `Successfully read and concatenated content from **${processedFilesRelativePaths.length} file(s)**.\n`;
- displayMessage += `\n**Processed Files:**\n`;
- processedFilesRelativePaths
- .slice(0, 10)
- .forEach((p) => (displayMessage += `- \`${p}\`\n`));
- if (processedFilesRelativePaths.length > 10) {
+ if (processedFilesRelativePaths.length <= 10) {
+ displayMessage += `\n**Processed Files:**\n`;
+ processedFilesRelativePaths.forEach(
+ (p) => (displayMessage += `- \`${p}\`\n`),
+ );
+ } else {
+ displayMessage += `\n**Processed Files (first 10 shown):**\n`;
+ processedFilesRelativePaths
+ .slice(0, 10)
+ .forEach((p) => (displayMessage += `- \`${p}\`\n`));
displayMessage += `- ...and ${processedFilesRelativePaths.length - 10} more.\n`;
}
- } else {
- displayMessage += `No files were read and concatenated based on the criteria.\n`;
}
if (skippedFiles.length > 0) {
+ if (processedFilesRelativePaths.length === 0) {
+ displayMessage += `No files were read and concatenated based on the criteria.\n`;
+ }
displayMessage += `\n**Skipped ${skippedFiles.length} item(s) (up to 5 shown):**\n`;
skippedFiles
.slice(0, 5)
@@ -373,18 +415,21 @@ Default excludes apply to common non-text files and large dependency directories
if (skippedFiles.length > 5) {
displayMessage += `- ...and ${skippedFiles.length - 5} more.\n`;
}
- }
- if (
- concatenatedContent.length === 0 &&
- processedFilesRelativePaths.length === 0
+ } else if (
+ processedFilesRelativePaths.length === 0 &&
+ skippedFiles.length === 0
) {
- concatenatedContent =
- 'No files matching the criteria were found or all were skipped.';
+ displayMessage += `No files were read and concatenated based on the criteria.\n`;
}
+ if (content.length === 0) {
+ content.push(
+ 'No files matching the criteria were found or all were skipped.',
+ );
+ }
return {
- llmContent: concatenatedContent,
- returnDisplay: displayMessage,
+ llmContent: content,
+ returnDisplay: displayMessage.trim(),
};
}
}