jeevesagent.tools
=================

.. py:module:: jeevesagent.tools

.. autoapi-nested-parse::

   Tool registry and decorators + built-in filesystem / shell tools.

   Users typically construct tools via :func:`tool` (decorator) and pass
   the resulting :class:`Tool` objects to :class:`Agent`. The agent wraps
   them in an :class:`InProcessToolHost`.

   For the canonical "Claude-Code-shaped" tool set (read / write / edit
   / bash), import the four factory functions from
   :mod:`jeevesagent.tools.builtin` (also re-exported at the top level).



Submodules
----------

.. toctree::
   :maxdepth: 1

   /api/jeevesagent/tools/builtin/index
   /api/jeevesagent/tools/registry/index


Exceptions
----------

.. autoapisummary::

   jeevesagent.tools.PathEscapeError


Classes
-------

.. autoapisummary::

   jeevesagent.tools.InProcessToolHost
   jeevesagent.tools.Tool


Functions
---------

.. autoapisummary::

   jeevesagent.tools.bash_tool
   jeevesagent.tools.default_workdir
   jeevesagent.tools.edit_tool
   jeevesagent.tools.filesystem_tools
   jeevesagent.tools.read_tool
   jeevesagent.tools.tool
   jeevesagent.tools.write_tool


Package Contents
----------------

.. py:exception:: PathEscapeError

   Bases: :py:obj:`ValueError`


   Raised when a tool argument resolves outside its workdir.

   Initialize self.  See help(type(self)) for accurate signature.


.. py:class:: InProcessToolHost(tools: list[Tool | collections.abc.Callable[Ellipsis, Any]] | None = None)

   A dict-backed :class:`~jeevesagent.core.protocols.ToolHost`.


   .. py:method:: call(tool: str, args: collections.abc.Mapping[str, Any], *, call_id: str = '') -> jeevesagent.core.types.ToolResult
      :async:



   .. py:method:: get(name: str) -> Tool | None


   .. py:method:: list_tools(*, query: str | None = None) -> list[jeevesagent.core.types.ToolDef]
      :async:



   .. py:method:: register(item: Tool | collections.abc.Callable[Ellipsis, Any]) -> Tool


   .. py:method:: unregister(name: str) -> bool

      Remove a tool by name. Returns ``True`` if removed.



   .. py:method:: watch() -> collections.abc.AsyncIterator[jeevesagent.core.types.ToolEvent]
      :async:


      In-process registry is static; the generator yields nothing.

      Iterating over an empty tuple keeps this an async generator
      (so the return type is ``AsyncIterator``) without ever producing
      an event at runtime.



.. py:class:: Tool

   A registered tool: definition plus the callable that executes it.


   .. py:method:: execute(args: collections.abc.Mapping[str, Any]) -> Any
      :async:


      Invoke the underlying callable.

      Async functions are awaited; sync functions are dispatched to a
      worker thread via :func:`anyio.to_thread.run_sync` so they don't
      block the event loop.



   .. py:method:: to_def() -> jeevesagent.core.types.ToolDef


   .. py:attribute:: description
      :type:  str


   .. py:attribute:: destructive
      :type:  bool
      :value: False



   .. py:attribute:: fn
      :type:  collections.abc.Callable[Ellipsis, Any]


   .. py:attribute:: input_schema
      :type:  dict[str, Any]


   .. py:attribute:: name
      :type:  str


.. py:function:: bash_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'bash', timeout: float = 30.0, allow_pattern: collections.abc.Callable[[str], bool] | None = None, extra_env: dict[str, str] | None = None) -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that runs a shell command with the
   workdir as the current working directory.

   Default safety:

   * Commands matching the built-in destructive patterns
     (``rm -rf /``, ``sudo``, ``mkfs``, fork bombs, ...) are
     rejected before being executed.
   * Commands run with a default ``timeout`` of 30 seconds; the
     subprocess is killed on timeout.
   * The shell is invoked via ``/bin/sh -c <command>``, so
     pipelines + redirections work the way you'd expect.

   Knobs:

   * ``allow_pattern`` — a callable that takes the command string
     and returns True if the command should run. When provided, it
     OVERRIDES the default deny list — you take full responsibility.
   * ``extra_env`` — extra environment variables merged into the
     subprocess env.
   * ``timeout`` — seconds before the command is killed.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools).


