summaryrefslogtreecommitdiff
path: root/packages/core/src
diff options
context:
space:
mode:
authorGal Zahavi <[email protected]>2025-08-18 16:39:05 -0700
committerGitHub <[email protected]>2025-08-18 23:39:05 +0000
commit6fc68ff8d4536f35f0ed76af535d5e1e7ac37675 (patch)
tree90df004ab64647037154c3f9923b5082d4ea23b1 /packages/core/src
parentfb3ceb0da4e2cd636013c2c36a9c0016c01aa47f (diff)
fix(tools): Handle special characters in file paths for glob and read_many_files (#6507)
Co-authored-by: Jacob Richman <[email protected]>
Diffstat (limited to 'packages/core/src')
-rw-r--r--packages/core/src/tools/glob.test.ts28
-rw-r--r--packages/core/src/tools/glob.ts10
-rw-r--r--packages/core/src/tools/read-many-files.test.ts37
-rw-r--r--packages/core/src/tools/read-many-files.ts36
4 files changed, 96 insertions, 15 deletions
diff --git a/packages/core/src/tools/glob.test.ts b/packages/core/src/tools/glob.test.ts
index 934b7ce7..7af09352 100644
--- a/packages/core/src/tools/glob.test.ts
+++ b/packages/core/src/tools/glob.test.ts
@@ -150,6 +150,34 @@ describe('GlobTool', () => {
expect(result.returnDisplay).toBe('No files found');
});
+ it('should find files with special characters in the name', async () => {
+ await fs.writeFile(path.join(tempRootDir, 'file[1].txt'), 'content');
+ const params: GlobToolParams = { pattern: 'file[1].txt' };
+ const invocation = globTool.build(params);
+ const result = await invocation.execute(abortSignal);
+ expect(result.llmContent).toContain('Found 1 file(s)');
+ expect(result.llmContent).toContain(
+ path.join(tempRootDir, 'file[1].txt'),
+ );
+ });
+
+ it('should find files with special characters like [] and () in the path', async () => {
+ const filePath = path.join(
+ tempRootDir,
+ 'src/app/[test]/(dashboard)/testing/components/code.tsx',
+ );
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
+ await fs.writeFile(filePath, 'content');
+
+ const params: GlobToolParams = {
+ pattern: 'src/app/[test]/(dashboard)/testing/components/code.tsx',
+ };
+ const invocation = globTool.build(params);
+ const result = await invocation.execute(abortSignal);
+ expect(result.llmContent).toContain('Found 1 file(s)');
+ expect(result.llmContent).toContain(filePath);
+ });
+
it('should correctly sort files by modification time (newest first)', async () => {
const params: GlobToolParams = { pattern: '*.sortme' };
const invocation = globTool.build(params);
diff --git a/packages/core/src/tools/glob.ts b/packages/core/src/tools/glob.ts
index 65454232..ae82de76 100644
--- a/packages/core/src/tools/glob.ts
+++ b/packages/core/src/tools/glob.ts
@@ -6,7 +6,7 @@
import fs from 'fs';
import path from 'path';
-import { glob } from 'glob';
+import { glob, escape } from 'glob';
import { SchemaValidator } from '../utils/schemaValidator.js';
import {
BaseDeclarativeTool,
@@ -137,7 +137,13 @@ class GlobToolInvocation extends BaseToolInvocation<
let allEntries: GlobPath[] = [];
for (const searchDir of searchDirectories) {
- const entries = (await glob(this.params.pattern, {
+ let pattern = this.params.pattern;
+ const fullPath = path.join(searchDir, pattern);
+ if (fs.existsSync(fullPath)) {
+ pattern = escape(pattern);
+ }
+
+ const entries = (await glob(pattern, {
cwd: searchDir,
withFileTypes: true,
nodir: true,
diff --git a/packages/core/src/tools/read-many-files.test.ts b/packages/core/src/tools/read-many-files.test.ts
index a57b3851..b432998d 100644
--- a/packages/core/src/tools/read-many-files.test.ts
+++ b/packages/core/src/tools/read-many-files.test.ts
@@ -527,6 +527,43 @@ describe('ReadManyFilesTool', () => {
expect(truncatedFileContent).toContain('L200');
expect(truncatedFileContent).not.toContain('L2400');
});
+
+ it('should read files with special characters like [] and () in the path', async () => {
+ const filePath = 'src/app/[test]/(dashboard)/testing/components/code.tsx';
+ createFile(filePath, 'Content of receive-detail');
+ const params = { paths: [filePath] };
+ const invocation = tool.build(params);
+ const result = await invocation.execute(new AbortController().signal);
+ const expectedPath = path.join(tempRootDir, filePath);
+ expect(result.llmContent).toEqual([
+ `--- ${expectedPath} ---
+
+Content of receive-detail
+
+`,
+ ]);
+ expect(result.returnDisplay).toContain(
+ 'Successfully read and concatenated content from **1 file(s)**',
+ );
+ });
+
+ it('should read files with special characters in the name', async () => {
+ createFile('file[1].txt', 'Content of file[1]');
+ const params = { paths: ['file[1].txt'] };
+ const invocation = tool.build(params);
+ const result = await invocation.execute(new AbortController().signal);
+ const expectedPath = path.join(tempRootDir, 'file[1].txt');
+ expect(result.llmContent).toEqual([
+ `--- ${expectedPath} ---
+
+Content of file[1]
+
+`,
+ ]);
+ expect(result.returnDisplay).toContain(
+ 'Successfully read and concatenated content from **1 file(s)**',
+ );
+ });
});
describe('Batch Processing', () => {
diff --git a/packages/core/src/tools/read-many-files.ts b/packages/core/src/tools/read-many-files.ts
index 46aab23d..6a6eca9b 100644
--- a/packages/core/src/tools/read-many-files.ts
+++ b/packages/core/src/tools/read-many-files.ts
@@ -13,8 +13,9 @@ import {
} from './tools.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { getErrorMessage } from '../utils/errors.js';
+import * as fs from 'fs';
import * as path from 'path';
-import { glob } from 'glob';
+import { glob, escape } from 'glob';
import { getCurrentGeminiMdFilename } from './memoryTool.js';
import {
detectFileType,
@@ -245,18 +246,27 @@ ${finalExclusionPatternsForDescription
const workspaceDirs = this.config.getWorkspaceContext().getDirectories();
for (const dir of workspaceDirs) {
- const entriesInDir = await glob(
- searchPatterns.map((p) => p.replace(/\\/g, '/')),
- {
- cwd: dir,
- ignore: effectiveExcludes,
- nodir: true,
- dot: true,
- absolute: true,
- nocase: true,
- signal,
- },
- );
+ const processedPatterns = [];
+ for (const p of searchPatterns) {
+ const normalizedP = p.replace(/\\/g, '/');
+ const fullPath = path.join(dir, normalizedP);
+ if (fs.existsSync(fullPath)) {
+ processedPatterns.push(escape(normalizedP));
+ } else {
+ // The path does not exist or is not a file, so we treat it as a glob pattern.
+ processedPatterns.push(normalizedP);
+ }
+ }
+
+ const entriesInDir = await glob(processedPatterns, {
+ cwd: dir,
+ ignore: effectiveExcludes,
+ nodir: true,
+ dot: true,
+ absolute: true,
+ nocase: true,
+ signal,
+ });
for (const entry of entriesInDir) {
allEntries.add(entry);
}