Source code for zett_agent.extensions.file_system
"""Filesystem tools exposed as an Agent extension."""
import json
from pathlib import Path
from ..agent import AgentRunContext
from ..messages import SystemMessage
from ..tools.base import AgentTool
from ..tools.coding import (
delete_file,
edit,
glob,
grep,
read,
read_file,
write_file,
)
from ..tools.images import view_image
from .base import AgentExtension
from .tool import ToolExtension
[docs]
class ReadFileExtension(ToolExtension):
"""Register ``read_file``, which returns a line range of a UTF-8 text file."""
def __init__(self) -> None:
super().__init__(read_file)
[docs]
class ReadExtension(ToolExtension):
"""Register ``read``, which returns UTF-8 text or a local image.
Text comes back as a line range with a continuation hint, exactly like
``read_file``. A PNG, JPEG, GIF, or WebP file comes back as an image plus a
one-line description, so one tool covers both without the model choosing
between them. It is separate from :class:`FileSystemExtension`, so add it
next to (or instead of) :class:`ReadFileExtension` when the model should
read images through the same tool.
"""
def __init__(self) -> None:
super().__init__(read)
[docs]
class ViewImageExtension(ToolExtension):
"""Register ``view_image`` for local images and HTTP(S) image URLs."""
def __init__(self) -> None:
super().__init__(view_image)
[docs]
class GlobExtension(ToolExtension):
"""Register ``glob`` to find paths by pattern."""
def __init__(self) -> None:
super().__init__(glob)
[docs]
class GrepExtension(ToolExtension):
"""Register ``grep`` to search file contents by regular expression."""
def __init__(self) -> None:
super().__init__(grep)
[docs]
class WriteFileExtension(ToolExtension):
"""Register ``write_file`` to create or replace a whole text file."""
def __init__(self) -> None:
super().__init__(write_file)
[docs]
class EditExtension(ToolExtension):
"""Register ``edit`` to change an existing file by exact-text replacement."""
def __init__(self) -> None:
super().__init__(edit)
[docs]
class DeleteFileExtension(ToolExtension):
"""Register ``delete_file`` to remove one regular file."""
def __init__(self) -> None:
super().__init__(delete_file)
[docs]
class FileSystemExtension(AgentExtension):
"""Register filesystem tools with an optional read-only boundary.
Relative paths start from the current working directory; absolute paths are
accepted. Read-only mode exposes read_file, view_image, glob, and grep. Writable mode
additionally exposes write_file, edit, and delete_file. Shell execution is
deliberately not a filesystem capability because arbitrary commands cannot
guarantee that they will leave the workspace unchanged.
Each tool also has its own extension (:class:`ReadFileExtension`,
:class:`EditExtension`, and so on) for an application that wants a subset.
Examples:
Usage::
extension = FileSystemExtension(read_only=True)
agent = await Agent.create(model, config=config, extensions=[extension])
"""
def __init__(self, *, read_only: bool = False) -> None:
self.read_only = read_only
@property
def tools(self) -> tuple[AgentTool, ...]:
"""Return the exact immutable registration set for the configured mode."""
read_tools = (read_file, view_image, glob, grep)
return read_tools if self.read_only else (*read_tools, write_file, edit, delete_file)
def _working_directory_instructions(self, working_directory: Path) -> str:
"""Describe the path base shared by this extension's tools."""
rendered = json.dumps(str(working_directory), ensure_ascii=False)
return (
"# Filesystem environment\n"
f"Current working directory: {rendered}.\n"
"Relative filesystem paths are resolved from this directory. Absolute paths are also allowed."
)
[docs]
async def on_state(self, context: AgentRunContext) -> None:
"""Inject the request's resolved working directory into model context."""
state = context.state
message = SystemMessage(content=self._working_directory_instructions(Path.cwd().resolve()))
instructions = [item for item in state.messages if isinstance(item, SystemMessage)]
context.add_message(message, index=len(instructions))