Files
browser-use/browser_use/filesystem/file_system.py
mertunsall bbc8d26f63 - Updated ActionResult to include an attachments field for displaying files in the done message.
- Enhanced logging in the Agent class to inform users about available attachments after task completion.
- Revised system prompt guidelines to clarify the use of text and files_to_display in the done action.
2025-06-18 14:24:27 +02:00

115 lines
3.6 KiB
Python

import asyncio
import re
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
INVALID_FILENAME_ERROR_MESSAGE = 'Error: Invalid filename format. Must be alphanumeric with .txt or .md extension.'
class FileSystem:
def __init__(self, dir_path: str):
# Create a base directory
self.base_dir = Path(dir_path)
self.base_dir.mkdir(parents=True, exist_ok=True)
# Create and use a dedicated subfolder for all operations
self.dir = self.base_dir / 'data_storage'
if self.dir.exists():
raise ValueError(
'File system directory already exists - stopping for safety purposes. Please delete it first if you want to use this directory.'
)
self.dir.mkdir(exist_ok=True)
# Initialize default files
self.results_file = self.dir / 'results.md'
self.todo_file = self.dir / 'todo.md'
self.results_file.touch(exist_ok=True)
self.todo_file.touch(exist_ok=True)
def get_dir(self) -> Path:
return self.dir
def _is_valid_filename(self, file_name: str) -> bool:
"""Check if filename matches the required pattern: name.extension"""
pattern = r'^[a-zA-Z0-9_\-]+\.(txt|md)$'
return bool(re.match(pattern, file_name))
def display_file(self, file_name: str) -> str | None:
if not self._is_valid_filename(file_name):
return None
path = self.dir / file_name
if not path.exists():
return None
try:
return path.read_text()
except Exception:
return None
async def read_file(self, file_name: str) -> str:
if not self._is_valid_filename(file_name):
return INVALID_FILENAME_ERROR_MESSAGE
path = self.dir / file_name
if not path.exists():
return f"File '{file_name}' not found."
try:
# Create a new executor for this operation
with ThreadPoolExecutor() as executor:
# Run file read in a thread to avoid blocking
content = await asyncio.get_event_loop().run_in_executor(executor, lambda: path.read_text())
return f'Read from file {file_name}:\n{content}'
except Exception:
return f"Error: Could not read file '{file_name}'."
async def write_file(self, file_name: str, content: str) -> str:
if not self._is_valid_filename(file_name):
return INVALID_FILENAME_ERROR_MESSAGE
try:
path = self.dir / file_name
# Create a new executor for this operation
with ThreadPoolExecutor() as executor:
# Run file write in a thread to avoid blocking
await asyncio.get_event_loop().run_in_executor(executor, lambda: path.write_text(content))
return f'Data written to {file_name} successfully.'
except Exception:
return f"Error: Could not write to file '{file_name}'."
async def append_file(self, file_name: str, content: str) -> str:
if not self._is_valid_filename(file_name):
return INVALID_FILENAME_ERROR_MESSAGE
path = self.dir / file_name
if not path.exists():
return f"File '{file_name}' not found."
try:
# Create a new executor for this operation
with ThreadPoolExecutor() as executor:
# Run file append in a thread to avoid blocking
def append_to_file():
with path.open('a') as f:
f.write(content)
await asyncio.get_event_loop().run_in_executor(executor, append_to_file)
return f'Data appended to {file_name} successfully.'
except Exception as e:
return f"Error: Could not append to file '{file_name}'. {str(e)}"
def describe(self) -> str:
"""List all files with their line counts."""
description = ''
for f in self.dir.iterdir():
if f.is_file():
try:
num_lines = len(f.read_text().splitlines())
description += f'- {f.name}{num_lines} lines\n'
except Exception:
description += f'- {f.name} — [error reading file]\n'
return description
def get_todo_contents(self) -> str:
return self.todo_file.read_text()