summaryrefslogtreecommitdiff
path: root/packages/core/src/tools/glob.ts
diff options
context:
space:
mode:
authorjoshualitt <[email protected]>2025-08-07 10:05:37 -0700
committerGitHub <[email protected]>2025-08-07 17:05:37 +0000
commit8bac9e7d048c7ff97f0942b23edb0167ee6ca83e (patch)
treec1a4d73348256a152e7c3dad2bbd89979a2ca30d /packages/core/src/tools/glob.ts
parent0d65baf9283138da56cdf08b00058ab3cf8cbaf9 (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.ts233
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);
+ }
+}