diff --git a/packages/core/src/tools/read-file.test.ts b/packages/core/src/tools/read-file.test.ts index 82958184..7f7f5ede 100644 --- a/packages/core/src/tools/read-file.test.ts +++ b/packages/core/src/tools/read-file.test.ts @@ -219,7 +219,7 @@ describe('ReadFileTool', () => { returnDisplay: 'Path is a directory.', error: { message: `Path is a directory, not a file: ${dirPath}`, - type: ToolErrorType.INVALID_TOOL_PARAMS, + type: ToolErrorType.TARGET_IS_DIRECTORY, }, }); }); diff --git a/packages/core/src/tools/read-file.ts b/packages/core/src/tools/read-file.ts index da8a004d..7e9e8535 100644 --- a/packages/core/src/tools/read-file.ts +++ b/packages/core/src/tools/read-file.ts @@ -14,7 +14,7 @@ import { ToolLocation, ToolResult, } from './tools.js'; -import { ToolErrorType } from './tool-error.js'; + import { PartUnion } from '@google/genai'; import { processSingleFileContent, @@ -79,44 +79,12 @@ class ReadFileToolInvocation extends BaseToolInvocation< ); if (result.error) { - // Map error messages to ToolErrorType - let errorType: ToolErrorType; - let llmContent: string; - - // Check error message patterns to determine error type - if ( - result.error.includes('File not found') || - result.error.includes('does not exist') || - result.error.includes('ENOENT') - ) { - errorType = ToolErrorType.FILE_NOT_FOUND; - llmContent = - 'Could not read file because no file was found at the specified path.'; - } else if ( - result.error.includes('is a directory') || - result.error.includes('EISDIR') - ) { - errorType = ToolErrorType.INVALID_TOOL_PARAMS; - llmContent = - 'Could not read file because the provided path is a directory, not a file.'; - } else if ( - result.error.includes('too large') || - result.error.includes('File size exceeds') - ) { - errorType = ToolErrorType.FILE_TOO_LARGE; - llmContent = `Could not read file. ${result.error}`; - } else { - // Other read errors map to READ_CONTENT_FAILURE - errorType = ToolErrorType.READ_CONTENT_FAILURE; - llmContent = `Could not read file. ${result.error}`; - } - return { - llmContent, + llmContent: result.llmContent, returnDisplay: result.returnDisplay || 'Error reading file', error: { message: result.error, - type: errorType, + type: result.errorType, }, }; } diff --git a/packages/core/src/tools/read-many-files.ts b/packages/core/src/tools/read-many-files.ts index 52529ce9..b3568cad 100644 --- a/packages/core/src/tools/read-many-files.ts +++ b/packages/core/src/tools/read-many-files.ts @@ -21,6 +21,7 @@ import { processSingleFileContent, DEFAULT_ENCODING, getSpecificMimeType, + ProcessedFileReadResult, } from '../utils/fileUtils.js'; import { PartListUnion } from '@google/genai'; import { Config, DEFAULT_FILE_FILTERING_OPTIONS } from '../config/config.js'; @@ -84,9 +85,7 @@ type FileProcessingResult = success: true; filePath: string; relativePathForDisplay: string; - fileReadResult: NonNullable< - Awaited> - >; + fileReadResult: ProcessedFileReadResult; reason?: undefined; } | { diff --git a/packages/core/src/utils/fileUtils.ts b/packages/core/src/utils/fileUtils.ts index 8dfdbc22..f0b491ed 100644 --- a/packages/core/src/utils/fileUtils.ts +++ b/packages/core/src/utils/fileUtils.ts @@ -9,6 +9,7 @@ import path from 'node:path'; import { PartUnion } from '@google/genai'; import mime from 'mime-types'; import { FileSystemService } from '../services/fileSystemService.js'; +import { ToolErrorType } from '../tools/tool-error.js'; // Constants for text file processing const DEFAULT_MAX_LINES_TEXT_FILE = 2000; @@ -196,18 +197,11 @@ export async function detectFileType( return 'text'; } -export enum FileErrorType { - FILE_NOT_FOUND = 'FILE_NOT_FOUND', - IS_DIRECTORY = 'IS_DIRECTORY', - FILE_TOO_LARGE = 'FILE_TOO_LARGE', - READ_ERROR = 'READ_ERROR', -} - export interface ProcessedFileReadResult { llmContent: PartUnion; // string for text, Part for image/pdf/unreadable binary returnDisplay: string; error?: string; // Optional error message for the LLM if file processing failed - errorType?: FileErrorType; // Structured error type using enum + errorType?: ToolErrorType; // Structured error type isTruncated?: boolean; // For text files, indicates if content was truncated originalLineCount?: number; // For text files linesShown?: [number, number]; // For text files [startLine, endLine] (1-based for display) @@ -232,33 +226,32 @@ export async function processSingleFileContent( if (!fs.existsSync(filePath)) { // Sync check is acceptable before async read return { - llmContent: '', + llmContent: + 'Could not read file because no file was found at the specified path.', returnDisplay: 'File not found.', error: `File not found: ${filePath}`, - errorType: FileErrorType.FILE_NOT_FOUND, + errorType: ToolErrorType.FILE_NOT_FOUND, }; } const stats = await fs.promises.stat(filePath); if (stats.isDirectory()) { return { - llmContent: '', + llmContent: + 'Could not read file because the provided path is a directory, not a file.', returnDisplay: 'Path is a directory.', error: `Path is a directory, not a file: ${filePath}`, - errorType: FileErrorType.IS_DIRECTORY, + errorType: ToolErrorType.TARGET_IS_DIRECTORY, }; } - const fileSizeInBytes = stats.size; - // 20MB limit - const maxFileSize = 20 * 1024 * 1024; - - if (fileSizeInBytes > maxFileSize) { - throw new Error( - `File size exceeds the 20MB limit: ${filePath} (${( - fileSizeInBytes / - (1024 * 1024) - ).toFixed(2)}MB)`, - ); + const fileSizeInMB = stats.size / (1024 * 1024); + if (fileSizeInMB > 20) { + return { + llmContent: 'File size exceeds the 20MB limit.', + returnDisplay: 'File size exceeds the 20MB limit.', + error: `File size exceeds the 20MB limit: ${filePath} (${fileSizeInMB.toFixed(2)}MB)`, + errorType: ToolErrorType.FILE_TOO_LARGE, + }; } const fileType = await detectFileType(filePath); @@ -373,6 +366,7 @@ export async function processSingleFileContent( llmContent: `Error reading file ${displayPath}: ${errorMessage}`, returnDisplay: `Error reading file ${displayPath}: ${errorMessage}`, error: `Error reading file ${filePath}: ${errorMessage}`, + errorType: ToolErrorType.READ_CONTENT_FAILURE, }; } }