tool

package
v0.0.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: MIT Imports: 27 Imported by: 0

Documentation

Overview

Package tool holds dax-coding's tools: read, write, edit, glob, grep, ls and bash. They are written against the agenttool contract, so the same values run under agentturn, under any other Open Responses loop, or behind an MCP server. They act through a workspace.Workspace, never os or os/exec, so they run unchanged on this machine (workspace.Local), in a container or on a remote runtime, and the policy's checks of their calls read that workspace's files too.

Any extension uses this package for its own tools: Files to turn a path the model gives into the workspace's and confine it (ReadFile, WriteFile, Update, Stat, ReadDir), DefaultEnv for the environment of a local workspace's processes, and BashSubjects, PathSubjects and Analyzer to read a command line or a path as the policy does. Each of dax's tools makes the facts claim (agenttool.Factual) from that same analysis: what a call would touch, and bash's stamped plan; the session takes the policy's subjects and the stamp from the claims.

Index

Constants

View Source
const DefaultMaxRead = 2 << 20

DefaultMaxRead is the most bytes of one file read scans, and the largest file edit will rewrite.

Variables

View Source
var ErrLinksUnknown = errors.New("the workspace cannot say where its links lead")

ErrLinksUnknown is Resolve's error where the workspace cannot say where a link leads.

View Source
var ErrOutside = workspace.ErrOutside

ErrOutside is what a path that leaves the workspace gets, whether it names another directory outright, climbs out with "..", or goes out through a symbolic link.

Functions

func Bash

func Bash(f *Files, opts ...BashOption) agenttool.Tool

Bash returns a tool that runs a shell command at the workspace's root, on whichever machine the workspace is, with the environment the workspace gives its processes. It is sequential: a batch that contains a shell command runs one call at a time, so a command never races a concurrent edit of the same file.

Its facts claim is what a call would touch, from the same analysis the policy's bash rules read (BashSubjects), and, for a line the analysis allows unasked, the arguments carrying the stamp of that plan and of those facts: the tool then runs only that plan, and only while the line still touches what it was allowed on (command). A line outside the analysis that writes through a redirect carries the stamp of the files its redirects write, and runs as typed only while they are the same.

func BashSubjects

func BashSubjects(f *Files, maxFile int64) agentpolicy.Subjects

BashSubjects returns the subjects splitter of the bash tool for the workspace f. What it decides is who may auto-run:

  • A command line inside the safe subset (see Analyzer) is one subject per stage, each its words joined by single spaces, so an allow rule for a command can match its stage. A stage of a command the check governs (git, ls, cat ...) whose arguments are not acceptable also gets a sentinel subject, which no rule matches, so `git log --output=x` is not covered by a rule for `git log`. So does a git stage in a repository whose configuration names a program, and the question says which.
  • Any other command line is cut into the parts a rule can still name, so a deny or ask rule for rm reaches `git status; rm x`, and a sentinel subject is added, so no allow rule for a command ever covers it. A redirect to a file is a write of the file it opens, links followed (redirectCalls). The cut is for the question the user is asked and for deny and ask rules; it is a best-effort reading of bash, not a parser, and nothing is allowed because of it.

A command with an unterminated quote is an error, which blocks the call, and so are arguments with a key that is command or dax_stamp in another case (exactKeys), which the tool would read and the analysis would not. The analysis runs under the context the policy passes, the decision's, so the git-config check it runs through the workspace's Exec is cancelled with the decision.

func Builtins

func Builtins(f *Files, maxRead int64, bash ...BashOption) []agenttool.Tool

Builtins returns read, write, edit, glob, grep, ls and bash, in the order the system prompt lists them. The file tools are confined to f's workspace; bash runs at its root but is not confined.

func ChildEnv

func ChildEnv(base, pass []string, secrets ...string) []string

ChildEnv is base without its credentials: every variable isSecret names, and every one in secrets, except those in pass. Bash commands and MCP servers get this environment, so a test, a build script or a server cannot read the provider's key from it. secrets is for a key whose variable the user named and isSecret may not recognise.

func DefaultEnv

func DefaultEnv(pass []string, secrets ...string) []string

DefaultEnv is the current environment through ChildEnv.

func Edit

