2025-04-19 19:45:42 +01:00
/ * *
* @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' ;
2025-06-29 18:09:15 +09:00
import {
isWithinRoot ,
processSingleFileContent ,
getSpecificMimeType ,
} from '../utils/fileUtils.js' ;
2025-06-05 10:15:27 -07:00
import { Config } from '../config/config.js' ;
Add file operation telemetry (#1068)
Introduces telemetry for file create, read, and update operations.
This change adds the `gemini_cli.file.operation.count` metric, recorded by the `read-file`, `read-many-files`, and `write-file` tools.
The metric includes the following attributes:
- `operation` (string: `create`, `read`, `update`): The type of file operation.
- `lines` (optional, Int): Number of lines in the file.
- `mimetype` (optional, string): Mimetype of the file.
- `extension` (optional, string): File extension of the file.
Here is a stacked bar chart of file operations by extension (`js`, `ts`, `md`):

Here is a stacked bar chart of file operations by type (`create`, `read`, `update`):

#750
cc @allenhutchison as discussed
2025-06-15 16:24:53 -04:00
import {
recordFileOperationMetric ,
FileOperation ,
} from '../telemetry/metrics.js' ;
2025-04-19 19:45:42 +01:00
/ * *
* Parameters for the ReadFile tool
* /
export interface ReadFileToolParams {
/ * *
* The absolute path to the file to read
* /
2025-06-14 21:16:11 -07:00
absolute_path : string ;
2025-04-19 19:45:42 +01:00
/ * *
* 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
* /
2025-04-21 10:53:11 -04:00
export class ReadFileTool extends BaseTool < ReadFileToolParams , ToolResult > {
2025-04-19 19:45:42 +01:00
static readonly Name : string = 'read_file' ;
2025-06-05 10:15:27 -07:00
constructor (
private rootDirectory : string ,
2025-06-14 10:25:34 -04:00
private config : Config ,
2025-06-05 10:15:27 -07:00
) {
2025-04-19 19:45:42 +01:00
super (
2025-04-21 10:53:11 -04:00
ReadFileTool . Name ,
'ReadFile' ,
2025-05-29 22:30:18 +00:00
'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.' ,
2025-04-19 19:45:42 +01:00
{
properties : {
2025-06-14 21:16:11 -07:00
absolute_path : {
2025-04-19 19:45:42 +01:00
description :
2025-06-14 21:16:11 -07:00
"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." ,
2025-04-19 19:45:42 +01:00
type : 'string' ,
2025-06-14 21:16:11 -07:00
pattern : '^/' ,
2025-04-19 19:45:42 +01:00
} ,
offset : {
description :
2025-05-29 22:30:18 +00:00
"Optional: For text files, the 0-based line number to start reading from. Requires 'limit' to be set. Use for paginating through large files." ,
2025-04-19 19:45:42 +01:00
type : 'number' ,
} ,
limit : {
description :
2025-05-29 22:30:18 +00:00
"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)." ,
2025-04-19 19:45:42 +01:00
type : 'number' ,
} ,
} ,
2025-06-14 21:16:11 -07:00
required : [ 'absolute_path' ] ,
2025-04-19 19:45:42 +01:00
type : 'object' ,
} ,
) ;
this . rootDirectory = path . resolve ( rootDirectory ) ;
}
validateToolParams ( params : ReadFileToolParams ) : string | null {
if (
this . schema . parameters &&
! SchemaValidator . validate (
this . schema . parameters as Record < string , unknown > ,
params ,
)
) {
return 'Parameters failed schema validation.' ;
}
2025-06-14 21:16:11 -07:00
const filePath = params . absolute_path ;
2025-04-19 19:45:42 +01:00
if ( ! path . isAbsolute ( filePath ) ) {
2025-06-14 21:16:11 -07:00
return ` File path must be absolute, but was relative: ${ filePath } . You must provide an absolute path. ` ;
2025-04-19 19:45:42 +01:00
}
2025-05-29 22:30:18 +00:00
if ( ! isWithinRoot ( filePath , this . rootDirectory ) ) {
2025-04-19 19:45:42 +01:00
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' ;
}
2025-06-05 10:15:27 -07:00
2025-06-14 10:25:34 -04:00
const fileService = this . config . getFileService ( ) ;
2025-06-14 21:16:11 -07:00
if ( fileService . shouldGeminiIgnoreFile ( params . absolute_path ) ) {
const relativePath = makeRelative (
params . absolute_path ,
this . rootDirectory ,
) ;
2025-06-14 10:25:34 -04:00
return ` File path ' ${ shortenPath ( relativePath ) } ' is ignored by .geminiignore pattern(s). ` ;
2025-06-05 10:15:27 -07:00
}
2025-04-19 19:45:42 +01:00
return null ;
}
getDescription ( params : ReadFileToolParams ) : string {
2025-06-01 00:02:00 -07:00
if (
! params ||
2025-06-14 21:16:11 -07:00
typeof params . absolute_path !== 'string' ||
params . absolute_path . trim ( ) === ''
2025-06-01 00:02:00 -07:00
) {
return ` Path unavailable ` ;
}
2025-06-14 21:16:11 -07:00
const relativePath = makeRelative ( params . absolute_path , this . rootDirectory ) ;
2025-04-19 19:45:42 +01:00
return shortenPath ( relativePath ) ;
}
2025-05-09 23:29:02 -07:00
async execute (
params : ReadFileToolParams ,
_signal : AbortSignal ,
) : Promise < ToolResult > {
2025-04-19 19:45:42 +01:00
const validationError = this . validateToolParams ( params ) ;
if ( validationError ) {
return {
llmContent : ` Error: Invalid parameters provided. Reason: ${ validationError } ` ,
2025-04-20 22:10:23 -04:00
returnDisplay : validationError ,
2025-04-19 19:45:42 +01:00
} ;
}
2025-05-29 22:30:18 +00:00
const result = await processSingleFileContent (
2025-06-14 21:16:11 -07:00
params . absolute_path ,
2025-05-29 22:30:18 +00:00
this . rootDirectory ,
params . offset ,
params . limit ,
) ;
2025-04-19 19:45:42 +01:00
2025-05-29 22:30:18 +00:00
if ( result . error ) {
2025-04-19 19:45:42 +01:00
return {
2025-05-29 22:30:18 +00:00
llmContent : result.error , // The detailed error for LLM
returnDisplay : result.returnDisplay , // User-friendly error
2025-04-19 19:45:42 +01:00
} ;
}
2025-05-29 22:30:18 +00:00
Add file operation telemetry (#1068)
Introduces telemetry for file create, read, and update operations.
This change adds the `gemini_cli.file.operation.count` metric, recorded by the `read-file`, `read-many-files`, and `write-file` tools.
The metric includes the following attributes:
- `operation` (string: `create`, `read`, `update`): The type of file operation.
- `lines` (optional, Int): Number of lines in the file.
- `mimetype` (optional, string): Mimetype of the file.
- `extension` (optional, string): File extension of the file.
Here is a stacked bar chart of file operations by extension (`js`, `ts`, `md`):

Here is a stacked bar chart of file operations by type (`create`, `read`, `update`):

#750
cc @allenhutchison as discussed
2025-06-15 16:24:53 -04:00
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 ) ,
) ;
2025-05-29 22:30:18 +00:00
return {
llmContent : result.llmContent ,
returnDisplay : result.returnDisplay ,
} ;
2025-04-19 19:45:42 +01:00
}
}