diff --git a/autogpt/agents/agent.py b/autogpt/agents/agent.py index e2d8fb27..e4893311 100644 --- a/autogpt/agents/agent.py +++ b/autogpt/agents/agent.py @@ -177,14 +177,11 @@ class Agent(ContextMixin, WorkspaceMixin, BaseAgent): return_value[1], ContextItem ): context_item = return_value[1] - # return_value = return_value[0] + return_value = return_value[0] logger.debug( f"Command {command_name} returned a ContextItem: {context_item}" ) - # self.context.add(context_item) - - # HACK: use content of ContextItem as return value, for legacy support - return_value = context_item.content + self.context.add(context_item) result = ActionSuccessResult(return_value) except AgentException as e: diff --git a/autogpt/agents/base.py b/autogpt/agents/base.py index d051a969..bef16cb0 100644 --- a/autogpt/agents/base.py +++ b/autogpt/agents/base.py @@ -102,7 +102,7 @@ class BaseAgent(metaclass=ABCMeta): super(BaseAgent, self).__init__() @property - def system_prompt(self): + def system_prompt(self) -> str: """ The system prompt sets up the AI's personality and explains its goals, available resources, and restrictions. @@ -297,7 +297,7 @@ class BaseAgent(metaclass=ABCMeta): return ( f"Respond strictly with JSON{', and also specify a command to use through a function_call' if use_functions else ''}. " "The JSON should be compatible with the TypeScript type `Response` from the following:\n" - f"{response_format}\n" + f"{response_format}" ) def on_before_think( diff --git a/autogpt/commands/file_context.py b/autogpt/commands/file_context.py index 9a645393..c5e00b8f 100644 --- a/autogpt/commands/file_context.py +++ b/autogpt/commands/file_context.py @@ -23,7 +23,7 @@ from autogpt.models.context_item import FileContextItem, FolderContextItem from .decorators import sanitize_path_arg -def compatible_with_agent(agent: BaseAgent) -> bool: +def agent_implements_context(agent: BaseAgent) -> bool: return isinstance(agent, ContextMixin) @@ -37,7 +37,7 @@ def compatible_with_agent(agent: BaseAgent) -> bool: "required": True, } }, - available=compatible_with_agent, + available=agent_implements_context, ) @sanitize_path_arg("file_path") def open_file(file_path: Path, agent: Agent) -> tuple[str, FileContextItem]: @@ -51,8 +51,9 @@ def open_file(file_path: Path, agent: Agent) -> tuple[str, FileContextItem]: FileContextItem: A ContextItem representing the opened file """ # Try to make the file path relative + relative_file_path = None with contextlib.suppress(ValueError): - file_path = file_path.relative_to(agent.workspace.root) + relative_file_path = file_path.relative_to(agent.workspace.root) assert (agent_context := get_agent_context(agent)) is not None @@ -63,12 +64,14 @@ def open_file(file_path: Path, agent: Agent) -> tuple[str, FileContextItem]: elif not file_path.is_file(): raise CommandExecutionError(f"{file_path} exists but is not a file") - file = FileContextItem(file_path) + file_path = relative_file_path or file_path + + file = FileContextItem(file_path, agent.workspace.root) if file in agent_context: raise DuplicateOperationError(f"The file {file_path} is already open") return ( - f"File {file}{' created,' if created else ''} opened and added to context ✅", + f"File {file_path}{' created,' if created else ''} opened and added to context ✅", file, ) @@ -83,7 +86,7 @@ def open_file(file_path: Path, agent: Agent) -> tuple[str, FileContextItem]: "required": True, } }, - available=compatible_with_agent, + available=agent_implements_context, ) @sanitize_path_arg("path") def open_folder(path: Path, agent: Agent) -> tuple[str, FolderContextItem]: @@ -97,8 +100,9 @@ def open_folder(path: Path, agent: Agent) -> tuple[str, FolderContextItem]: FolderContextItem: A ContextItem representing the opened folder """ # Try to make the path relative + relative_path = None with contextlib.suppress(ValueError): - path = path.relative_to(agent.workspace.root) + relative_path = path.relative_to(agent.workspace.root) assert (agent_context := get_agent_context(agent)) is not None @@ -107,8 +111,10 @@ def open_folder(path: Path, agent: Agent) -> tuple[str, FolderContextItem]: elif not path.is_dir(): raise CommandExecutionError(f"{path} exists but is not a folder") - folder = FolderContextItem(path) + path = relative_path or path + + folder = FolderContextItem(path, agent.workspace.root) if folder in agent_context: raise DuplicateOperationError(f"The folder {path} is already open") - return f"Folder {folder} opened and added to context ✅", folder + return f"Folder {path} opened and added to context ✅", folder diff --git a/autogpt/commands/system.py b/autogpt/commands/system.py index 4e572f80..643b67fe 100644 --- a/autogpt/commands/system.py +++ b/autogpt/commands/system.py @@ -6,8 +6,13 @@ COMMAND_CATEGORY = "system" COMMAND_CATEGORY_TITLE = "System" import logging +from typing import TYPE_CHECKING -from autogpt.agents.agent import Agent +if TYPE_CHECKING: + from autogpt.agents.agent import Agent + +from autogpt.agents.features.context import get_agent_context +from autogpt.agents.utils.exceptions import InvalidArgumentError from autogpt.command_decorator import command logger = logging.getLogger(__name__) @@ -36,3 +41,25 @@ def task_complete(reason: str, agent: Agent) -> None: """ logger.info(reason, extra={"title": "Shutting down...\n"}) quit() + + +@command( + "close_context_item", + "Close an open file, folder or other context item", + { + "index": { + "type": "integer", + "description": "The 1-based index of the context item to close", + "required": True, + } + }, + available=lambda a: get_agent_context(a) is not None, +) +def close_context_item(index: int, agent: Agent) -> str: + assert (context := get_agent_context(agent)) is not None + + if index > len(context.items) or index == 0: + raise InvalidArgumentError(f"Index {index} out of range") + + context.close(index) + return f"Context item {index} closed ✅" diff --git a/autogpt/models/context_item.py b/autogpt/models/context_item.py index eec579f7..cbf49084 100644 --- a/autogpt/models/context_item.py +++ b/autogpt/models/context_item.py @@ -39,15 +39,20 @@ class ContextItem(ABC): @dataclass class FileContextItem(ContextItem): - file_path: Path + file_path_in_workspace: Path + workspace_path: Path + + @property + def file_path(self) -> Path: + return self.workspace_path / self.file_path_in_workspace @property def description(self) -> str: - return f"The current content of the file '{self.file_path}'" + return f"The current content of the file '{self.file_path_in_workspace}'" @property def source(self) -> str: - return str(self.file_path) + return str(self.file_path_in_workspace) @property def content(self) -> str: @@ -56,7 +61,12 @@ class FileContextItem(ContextItem): @dataclass class FolderContextItem(ContextItem): - path: Path + path_in_workspace: Path + workspace_path: Path + + @property + def path(self) -> Path: + return self.workspace_path / self.path_in_workspace def __post_init__(self) -> None: assert self.path.exists(), "Selected path does not exist" @@ -64,11 +74,11 @@ class FolderContextItem(ContextItem): @property def description(self) -> str: - return f"The contents of the folder '{self.path}' in the workspace" + return f"The contents of the folder '{self.path_in_workspace}' in the workspace" @property def source(self) -> str: - return str(self.path) + return str(self.path_in_workspace) @property def content(self) -> str: