Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions packages/core/src/core/__snapshots__/prompts.test.ts.snap

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/src/prompts/snippets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ Use the following guidelines to optimize your search and read patterns.
- **Searching:** utilize search tools like ${GREP_TOOL_NAME} and ${GLOB_TOOL_NAME} with a conservative result count (\`total_max_matches\`) and a narrow scope (\`include\` and \`exclude\` parameters).
- **Searching and editing:** utilize search tools like ${GREP_TOOL_NAME} with a conservative result count and a narrow scope. Use \`context\`, \`before\`, and/or \`after\` to request enough context to avoid the need to read the file before editing matches.
- **Understanding:** minimize turns needed to understand a file. It's most efficient to read small files in their entirety.
- **Large files:** utilize search tools like ${GREP_TOOL_NAME} and/or ${READ_FILE_TOOL_NAME} called in parallel with an offset and a limit to reduce the impact on context. Minmize extra turns, unless unavoidable due to the file being too large.
- **Large files:** utilize search tools like ${GREP_TOOL_NAME} and/or ${READ_FILE_TOOL_NAME} called in parallel with 'start_line' and 'end_line' to reduce the impact on context. Minimize extra turns, unless unavoidable due to the file being too large.
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
</examples>

Expand Down
Original file line number Diff line number Diff line change
@@ -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."`;
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Comment thread
adamfweidman marked this conversation as resolved.
"type": "number",
},
},
Expand Down Expand Up @@ -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",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,22 @@ 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.`,
Comment thread
adamfweidman marked this conversation as resolved.
parametersJsonSchema: {
type: 'object',
properties: {
file_path: {
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',
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,22 +38,22 @@ 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: {
file_path: {
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',
},
},
Expand Down
35 changes: 23 additions & 12 deletions packages/core/src/tools/read-file.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 end_line is less than 1', () => {
const params: ReadFileToolParams = {
file_path: path.join(tempRootDir, 'test.txt'),
end_line: 0,
};
expect(() => tool.build(params)).toThrow('end_line must be at least 1');
});

it('should throw error if limit is zero or negative', () => {
it('should throw error if start_line is greater than end_line', () => {
const params: ReadFileToolParams = {
file_path: path.join(tempRootDir, 'test.txt'),
limit: 0,
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'),
Expand Down Expand Up @@ -393,16 +400,16 @@ 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');
await fsp.writeFile(filePath, fileContent, 'utf-8');

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);

Expand Down Expand Up @@ -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', () => {
Expand Down
40 changes: 25 additions & 15 deletions packages/core/src/tools/read-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down Expand Up @@ -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> {
Expand All @@ -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) {
Expand All @@ -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}`;
Expand Down Expand Up @@ -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.end_line !== undefined && params.end_line < 1) {
return 'end_line must be at least 1';
}
if (params.limit !== undefined && params.limit <= 0) {
return 'Limit must be a positive number';
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();
Expand Down
Loading
Loading