func Edit(ws *Files, opts ...ReadOption) agenttool.Tool

Edit returns a tool that replaces one exact occurrence of a string.

func Glob

func Glob(ws *Files) agenttool.Tool

Glob returns a tool that lists files whose path matches a doublestar-style pattern, sorted. It skips .git and common vendored directories.

func Grep

func Grep(ws *Files) agenttool.Tool

Grep returns a tool that searches files for a regular expression and returns path:line:text for each matching line.

func LS

func LS(ws *Files) agenttool.Tool

LS returns a tool that lists one directory: names sorted, directories with a trailing slash, files with their size.

func PathCalls added in v0.0.9

func PathCalls(f *Files, name string) ([]agenttool.FactCall, error)

PathCalls is what a read of name, a path as a file tool takes it, would touch, as dax-coding's file tools claim it: name normalised against the workspace's root, and what its links lead to, read through f's workspace. The calls name no tool (Tool is empty, the tool that reads), except the one a workspace that cannot read links adds, which names a tool no rule names, so it asks. It is exported for an extension whose tool reads a workspace file by another name, dax-skills' skill tool reading a file of a project's skill, so its facts claim can name the file as a read would (see extension.Extension.HeldTo).

func PathSubjects

func PathSubjects(f *Files, field, def string) agentpolicy.Subjects

PathSubjects returns the subjects splitter of a file tool: its one subject is the path in field, normalised against the workspace's root (see normalizePath), so a rule written for docs/** meets docs/../.git/x as .git/x, ./.env and a/../.env as .env and the absolute path of a file as the same name. The name the model used is matched, and so is what its links lead to, read through the workspace: a file called notes.txt that is a link to .env is .env to the rules. A workspace that cannot read links adds a subject no rule allows, so the call asks. A path that leaves the workspace gets a subject no rule names, so it asks. When the field is absent the subject is def, which is the directory a search defaults to. Arguments with a key that is field in another case are an error, which blocks the call (exactKeys): the tool would decode that key, and this reads field.

func Read

func Read(ws *Files, opts ...ReadOption) agenttool.Tool

Read returns a tool that reads a file with line numbers. It streams the file: memory is bounded by the output and one line, not by the file, and it scans at most the size cap, so a multi-gigabyte file costs what a small one does. Past the cap, use grep to find the line.

func ReadOnly

func ReadOnly(f *Files, maxRead int64) []agenttool.Tool

ReadOnly returns the tools that only look: read, glob, grep and ls.

func StampArgs

func StampArgs(ctx context.Context, an *Analyzer, args json.RawMessage) (out json.RawMessage, changed bool, err error)

StampArgs is the policy's side of an auto-allowed bash call: when the command line analyses as Auto, the arguments come back carrying the stamp of the plan that was approved and of the facts it was approved on (planStamp), and the bash tool will run only that plan on those facts. Any other call comes back with a stamp the model supplied, which is not valid, removed. changed says the arguments differ. Arguments with a key that is command or dax_stamp in another case are an error (exactKeys).

func Text

func Text(r agenttool.Result) string

Text returns the text a tool produced, for tests and renderers. Text parts are shown as their text and any other part as its JSON.

func Write

func Write(ws *Files) agenttool.Tool

Write returns a tool that creates or replaces a file.

Types

type Analyzer

type Analyzer struct {
	// Files is the workspace the commands run in, at its root.
	Files *Files
	// MaxFile is the largest file cat, head, tail, wc and grep may be
	// given; zero is DefaultMaxRead.
	MaxFile int64
	// ConfigKey reads the exec-bearing key a repository's git
	// configuration names in dir, absolute in the workspace's namespace;
	// nil runs git in the workspace (execConfigKey). strict is set for
	// a line that will run as typed, without the auto-allow environment.
	ConfigKey func(ctx context.Context, dir string, strict bool) (string, error)
}

Analyzer decides which command lines dax runs without asking. It is an allow-list: a command line is auto-allowed only if it parses in the safe subset (parsePlan) and every stage is one of the commands below, run with arguments that can be shown to be read-only and to stay in the workspace. A stage whose command is one of these but whose arguments fail is not merely unlisted: it is a stage no allow rule may cover, because a rule for "git log" must not allow `git log --output=x`.

