diff --git a/autogpts/autogpt/autogpt/agents/agent.py b/autogpts/autogpt/autogpt/agents/agent.py index 94e15eb6..b1363105 100644 --- a/autogpts/autogpt/autogpt/agents/agent.py +++ b/autogpts/autogpt/autogpt/agents/agent.py @@ -38,8 +38,8 @@ from autogpt.models.context_item import ContextItem from .base import BaseAgent, BaseAgentConfiguration, BaseAgentSettings from .features.context import ContextMixin +from .features.file_workspace import FileWorkspaceMixin from .features.watchdog import WatchdogMixin -from .features.workspace import WorkspaceMixin from .prompt_strategies.one_shot import ( OneShotAgentPromptConfiguration, OneShotAgentPromptStrategy, @@ -64,7 +64,7 @@ class AgentSettings(BaseAgentSettings): class Agent( ContextMixin, - WorkspaceMixin, + FileWorkspaceMixin, WatchdogMixin, BaseAgent, Configurable[AgentSettings], diff --git a/autogpts/autogpt/autogpt/agents/features/workspace.py b/autogpts/autogpt/autogpt/agents/features/file_workspace.py similarity index 74% rename from autogpts/autogpt/autogpt/agents/features/workspace.py rename to autogpts/autogpt/autogpt/agents/features/file_workspace.py index 0e133aa0..ecdd2874 100644 --- a/autogpts/autogpt/autogpt/agents/features/workspace.py +++ b/autogpts/autogpt/autogpt/agents/features/file_workspace.py @@ -7,20 +7,20 @@ if TYPE_CHECKING: from ..base import BaseAgent -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from ..base import AgentFileManager, BaseAgentConfiguration -class WorkspaceMixin: +class FileWorkspaceMixin: """Mixin that adds workspace support to a class""" - workspace: Workspace | None + workspace: FileWorkspace = None """Workspace that the agent has access to, e.g. for reading/writing files.""" def __init__(self, **kwargs): # Initialize other bases first, because we need the config from BaseAgent - super(WorkspaceMixin, self).__init__(**kwargs) + super(FileWorkspaceMixin, self).__init__(**kwargs) config: BaseAgentConfiguration = getattr(self, "config") if not isinstance(config, BaseAgentConfiguration): @@ -34,7 +34,7 @@ class WorkspaceMixin: self.workspace = _setup_workspace(file_manager, config) def attach_fs(self, agent_dir: Path): - res = super(WorkspaceMixin, self).attach_fs(agent_dir) + res = super(FileWorkspaceMixin, self).attach_fs(agent_dir) self.workspace = _setup_workspace(self.file_manager, self.config) @@ -42,16 +42,16 @@ class WorkspaceMixin: def _setup_workspace(file_manager: AgentFileManager, config: BaseAgentConfiguration): - workspace = Workspace( + workspace = FileWorkspace( file_manager.root / "workspace", - restrict_to_workspace=not config.allow_fs_access, + restrict_to_root=not config.allow_fs_access, ) workspace.initialize() return workspace -def get_agent_workspace(agent: BaseAgent) -> Workspace | None: - if isinstance(agent, WorkspaceMixin): +def get_agent_workspace(agent: BaseAgent) -> FileWorkspace | None: + if isinstance(agent, FileWorkspaceMixin): return agent.workspace return None diff --git a/autogpts/autogpt/autogpt/agents/planning_agent.py b/autogpts/autogpt/autogpt/agents/planning_agent.py index 53634176..f68611e3 100644 --- a/autogpts/autogpt/autogpt/agents/planning_agent.py +++ b/autogpts/autogpt/autogpt/agents/planning_agent.py @@ -32,12 +32,12 @@ from autogpt.models.context_item import ContextItem from .agent import execute_command, extract_command from .base import BaseAgent from .features.context import ContextMixin -from .features.workspace import WorkspaceMixin +from .features.file_workspace import FileWorkspaceMixin logger = logging.getLogger(__name__) -class PlanningAgent(ContextMixin, WorkspaceMixin, BaseAgent): +class PlanningAgent(ContextMixin, FileWorkspaceMixin, BaseAgent): """Agent class for interacting with AutoGPT.""" ThoughtProcessID = Literal["plan", "action", "evaluate"] diff --git a/autogpts/autogpt/autogpt/file_workspace/__init__.py b/autogpts/autogpt/autogpt/file_workspace/__init__.py new file mode 100644 index 00000000..76a26eef --- /dev/null +++ b/autogpts/autogpt/autogpt/file_workspace/__init__.py @@ -0,0 +1,5 @@ +from .file_workspace import FileWorkspace + +__all__ = [ + "FileWorkspace", +] diff --git a/autogpts/autogpt/autogpt/file_workspace/file_workspace.py b/autogpts/autogpt/autogpt/file_workspace/file_workspace.py new file mode 100644 index 00000000..f4f572b7 --- /dev/null +++ b/autogpts/autogpt/autogpt/file_workspace/file_workspace.py @@ -0,0 +1,145 @@ +""" +The FileWorkspace class provides an interface for interacting with a file workspace. +""" +from __future__ import annotations + +import logging +from pathlib import Path +from typing import Any, Callable, Optional + +logger = logging.getLogger(__name__) + + +class FileWorkspace: + """A class that represents a file workspace.""" + + NULL_BYTES = ["\0", "\000", "\x00", "\u0000"] + + on_write_file: Callable[[Path], Any] | None = None + """ + Event hook, executed after writing a file. + + Params: + Path: The path of the file that was written, relative to the workspace root. + """ + + def __init__(self, root: str | Path, restrict_to_root: bool): + self._root = self._sanitize_path(root) + self._restrict_to_root = restrict_to_root + + @property + def root(self) -> Path: + """The root directory of the file workspace.""" + return self._root + + @property + def restrict_to_root(self): + """Whether to restrict generated paths to the root.""" + return self._restrict_to_root + + def initialize(self) -> None: + self.root.mkdir(exist_ok=True, parents=True) + + def get_path(self, relative_path: str | Path) -> Path: + """Get the full path for an item in the workspace. + + Parameters: + relative_path: The relative path to resolve in the workspace. + + Returns: + Path: The resolved path relative to the workspace. + """ + return self._sanitize_path( + relative_path, + root=self.root, + restrict_to_root=self.restrict_to_root, + ) + + def open_file(self, path: str | Path, mode: str = "r"): + """Open a file in the workspace.""" + full_path = self.get_path(path) + return open(full_path, mode) + + def read_file(self, path: str | Path): + """Read a file in the workspace.""" + with self.open_file(path, "r") as file: + return file.read() + + def write_file(self, path: str | Path, content: str | bytes): + """Write to a file in the workspace.""" + with self.open_file(path, "w") as file: + file.write(content) + + if self.on_write_file: + path = Path(path) + if path.is_absolute(): + path = path.relative_to(self.root) + self.on_write_file(path) + + def list_files(self, path: str | Path = "."): + """List all files in a directory in the workspace.""" + full_path = self.get_path(path) + return [str(file) for file in full_path.glob("*") if file.is_file()] + + def delete_file(self, path: str | Path): + """Delete a file in the workspace.""" + full_path = self.get_path(path) + full_path.unlink() + + @staticmethod + def _sanitize_path( + relative_path: str | Path, + root: Optional[str | Path] = None, + restrict_to_root: bool = True, + ) -> Path: + """Resolve the relative path within the given root if possible. + + Parameters: + relative_path: The relative path to resolve. + root: The root path to resolve the relative path within. + restrict_to_root: Whether to restrict the path to the root. + + Returns: + Path: The resolved path. + + Raises: + ValueError: If the path is absolute and a root is provided. + ValueError: If the path is outside the root and the root is restricted. + """ + + # Posix systems disallow null bytes in paths. Windows is agnostic about it. + # Do an explicit check here for all sorts of null byte representations. + + for null_byte in FileWorkspace.NULL_BYTES: + if null_byte in str(relative_path) or null_byte in str(root): + raise ValueError("embedded null byte") + + if root is None: + return Path(relative_path).resolve() + + logger.debug(f"Resolving path '{relative_path}' in workspace '{root}'") + + root, relative_path = Path(root).resolve(), Path(relative_path) + + logger.debug(f"Resolved root as '{root}'") + + # Allow absolute paths if they are contained in the workspace. + if ( + relative_path.is_absolute() + and restrict_to_root + and not relative_path.is_relative_to(root) + ): + raise ValueError( + f"Attempted to access absolute path '{relative_path}' in workspace '{root}'." + ) + + full_path = root.joinpath(relative_path).resolve() + + logger.debug(f"Joined paths as '{full_path}'") + + if restrict_to_root and not full_path.is_relative_to(root): + raise ValueError( + f"Attempted to access path '{full_path}' outside of workspace '{root}'." + ) + + return full_path diff --git a/autogpts/autogpt/autogpt/workspace/__init__.py b/autogpts/autogpt/autogpt/workspace/__init__.py deleted file mode 100644 index b348144b..00000000 --- a/autogpts/autogpt/autogpt/workspace/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from autogpt.workspace.workspace import Workspace - -__all__ = [ - "Workspace", -] diff --git a/autogpts/autogpt/autogpt/workspace/workspace.py b/autogpts/autogpt/autogpt/workspace/workspace.py deleted file mode 100644 index b2a81e74..00000000 --- a/autogpts/autogpt/autogpt/workspace/workspace.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -========= -Workspace -========= - -The workspace is a directory containing configuration and working files for an AutoGPT -agent. - -""" -from __future__ import annotations - -import logging -from pathlib import Path -from typing import Optional - -logger = logging.getLogger(__name__) - - -class Workspace: - """A class that represents a workspace for an AutoGPT agent.""" - - NULL_BYTES = ["\0", "\000", "\x00", "\u0000"] - - def __init__(self, workspace_root: str | Path, restrict_to_workspace: bool): - self._root = self._sanitize_path(workspace_root) - self._restrict_to_workspace = restrict_to_workspace - - @property - def root(self) -> Path: - """The root directory of the workspace.""" - return self._root - - @property - def restrict_to_workspace(self): - """Whether to restrict generated paths to the workspace.""" - return self._restrict_to_workspace - - def initialize(self) -> None: - self.root.mkdir(exist_ok=True, parents=True) - - def get_path(self, relative_path: str | Path) -> Path: - """Get the full path for an item in the workspace. - - Parameters - ---------- - relative_path - The relative path to resolve in the workspace. - - Returns - ------- - Path - The resolved path relative to the workspace. - - """ - return self._sanitize_path( - relative_path, - root=self.root, - restrict_to_root=self.restrict_to_workspace, - ) - - @staticmethod - def _sanitize_path( - relative_path: str | Path, - root: Optional[str | Path] = None, - restrict_to_root: bool = True, - ) -> Path: - """Resolve the relative path within the given root if possible. - - Parameters - ---------- - relative_path - The relative path to resolve. - root - The root path to resolve the relative path within. - restrict_to_root - Whether to restrict the path to the root. - - Returns - ------- - Path - The resolved path. - - Raises - ------ - ValueError - If the path is absolute and a root is provided. - ValueError - If the path is outside the root and the root is restricted. - - """ - - # Posix systems disallow null bytes in paths. Windows is agnostic about it. - # Do an explicit check here for all sorts of null byte representations. - - for null_byte in Workspace.NULL_BYTES: - if null_byte in str(relative_path) or null_byte in str(root): - raise ValueError("embedded null byte") - - if root is None: - return Path(relative_path).resolve() - - logger.debug(f"Resolving path '{relative_path}' in workspace '{root}'") - - root, relative_path = Path(root).resolve(), Path(relative_path) - - logger.debug(f"Resolved root as '{root}'") - - # Allow exception for absolute paths if they are contained in your workspace directory. - if ( - relative_path.is_absolute() - and restrict_to_root - and not relative_path.is_relative_to(root) - ): - raise ValueError( - f"Attempted to access absolute path '{relative_path}' in workspace '{root}'." - ) - - full_path = root.joinpath(relative_path).resolve() - - logger.debug(f"Joined paths as '{full_path}'") - - if restrict_to_root and not full_path.is_relative_to(root): - raise ValueError( - f"Attempted to access path '{full_path}' outside of workspace '{root}'." - ) - - return full_path diff --git a/autogpts/autogpt/tests/challenges/basic_abilities/test_browse_website.py b/autogpts/autogpt/tests/challenges/basic_abilities/test_browse_website.py index fafa9ad6..47e8733d 100644 --- a/autogpts/autogpt/tests/challenges/basic_abilities/test_browse_website.py +++ b/autogpts/autogpt/tests/challenges/basic_abilities/test_browse_website.py @@ -1,6 +1,6 @@ import pytest -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import run_challenge @@ -16,7 +16,7 @@ def test_browse_website( monkeypatch: pytest.MonkeyPatch, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: run_challenge( diff --git a/autogpts/autogpt/tests/challenges/basic_abilities/test_write_file.py b/autogpts/autogpt/tests/challenges/basic_abilities/test_write_file.py index 2a202ee3..66597813 100644 --- a/autogpts/autogpt/tests/challenges/basic_abilities/test_write_file.py +++ b/autogpts/autogpt/tests/challenges/basic_abilities/test_write_file.py @@ -1,7 +1,7 @@ import pytest from autogpt.config import Config -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import get_workspace_path, run_challenge @@ -23,7 +23,7 @@ def test_write_file( monkeypatch: pytest.MonkeyPatch, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: run_challenge( diff --git a/autogpts/autogpt/tests/challenges/conftest.py b/autogpts/autogpt/tests/challenges/conftest.py index 784dbf71..08803e15 100644 --- a/autogpts/autogpt/tests/challenges/conftest.py +++ b/autogpts/autogpt/tests/challenges/conftest.py @@ -6,7 +6,7 @@ from _pytest.config.argparsing import Parser from _pytest.fixtures import FixtureRequest from pytest_mock import MockerFixture -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge import Challenge from tests.vcr import before_record_response @@ -64,12 +64,14 @@ def check_beat_challenges(request: FixtureRequest) -> None: @pytest.fixture -def patched_make_workspace(mocker: MockerFixture, workspace: Workspace) -> Generator: +def patched_make_workspace( + mocker: MockerFixture, workspace: FileWorkspace +) -> Generator: def patched_make_workspace(*args: Any, **kwargs: Any) -> str: return workspace.root mocker.patch.object( - Workspace, + FileWorkspace, "make_workspace", new=patched_make_workspace, ) diff --git a/autogpts/autogpt/tests/challenges/debug_code/test_debug_code_challenge_a.py b/autogpts/autogpt/tests/challenges/debug_code/test_debug_code_challenge_a.py index 9bd49271..bdcfbf39 100644 --- a/autogpts/autogpt/tests/challenges/debug_code/test_debug_code_challenge_a.py +++ b/autogpts/autogpt/tests/challenges/debug_code/test_debug_code_challenge_a.py @@ -5,7 +5,7 @@ from pytest_mock import MockerFixture from autogpt.agents import Agent from autogpt.commands.execute_code import execute_python_file -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import ( copy_file_into_workspace, @@ -32,7 +32,7 @@ def test_debug_code_challenge_a( patched_api_requestor: MockerFixture, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ diff --git a/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_a.py b/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_a.py index e117dba9..e5417c9d 100644 --- a/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_a.py +++ b/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_a.py @@ -1,7 +1,7 @@ import pytest from pytest_mock import MockerFixture -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.basic_abilities.test_browse_website import USER_INPUTS from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import get_workspace_path, run_challenge @@ -23,7 +23,7 @@ def test_information_retrieval_challenge_a( patched_api_requestor: MockerFixture, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ diff --git a/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_b.py b/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_b.py index 010afd87..6ec21601 100644 --- a/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_b.py +++ b/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_b.py @@ -1,7 +1,7 @@ import pytest from pytest_mock import MockerFixture -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import get_workspace_path, run_challenge @@ -18,7 +18,7 @@ def test_information_retrieval_challenge_b( patched_api_requestor: MockerFixture, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ diff --git a/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_c.py b/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_c.py index e827967d..2917f34a 100644 --- a/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_c.py +++ b/autogpts/autogpt/tests/challenges/information_retrieval/test_information_retrieval_challenge_c.py @@ -1,7 +1,7 @@ import pytest from pytest_mock import MockerFixture -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import get_workspace_path, run_challenge @@ -22,7 +22,7 @@ def test_information_retrieval_challenge_c( patched_api_requestor: MockerFixture, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ diff --git a/autogpts/autogpt/tests/challenges/kubernetes/test_kubernetes_template_challenge_a.py b/autogpts/autogpt/tests/challenges/kubernetes/test_kubernetes_template_challenge_a.py index cd923e67..8a369cbc 100644 --- a/autogpts/autogpt/tests/challenges/kubernetes/test_kubernetes_template_challenge_a.py +++ b/autogpts/autogpt/tests/challenges/kubernetes/test_kubernetes_template_challenge_a.py @@ -4,7 +4,7 @@ import pytest import yaml from pytest_mock import MockerFixture -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import get_workspace_path, run_challenge @@ -19,7 +19,7 @@ def test_kubernetes_template_challenge_a( patched_api_requestor: MockerFixture, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ diff --git a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_a.py b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_a.py index bbd221f4..0db72f6d 100644 --- a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_a.py +++ b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_a.py @@ -2,7 +2,7 @@ import pytest from pytest_mock import MockerFixture from autogpt.config import Config -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import get_workspace_path, run_challenge @@ -18,7 +18,7 @@ def test_memory_challenge_a( monkeypatch: pytest.MonkeyPatch, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ @@ -44,7 +44,7 @@ def test_memory_challenge_a( def create_instructions_files( - workspace: Workspace, + workspace: FileWorkspace, num_files: int, task_id: str, base_filename: str = "instructions_", diff --git a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_b.py b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_b.py index 30d9b161..d2012058 100644 --- a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_b.py +++ b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_b.py @@ -1,7 +1,7 @@ import pytest from pytest_mock import MockerFixture -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import generate_noise, get_workspace_path, run_challenge @@ -16,7 +16,7 @@ def test_memory_challenge_b( monkeypatch: pytest.MonkeyPatch, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ @@ -44,7 +44,7 @@ def test_memory_challenge_b( def create_instructions_files( - workspace: Workspace, + workspace: FileWorkspace, level: int, task_ids: list, base_filename: str = "instructions_", diff --git a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_c.py b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_c.py index db58cd4b..0918d6c0 100644 --- a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_c.py +++ b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_c.py @@ -2,7 +2,7 @@ import pytest from pytest_mock import MockerFixture from autogpt.commands.file_operations import read_file -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import generate_noise, get_workspace_path, run_challenge @@ -17,7 +17,7 @@ def test_memory_challenge_c( monkeypatch: pytest.MonkeyPatch, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ @@ -62,7 +62,7 @@ def test_memory_challenge_c( def create_instructions_files( - workspace: Workspace, + workspace: FileWorkspace, level: int, task_ids: list, base_filename: str = "instructions_", diff --git a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_d.py b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_d.py index 9cd9c8b0..28fdf3e5 100644 --- a/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_d.py +++ b/autogpts/autogpt/tests/challenges/memory/test_memory_challenge_d.py @@ -5,7 +5,7 @@ import pytest from pytest_mock import MockerFixture from autogpt.commands.file_operations import read_file -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace from tests.challenges.challenge_decorator.challenge_decorator import challenge from tests.challenges.utils import get_workspace_path, run_challenge @@ -21,7 +21,7 @@ def test_memory_challenge_d( monkeypatch: pytest.MonkeyPatch, level_to_run: int, challenge_name: str, - workspace: Workspace, + workspace: FileWorkspace, patched_make_workspace: pytest.fixture, ) -> None: """ @@ -173,7 +173,7 @@ def extract_beliefs(content: str) -> Dict[str, Dict[str, str]]: def create_instructions_files( - workspace: Workspace, + workspace: FileWorkspace, level: int, test_phrases: list, base_filename: str = "instructions_", diff --git a/autogpts/autogpt/tests/challenges/utils.py b/autogpts/autogpt/tests/challenges/utils.py index 883f3f11..f21dac12 100644 --- a/autogpts/autogpt/tests/challenges/utils.py +++ b/autogpts/autogpt/tests/challenges/utils.py @@ -7,8 +7,8 @@ from typing import Any, AsyncIterator import pytest from agbenchmark_config.benchmarks import run_specific_agent +from autogpt.file_workspace import FileWorkspace from autogpt.logs import LogCycleHandler -from autogpt.workspace import Workspace from tests.challenges.schema import Task @@ -55,12 +55,12 @@ def setup_mock_log_cycle_agent_name( ) -def get_workspace_path(workspace: Workspace, file_name: str) -> str: +def get_workspace_path(workspace: FileWorkspace, file_name: str) -> str: return str(workspace.get_path(file_name)) def copy_file_into_workspace( - workspace: Workspace, directory_path: Path, file_path: str + workspace: FileWorkspace, directory_path: Path, file_path: str ) -> None: workspace_code_file_path = get_workspace_path(workspace, file_path) code_file_path = directory_path / file_path diff --git a/autogpts/autogpt/tests/conftest.py b/autogpts/autogpt/tests/conftest.py index 78c7e61d..460c0a66 100644 --- a/autogpts/autogpt/tests/conftest.py +++ b/autogpts/autogpt/tests/conftest.py @@ -10,11 +10,11 @@ from autogpt.agents.agent import Agent, AgentConfiguration, AgentSettings from autogpt.app.main import _configure_openai_provider from autogpt.config import AIProfile, Config, ConfigBuilder from autogpt.core.resource.model_providers import ChatModelProvider, OpenAIProvider +from autogpt.file_workspace import FileWorkspace from autogpt.llm.api_manager import ApiManager from autogpt.logs.config import configure_logging from autogpt.memory.vector import get_memory from autogpt.models.command_registry import CommandRegistry -from autogpt.workspace import Workspace pytest_plugins = [ "tests.integration.agent_factory", @@ -44,8 +44,8 @@ def workspace_root(agent_data_dir: Path) -> Path: @pytest.fixture() -def workspace(workspace_root: Path) -> Workspace: - workspace = Workspace(workspace_root, restrict_to_workspace=True) +def workspace(workspace_root: Path) -> FileWorkspace: + workspace = FileWorkspace(workspace_root, restrict_to_root=True) workspace.initialize() return workspace diff --git a/autogpts/autogpt/tests/integration/memory/test_json_file_memory.py b/autogpts/autogpt/tests/integration/memory/test_json_file_memory.py index 76f867e2..d8e82c69 100644 --- a/autogpts/autogpt/tests/integration/memory/test_json_file_memory.py +++ b/autogpts/autogpt/tests/integration/memory/test_json_file_memory.py @@ -4,11 +4,13 @@ import orjson import pytest from autogpt.config import Config +from autogpt.file_workspace import FileWorkspace from autogpt.memory.vector import JSONFileMemory, MemoryItem -from autogpt.workspace import Workspace -def test_json_memory_init_without_backing_file(config: Config, workspace: Workspace): +def test_json_memory_init_without_backing_file( + config: Config, workspace: FileWorkspace +): index_file = workspace.root / f"{config.memory_index}.json" assert not index_file.exists() @@ -17,7 +19,9 @@ def test_json_memory_init_without_backing_file(config: Config, workspace: Worksp assert index_file.read_text() == "[]" -def test_json_memory_init_with_backing_empty_file(config: Config, workspace: Workspace): +def test_json_memory_init_with_backing_empty_file( + config: Config, workspace: FileWorkspace +): index_file = workspace.root / f"{config.memory_index}.json" index_file.touch() @@ -28,7 +32,7 @@ def test_json_memory_init_with_backing_empty_file(config: Config, workspace: Wor def test_json_memory_init_with_backing_invalid_file( - config: Config, workspace: Workspace + config: Config, workspace: FileWorkspace ): index_file = workspace.root / f"{config.memory_index}.json" index_file.touch() diff --git a/autogpts/autogpt/tests/integration/test_setup.py b/autogpts/autogpt/tests/integration/test_setup.py index e1917189..400886fa 100644 --- a/autogpts/autogpt/tests/integration/test_setup.py +++ b/autogpts/autogpt/tests/integration/test_setup.py @@ -6,16 +6,29 @@ from autogpt.app.setup import ( apply_overrides_to_ai_settings, interactively_revise_ai_settings, ) -from autogpt.config.ai_profile import AIProfile from autogpt.config import AIDirectives, Config +from autogpt.config.ai_profile import AIProfile @pytest.mark.asyncio async def test_apply_overrides_to_ai_settings(): ai_profile = AIProfile(ai_name="Test AI", ai_role="Test Role") - directives = AIDirectives(resources=["Resource1"], constraints=["Constraint1"], best_practices=["BestPractice1"]) + directives = AIDirectives( + resources=["Resource1"], + constraints=["Constraint1"], + best_practices=["BestPractice1"], + ) - apply_overrides_to_ai_settings(ai_profile, directives, override_name="New AI", override_role="New Role", replace_directives=True, resources=["NewResource"], constraints=["NewConstraint"], best_practices=["NewBestPractice"]) + apply_overrides_to_ai_settings( + ai_profile, + directives, + override_name="New AI", + override_role="New Role", + replace_directives=True, + resources=["NewResource"], + constraints=["NewConstraint"], + best_practices=["NewBestPractice"], + ) assert ai_profile.ai_name == "New AI" assert ai_profile.ai_role == "New Role" @@ -27,7 +40,11 @@ async def test_apply_overrides_to_ai_settings(): @pytest.mark.asyncio async def test_interactively_revise_ai_settings(config: Config): ai_profile = AIProfile(ai_name="Test AI", ai_role="Test Role") - directives = AIDirectives(resources=["Resource1"], constraints=["Constraint1"], best_practices=["BestPractice1"]) + directives = AIDirectives( + resources=["Resource1"], + constraints=["Constraint1"], + best_practices=["BestPractice1"], + ) user_inputs = [ "y", @@ -41,7 +58,9 @@ async def test_interactively_revise_ai_settings(config: Config): "", ] with patch("autogpt.app.utils.clean_input", side_effect=user_inputs): - ai_profile, directives = await interactively_revise_ai_settings(ai_profile, directives, config) + ai_profile, directives = await interactively_revise_ai_settings( + ai_profile, directives, config + ) assert ai_profile.ai_name == "New AI" assert ai_profile.ai_role == "New Role" diff --git a/autogpts/autogpt/tests/unit/test_config.py b/autogpts/autogpt/tests/unit/test_config.py index 458898ba..8fc85262 100644 --- a/autogpts/autogpt/tests/unit/test_config.py +++ b/autogpts/autogpt/tests/unit/test_config.py @@ -11,7 +11,7 @@ import pytest from autogpt.app.configurator import GPT_3_MODEL, GPT_4_MODEL, apply_overrides_to_config from autogpt.config import Config, ConfigBuilder -from autogpt.workspace.workspace import Workspace +from autogpt.file_workspace import FileWorkspace def test_initial_values(config: Config) -> None: @@ -122,7 +122,7 @@ def test_smart_and_fast_llms_set_to_gpt4(mock_list_models: Any, config: Config) config.smart_llm = smart_llm -def test_missing_azure_config(workspace: Workspace) -> None: +def test_missing_azure_config(workspace: FileWorkspace) -> None: config_file = workspace.get_path("azure_config.yaml") with pytest.raises(FileNotFoundError): ConfigBuilder.load_azure_config(config_file) @@ -136,7 +136,7 @@ def test_missing_azure_config(workspace: Workspace) -> None: assert azure_config["azure_model_to_deployment_id_map"] == {} -def test_azure_config(config: Config, workspace: Workspace) -> None: +def test_azure_config(config: Config, workspace: FileWorkspace) -> None: config_file = workspace.get_path("azure_config.yaml") yaml_content = """ azure_api_type: azure diff --git a/autogpts/autogpt/tests/unit/test_file_operations.py b/autogpts/autogpt/tests/unit/test_file_operations.py index 0ea9ed73..a5929ab1 100644 --- a/autogpts/autogpt/tests/unit/test_file_operations.py +++ b/autogpts/autogpt/tests/unit/test_file_operations.py @@ -15,9 +15,9 @@ import autogpt.commands.file_operations as file_ops from autogpt.agents.agent import Agent from autogpt.agents.utils.exceptions import DuplicateOperationError from autogpt.config import Config +from autogpt.file_workspace import FileWorkspace from autogpt.memory.vector.memory_item import MemoryItem from autogpt.memory.vector.utils import Embedding -from autogpt.workspace import Workspace @pytest.fixture() @@ -50,7 +50,7 @@ def test_file_name(): @pytest.fixture -def test_file_path(test_file_name: Path, workspace: Workspace): +def test_file_path(test_file_name: Path, workspace: FileWorkspace): return workspace.get_path(test_file_name) @@ -73,12 +73,12 @@ def test_file_with_content_path(test_file: TextIOWrapper, file_content, agent: A @pytest.fixture() -def test_directory(workspace: Workspace): +def test_directory(workspace: FileWorkspace): return workspace.get_path("test_directory") @pytest.fixture() -def test_nested_file(workspace: Workspace): +def test_nested_file(workspace: FileWorkspace): return workspace.get_path("nested/test_file.txt") @@ -280,7 +280,7 @@ def test_append_to_file_uses_checksum_from_appended_file( ) -def test_list_files(workspace: Workspace, test_directory: Path, agent: Agent): +def test_list_files(workspace: FileWorkspace, test_directory: Path, agent: Agent): # Case 1: Create files A and B, search for A, and ensure we don't return A and B file_a = workspace.get_path("file_a.txt") file_b = workspace.get_path("file_b.txt") diff --git a/autogpts/autogpt/tests/unit/test_workspace.py b/autogpts/autogpt/tests/unit/test_workspace.py index fbe14d8c..58cad459 100644 --- a/autogpts/autogpt/tests/unit/test_workspace.py +++ b/autogpts/autogpt/tests/unit/test_workspace.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest -from autogpt.workspace import Workspace +from autogpt.file_workspace import FileWorkspace _WORKSPACE_ROOT = Path("home/users/monty/auto_gpt_workspace") @@ -40,7 +40,7 @@ _INACCESSIBLE_PATHS = ( "test_folder/{null_byte}", "test_folder/{null_byte}test_file.txt", ], - Workspace.NULL_BYTES, + FileWorkspace.NULL_BYTES, ) ] + [ @@ -68,7 +68,7 @@ def inaccessible_path(request): def test_sanitize_path_accessible(accessible_path, workspace_root): - full_path = Workspace._sanitize_path( + full_path = FileWorkspace._sanitize_path( accessible_path, root=workspace_root, restrict_to_root=True, @@ -79,7 +79,7 @@ def test_sanitize_path_accessible(accessible_path, workspace_root): def test_sanitize_path_inaccessible(inaccessible_path, workspace_root): with pytest.raises(ValueError): - Workspace._sanitize_path( + FileWorkspace._sanitize_path( inaccessible_path, root=workspace_root, restrict_to_root=True, @@ -87,13 +87,13 @@ def test_sanitize_path_inaccessible(inaccessible_path, workspace_root): def test_get_path_accessible(accessible_path, workspace_root): - workspace = Workspace(workspace_root, True) + workspace = FileWorkspace(workspace_root, True) full_path = workspace.get_path(accessible_path) assert full_path.is_absolute() assert full_path.is_relative_to(workspace_root) def test_get_path_inaccessible(inaccessible_path, workspace_root): - workspace = Workspace(workspace_root, True) + workspace = FileWorkspace(workspace_root, True) with pytest.raises(ValueError): workspace.get_path(inaccessible_path)