diff --git a/docs/cli/settings.md b/docs/cli/settings.md index 01ca1afdac0..2e5c5312763 100644 --- a/docs/cli/settings.md +++ b/docs/cli/settings.md @@ -91,6 +91,7 @@ they appear in the UI. | Show Color | `tools.shell.showColor` | Show color in shell output. | `false` | | Auto Accept | `tools.autoAccept` | Automatically accept and execute tool calls that are considered safe (e.g., read-only operations). | `false` | | Use Ripgrep | `tools.useRipgrep` | Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance. | `true` | +| Ripgrep Max Matches | `tools.ripgrep.maxMatches` | Maximum number of matches returned by the ripgrep tool. Increase for large repos or lower to conserve context. | `20000` | | Enable Tool Output Truncation | `tools.enableToolOutputTruncation` | Enable truncation of large tool outputs. | `true` | | Tool Output Truncation Threshold | `tools.truncateToolOutputThreshold` | Truncate tool output if it is larger than this many characters. Set to -1 to disable. | `10000` | | Tool Output Truncation Lines | `tools.truncateToolOutputLines` | The number of lines to keep when truncating tool output. | `100` | diff --git a/docs/get-started/configuration.md b/docs/get-started/configuration.md index a7a7213df23..3e3eec1b6a9 100644 --- a/docs/get-started/configuration.md +++ b/docs/get-started/configuration.md @@ -617,6 +617,11 @@ their corresponding top-level category object in your `settings.json` file. implementation. Provides faster search performance. - **Default:** `true` +- **`tools.ripgrep.maxMatches`** (number): + - **Description:** Maximum number of matches returned by the ripgrep tool. + Increase for larger repositories or decrease to conserve context. + - **Default:** `20000` + - **`tools.enableToolOutputTruncation`** (boolean): - **Description:** Enable truncation of large tool outputs. - **Default:** `true` diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index c4c56424a58..c1f5d3e7432 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -651,6 +651,7 @@ export async function loadCliConfig( truncateToolOutputThreshold: settings.tools?.truncateToolOutputThreshold, truncateToolOutputLines: settings.tools?.truncateToolOutputLines, enableToolOutputTruncation: settings.tools?.enableToolOutputTruncation, + ripgrepMaxMatches: settings.tools?.ripgrep?.maxMatches, eventEmitter: appEvents, useSmartEdit: argv.useSmartEdit ?? settings.useSmartEdit, useWriteTodos: argv.useWriteTodos ?? settings.useWriteTodos, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 0fcae7874a5..7f6193e8f06 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -20,6 +20,7 @@ import type { import { DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES, DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD, + DEFAULT_RIPGREP_MAX_MATCHES, DEFAULT_GEMINI_MODEL, DEFAULT_MODEL_CONFIGS, } from '@google/gemini-cli-core'; @@ -1012,6 +1013,27 @@ const SETTINGS_SCHEMA = { 'Use ripgrep for file content search instead of the fallback implementation. Provides faster search performance.', showInDialog: true, }, + ripgrep: { + type: 'object', + label: 'Ripgrep', + category: 'Tools', + requiresRestart: false, + default: {}, + description: 'Ripgrep-specific settings.', + showInDialog: false, + properties: { + maxMatches: { + type: 'number', + label: 'Ripgrep Max Matches', + category: 'Tools', + requiresRestart: false, + default: DEFAULT_RIPGREP_MAX_MATCHES, + description: + 'Maximum number of matches returned by the ripgrep tool. Increase for larger repositories or decrease to conserve context.', + showInDialog: true, + }, + }, + }, enableToolOutputTruncation: { type: 'boolean', label: 'Enable Tool Output Truncation', diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index a3dda9371db..4bad01e5ffd 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -7,7 +7,11 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import type { Mock } from 'vitest'; import type { ConfigParameters, SandboxConfig } from './config.js'; -import { Config, DEFAULT_FILE_FILTERING_OPTIONS } from './config.js'; +import { + Config, + DEFAULT_FILE_FILTERING_OPTIONS, + DEFAULT_RIPGREP_MAX_MATCHES, +} from './config.js'; import { ExperimentFlags } from '../code_assist/experiments/flagNames.js'; import { debugLogger } from '../utils/debugLogger.js'; import { ApprovalMode } from '../policy/types.js'; @@ -70,6 +74,7 @@ vi.mock('../tools/grep.js'); vi.mock('../tools/ripGrep.js', () => ({ canUseRipgrep: vi.fn(), RipGrepTool: class MockRipGrepTool {}, + RIPGREP_DEFAULT_MAX_MATCHES: 20000, })); vi.mock('../tools/glob'); vi.mock('../tools/edit'); @@ -987,6 +992,22 @@ describe('Server Config (config.ts)', () => { }); }); + describe('getRipgrepMaxMatches', () => { + it('returns the default value when not configured', () => { + const config = new Config(baseParams); + expect(config.getRipgrepMaxMatches()).toBe(DEFAULT_RIPGREP_MAX_MATCHES); + }); + + it('returns the configured value when provided', () => { + const customValue = 1234; + const config = new Config({ + ...baseParams, + ripgrepMaxMatches: customValue, + }); + expect(config.getRipgrepMaxMatches()).toBe(customValue); + }); + }); + describe('Proxy Configuration Error Handling', () => { beforeEach(() => { vi.clearAllMocks(); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index ef906341343..277c7aaf561 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -171,6 +171,7 @@ export { export const DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD = 4_000_000; export const DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES = 1000; +export const DEFAULT_RIPGREP_MAX_MATCHES = 20_000; export class MCPServerConfig { constructor( @@ -282,6 +283,7 @@ export interface ConfigParameters { truncateToolOutputThreshold?: number; truncateToolOutputLines?: number; enableToolOutputTruncation?: boolean; + ripgrepMaxMatches?: number; eventEmitter?: EventEmitter; useSmartEdit?: boolean; useWriteTodos?: boolean; @@ -391,6 +393,7 @@ export class Config { private readonly truncateToolOutputThreshold: number; private readonly truncateToolOutputLines: number; private readonly enableToolOutputTruncation: boolean; + private readonly ripgrepMaxMatches: number; private initialized: boolean = false; readonly storage: Storage; private readonly fileExclusions: FileExclusions; @@ -512,6 +515,8 @@ export class Config { this.truncateToolOutputLines = params.truncateToolOutputLines ?? DEFAULT_TRUNCATE_TOOL_OUTPUT_LINES; this.enableToolOutputTruncation = params.enableToolOutputTruncation ?? true; + this.ripgrepMaxMatches = + params.ripgrepMaxMatches ?? DEFAULT_RIPGREP_MAX_MATCHES; this.useSmartEdit = params.useSmartEdit ?? true; this.useWriteTodos = params.useWriteTodos ?? true; this.enableHooks = params.enableHooks ?? false; @@ -1263,6 +1268,10 @@ export class Config { return this.useRipgrep; } + getRipgrepMaxMatches(): number { + return this.ripgrepMaxMatches; + } + getEnableInteractiveShell(): boolean { return this.enableInteractiveShell; } diff --git a/packages/core/src/tools/ripGrep.test.ts b/packages/core/src/tools/ripGrep.test.ts index 24b2de35bb7..914288b5cdf 100644 --- a/packages/core/src/tools/ripGrep.test.ts +++ b/packages/core/src/tools/ripGrep.test.ts @@ -530,6 +530,42 @@ describe('RipGrepTool', () => { expect(result.returnDisplay).toBe('Found 1 match'); }); + it('should respect the configured ripgrep max matches', async () => { + const limitedConfig = { + ...mockConfig, + getRipgrepMaxMatches: () => 5, + } as unknown as Config; + const limitedTool = new RipGrepTool(limitedConfig); + + const outputData = Array.from({ length: 6 }, (_, index) => + JSON.stringify({ + type: 'match', + data: { + path: { text: `file${index}.txt` }, + line_number: index + 1, + lines: { text: `match ${index}\n` }, + }, + }), + ).join('\n'); + + mockSpawn.mockImplementationOnce( + createMockSpawn({ + outputData, + exitCode: 0, + }), + ); + + const params: RipGrepToolParams = { pattern: 'match' }; + const invocation = limitedTool.build(params); + const result = await invocation.execute(abortSignal); + + expect(result.returnDisplay).toBe('Found 5 matches (limited)'); + expect(result.llmContent).toContain( + '(results limited to 5 matches for performance)', + ); + expect(result.llmContent).not.toContain('File: file5.txt'); + }); + it('should return "No matches found" when pattern does not exist', async () => { // Setup specific mock for no matches mockSpawn.mockImplementationOnce( diff --git a/packages/core/src/tools/ripGrep.ts b/packages/core/src/tools/ripGrep.ts index 674fedb5524..f434162a640 100644 --- a/packages/core/src/tools/ripGrep.ts +++ b/packages/core/src/tools/ripGrep.ts @@ -14,7 +14,7 @@ import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js'; import { SchemaValidator } from '../utils/schemaValidator.js'; import { makeRelative, shortenPath } from '../utils/paths.js'; import { getErrorMessage, isNodeError } from '../utils/errors.js'; -import type { Config } from '../config/config.js'; +import { DEFAULT_RIPGREP_MAX_MATCHES, type Config } from '../config/config.js'; import { fileExists } from '../utils/fileUtils.js'; import { Storage } from '../config/storage.js'; import { GREP_TOOL_NAME } from './tool-names.js'; @@ -24,8 +24,6 @@ import { COMMON_DIRECTORY_EXCLUDES, } from '../utils/ignorePatterns.js'; -const DEFAULT_TOTAL_MAX_MATCHES = 20000; - function getRgCandidateFilenames(): readonly string[] { return process.platform === 'win32' ? ['rg.exe', 'rg'] : ['rg']; } @@ -206,7 +204,8 @@ class GrepToolInvocation extends BaseToolInvocation< const searchDirAbs = resolveAndValidatePath(this.config, pathParam); const searchDirDisplay = pathParam; - const totalMaxMatches = DEFAULT_TOTAL_MAX_MATCHES; + const totalMaxMatches = + this.config.getRipgrepMaxMatches() ?? DEFAULT_RIPGREP_MAX_MATCHES; if (this.config.getDebugMode()) { debugLogger.log(`[GrepTool] Total result limit: ${totalMaxMatches}`); } diff --git a/schemas/settings.schema.json b/schemas/settings.schema.json index 8f2f74f7f51..42776f55898 100644 --- a/schemas/settings.schema.json +++ b/schemas/settings.schema.json @@ -994,6 +994,23 @@ "default": true, "type": "boolean" }, + "ripgrep": { + "title": "Ripgrep", + "description": "Ripgrep-specific settings.", + "markdownDescription": "Ripgrep-specific settings.\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `{}`", + "default": {}, + "type": "object", + "properties": { + "maxMatches": { + "title": "Ripgrep Max Matches", + "description": "Maximum number of matches returned by the ripgrep tool. Increase for larger repositories or decrease to conserve context.", + "markdownDescription": "Maximum number of matches returned by the ripgrep tool. Increase for larger repositories or decrease to conserve context.\n\n- Category: `Tools`\n- Requires restart: `no`\n- Default: `20000`", + "default": 20000, + "type": "number" + } + }, + "additionalProperties": false + }, "enableToolOutputTruncation": { "title": "Enable Tool Output Truncation", "description": "Enable truncation of large tool outputs.",