Every look it takes at a file goes through the workspace (Files), and git's configuration is read by running git there, so a command that will run in a container is checked against the container's files.

func (*Analyzer) Check

func (a *Analyzer) Check(ctx context.Context, cmd string) *Check

Check analyses a command line.

type BashArgs

type BashArgs struct {
	Command string `json:"command" desc:"The command to run"`
	Timeout int    `json:"timeout_seconds,omitempty" desc:"Kill the command after this many seconds (default 120)"`
	// Stamp is set by dax when the policy allowed the command without
	// asking; a value that dax did not set makes the call fail.
	Stamp string `json:"dax_stamp,omitempty" desc:"Set by dax; leave it out"`
}

BashArgs are the arguments of the bash tool.

type BashOption

type BashOption func(*bashConfig)

BashOption configures the bash tool.

func WithMaxFile

func WithMaxFile(n int64) BashOption

WithMaxFile sets the largest file the auto-allowed cat, head, tail, wc and grep may be given; see Analyzer.

type Check

type Check struct {
	// Parsed is whether the line is in the safe subset. When it is not,
	// Stages is empty and nothing is auto-allowed.
	Parsed bool
	Stages []StageCheck
	// Auto is whether the whole line runs without asking.
	Auto bool
	// contains filtered or unexported fields
}

Check is the verdict on a command line.

func (*Check) Render

func (c *Check) Render() string

Render is the command bash is run with for an auto-allowed line.

type EditArgs

type EditArgs struct {
	Path string `json:"path" desc:"File path"`
	Old  string `json:"old_string" desc:"Exact text to find"`
	New  string `json:"new_string" desc:"Replacement text"`
	// Stamp is the stamp of the facts the policy decided this call on
	// (dax sets it); the tool runs only if they still hold.
	Stamp string `json:"dax_stamp,omitempty" desc:"Set by dax; leave it out"`
}

EditArgs are the arguments of the edit tool.

type Files

type Files struct {
	// contains filtered or unexported fields
}

Files is the file tools' view of a workspace.Workspace: the paths the model writes, absolute or relative to the workspace's root, turned into the workspace's names, and the lock that keeps a write from landing between another's read and write. The workspace does the rest, so the same tools act on this machine's directory (workspace.Local), a container or a remote runtime alike.

bash is not confined by it: a shell can reach anything the workspace's processes can. That is the policy's to decide.

A session makes one Files for its workspace, and every tool that writes shares it, so the lock is one lock.

func NewFiles

func NewFiles(ws workspace.Workspace) *Files

NewFiles is the tools' view of ws. The workspace stays its owner's to close.

func (*Files) Dir

func (w *Files) Dir() string

Dir is the workspace's root, as the model sees it.

func (*Files) ReadDir

func (w *Files) ReadDir(path string) ([]fs.DirEntry, error)

ReadDir lists a directory, in the order the system gives.

func (*Files) ReadFile

func (w *Files) ReadFile(path string, max int64) ([]byte, error)

ReadFile reads a regular file no larger than max bytes; zero or less is DefaultMaxRead.

func (*Files) Rel

func (w *Files) Rel(path string) (string, error)

Rel turns a path the model gave, absolute or relative to the workspace, into a name relative to its root, cleaned. A path that names somewhere else is ErrOutside. It checks the name alone; the Root's own checks catch a link that leaves.

func (*Files) Resolve

func (w *Files) Resolve(path string) (string, error)

Resolve is the name, relative to the workspace's root, that path leads to with every link on its way followed, read through the workspace's file system, as the policy's checks follow them. A path or a link that leads out of the workspace is ErrOutside; a workspace whose file system cannot read links, or a chain of links too long to follow, is ErrLinksUnknown, so a caller that must know where a name leads fails toward refusing. A name that does not exist is judged by where its directory leads. An extension that offers a project's files through a tool other than the file tools (dax-skills) uses it to keep a link from reaching past what it offers.

func (*Files) Stat

func (w *Files) Stat(path string) (fs.FileInfo, error)

Stat describes a file, following a link only while it stays inside.

func (*Files) Update

func (w *Files) Update(path string, max int64, fn func(old []byte) ([]byte, error)) (string, error)

