feat(core): Save large tool outputs to a file and return truncated lines (#6240)

This commit is contained in:
Sandy Tao
2025-09-05 15:37:29 -07:00
committed by GitHub
parent 7239c5cd9a
commit dd23c77469
14 changed files with 511 additions and 10 deletions
+39
View File
@@ -38,6 +38,9 @@ describe('ReadFileTool', () => {
getFileSystemService: () => new StandardFileSystemService(),
getTargetDir: () => tempRootDir,
getWorkspaceContext: () => createMockWorkspaceContext(tempRootDir),
storage: {
getProjectTempDir: () => path.join(tempRootDir, '.temp'),
},
} as unknown as Config;
tool = new ReadFileTool(mockConfigInstance);
});
@@ -76,6 +79,24 @@ describe('ReadFileTool', () => {
);
});
it('should allow access to files in project temp directory', () => {
const tempDir = path.join(tempRootDir, '.temp');
const params: ReadFileToolParams = {
absolute_path: path.join(tempDir, 'temp-file.txt'),
};
const result = tool.build(params);
expect(typeof result).not.toBe('string');
});
it('should show temp directory in error message when path is outside workspace and temp dir', () => {
const params: ReadFileToolParams = {
absolute_path: '/completely/outside/path.txt',
};
expect(() => tool.build(params)).toThrow(
/File path must be within one of the workspace directories.*or within the project temp directory/,
);
});
it('should throw error if path is empty', () => {
const params: ReadFileToolParams = {
absolute_path: '',
@@ -409,6 +430,24 @@ describe('ReadFileTool', () => {
);
});
it('should successfully read files from project temp directory', async () => {
const tempDir = path.join(tempRootDir, '.temp');
await fsp.mkdir(tempDir, { recursive: true });
const tempFilePath = path.join(tempDir, 'temp-output.txt');
const tempFileContent = 'This is temporary output content';
await fsp.writeFile(tempFilePath, tempFileContent, 'utf-8');
const params: ReadFileToolParams = { absolute_path: tempFilePath };
const invocation = tool.build(params) as ToolInvocation<
ReadFileToolParams,
ToolResult
>;
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toBe(tempFileContent);
expect(result.returnDisplay).toBe('');
});
describe('with .geminiignore', () => {
beforeEach(async () => {
await fsp.writeFile(
+9 -2
View File
@@ -180,9 +180,16 @@ export class ReadFileTool extends BaseDeclarativeTool<
}
const workspaceContext = this.config.getWorkspaceContext();
if (!workspaceContext.isPathWithinWorkspace(filePath)) {
const projectTempDir = this.config.storage.getProjectTempDir();
const resolvedFilePath = path.resolve(filePath);
const resolvedProjectTempDir = path.resolve(projectTempDir);
const isWithinTempDir =
resolvedFilePath.startsWith(resolvedProjectTempDir + path.sep) ||
resolvedFilePath === resolvedProjectTempDir;
if (!workspaceContext.isPathWithinWorkspace(filePath) && !isWithinTempDir) {
const directories = workspaceContext.getDirectories();
return `File path must be within one of the workspace directories: ${directories.join(', ')}`;
return `File path must be within one of the workspace directories: ${directories.join(', ')} or within the project temp directory: ${projectTempDir}`;
}
if (params.offset !== undefined && params.offset < 0) {
return 'Offset must be a non-negative number';