mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-28 20:27:08 -07:00
feat(core): migrate read_file to 1-based start_line/end_line parameters (#19526)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`ReadFileTool > getSchema > should return the base schema when no modelId is provided 1`] = `"Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'offset' and 'limit' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges."`;
|
||||
exports[`ReadFileTool > getSchema > should return the base schema when no modelId is provided 1`] = `"Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'start_line' and 'end_line' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges."`;
|
||||
|
||||
exports[`ReadFileTool > getSchema > should return the schema from the resolver when modelId is provided 1`] = `"Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'offset' and 'limit' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges."`;
|
||||
exports[`ReadFileTool > getSchema > should return the schema from the resolver when modelId is provided 1`] = `"Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'start_line' and 'end_line' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges."`;
|
||||
|
||||
+14
-14
@@ -411,20 +411,20 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps
|
||||
|
||||
exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snapshot for tool: read_file 1`] = `
|
||||
{
|
||||
"description": "Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'offset' and 'limit' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.",
|
||||
"description": "Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'start_line' and 'end_line' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.",
|
||||
"name": "read_file",
|
||||
"parametersJsonSchema": {
|
||||
"properties": {
|
||||
"end_line": {
|
||||
"description": "Optional: The 1-based line number to end reading at (inclusive).",
|
||||
"type": "number",
|
||||
},
|
||||
"file_path": {
|
||||
"description": "The path to the file to read.",
|
||||
"type": "string",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
"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.",
|
||||
"start_line": {
|
||||
"description": "Optional: The 1-based line number to start reading from.",
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
@@ -1200,20 +1200,20 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview >
|
||||
|
||||
exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview > snapshot for tool: read_file 1`] = `
|
||||
{
|
||||
"description": "Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'offset' and 'limit' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.",
|
||||
"description": "Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'start_line' and 'end_line' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.",
|
||||
"name": "read_file",
|
||||
"parametersJsonSchema": {
|
||||
"properties": {
|
||||
"end_line": {
|
||||
"description": "Optional: The 1-based line number to end reading at (inclusive).",
|
||||
"type": "number",
|
||||
},
|
||||
"file_path": {
|
||||
"description": "The path to the file to read.",
|
||||
"type": "string",
|
||||
},
|
||||
"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",
|
||||
},
|
||||
"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.",
|
||||
"start_line": {
|
||||
"description": "Optional: The 1-based line number to start reading from.",
|
||||
"type": "number",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@ import {
|
||||
export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
read_file: {
|
||||
name: READ_FILE_TOOL_NAME,
|
||||
description: `Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'offset' and 'limit' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.`,
|
||||
description: `Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'start_line' and 'end_line' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.`,
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -43,14 +43,14 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
description: 'The path to the file to read.',
|
||||
type: 'string',
|
||||
},
|
||||
offset: {
|
||||
start_line: {
|
||||
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.",
|
||||
'Optional: The 1-based line number to start reading from.',
|
||||
type: 'number',
|
||||
},
|
||||
limit: {
|
||||
end_line: {
|
||||
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).",
|
||||
'Optional: The 1-based line number to end reading at (inclusive).',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ import {
|
||||
export const GEMINI_3_SET: CoreToolSet = {
|
||||
read_file: {
|
||||
name: READ_FILE_TOOL_NAME,
|
||||
description: `Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'offset' and 'limit' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.`,
|
||||
description: `Reads and returns the content of a specified file. If the file is large, the content will be truncated. The tool's response will clearly indicate if truncation has occurred and will provide details on how to read more of the file using the 'start_line' and 'end_line' parameters. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files. For text files, it can read specific line ranges.`,
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -46,14 +46,14 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
description: 'The path to the file to read.',
|
||||
type: 'string',
|
||||
},
|
||||
offset: {
|
||||
start_line: {
|
||||
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.",
|
||||
'Optional: The 1-based line number to start reading from.',
|
||||
type: 'number',
|
||||
},
|
||||
limit: {
|
||||
end_line: {
|
||||
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).",
|
||||
'Optional: The 1-based line number to end reading at (inclusive).',
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -130,29 +130,36 @@ describe('ReadFileTool', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw error if offset is negative', () => {
|
||||
it('should throw error if start_line is less than 1', () => {
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: path.join(tempRootDir, 'test.txt'),
|
||||
offset: -1,
|
||||
start_line: 0,
|
||||
};
|
||||
expect(() => tool.build(params)).toThrow(
|
||||
'Offset must be a non-negative number',
|
||||
);
|
||||
expect(() => tool.build(params)).toThrow('start_line must be at least 1');
|
||||
});
|
||||
|
||||
it('should throw error if limit is zero or negative', () => {
|
||||
it('should throw error if end_line is less than 1', () => {
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: path.join(tempRootDir, 'test.txt'),
|
||||
limit: 0,
|
||||
end_line: 0,
|
||||
};
|
||||
expect(() => tool.build(params)).toThrow('end_line must be at least 1');
|
||||
});
|
||||
|
||||
it('should throw error if start_line is greater than end_line', () => {
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: path.join(tempRootDir, 'test.txt'),
|
||||
start_line: 10,
|
||||
end_line: 5,
|
||||
};
|
||||
expect(() => tool.build(params)).toThrow(
|
||||
'Limit must be a positive number',
|
||||
'start_line cannot be greater than end_line',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDescription', () => {
|
||||
it('should return relative path without limit/offset', () => {
|
||||
it('should return relative path without ranges', () => {
|
||||
const subDir = path.join(tempRootDir, 'sub', 'dir');
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: path.join(subDir, 'file.txt'),
|
||||
@@ -393,7 +400,7 @@ describe('ReadFileTool', () => {
|
||||
expect(result.returnDisplay).toBe('');
|
||||
});
|
||||
|
||||
it('should support offset and limit for text files', async () => {
|
||||
it('should support start_line and end_line for text files', async () => {
|
||||
const filePath = path.join(tempRootDir, 'paginated.txt');
|
||||
const lines = Array.from({ length: 20 }, (_, i) => `Line ${i + 1}`);
|
||||
const fileContent = lines.join('\n');
|
||||
@@ -401,8 +408,8 @@ describe('ReadFileTool', () => {
|
||||
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: filePath,
|
||||
offset: 5, // Start from line 6
|
||||
limit: 3,
|
||||
start_line: 6,
|
||||
end_line: 8,
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
|
||||
@@ -569,6 +576,10 @@ describe('ReadFileTool', () => {
|
||||
const schema = tool.getSchema();
|
||||
expect(schema.name).toBe(ReadFileTool.Name);
|
||||
expect(schema.description).toMatchSnapshot();
|
||||
expect(
|
||||
(schema.parametersJsonSchema as { properties: Record<string, unknown> })
|
||||
.properties,
|
||||
).not.toHaveProperty('offset');
|
||||
});
|
||||
|
||||
it('should return the schema from the resolver when modelId is provided', () => {
|
||||
|
||||
@@ -36,14 +36,14 @@ export interface ReadFileToolParams {
|
||||
file_path: string;
|
||||
|
||||
/**
|
||||
* The line number to start reading from (optional)
|
||||
* The line number to start reading from (optional, 1-based)
|
||||
*/
|
||||
offset?: number;
|
||||
start_line?: number;
|
||||
|
||||
/**
|
||||
* The number of lines to read (optional)
|
||||
* The line number to end reading at (optional, 1-based, inclusive)
|
||||
*/
|
||||
limit?: number;
|
||||
end_line?: number;
|
||||
}
|
||||
|
||||
class ReadFileToolInvocation extends BaseToolInvocation<
|
||||
@@ -74,7 +74,12 @@ class ReadFileToolInvocation extends BaseToolInvocation<
|
||||
}
|
||||
|
||||
override toolLocations(): ToolLocation[] {
|
||||
return [{ path: this.resolvedPath, line: this.params.offset }];
|
||||
return [
|
||||
{
|
||||
path: this.resolvedPath,
|
||||
line: this.params.start_line,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async execute(): Promise<ToolResult> {
|
||||
@@ -97,8 +102,8 @@ class ReadFileToolInvocation extends BaseToolInvocation<
|
||||
this.resolvedPath,
|
||||
this.config.getTargetDir(),
|
||||
this.config.getFileSystemService(),
|
||||
this.params.offset,
|
||||
this.params.limit,
|
||||
this.params.start_line,
|
||||
this.params.end_line,
|
||||
);
|
||||
|
||||
if (result.error) {
|
||||
@@ -116,13 +121,11 @@ class ReadFileToolInvocation extends BaseToolInvocation<
|
||||
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}.
|
||||
Action: To read more of the file, you can use the 'start_line' and 'end_line' parameters in a subsequent 'read_file' call. For example, to read the next section of the file, use start_line: ${end + 1}.
|
||||
|
||||
--- FILE CONTENT (truncated) ---
|
||||
${result.llmContent}`;
|
||||
@@ -207,11 +210,18 @@ export class ReadFileTool extends BaseDeclarativeTool<
|
||||
return validationError;
|
||||
}
|
||||
|
||||
if (params.offset !== undefined && params.offset < 0) {
|
||||
return 'Offset must be a non-negative number';
|
||||
if (params.start_line !== undefined && params.start_line < 1) {
|
||||
return 'start_line must be at least 1';
|
||||
}
|
||||
if (params.limit !== undefined && params.limit <= 0) {
|
||||
return 'Limit must be a positive number';
|
||||
if (params.end_line !== undefined && params.end_line < 1) {
|
||||
return 'end_line must be at least 1';
|
||||
}
|
||||
if (
|
||||
params.start_line !== undefined &&
|
||||
params.end_line !== undefined &&
|
||||
params.start_line > params.end_line
|
||||
) {
|
||||
return 'start_line cannot be greater than end_line';
|
||||
}
|
||||
|
||||
const fileFilteringOptions = this.config.getFileFilteringOptions();
|
||||
|
||||
Reference in New Issue
Block a user