Update reads a regular file no larger than max bytes (zero or less is DefaultMaxRead), and writes what fn makes of its content, holding the write lock across both, as the edit tool does. An error from fn leaves the file as it was. It returns the file's name relative to the workspace.

func (*Files) Workspace

func (w *Files) Workspace() workspace.Workspace

Workspace is the workspace the files are in.

func (*Files) WriteFile

func (w *Files) WriteFile(path string, data []byte) (string, error)

WriteFile writes data to a regular file, creating it and the directories above it, and returns its name relative to the workspace.

type GlobArgs

type GlobArgs struct {
	Pattern    string `` /* 159-byte string literal not displayed */
	Path       string `` /* 135-byte string literal not displayed */
	MaxResults int    `json:"max_results,omitempty" desc:"Maximum paths to return (default 1000)"`
	// Stamp is the stamp of the facts the policy decided this call on
	// (dax sets it); the tool runs only if they still hold.
	Stamp string `json:"dax_stamp,omitempty" desc:"Set by dax; leave it out"`
}

GlobArgs are the arguments of the glob tool.

type GrepArgs

type GrepArgs struct {
	Pattern    string `json:"pattern" desc:"Regular expression (Go RE2 syntax)"`
	Path       string `json:"path,omitempty" desc:"File or directory to search, relative to the workspace (default the workspace root)"`
	Include    string `` /* 152-byte string literal not displayed */
	IgnoreCase bool   `json:"ignore_case,omitempty" desc:"Match case-insensitively"`
	MaxResults int    `json:"max_results,omitempty" desc:"Maximum matching lines to return (default 200)"`
	// Stamp is the stamp of the facts the policy decided this call on
	// (dax sets it); the tool runs only if they still hold.
	Stamp string `json:"dax_stamp,omitempty" desc:"Set by dax; leave it out"`
}

GrepArgs are the arguments of the grep tool.

type LSArgs

type LSArgs struct {
	Path string `json:"path,omitempty" desc:"Directory to list, relative to the workspace (default the workspace root)"`
	// Stamp is the stamp of the facts the policy decided this call on
	// (dax sets it); the tool runs only if they still hold.
	Stamp string `json:"dax_stamp,omitempty" desc:"Set by dax; leave it out"`
}

LSArgs are the arguments of the ls tool.

type ReadArgs

type ReadArgs struct {
	Path   string `json:"path" desc:"File path, relative to the workspace or absolute inside it"`
	Offset int    `json:"offset,omitempty" desc:"1-based line to start at (default 1)"`
	Limit  int    `json:"limit,omitempty" desc:"Maximum lines to return (default 2000)"`
	// Stamp is the stamp of the facts the policy decided this call on
	// (dax sets it); the tool runs only if they still hold.
	Stamp string `json:"dax_stamp,omitempty" desc:"Set by dax; leave it out"`
}

ReadArgs are the arguments of the read tool.

type ReadOption

type ReadOption func(*readConfig)

ReadOption configures the read tool.

func WithMaxRead

func WithMaxRead(n int64) ReadOption

WithMaxRead sets the most bytes one read call scans, which is also the largest file edit will rewrite. Zero or less is DefaultMaxRead.

type StageCheck

type StageCheck struct {
	// Words are the stage's words as text; Text what a person is asked
	// about; Match what an allow rule is matched against.
	Words       []string
	Text, Match string
	// Governed is set for a command this check has opinions about.
	Governed bool
	// OK is whether the arguments of a governed command are acceptable.
	OK bool
	// Reads are the files the stage reads, relative to the workspace,
	// for the policy's path rules.
	Reads []string
	// Why says what was wrong, or, for a git stage, the key of the
	// repository's configuration that names a program.
	Why string
}

StageCheck is the verdict on one stage of a command line.

type WriteArgs

type WriteArgs struct {
	Path    string `json:"path" desc:"File path"`
	Content string `json:"content" desc:"Full file content"`
	// Stamp is the stamp of the facts the policy decided this call on
	// (dax sets it); the tool runs only if they still hold.
	Stamp string `json:"dax_stamp,omitempty" desc:"Set by dax; leave it out"`
}

WriteArgs are the arguments of the write tool.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL