fix(tools): Handle special characters in file paths for glob and read_many_files (#6507)

Co-authored-by: Jacob Richman <jacob314@gmail.com>
This commit is contained in:
Gal Zahavi 2025-08-18 16:39:05 -07:00 committed by GitHub
parent fb3ceb0da4
commit 6fc68ff8d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 96 additions and 15 deletions

View File

@ -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);

View File

@ -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,

View File

@ -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', () => {

View File

@ -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);
}