ExAthena.ToolContext (ExAthena v0.17.0)

Copy Markdown View Source

Context handed to every tool execution.

Carries the working directory the loop should treat as root, the current permission mode, the session id (if any), and a free-form assigns map for custom tools to stash arbitrary data the consumer needs (project id, conversation id, ticket id — whatever the host cares about).

Tools that don't care about context can ignore it, but cwd and phase are load-bearing for any tool that touches the filesystem or checks permissions.

Summary

Functions

Whether this context confines tools to allowed_roots.

Build a context. :cwd is required; everything else defaults.

Resolve a user-supplied path against ctx.cwd.

Whether an absolute path lies within one of roots. Compares on path segments (not string prefixes) so /foo never matches /foobar. A path equal to a root counts as inside it.

Types

phase()

@type phase() :: :plan | :default | :accept_edits | :trusted | :bypass_permissions

t()

@type t() :: %ExAthena.ToolContext{
  allowed_roots: [Path.t()] | nil,
  assigns: map(),
  cwd: Path.t(),
  phase: phase(),
  session_id: String.t() | nil,
  tool_call_id: String.t() | nil
}

Functions

confined?(tool_context)

@spec confined?(t()) :: boolean()

Whether this context confines tools to allowed_roots.

new(opts)

@spec new(keyword()) :: t()

Build a context. :cwd is required; everything else defaults.

resolve_path(ctx, path)

@spec resolve_path(t(), String.t()) :: {:ok, Path.t()} | {:error, term()}

Resolve a user-supplied path against ctx.cwd.

Unconfined (allowed_roots: nil, the default) keeps the historical behaviour: relative paths are expanded against cwd, .. traversal is rejected, and absolute paths pass through untouched.

Confined (allowed_roots is a list) expands the path to an absolute one (handling ../~ lexically) and requires the result to fall inside one of the roots, returning {:error, {:path_outside_roots, abs}} otherwise — so neither /etc/passwd nor ../../secret can escape.

within_roots?(path, roots)

@spec within_roots?(Path.t(), [Path.t()]) :: boolean()

Whether an absolute path lies within one of roots. Compares on path segments (not string prefixes) so /foo never matches /foobar. A path equal to a root counts as inside it.