fix: handle case-insensitive path comparison in glob tool on Windows

This commit is contained in:
xuewenjie
2025-12-08 10:58:50 +08:00
parent b4be2c6c7f
commit 0a8281f2dd
2 changed files with 45 additions and 2 deletions

View File

@@ -198,6 +198,42 @@ describe('GlobTool', () => {
); );
}); });
it('should find files even if workspace path casing differs from glob results (Windows)', async () => {
// Only relevant for Windows
if (process.platform !== 'win32') {
return;
}
// 1. Create a path with mismatched casing for the workspace root
// e.g., if tempRootDir is "C:\Users\...", make it "c:\Users\..."
const drive = path.parse(tempRootDir).root;
if (!drive || !drive.match(/^[A-Z]:\\/)) {
// Skip if we can't determine/manipulate the drive letter easily
return;
}
const lowerDrive = drive.toLowerCase();
const mismatchedRootDir =
lowerDrive + tempRootDir.substring(drive.length);
// 2. Create a new GlobTool instance with this mismatched root
const mismatchedConfig = {
...mockConfig,
getTargetDir: () => mismatchedRootDir,
getWorkspaceContext: () =>
createMockWorkspaceContext(mismatchedRootDir),
} as unknown as Config;
const mismatchedGlobTool = new GlobTool(mismatchedConfig);
// 3. Execute search
const params: GlobToolParams = { pattern: '*.txt' };
const invocation = mismatchedGlobTool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain('Found 2 file(s)');
});
it('should return error if path is outside workspace', async () => { it('should return error if path is outside workspace', async () => {
// Bypassing validation to test execute method directly // Bypassing validation to test execute method directly
vi.spyOn(globTool, 'validateToolParams').mockReturnValue(null); vi.spyOn(globTool, 'validateToolParams').mockReturnValue(null);

View File

@@ -134,12 +134,19 @@ class GlobToolInvocation extends BaseToolInvocation<
this.getFileFilteringOptions(), this.getFileFilteringOptions(),
); );
const normalizePathForComparison = (p: string) =>
process.platform === 'win32' ? p.toLowerCase() : p;
const filteredAbsolutePaths = new Set( const filteredAbsolutePaths = new Set(
filteredPaths.map((p) => path.resolve(this.config.getTargetDir(), p)), filteredPaths.map((p) =>
normalizePathForComparison(
path.resolve(this.config.getTargetDir(), p),
),
),
); );
const filteredEntries = allEntries.filter((entry) => const filteredEntries = allEntries.filter((entry) =>
filteredAbsolutePaths.has(entry.fullpath()), filteredAbsolutePaths.has(normalizePathForComparison(entry.fullpath())),
); );
if (!filteredEntries || filteredEntries.length === 0) { if (!filteredEntries || filteredEntries.length === 0) {