.. py:function:: default_workdir() -> pathlib.Path

   Return the framework's default workdir for built-in tools,
   creating it lazily on first call.

   The directory is a fresh tempdir under ``$TMPDIR/jeeves_agent_*``,
   created once per process. All built-in tool factories share it
   when called without an explicit ``workdir`` argument, so an
   Agent that registers ``read_tool()`` and ``write_tool()`` (no
   args) sees the same place.

   The directory is NOT auto-cleaned at process exit — leave that
   to the OS's tempdir cleanup so debug data survives a crash.


.. py:function:: edit_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'edit') -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that does find-and-replace inside an
   existing file under ``workdir``.

   The tool's signature seen by the model:
       ``edit(path: str, old_string: str, new_string: str,
              replace_all: bool = False)``

   Behaviour matches Claude Code's Edit tool:

   * ``old_string`` must be EXACTLY present in the file. Mismatch
     (whitespace, indentation, line breaks) → error.
   * ``old_string`` must appear EXACTLY once in the file unless
     ``replace_all=True`` is passed — forces the model to give
     enough surrounding context for unambiguous matches.
   * ``new_string`` replaces ``old_string`` (or every occurrence
     if ``replace_all=True``).

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools).


.. py:function:: filesystem_tools(workdir: pathlib.Path | str | None = None) -> list[jeevesagent.tools.registry.Tool]

   Return all three filesystem tools (read + write + edit)
   bound to a single workdir. ``bash_tool`` is excluded — pair
   them only when you want shell access too.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with bash_tool() called the same way).


.. py:function:: read_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'read', line_limit: int = _DEFAULT_READ_LINE_LIMIT) -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that reads a text file under ``workdir``.

   The tool's signature seen by the model:
       ``read(path: str, offset: int = 0, limit: int | None = None)``

   Returns the file's text with line numbers prefixed (one
   line per output line), in the same format Claude Code's Read
   tool uses — that lets the ``edit`` tool work without ambiguity
   later. Long files are truncated to ``line_limit`` lines per
   call; pass ``offset`` / ``limit`` to read further chunks.

   Errors (file-not-found, path-escape) are returned as a string
   starting with ``"ERROR: "`` rather than raising — the model
   sees them as a tool result and can adjust.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools called without
   a workdir).


.. py:function:: tool(fn: collections.abc.Callable[Ellipsis, Any]) -> Tool
                 tool(*, name: str | None = None, description: str | None = None, destructive: bool = False) -> collections.abc.Callable[[collections.abc.Callable[Ellipsis, Any]], Tool]

   Promote a callable to a :class:`Tool`.

   Use as ``@tool`` (bare) or ``@tool(name=..., description=..., destructive=...)``.
   The schema is derived from parameter annotations; primitive types map
   to their JSON-Schema equivalents, anything else falls back to ``string``.


.. py:function:: write_tool(workdir: pathlib.Path | str | None = None, *, name: str = 'write', create_parents: bool = True) -> jeevesagent.tools.registry.Tool

   Build a :class:`Tool` that writes / overwrites a text file
   under ``workdir``.

   The tool's signature seen by the model:
       ``write(path: str, content: str)``

   Overwrites existing files. With ``create_parents=True`` (the
   default), missing parent directories are created automatically.

   Returns a confirmation string with the byte count, or an
   ``"ERROR: "``-prefixed message on failure.

   ``workdir`` is optional; ``None`` uses the framework's default
   tempdir (shared with the other built-in tools).


