mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
fix(tools): Add an end of file list marker to ReadManyFilesTool (#5967)
Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com> Co-authored-by: Allen Hutchison <adh@google.com>
This commit is contained in:
@@ -29,6 +29,7 @@ Use `read_many_files` to read content from multiple files specified by paths or
|
|||||||
`read_many_files` searches for files matching the provided `paths` and `include` patterns, while respecting `exclude` patterns and default excludes (if enabled).
|
`read_many_files` searches for files matching the provided `paths` and `include` patterns, while respecting `exclude` patterns and default excludes (if enabled).
|
||||||
|
|
||||||
- For text files: it reads the content of each matched file (attempting to skip binary files not explicitly requested as image/PDF) and concatenates it into a single string, with a separator `--- {filePath} ---` between the content of each file. Uses UTF-8 encoding by default.
|
- For text files: it reads the content of each matched file (attempting to skip binary files not explicitly requested as image/PDF) and concatenates it into a single string, with a separator `--- {filePath} ---` between the content of each file. Uses UTF-8 encoding by default.
|
||||||
|
- The tool inserts a `--- End of content ---` after the last file.
|
||||||
- For image and PDF files: if explicitly requested by name or extension (e.g., `paths: ["logo.png"]` or `include: ["*.pdf"]`), the tool reads the file and returns its content as a base64 encoded string.
|
- For image and PDF files: if explicitly requested by name or extension (e.g., `paths: ["logo.png"]` or `include: ["*.pdf"]`), the tool reads the file and returns its content as a base64 encoded string.
|
||||||
- The tool attempts to detect and skip other binary files (those not matching common image/PDF types or not explicitly requested) by checking for null bytes in their initial content.
|
- The tool attempts to detect and skip other binary files (those not matching common image/PDF types or not explicitly requested) by checking for null bytes in their initial content.
|
||||||
|
|
||||||
|
|||||||
@@ -447,7 +447,6 @@ export async function handleAtCommand({
|
|||||||
processedQueryParts.push(part);
|
processedQueryParts.push(part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processedQueryParts.push({ text: '\n--- End of content ---' });
|
|
||||||
} else {
|
} else {
|
||||||
onDebugMessage(
|
onDebugMessage(
|
||||||
'read_many_files tool returned no content or empty content.',
|
'read_many_files tool returned no content or empty content.',
|
||||||
|
|||||||
@@ -750,7 +750,6 @@ class Session {
|
|||||||
processedQueryParts.push(part);
|
processedQueryParts.push(part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
processedQueryParts.push({ text: '\n--- End of content ---' });
|
|
||||||
} else {
|
} else {
|
||||||
console.warn(
|
console.warn(
|
||||||
'read_many_files tool returned no content or empty content.',
|
'read_many_files tool returned no content or empty content.',
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ describe('ReadManyFilesTool', () => {
|
|||||||
const expectedPath = path.join(tempRootDir, 'file1.txt');
|
const expectedPath = path.join(tempRootDir, 'file1.txt');
|
||||||
expect(result.llmContent).toEqual([
|
expect(result.llmContent).toEqual([
|
||||||
`--- ${expectedPath} ---\n\nContent of file1\n\n`,
|
`--- ${expectedPath} ---\n\nContent of file1\n\n`,
|
||||||
|
`\n--- End of content ---`,
|
||||||
]);
|
]);
|
||||||
expect(result.returnDisplay).toContain(
|
expect(result.returnDisplay).toContain(
|
||||||
'Successfully read and concatenated content from **1 file(s)**',
|
'Successfully read and concatenated content from **1 file(s)**',
|
||||||
@@ -285,7 +286,10 @@ describe('ReadManyFilesTool', () => {
|
|||||||
const result = await invocation.execute(new AbortController().signal);
|
const result = await invocation.execute(new AbortController().signal);
|
||||||
const content = result.llmContent as string[];
|
const content = result.llmContent as string[];
|
||||||
const expectedPath = path.join(tempRootDir, 'src/main.ts');
|
const expectedPath = path.join(tempRootDir, 'src/main.ts');
|
||||||
expect(content).toEqual([`--- ${expectedPath} ---\n\nMain content\n\n`]);
|
expect(content).toEqual([
|
||||||
|
`--- ${expectedPath} ---\n\nMain content\n\n`,
|
||||||
|
`\n--- End of content ---`,
|
||||||
|
]);
|
||||||
expect(
|
expect(
|
||||||
content.find((c) => c.includes('src/main.test.ts')),
|
content.find((c) => c.includes('src/main.test.ts')),
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
@@ -314,7 +318,10 @@ describe('ReadManyFilesTool', () => {
|
|||||||
const result = await invocation.execute(new AbortController().signal);
|
const result = await invocation.execute(new AbortController().signal);
|
||||||
const content = result.llmContent as string[];
|
const content = result.llmContent as string[];
|
||||||
const expectedPath = path.join(tempRootDir, 'src/app.js');
|
const expectedPath = path.join(tempRootDir, 'src/app.js');
|
||||||
expect(content).toEqual([`--- ${expectedPath} ---\n\napp code\n\n`]);
|
expect(content).toEqual([
|
||||||
|
`--- ${expectedPath} ---\n\napp code\n\n`,
|
||||||
|
`\n--- End of content ---`,
|
||||||
|
]);
|
||||||
expect(
|
expect(
|
||||||
content.find((c) => c.includes('node_modules/some-lib/index.js')),
|
content.find((c) => c.includes('node_modules/some-lib/index.js')),
|
||||||
).toBeUndefined();
|
).toBeUndefined();
|
||||||
@@ -367,6 +374,7 @@ describe('ReadManyFilesTool', () => {
|
|||||||
mimeType: 'image/png',
|
mimeType: 'image/png',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'\n--- End of content ---',
|
||||||
]);
|
]);
|
||||||
expect(result.returnDisplay).toContain(
|
expect(result.returnDisplay).toContain(
|
||||||
'Successfully read and concatenated content from **1 file(s)**',
|
'Successfully read and concatenated content from **1 file(s)**',
|
||||||
@@ -390,6 +398,7 @@ describe('ReadManyFilesTool', () => {
|
|||||||
mimeType: 'image/png',
|
mimeType: 'image/png',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'\n--- End of content ---',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -426,6 +435,7 @@ describe('ReadManyFilesTool', () => {
|
|||||||
mimeType: 'application/pdf',
|
mimeType: 'application/pdf',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'\n--- End of content ---',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -441,6 +451,7 @@ describe('ReadManyFilesTool', () => {
|
|||||||
mimeType: 'application/pdf',
|
mimeType: 'application/pdf',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'\n--- End of content ---',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -549,6 +560,7 @@ describe('ReadManyFilesTool', () => {
|
|||||||
Content of receive-detail
|
Content of receive-detail
|
||||||
|
|
||||||
`,
|
`,
|
||||||
|
`\n--- End of content ---`,
|
||||||
]);
|
]);
|
||||||
expect(result.returnDisplay).toContain(
|
expect(result.returnDisplay).toContain(
|
||||||
'Successfully read and concatenated content from **1 file(s)**',
|
'Successfully read and concatenated content from **1 file(s)**',
|
||||||
@@ -567,6 +579,7 @@ Content of receive-detail
|
|||||||
Content of file[1]
|
Content of file[1]
|
||||||
|
|
||||||
`,
|
`,
|
||||||
|
`\n--- End of content ---`,
|
||||||
]);
|
]);
|
||||||
expect(result.returnDisplay).toContain(
|
expect(result.returnDisplay).toContain(
|
||||||
'Successfully read and concatenated content from **1 file(s)**',
|
'Successfully read and concatenated content from **1 file(s)**',
|
||||||
@@ -634,9 +647,10 @@ Content of file[1]
|
|||||||
const invocation = tool.build(params);
|
const invocation = tool.build(params);
|
||||||
const result = await invocation.execute(new AbortController().signal);
|
const result = await invocation.execute(new AbortController().signal);
|
||||||
|
|
||||||
// Verify all files were processed
|
// Verify all files were processed. The content should have fileCount
|
||||||
|
// entries + 1 for the output terminator.
|
||||||
const content = result.llmContent as string[];
|
const content = result.llmContent as string[];
|
||||||
expect(content).toHaveLength(fileCount);
|
expect(content).toHaveLength(fileCount + 1);
|
||||||
for (let i = 0; i < fileCount; i++) {
|
for (let i = 0; i < fileCount; i++) {
|
||||||
expect(content.join('')).toContain(`Batch test ${i}`);
|
expect(content.join('')).toContain(`Batch test ${i}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ const DEFAULT_EXCLUDES: string[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_OUTPUT_SEPARATOR_FORMAT = '--- {filePath} ---';
|
const DEFAULT_OUTPUT_SEPARATOR_FORMAT = '--- {filePath} ---';
|
||||||
|
const DEFAULT_OUTPUT_TERMINATOR = '\n--- End of content ---';
|
||||||
|
|
||||||
class ReadManyFilesToolInvocation extends BaseToolInvocation<
|
class ReadManyFilesToolInvocation extends BaseToolInvocation<
|
||||||
ReadManyFilesParams,
|
ReadManyFilesParams,
|
||||||
@@ -534,7 +535,9 @@ ${finalExclusionPatternsForDescription
|
|||||||
displayMessage += `No files were read and concatenated based on the criteria.\n`;
|
displayMessage += `No files were read and concatenated based on the criteria.\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentParts.length === 0) {
|
if (contentParts.length > 0) {
|
||||||
|
contentParts.push(DEFAULT_OUTPUT_TERMINATOR);
|
||||||
|
} else {
|
||||||
contentParts.push(
|
contentParts.push(
|
||||||
'No files matching the criteria were found or all were skipped.',
|
'No files matching the criteria were found or all were skipped.',
|
||||||
);
|
);
|
||||||
@@ -636,7 +639,7 @@ This tool is useful when you need to understand or analyze a collection of files
|
|||||||
- Gathering context from multiple configuration files.
|
- Gathering context from multiple configuration files.
|
||||||
- When the user asks to "read all files in X directory" or "show me the content of all Y files".
|
- When the user asks to "read all files in X directory" or "show me the content of all Y files".
|
||||||
|
|
||||||
Use this tool when the user's query implies needing the content of several files simultaneously for context, analysis, or summarization. For text files, it uses default UTF-8 encoding and a '--- {filePath} ---' separator between file contents. Ensure paths are relative to the target directory. Glob patterns like 'src/**/*.js' are supported. Avoid using for single files if a more specific single-file reading tool is available, unless the user specifically requests to process a list containing just one file via this tool. Other binary files (not explicitly requested as image/PDF) are generally skipped. Default excludes apply to common non-text files (except for explicitly requested images/PDFs) and large dependency directories unless 'useDefaultExcludes' is false.`,
|
Use this tool when the user's query implies needing the content of several files simultaneously for context, analysis, or summarization. For text files, it uses default UTF-8 encoding and a '--- {filePath} ---' separator between file contents. The tool inserts a '--- End of content ---' after the last file. Ensure paths are relative to the target directory. Glob patterns like 'src/**/*.js' are supported. Avoid using for single files if a more specific single-file reading tool is available, unless the user specifically requests to process a list containing just one file via this tool. Other binary files (not explicitly requested as image/PDF) are generally skipped. Default excludes apply to common non-text files (except for explicitly requested images/PDFs) and large dependency directories unless 'useDefaultExcludes' is false.`,
|
||||||
Kind.Read,
|
Kind.Read,
|
||||||
parameterSchema,
|
parameterSchema,
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user