mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-24 08:40:58 -07:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f01a7d2eac | |||
| 9db123b3b3 | |||
| 7237159d9b |
@@ -25,6 +25,7 @@ import {
|
||||
GREP_PARAM_AFTER,
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
READ_FILE_PARAM_FULL,
|
||||
SHELL_PARAM_IS_BACKGROUND,
|
||||
EDIT_PARAM_OLD_STRING,
|
||||
TRACKER_CREATE_TASK_TOOL_NAME,
|
||||
@@ -215,7 +216,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 (\`${GREP_PARAM_TOTAL_MAX_MATCHES}\`) and a narrow scope (\`${GREP_PARAM_INCLUDE_PATTERN}\` and \`${GREP_PARAM_EXCLUDE_PATTERN}\` parameters).
|
||||
- **Searching and editing:** utilize search tools like ${GREP_TOOL_NAME} with a conservative result count and a narrow scope. Use \`${GREP_PARAM_CONTEXT}\`, \`${GREP_PARAM_BEFORE}\`, and/or \`${GREP_PARAM_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 '${READ_FILE_PARAM_START_LINE}' and '${READ_FILE_PARAM_END_LINE}' to reduce the impact on context. Minimize extra turns, unless unavoidable due to the file being too large.
|
||||
- **Large files:** utilize ${READ_FILE_TOOL_NAME} to get a summary/outline of the file, then use '${READ_FILE_PARAM_START_LINE}' and '${READ_FILE_PARAM_END_LINE}' for targeted reads of relevant sections, or use '${READ_FILE_PARAM_FULL}: true' for small to medium files when the complete context is required.
|
||||
- **Navigating:** read the minimum required to not require additional turns spent reading the file.
|
||||
</examples>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`ReadFileTool > getSchema > should return the Gemini 3 schema when a Gemini 3 modelId is provided 1`] = `"Reads and returns the content of a specified file. To maintain context efficiency, you MUST use 'start_line' and 'end_line' for targeted, surgical reads of specific sections. For your safety, the tool will automatically truncate output exceeding 2000 lines, 2000 characters per line, or 20MB in size; however, triggering these limits is considered token-inefficient. Always retrieve only the minimum content necessary for your next step. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files."`;
|
||||
exports[`ReadFileTool > getSchema > should return the Gemini 3 schema when a Gemini 3 modelId is provided 1`] = `"Reads and returns the content of a specified file. For large files, the tool will return a summary/outline to help you understand the file's structure. You can then use 'start_line' and 'end_line' for targeted reads of specific sections, or 'full: true' to retrieve the complete content. For your safety, the tool will automatically truncate output exceeding 2000 lines, 2000 characters per line, or 20MB in size; however, triggering these limits is considered token-inefficient. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files."`;
|
||||
|
||||
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 base schema when no modelId is provided 1`] = `"Reads and returns the content of a specified file. For large files, the tool will return a summary/outline to help you understand the file's structure. You can then use 'start_line' and 'end_line' for targeted reads of specific sections, or 'full: true' to retrieve the complete content. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files."`;
|
||||
|
||||
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."`;
|
||||
exports[`ReadFileTool > getSchema > should return the schema from the resolver when modelId is provided 1`] = `"Reads and returns the content of a specified file. For large files, the tool will return a summary/outline to help you understand the file's structure. You can then use 'start_line' and 'end_line' for targeted reads of specific sections, or 'full: true' to retrieve the complete content. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files."`;
|
||||
|
||||
+10
-2
@@ -410,7 +410,7 @@ 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 '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.",
|
||||
"description": "Reads and returns the content of a specified file. For large files, the tool will return a summary/outline to help you understand the file's structure. You can then use 'start_line' and 'end_line' for targeted reads of specific sections, or 'full: true' to retrieve the complete content. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files.",
|
||||
"name": "read_file",
|
||||
"parametersJsonSchema": {
|
||||
"properties": {
|
||||
@@ -422,6 +422,10 @@ exports[`coreTools snapshots for specific models > Model: gemini-2.5-pro > snaps
|
||||
"description": "The path to the file to read.",
|
||||
"type": "string",
|
||||
},
|
||||
"full": {
|
||||
"description": "Optional: If true, returns the full file contents. If false (default), large files may be summarized for efficiency.",
|
||||
"type": "boolean",
|
||||
},
|
||||
"start_line": {
|
||||
"description": "Optional: The 1-based line number to start reading from.",
|
||||
"type": "number",
|
||||
@@ -1199,7 +1203,7 @@ 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. To maintain context efficiency, you MUST use 'start_line' and 'end_line' for targeted, surgical reads of specific sections. For your safety, the tool will automatically truncate output exceeding 2000 lines, 2000 characters per line, or 20MB in size; however, triggering these limits is considered token-inefficient. Always retrieve only the minimum content necessary for your next step. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files.",
|
||||
"description": "Reads and returns the content of a specified file. For large files, the tool will return a summary/outline to help you understand the file's structure. You can then use 'start_line' and 'end_line' for targeted reads of specific sections, or 'full: true' to retrieve the complete content. For your safety, the tool will automatically truncate output exceeding 2000 lines, 2000 characters per line, or 20MB in size; however, triggering these limits is considered token-inefficient. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files.",
|
||||
"name": "read_file",
|
||||
"parametersJsonSchema": {
|
||||
"properties": {
|
||||
@@ -1211,6 +1215,10 @@ exports[`coreTools snapshots for specific models > Model: gemini-3-pro-preview >
|
||||
"description": "The path to the file to read.",
|
||||
"type": "string",
|
||||
},
|
||||
"full": {
|
||||
"description": "Optional: If true, returns the full file contents. If false (default), large files may be summarized for efficiency.",
|
||||
"type": "boolean",
|
||||
},
|
||||
"start_line": {
|
||||
"description": "Optional: The 1-based line number to start reading from.",
|
||||
"type": "number",
|
||||
|
||||
@@ -51,6 +51,7 @@ export const LS_PARAM_IGNORE = 'ignore';
|
||||
export const READ_FILE_TOOL_NAME = 'read_file';
|
||||
export const READ_FILE_PARAM_START_LINE = 'start_line';
|
||||
export const READ_FILE_PARAM_END_LINE = 'end_line';
|
||||
export const READ_FILE_PARAM_FULL = 'full';
|
||||
|
||||
// -- run_shell_command --
|
||||
export const SHELL_TOOL_NAME = 'run_shell_command';
|
||||
|
||||
@@ -50,6 +50,7 @@ export {
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
READ_FILE_PARAM_FULL,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
READ_FILE_PARAM_FULL,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
@@ -83,7 +84,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 '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.`,
|
||||
description: `Reads and returns the content of a specified file. For large files, the tool will return a summary/outline to help you understand the file's structure. You can then use 'start_line' and 'end_line' for targeted reads of specific sections, or 'full: true' to retrieve the complete content. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files.`,
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -101,6 +102,11 @@ export const DEFAULT_LEGACY_SET: CoreToolSet = {
|
||||
'Optional: The 1-based line number to end reading at (inclusive).',
|
||||
type: 'number',
|
||||
},
|
||||
[READ_FILE_PARAM_FULL]: {
|
||||
description:
|
||||
'Optional: If true, returns the full file contents. If false (default), large files may be summarized for efficiency.',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
required: [PARAM_FILE_PATH],
|
||||
},
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
READ_FILE_PARAM_FULL,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
@@ -91,7 +92,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. To maintain context efficiency, you MUST use 'start_line' and 'end_line' for targeted, surgical reads of specific sections. For your safety, the tool will automatically truncate output exceeding ${DEFAULT_MAX_LINES_TEXT_FILE} lines, ${MAX_LINE_LENGTH_TEXT_FILE} characters per line, or ${MAX_FILE_SIZE_MB}MB in size; however, triggering these limits is considered token-inefficient. Always retrieve only the minimum content necessary for your next step. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files.`,
|
||||
description: `Reads and returns the content of a specified file. For large files, the tool will return a summary/outline to help you understand the file's structure. You can then use 'start_line' and 'end_line' for targeted reads of specific sections, or 'full: true' to retrieve the complete content. For your safety, the tool will automatically truncate output exceeding ${DEFAULT_MAX_LINES_TEXT_FILE} lines, ${MAX_LINE_LENGTH_TEXT_FILE} characters per line, or ${MAX_FILE_SIZE_MB}MB in size; however, triggering these limits is considered token-inefficient. Handles text, images (PNG, JPG, GIF, WEBP, SVG, BMP), audio files (MP3, WAV, AIFF, AAC, OGG, FLAC), and PDF files.`,
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
@@ -109,6 +110,11 @@ export const GEMINI_3_SET: CoreToolSet = {
|
||||
'Optional: The 1-based line number to end reading at (inclusive).',
|
||||
type: 'number',
|
||||
},
|
||||
[READ_FILE_PARAM_FULL]: {
|
||||
description:
|
||||
'Optional: If true, returns the full file contents. If false (default), large files may be summarized for efficiency.',
|
||||
type: 'boolean',
|
||||
},
|
||||
},
|
||||
required: [PARAM_FILE_PATH],
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { ReadFileTool, type ReadFileToolParams } from './read-file.js';
|
||||
import { ToolErrorType } from './tool-error.js';
|
||||
import path from 'node:path';
|
||||
import { execSync } from 'node:child_process';
|
||||
import { isSubpath } from '../utils/paths.js';
|
||||
import os from 'node:os';
|
||||
import fs from 'node:fs';
|
||||
@@ -41,6 +42,14 @@ vi.mock('./jit-context.js', () => ({
|
||||
JIT_CONTEXT_SUFFIX: '\n--- End Project Context ---',
|
||||
}));
|
||||
|
||||
vi.mock('node:child_process', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('node:child_process')>();
|
||||
return {
|
||||
...actual,
|
||||
execSync: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('ReadFileTool', () => {
|
||||
let tempRootDir: string;
|
||||
let tool: ReadFileTool;
|
||||
@@ -610,7 +619,7 @@ describe('ReadFileTool', () => {
|
||||
const schema = tool.getSchema(modelId);
|
||||
expect(schema.name).toBe(ReadFileTool.Name);
|
||||
expect(schema.description).toMatchSnapshot();
|
||||
expect(schema.description).toContain('surgical reads');
|
||||
expect(schema.description).toContain('targeted reads');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -685,4 +694,124 @@ describe('ReadFileTool', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tilth and full parameter', () => {
|
||||
it('should use tilth for large files when full is false', async () => {
|
||||
const filePath = path.join(tempRootDir, 'large.txt');
|
||||
const content = 'A'.repeat(5000); // Exceeds default 4096 threshold
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
vi.mocked(execSync).mockReturnValue('tilth summary');
|
||||
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: 'large.txt',
|
||||
full: false,
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(execSync).toHaveBeenCalledWith(
|
||||
expect.stringContaining(`npx -y tilth --budget 1000 "${filePath}"`),
|
||||
expect.any(Object),
|
||||
);
|
||||
expect(result.llmContent).toContain('tilth summary');
|
||||
expect(result.returnDisplay).toContain('Summarized large file');
|
||||
});
|
||||
|
||||
it('should NOT use tilth when full is true', async () => {
|
||||
const filePath = path.join(tempRootDir, 'large.txt');
|
||||
const content = 'A'.repeat(5000);
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
vi.mocked(execSync).mockClear();
|
||||
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: 'large.txt',
|
||||
full: true,
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(execSync).not.toHaveBeenCalled();
|
||||
// Should return full content (clamped by MAX_LINE_LENGTH_TEXT_FILE per line, but not summarized by tilth)
|
||||
expect(result.llmContent).not.toContain('--- FILE SUMMARY ---');
|
||||
});
|
||||
|
||||
it('should use tilth when start_line is 1 and no end_line is provided', async () => {
|
||||
const filePath = path.join(tempRootDir, 'large.txt');
|
||||
const content = 'A\n'.repeat(3000); // 6000 bytes
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
vi.mocked(execSync).mockClear();
|
||||
vi.mocked(execSync).mockReturnValue('tilth summary for line 1');
|
||||
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: 'large.txt',
|
||||
start_line: 1,
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(execSync).toHaveBeenCalled();
|
||||
expect(result.llmContent).toContain('tilth summary for line 1');
|
||||
});
|
||||
|
||||
it('should NOT use tilth when custom start_line (not 1) is provided', async () => {
|
||||
const filePath = path.join(tempRootDir, 'large.txt');
|
||||
const content = 'A\n'.repeat(3000);
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
vi.mocked(execSync).mockClear();
|
||||
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: 'large.txt',
|
||||
start_line: 10,
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(execSync).not.toHaveBeenCalled();
|
||||
expect(result.llmContent).not.toContain('--- FILE SUMMARY ---');
|
||||
});
|
||||
|
||||
it('should read to end when start_line > 1 is provided without end_line', async () => {
|
||||
const filePath = path.join(tempRootDir, 'large.txt');
|
||||
const lines = Array.from({ length: 3000 }, (_, i) => `Line ${i + 1}`);
|
||||
fs.writeFileSync(filePath, lines.join('\n'));
|
||||
|
||||
vi.mocked(execSync).mockClear();
|
||||
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: 'large.txt',
|
||||
start_line: 10,
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(execSync).not.toHaveBeenCalled();
|
||||
expect(result.llmContent).toContain('Line 10');
|
||||
expect(result.llmContent).not.toContain('Line 1\n');
|
||||
expect(result.returnDisplay).toContain('Read lines 10-'); // Clamped by default max lines, but direct read
|
||||
});
|
||||
|
||||
it('should respect custom GEMINI_CLI_FULL_READ_THRESHOLD', async () => {
|
||||
const filePath = path.join(tempRootDir, 'medium.txt');
|
||||
const content = 'A'.repeat(1000);
|
||||
fs.writeFileSync(filePath, content);
|
||||
|
||||
vi.stubEnv('GEMINI_CLI_FULL_READ_THRESHOLD', '500');
|
||||
vi.mocked(execSync).mockReturnValue('tilth summary');
|
||||
|
||||
const params: ReadFileToolParams = {
|
||||
file_path: 'medium.txt',
|
||||
};
|
||||
const invocation = tool.build(params);
|
||||
const result = await invocation.execute(abortSignal);
|
||||
|
||||
expect(execSync).toHaveBeenCalled();
|
||||
expect(result.llmContent).toContain('tilth summary');
|
||||
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import path from 'node:path';
|
||||
import { execSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import { makeRelative, shortenPath } from '../utils/paths.js';
|
||||
import {
|
||||
BaseDeclarativeTool,
|
||||
@@ -24,6 +26,8 @@ import type { PartListUnion } from '@google/genai';
|
||||
import {
|
||||
processSingleFileContent,
|
||||
getSpecificMimeType,
|
||||
detectFileType,
|
||||
type ProcessedFileReadResult,
|
||||
} from '../utils/fileUtils.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { FileOperation } from '../telemetry/metrics.js';
|
||||
@@ -34,6 +38,7 @@ import { READ_FILE_TOOL_NAME, READ_FILE_DISPLAY_NAME } from './tool-names.js';
|
||||
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
||||
import { READ_FILE_DEFINITION } from './definitions/coreTools.js';
|
||||
import { resolveToolDeclaration } from './definitions/resolver.js';
|
||||
import { MAX_FILE_SIZE_MB } from '../utils/constants.js';
|
||||
import {
|
||||
discoverJitContext,
|
||||
appendJitContext,
|
||||
@@ -58,6 +63,12 @@ export interface ReadFileToolParams {
|
||||
* The line number to end reading at (optional, 1-based, inclusive)
|
||||
*/
|
||||
end_line?: number;
|
||||
|
||||
/**
|
||||
* If true, returns the full file contents.
|
||||
* If false (default), large files may be summarized for efficiency.
|
||||
*/
|
||||
full?: boolean;
|
||||
}
|
||||
|
||||
class ReadFileToolInvocation extends BaseToolInvocation<
|
||||
@@ -120,13 +131,81 @@ class ReadFileToolInvocation extends BaseToolInvocation<
|
||||
};
|
||||
}
|
||||
|
||||
const result = await processSingleFileContent(
|
||||
this.resolvedPath,
|
||||
this.config.getTargetDir(),
|
||||
this.config.getFileSystemService(),
|
||||
this.params.start_line,
|
||||
this.params.end_line,
|
||||
);
|
||||
let result: ProcessedFileReadResult;
|
||||
if (!fs.existsSync(this.resolvedPath)) {
|
||||
result = await processSingleFileContent(
|
||||
this.resolvedPath,
|
||||
this.config.getTargetDir(),
|
||||
this.config.getFileSystemService(),
|
||||
this.params.start_line,
|
||||
this.params.end_line,
|
||||
this.params.full,
|
||||
);
|
||||
} else {
|
||||
const stats = await fs.promises.stat(this.resolvedPath);
|
||||
const fileSizeInMB = stats.size / (1024 * 1024);
|
||||
if (fileSizeInMB > MAX_FILE_SIZE_MB) {
|
||||
result = await processSingleFileContent(
|
||||
this.resolvedPath,
|
||||
this.config.getTargetDir(),
|
||||
this.config.getFileSystemService(),
|
||||
this.params.start_line,
|
||||
this.params.end_line,
|
||||
this.params.full,
|
||||
);
|
||||
} else {
|
||||
const fileType = await detectFileType(this.resolvedPath);
|
||||
const fullReadThreshold = parseInt(
|
||||
process.env['GEMINI_CLI_FULL_READ_THRESHOLD'] ?? '4096',
|
||||
10,
|
||||
);
|
||||
|
||||
const isExplicitLineRange =
|
||||
(this.params.start_line !== undefined &&
|
||||
this.params.start_line !== 1) ||
|
||||
this.params.end_line !== undefined;
|
||||
|
||||
const isDirectReadRequired =
|
||||
this.params.full === true ||
|
||||
isExplicitLineRange ||
|
||||
stats.size < fullReadThreshold ||
|
||||
fileType !== 'text';
|
||||
if (isDirectReadRequired) {
|
||||
result = await processSingleFileContent(
|
||||
this.resolvedPath,
|
||||
this.config.getTargetDir(),
|
||||
this.config.getFileSystemService(),
|
||||
this.params.start_line,
|
||||
this.params.end_line,
|
||||
this.params.full,
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
const summary = execSync(
|
||||
`npx -y tilth --budget 1000 "${this.resolvedPath}"`,
|
||||
{
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
},
|
||||
).toString();
|
||||
result = {
|
||||
llmContent: summary,
|
||||
returnDisplay: `Summarized large file: ${shortenPath(makeRelative(this.resolvedPath, this.config.getTargetDir()))}`,
|
||||
};
|
||||
} catch (_error) {
|
||||
// Fallback to normal read if tilth fails
|
||||
result = await processSingleFileContent(
|
||||
this.resolvedPath,
|
||||
this.config.getTargetDir(),
|
||||
this.config.getFileSystemService(),
|
||||
this.params.start_line,
|
||||
this.params.end_line,
|
||||
this.params.full,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.error) {
|
||||
return {
|
||||
@@ -256,6 +335,10 @@ export class ReadFileTool extends BaseDeclarativeTool<
|
||||
return 'start_line cannot be greater than end_line';
|
||||
}
|
||||
|
||||
if (params.full !== undefined && typeof params.full !== 'boolean') {
|
||||
return 'full must be a boolean';
|
||||
}
|
||||
|
||||
const fileFilteringOptions = this.config.getFileFilteringOptions();
|
||||
if (
|
||||
this.fileDiscoveryService.shouldIgnoreFile(
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
READ_FILE_PARAM_FULL,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
@@ -107,6 +108,7 @@ export {
|
||||
// Tool-specific parameter names
|
||||
READ_FILE_PARAM_START_LINE,
|
||||
READ_FILE_PARAM_END_LINE,
|
||||
READ_FILE_PARAM_FULL,
|
||||
WRITE_FILE_PARAM_CONTENT,
|
||||
GREP_PARAM_INCLUDE_PATTERN,
|
||||
GREP_PARAM_EXCLUDE_PATTERN,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -402,6 +402,7 @@ export interface ProcessedFileReadResult {
|
||||
* @param _fileSystemService Currently unused in this function; kept for signature stability.
|
||||
* @param startLine Optional 1-based line number to start reading from.
|
||||
* @param endLine Optional 1-based line number to end reading at (inclusive).
|
||||
* @param full Optional boolean to indicate if full file content should be returned without line truncation.
|
||||
* @returns ProcessedFileReadResult object.
|
||||
*/
|
||||
export async function processSingleFileContent(
|
||||
@@ -410,6 +411,7 @@ export async function processSingleFileContent(
|
||||
_fileSystemService: FileSystemService,
|
||||
startLine?: number,
|
||||
endLine?: number,
|
||||
full?: boolean,
|
||||
): Promise<ProcessedFileReadResult> {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
@@ -482,11 +484,13 @@ export async function processSingleFileContent(
|
||||
sliceStart = startLine ? startLine - 1 : 0;
|
||||
sliceEnd = endLine
|
||||
? Math.min(endLine, originalLineCount)
|
||||
: Math.min(
|
||||
sliceStart + DEFAULT_MAX_LINES_TEXT_FILE,
|
||||
originalLineCount,
|
||||
);
|
||||
} else {
|
||||
: full
|
||||
? originalLineCount
|
||||
: Math.min(
|
||||
sliceStart + DEFAULT_MAX_LINES_TEXT_FILE,
|
||||
originalLineCount,
|
||||
);
|
||||
} else if (!full) {
|
||||
sliceEnd = Math.min(DEFAULT_MAX_LINES_TEXT_FILE, originalLineCount);
|
||||
}
|
||||
|
||||
@@ -496,7 +500,7 @@ export async function processSingleFileContent(
|
||||
|
||||
let linesWereTruncatedInLength = false;
|
||||
const formattedLines = selectedLines.map((line) => {
|
||||
if (line.length > MAX_LINE_LENGTH_TEXT_FILE) {
|
||||
if (!full && line.length > MAX_LINE_LENGTH_TEXT_FILE) {
|
||||
linesWereTruncatedInLength = true;
|
||||
return (
|
||||
line.substring(0, MAX_LINE_LENGTH_TEXT_FILE) + '... [truncated]'
|
||||
|
||||
Reference in New Issue
Block a user