diff options
| author | joshualitt <[email protected]> | 2025-08-07 10:05:37 -0700 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-08-07 17:05:37 +0000 |
| commit | 8bac9e7d048c7ff97f0942b23edb0167ee6ca83e (patch) | |
| tree | c1a4d73348256a152e7c3dad2bbd89979a2ca30d /packages/core/src/tools/glob.ts | |
| parent | 0d65baf9283138da56cdf08b00058ab3cf8cbaf9 (diff) | |
Migrate EditTool, GrepTool, and GlobTool to DeclarativeTool (#5744)
Diffstat (limited to 'packages/core/src/tools/glob.ts')
| -rw-r--r-- | packages/core/src/tools/glob.ts | 233 |
1 files changed, 120 insertions, 113 deletions
diff --git a/packages/core/src/tools/glob.ts b/packages/core/src/tools/glob.ts index 5bcb9778..df0cc348 100644 --- a/packages/core/src/tools/glob.ts +++ b/packages/core/src/tools/glob.ts @@ -8,7 +8,13 @@ import fs from 'fs'; import path from 'path'; import { glob } from 'glob'; import { SchemaValidator } from '../utils/schemaValidator.js'; -import { BaseTool, Icon, ToolResult } from './tools.js'; +import { + BaseDeclarativeTool, + BaseToolInvocation, + Icon, + ToolInvocation, + ToolResult, +} from './tools.js'; import { Type } from '@google/genai'; import { shortenPath, makeRelative } from '../utils/paths.js'; import { Config } from '../config/config.js'; @@ -74,99 +80,23 @@ export interface GlobToolParams { respect_git_ignore?: boolean; } -/** - * Implementation of the Glob tool logic - */ -export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { - static readonly Name = 'glob'; - - constructor(private config: Config) { - super( - GlobTool.Name, - 'FindFiles', - 'Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Ideal for quickly locating files based on their name or path structure, especially in large codebases.', - Icon.FileSearch, - { - properties: { - pattern: { - description: - "The glob pattern to match against (e.g., '**/*.py', 'docs/*.md').", - type: Type.STRING, - }, - path: { - description: - 'Optional: The absolute path to the directory to search within. If omitted, searches the root directory.', - type: Type.STRING, - }, - case_sensitive: { - description: - 'Optional: Whether the search should be case-sensitive. Defaults to false.', - type: Type.BOOLEAN, - }, - respect_git_ignore: { - description: - 'Optional: Whether to respect .gitignore patterns when finding files. Only available in git repositories. Defaults to true.', - type: Type.BOOLEAN, - }, - }, - required: ['pattern'], - type: Type.OBJECT, - }, - ); - } - - /** - * Validates the parameters for the tool. - */ - validateToolParams(params: GlobToolParams): string | null { - const errors = SchemaValidator.validate(this.schema.parameters, params); - if (errors) { - return errors; - } - - const searchDirAbsolute = path.resolve( - this.config.getTargetDir(), - params.path || '.', - ); - - const workspaceContext = this.config.getWorkspaceContext(); - if (!workspaceContext.isPathWithinWorkspace(searchDirAbsolute)) { - const directories = workspaceContext.getDirectories(); - return `Search path ("${searchDirAbsolute}") resolves outside the allowed workspace directories: ${directories.join(', ')}`; - } - - const targetDir = searchDirAbsolute || this.config.getTargetDir(); - try { - if (!fs.existsSync(targetDir)) { - return `Search path does not exist ${targetDir}`; - } - if (!fs.statSync(targetDir).isDirectory()) { - return `Search path is not a directory: ${targetDir}`; - } - } catch (e: unknown) { - return `Error accessing search path: ${e}`; - } - - if ( - !params.pattern || - typeof params.pattern !== 'string' || - params.pattern.trim() === '' - ) { - return "The 'pattern' parameter cannot be empty."; - } - - return null; +class GlobToolInvocation extends BaseToolInvocation< + GlobToolParams, + ToolResult +> { + constructor( + private config: Config, + params: GlobToolParams, + ) { + super(params); } - /** - * Gets a description of the glob operation. - */ - getDescription(params: GlobToolParams): string { - let description = `'${params.pattern}'`; - if (params.path) { + getDescription(): string { + let description = `'${this.params.pattern}'`; + if (this.params.path) { const searchDir = path.resolve( this.config.getTargetDir(), - params.path || '.', + this.params.path || '.', ); const relativePath = makeRelative(searchDir, this.config.getTargetDir()); description += ` within ${shortenPath(relativePath)}`; @@ -174,35 +104,21 @@ export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { return description; } - /** - * Executes the glob search with the given parameters - */ - async execute( - params: GlobToolParams, - signal: AbortSignal, - ): Promise<ToolResult> { - const validationError = this.validateToolParams(params); - if (validationError) { - return { - llmContent: `Error: Invalid parameters provided. Reason: ${validationError}`, - returnDisplay: validationError, - }; - } - + async execute(signal: AbortSignal): Promise<ToolResult> { try { const workspaceContext = this.config.getWorkspaceContext(); const workspaceDirectories = workspaceContext.getDirectories(); // If a specific path is provided, resolve it and check if it's within workspace let searchDirectories: readonly string[]; - if (params.path) { + if (this.params.path) { const searchDirAbsolute = path.resolve( this.config.getTargetDir(), - params.path, + this.params.path, ); if (!workspaceContext.isPathWithinWorkspace(searchDirAbsolute)) { return { - llmContent: `Error: Path "${params.path}" is not within any workspace directory`, + llmContent: `Error: Path "${this.params.path}" is not within any workspace directory`, returnDisplay: `Path is not within workspace`, }; } @@ -214,7 +130,7 @@ export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { // Get centralized file discovery service const respectGitIgnore = - params.respect_git_ignore ?? + this.params.respect_git_ignore ?? this.config.getFileFilteringRespectGitIgnore(); const fileDiscovery = this.config.getFileService(); @@ -222,12 +138,12 @@ export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { let allEntries: GlobPath[] = []; for (const searchDir of searchDirectories) { - const entries = (await glob(params.pattern, { + const entries = (await glob(this.params.pattern, { cwd: searchDir, withFileTypes: true, nodir: true, stat: true, - nocase: !params.case_sensitive, + nocase: !this.params.case_sensitive, dot: true, ignore: ['**/node_modules/**', '**/.git/**'], follow: false, @@ -263,7 +179,7 @@ export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { } if (!filteredEntries || filteredEntries.length === 0) { - let message = `No files found matching pattern "${params.pattern}"`; + let message = `No files found matching pattern "${this.params.pattern}"`; if (searchDirectories.length === 1) { message += ` within ${searchDirectories[0]}`; } else { @@ -295,7 +211,7 @@ export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { const fileListDescription = sortedAbsolutePaths.join('\n'); const fileCount = sortedAbsolutePaths.length; - let resultMessage = `Found ${fileCount} file(s) matching "${params.pattern}"`; + let resultMessage = `Found ${fileCount} file(s) matching "${this.params.pattern}"`; if (searchDirectories.length === 1) { resultMessage += ` within ${searchDirectories[0]}`; } else { @@ -321,3 +237,94 @@ export class GlobTool extends BaseTool<GlobToolParams, ToolResult> { } } } + +/** + * Implementation of the Glob tool logic + */ +export class GlobTool extends BaseDeclarativeTool<GlobToolParams, ToolResult> { + static readonly Name = 'glob'; + + constructor(private config: Config) { + super( + GlobTool.Name, + 'FindFiles', + 'Efficiently finds files matching specific glob patterns (e.g., `src/**/*.ts`, `**/*.md`), returning absolute paths sorted by modification time (newest first). Ideal for quickly locating files based on their name or path structure, especially in large codebases.', + Icon.FileSearch, + { + properties: { + pattern: { + description: + "The glob pattern to match against (e.g., '**/*.py', 'docs/*.md').", + type: Type.STRING, + }, + path: { + description: + 'Optional: The absolute path to the directory to search within. If omitted, searches the root directory.', + type: Type.STRING, + }, + case_sensitive: { + description: + 'Optional: Whether the search should be case-sensitive. Defaults to false.', + type: Type.BOOLEAN, + }, + respect_git_ignore: { + description: + 'Optional: Whether to respect .gitignore patterns when finding files. Only available in git repositories. Defaults to true.', + type: Type.BOOLEAN, + }, + }, + required: ['pattern'], + type: Type.OBJECT, + }, + ); + } + + /** + * Validates the parameters for the tool. + */ + validateToolParams(params: GlobToolParams): string | null { + const errors = SchemaValidator.validate(this.schema.parameters, params); + if (errors) { + return errors; + } + + const searchDirAbsolute = path.resolve( + this.config.getTargetDir(), + params.path || '.', + ); + + const workspaceContext = this.config.getWorkspaceContext(); + if (!workspaceContext.isPathWithinWorkspace(searchDirAbsolute)) { + const directories = workspaceContext.getDirectories(); + return `Search path ("${searchDirAbsolute}") resolves outside the allowed workspace directories: ${directories.join(', ')}`; + } + + const targetDir = searchDirAbsolute || this.config.getTargetDir(); + try { + if (!fs.existsSync(targetDir)) { + return `Search path does not exist ${targetDir}`; + } + if (!fs.statSync(targetDir).isDirectory()) { + return `Search path is not a directory: ${targetDir}`; + } + } catch (e: unknown) { + return `Error accessing search path: ${e}`; + } + + if ( + !params.pattern || + typeof params.pattern !== 'string' || + params.pattern.trim() === '' + ) { + return "The 'pattern' parameter cannot be empty."; + } + + return null; + } + + protected createInvocation( + params: GlobToolParams, + ): ToolInvocation<GlobToolParams, ToolResult> { + return new GlobToolInvocation(this.config, params); + } +} |
