2025-04-19 19:45:42 +01:00
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
2025-10-21 11:45:33 -07:00
import type { MessageBus } from '../confirmation-bus/message-bus.js' ;
2025-08-25 22:11:27 +02:00
import path from 'node:path' ;
2025-04-19 19:45:42 +01:00
import { makeRelative , shortenPath } from '../utils/paths.js' ;
2025-08-26 00:04:53 +02:00
import type { ToolInvocation , ToolLocation , ToolResult } from './tools.js' ;
import { BaseDeclarativeTool , BaseToolInvocation , Kind } from './tools.js' ;
2026-01-27 13:17:40 -08:00
import { ToolErrorType } from './tool-error.js' ;
2025-08-20 16:13:29 -07:00
2025-08-26 00:04:53 +02:00
import type { PartUnion } from '@google/genai' ;
2025-06-29 18:09:15 +09:00
import {
processSingleFileContent ,
getSpecificMimeType ,
} from '../utils/fileUtils.js' ;
2025-08-26 00:04:53 +02:00
import type { Config } from '../config/config.js' ;
2025-08-22 17:47:32 +05:30
import { FileOperation } from '../telemetry/metrics.js' ;
import { getProgrammingLanguage } from '../telemetry/telemetry-utils.js' ;
import { logFileOperation } from '../telemetry/loggers.js' ;
import { FileOperationEvent } from '../telemetry/types.js' ;
2025-10-19 19:21:47 -04:00
import { READ_FILE_TOOL_NAME } from './tool-names.js' ;
2026-01-27 17:19:13 -08:00
import { FileDiscoveryService } from '../services/fileDiscoveryService.js' ;
2026-02-09 15:46:23 -05:00
import { READ_FILE_DEFINITION } from './definitions/coreTools.js' ;
import { resolveToolDeclaration } from './definitions/resolver.js' ;
2025-04-19 19:45:42 +01:00
/**
* Parameters for the ReadFile tool
*/
export interface ReadFileToolParams {
/**
2025-11-06 15:03:52 -08:00
* The path to the file to read
2025-04-19 19:45:42 +01:00
*/
2025-11-06 15:03:52 -08:00
file_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 ;
}
2025-08-07 10:05:37 -07:00
class ReadFileToolInvocation extends BaseToolInvocation <
ReadFileToolParams ,
ToolResult
> {
2025-11-06 15:03:52 -08:00
private readonly resolvedPath : string ;
2025-08-06 10:50:02 -07:00
constructor (
private config : Config ,
2025-08-07 10:05:37 -07:00
params : ReadFileToolParams ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-10-21 11:45:33 -07:00
_toolName? : string ,
_toolDisplayName? : string ,
2025-08-07 10:05:37 -07:00
) {
2025-10-21 11:45:33 -07:00
super ( params , messageBus , _toolName , _toolDisplayName ) ;
2025-11-06 15:03:52 -08:00
this . resolvedPath = path . resolve (
this . config . getTargetDir ( ) ,
this . params . file_path ,
) ;
2025-08-07 10:05:37 -07:00
}
2025-08-06 10:50:02 -07:00
getDescription ( ) : string {
const relativePath = makeRelative (
2025-11-06 15:03:52 -08:00
this . resolvedPath ,
2025-08-06 10:50:02 -07:00
this . config . getTargetDir ( ) ,
) ;
return shortenPath ( relativePath ) ;
}
2025-08-07 10:05:37 -07:00
override toolLocations ( ) : ToolLocation [ ] {
2025-11-06 15:03:52 -08:00
return [ { path : this.resolvedPath , line : this.params.offset } ] ;
2025-08-06 10:50:02 -07:00
}
async execute ( ) : Promise < ToolResult > {
2026-02-09 12:24:28 -08:00
const validationError = this . config . validatePathAccess (
this . resolvedPath ,
'read' ,
) ;
2026-01-27 13:17:40 -08:00
if ( validationError ) {
return {
llmContent : validationError ,
returnDisplay : 'Path not in workspace.' ,
error : {
message : validationError ,
type : ToolErrorType . PATH_NOT_IN_WORKSPACE ,
} ,
} ;
}
2025-08-06 10:50:02 -07:00
const result = await processSingleFileContent (
2025-11-06 15:03:52 -08:00
this . resolvedPath ,
2025-08-06 10:50:02 -07:00
this . config . getTargetDir ( ) ,
2025-08-18 16:29:45 -06:00
this . config . getFileSystemService ( ) ,
2025-08-06 10:50:02 -07:00
this . params . offset ,
this . params . limit ,
) ;
if ( result . error ) {
return {
2025-08-20 16:13:29 -07:00
llmContent : result.llmContent ,
2025-08-08 04:33:42 -07:00
returnDisplay : result.returnDisplay || 'Error reading file' ,
error : {
message : result.error ,
2025-08-20 16:13:29 -07:00
type : result . errorType ,
2025-08-08 04:33:42 -07:00
} ,
2025-08-06 10:50:02 -07:00
} ;
}
2025-08-06 13:52:04 -07:00
let llmContent : PartUnion ;
if ( result . isTruncated ) {
const [ start , end ] = result . linesShown ! ;
const total = result . originalLineCount ! ;
const nextOffset = this . params . offset
? this . params . offset + end - start + 1
: end ;
llmContent = `
IMPORTANT: The file content has been truncated.
Status: Showing lines ${ start } - ${ end } of ${ total } total lines.
Action: To read more of the file, you can use the 'offset' and 'limit' parameters in a subsequent 'read_file' call. For example, to read the next section of the file, use offset: ${ nextOffset } .
--- FILE CONTENT (truncated) ---
${ result . llmContent } ` ;
} else {
llmContent = result . llmContent || '' ;
}
2025-08-06 10:50:02 -07:00
const lines =
typeof result . llmContent === 'string'
? result . llmContent . split ( '\n' ) . length
: undefined ;
2025-11-06 15:03:52 -08:00
const mimetype = getSpecificMimeType ( this . resolvedPath ) ;
2025-08-22 17:47:32 +05:30
const programming_language = getProgrammingLanguage ( {
2025-11-06 15:03:52 -08:00
file_path : this.resolvedPath ,
2025-08-22 17:47:32 +05:30
} ) ;
logFileOperation (
2025-08-06 10:50:02 -07:00
this . config ,
2025-08-22 17:47:32 +05:30
new FileOperationEvent (
2025-10-19 19:21:47 -04:00
READ_FILE_TOOL_NAME ,
2025-08-22 17:47:32 +05:30
FileOperation . READ ,
lines ,
mimetype ,
2025-11-06 15:03:52 -08:00
path . extname ( this . resolvedPath ) ,
2025-08-22 17:47:32 +05:30
programming_language ,
) ,
2025-08-06 10:50:02 -07:00
) ;
return {
2025-08-06 13:52:04 -07:00
llmContent ,
2025-08-06 10:50:02 -07:00
returnDisplay : result.returnDisplay || '' ,
} ;
}
}
2025-04-19 19:45:42 +01:00
/**
* Implementation of the ReadFile tool logic
*/
2025-08-06 10:50:02 -07:00
export class ReadFileTool extends BaseDeclarativeTool <
ReadFileToolParams ,
ToolResult
> {
2025-10-20 22:35:35 -04:00
static readonly Name = READ_FILE_TOOL_NAME ;
2026-01-27 17:19:13 -08:00
private readonly fileDiscoveryService : FileDiscoveryService ;
2025-10-20 22:35:35 -04:00
2025-10-21 11:45:33 -07:00
constructor (
private config : Config ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-10-21 11:45:33 -07:00
) {
2025-04-19 19:45:42 +01:00
super (
2025-10-20 22:35:35 -04:00
ReadFileTool . Name ,
2025-04-21 10:53:11 -04:00
'ReadFile' ,
2026-02-09 15:46:23 -05:00
READ_FILE_DEFINITION . base . description ! ,
2025-08-13 12:58:26 -03:00
Kind . Read ,
2026-02-09 15:46:23 -05:00
READ_FILE_DEFINITION . base . parameters ! ,
2026-01-04 17:11:43 -05:00
messageBus ,
2025-10-21 11:45:33 -07:00
true ,
false ,
2025-04-19 19:45:42 +01:00
) ;
2026-01-27 17:19:13 -08:00
this . fileDiscoveryService = new FileDiscoveryService (
config . getTargetDir ( ) ,
config . getFileFilteringOptions ( ) ,
) ;
2025-04-19 19:45:42 +01:00
}
2025-08-19 13:55:06 -07:00
protected override validateToolParamValues (
2025-08-13 16:17:38 -04:00
params : ReadFileToolParams ,
) : string | null {
2025-11-06 15:03:52 -08:00
if ( params . file_path . trim ( ) === '' ) {
return "The 'file_path' parameter must be non-empty." ;
2025-04-19 19:45:42 +01:00
}
2025-07-31 05:38:20 +09:00
2025-11-06 15:03:52 -08:00
const resolvedPath = path . resolve (
this . config . getTargetDir ( ) ,
params . file_path ,
) ;
2026-01-27 13:17:40 -08:00
2026-02-09 12:24:28 -08:00
const validationError = this . config . validatePathAccess (
resolvedPath ,
'read' ,
) ;
2026-01-27 13:17:40 -08:00
if ( validationError ) {
return validationError ;
2025-04-19 19:45:42 +01:00
}
2026-01-27 13:17:40 -08:00
2025-04-19 19:45:42 +01:00
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-10-24 18:55:12 -07:00
const fileFilteringOptions = this . config . getFileFilteringOptions ( ) ;
2026-01-27 17:19:13 -08:00
if (
this . fileDiscoveryService . shouldIgnoreFile (
resolvedPath ,
fileFilteringOptions ,
)
) {
2025-11-06 15:03:52 -08:00
return ` File path ' ${ resolvedPath } ' is ignored by configured ignore patterns. ` ;
2025-06-05 10:15:27 -07:00
}
2025-04-19 19:45:42 +01:00
return null ;
}
2025-08-06 10:50:02 -07:00
protected createInvocation (
2025-05-09 23:29:02 -07:00
params : ReadFileToolParams ,
2026-01-04 17:11:43 -05:00
messageBus : MessageBus ,
2025-10-21 11:45:33 -07:00
_toolName? : string ,
_toolDisplayName? : string ,
2025-08-06 10:50:02 -07:00
) : ToolInvocation < ReadFileToolParams , ToolResult > {
2025-10-21 11:45:33 -07:00
return new ReadFileToolInvocation (
this . config ,
params ,
2026-01-04 17:11:43 -05:00
messageBus ,
2025-10-21 11:45:33 -07:00
_toolName ,
_toolDisplayName ,
) ;
2025-04-19 19:45:42 +01:00
}
2026-02-09 15:46:23 -05:00
override getSchema ( modelId? : string ) {
return resolveToolDeclaration ( READ_FILE_DEFINITION , modelId ) ;
}
2025-04-19 19:45:42 +01:00
}