/** * @license * Copyright 2025 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import path from 'path'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { makeRelative, shortenPath } from '../utils/paths.js'; import { BaseTool, ToolResult } from './tools.js'; import { isWithinRoot, processSingleFileContent } from '../utils/fileUtils.js'; import { Config } from '../config/config.js'; import { getSpecificMimeType } from '../utils/fileUtils.js'; import { recordFileOperationMetric, FileOperation, } from '../telemetry/metrics.js'; /** * Parameters for the ReadFile tool */ export interface ReadFileToolParams { /** * The absolute path to the file to read */ absolute_path: string; /** * The line number to start reading from (optional) */ offset?: number; /** * The number of lines to read (optional) */ limit?: number; } /** * Implementation of the ReadFile tool logic */ export class ReadFileTool extends BaseTool { static readonly Name: string = 'read_file'; constructor( private rootDirectory: string, private config: Config, ) { super( ReadFileTool.Name, 'ReadFile', 'Reads and returns the content of a specified file from the local filesystem. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), and PDF files. For text files, it can read specific line ranges.', { properties: { absolute_path: { description: "The absolute path to the file to read (e.g., '/home/user/project/file.txt'). Relative paths are not supported. You must provide an absolute path.", type: 'string', pattern: '^/', }, offset: { description: "Optional: For text files, the 0-based line number to start reading from. Requires 'limit' to be set. Use for paginating through large files.", type: 'number', }, limit: { description: "Optional: For text files, maximum number of lines to read. Use with 'offset' to paginate through large files. If omitted, reads the entire file (if feasible, up to a default limit).", type: 'number', }, }, required: ['absolute_path'], type: 'object', }, ); this.rootDirectory = path.resolve(rootDirectory); } validateToolParams(params: ReadFileToolParams): string | null { if ( this.schema.parameters && !SchemaValidator.validate( this.schema.parameters as Record, params, ) ) { return 'Parameters failed schema validation.'; } const filePath = params.absolute_path; if (!path.isAbsolute(filePath)) { return `File path must be absolute, but was relative: ${filePath}. You must provide an absolute path.`; } if (!isWithinRoot(filePath, this.rootDirectory)) { return `File path must be within the root directory (${this.rootDirectory}): ${filePath}`; } if (params.offset !== undefined && params.offset < 0) { return 'Offset must be a non-negative number'; } if (params.limit !== undefined && params.limit <= 0) { return 'Limit must be a positive number'; } const fileService = this.config.getFileService(); if (fileService.shouldGeminiIgnoreFile(params.absolute_path)) { const relativePath = makeRelative( params.absolute_path, this.rootDirectory, ); return `File path '${shortenPath(relativePath)}' is ignored by .geminiignore pattern(s).`; } return null; } getDescription(params: ReadFileToolParams): string { if ( !params || typeof params.absolute_path !== 'string' || params.absolute_path.trim() === '' ) { return `Path unavailable`; } const relativePath = makeRelative(params.absolute_path, this.rootDirectory); return shortenPath(relativePath); } async execute( params: ReadFileToolParams, _signal: AbortSignal, ): Promise { const validationError = this.validateToolParams(params); if (validationError) { return { llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`, returnDisplay: validationError, }; } const result = await processSingleFileContent( params.absolute_path, this.rootDirectory, params.offset, params.limit, ); if (result.error) { return { llmContent: result.error, // The detailed error for LLM returnDisplay: result.returnDisplay, // User-friendly error }; } const lines = typeof result.llmContent === 'string' ? result.llmContent.split('\n').length : undefined; const mimetype = getSpecificMimeType(params.absolute_path); recordFileOperationMetric( this.config, FileOperation.READ, lines, mimetype, path.extname(params.absolute_path), ); return { llmContent: result.llmContent, returnDisplay: result.returnDisplay, }; } }