tools

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Mar 18, 2026 License: MIT Imports: 21 Imported by: 0

README

terse-tools

A Go library that provides a registry of 18 builtin tools for agentic LLM workflows, with provider adapters for Anthropic, OpenAI, and Gemini. Import the core library with zero external dependencies, then add only the provider adapter you need.

Features

  • 18 builtin tools executing in-process (no external binaries required): file I/O, search, replace, diff, parse, transform, and more
  • Provider adapters for Anthropic, OpenAI, and Gemini — each in a separate Go module so you only pull the SDK you use
  • Middleware chain for logging, access control, or custom interception before/after tool execution
  • Registry filtering with presets like ExcludeDangerous() and ReadOnlyTools() for sandboxing agents at different trust levels
  • Read-before-write safety — the executor tracks file reads and blocks blind overwrites
  • Thread-safe registry with concurrent access via sync.RWMutex

Installation

Core library (zero external dependencies):

go get github.com/hegner123/terse-tools

Add a provider adapter (pulls only that SDK):

go get github.com/hegner123/terse-tools/provider/anthropic
go get github.com/hegner123/terse-tools/provider/openai
go get github.com/hegner123/terse-tools/provider/gemini

Usage

Basic: Registry + Executor
package main

import (
    "context"
    "fmt"

    tools "github.com/hegner123/terse-tools"
)

func main() {
    reg := tools.DefaultRegistry()  // All 18 tools
    exec := tools.NewExecutor(reg, "/workspace")

    result := exec.Execute(context.Background(), "checkfor", map[string]any{
        "search": "TODO",
        "dir":    ".",
        "ext":    ".go",
    })

    if result.IsError {
        fmt.Println("Error:", result.Error)
        return
    }
    fmt.Println(result.Output)
}
With Middleware
runner := tools.NewToolRunner(exec)

// Log every tool call
runner.Use(tools.LogMiddleware(nil, func(ctx context.Context, call tools.ToolCall, result tools.ToolResult, elapsed time.Duration) {
    log.Printf("[%s] %s %v", call.Name, elapsed, result.IsError)
}))

// Block dangerous tools
runner.Use(tools.DenyListMiddleware("bash", "delete", "write"))
With Provider Adapters (Anthropic example)
import (
    tools "github.com/hegner123/terse-tools"
    adapter "github.com/hegner123/terse-tools/provider/anthropic"
    "github.com/anthropics/anthropic-sdk-go"
)

// Set up
reg := tools.TerseRegistry().Filter(tools.ExcludeDangerous())
exec := tools.NewExecutor(reg, "/workspace")
runner := tools.NewToolRunner(exec)
a := adapter.NewAdapter(runner)

// Convert tools for the API request
toolParams := a.Tools(reg)
// Pass toolParams to anthropic.MessageNewParams{Tools: toolParams}

// When the response contains tool_use blocks:
// results := a.RunAll(ctx, toolUseBlocks)
// Append results to the next user message
Registry Filtering
// Only read-only tools (safe for untrusted agents)
safe := tools.DefaultRegistry().Filter(tools.ReadOnlyTools())

// Remove specific tools
limited := tools.DefaultRegistry().Filter(tools.ExcludeNames("bash", "write"))

// Custom filter
goOnly := tools.DefaultRegistry().Filter(func(def tools.ToolDef) bool {
    return def.Name != "bash"
})

Builtin Tools

Tool Description
read Read file contents with optional line offset/limit
write Write content to a file (enforces read-before-write)
bash Execute shell commands
checkfor Search for text across files and directories
repfor Replace text across multiple files
stump Directory tree visualization
sig Extract function signatures and type definitions (Go, TypeScript, C#)
errs Parse compiler/linter error output into structured JSON
imports Map import/dependency statements across 11 languages
conflicts Parse git merge conflict markers into structured JSON
cleanDiff Compact git diff as structured JSON
transform Composable JSON array pipeline (group, sort, filter, format)
split Split a file at specified line numbers
splice Insert, append, prepend, or replace content in a file
tabcount Count tab characters in a file
notab Convert between tabs and spaces
utf8 Fix malformed UTF-8/UTF-16 encoding
delete Move files to trash (macOS)

API Reference

Core Types
  • ToolDef — Defines a tool: name, description, JSON Schema, and either a builtin function or binary path
  • Registry — Thread-safe collection of tool definitions with Register, Get, Remove, Filter, Defs
  • Executor — Runs tools by name, handling timeout, output truncation, and read-before-write tracking
  • ToolRunner — Wraps an Executor with a middleware chain
  • ToolCall / ToolResult — Provider-agnostic types that adapters convert to/from SDK-specific types
Registry Factories
  • DefaultRegistry() — All 18 tools
  • TerseRegistry() — 15 domain tools (no read/write/bash)
  • ToolRegistry(toolSets...) — Custom composition from tool slices
Filter Presets
  • ExcludeDangerous() — Removes bash, delete, write, transform, splice, split, repfor
  • ReadOnlyTools() — Keeps only non-mutating tools
  • ExcludeNames(names...) / IncludeNames(names...) — Explicit allow/deny lists
Built-in Middleware
  • LogMiddleware(before, after) — Hook into tool execution lifecycle
  • DenyListMiddleware(names...) — Block specific tools at runtime

Contributing

See CONTRIBUTING.md.

License

MIT License. See LICENSE.

Documentation

Overview

Package tools provides a tool registry and executor for agentic tool use. Each tool is either a standalone CLI binary invoked via exec.Command, or a builtin that executes directly in-process.

Index

Constants

View Source
const (
	// DefaultTimeout is the per-tool execution timeout when none is specified.
	DefaultTimeout = 30 * time.Second

	// MaxOutputBytes caps tool stdout to prevent context explosion (50KB).
	MaxOutputBytes = 50 * 1024
)

Variables

View Source
var BashDef = ToolDef{
	Name:        "bash",
	Description: "Execute a bash command and return its output. The command runs in the configured working directory. Use for running builds, tests, git commands, or any shell operation.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"command": map[string]any{
				"type":        "string",
				"description": "The shell command to execute.",
			},
			"timeout": map[string]any{
				"type":        "integer",
				"description": "Timeout in seconds. Defaults to 120.",
			},
		},
		"required": []string{"command"},
	},
	Builtin: builtinBash,
	Timeout: 2 * time.Minute,
}

BashDef executes a shell command and returns stdout/stderr.

View Source
var CheckforDef = ToolDef{
	Name:        "checkfor",
	Description: "Search files in directories for a string pattern. Single-depth (non-recursive) scanning with optional extension filtering, case-insensitive search, whole-word matching, and context lines.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"search": map[string]any{
				"type":        "string",
				"description": "String pattern to search for. Supports multi-line search: \\n in the string matches literal newlines.",
			},
			"dir": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of directory paths to search. Defaults to current directory if neither dir nor file is provided.",
			},
			"file": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of file paths to search directly. Bypasses directory scanning.",
			},
			"ext": map[string]any{
				"type":        "string",
				"description": "File extension to filter (e.g., '.go', '.rtf').",
			},
			"case_insensitive": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Perform case-insensitive search.",
			},
			"whole_word": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Match whole words only.",
			},
			"context": map[string]any{
				"type":        "integer",
				"default":     0,
				"description": "Number of context lines before and after each match.",
			},
			"exclude": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of strings to exclude from results.",
			},
		},
		"required": []string{"search"},
	},
	Builtin: builtinCheckfor,
	Timeout: 30 * time.Second,
}

CheckforDef searches files for exact string matches.

View Source
var CleanDiffDef = ToolDef{
	Name:        "cleanDiff",
	Description: "Compact git diff as structured JSON. Strips context padding by default, outputs only changed lines grouped by file. Cuts diff token cost by 60-80% compared to raw diff output.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"path": map[string]any{
				"type":        "string",
				"description": "Path to git repository. Defaults to current directory.",
			},
			"ref": map[string]any{
				"type":        "string",
				"description": "Git ref or range (e.g. 'HEAD~1', 'main..feature'). Empty for unstaged changes.",
			},
			"staged": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Show staged changes only (git diff --cached).",
			},
			"stat_only": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Only return file-level stats, omit line content.",
			},
			"context_lines": map[string]any{
				"type":        "integer",
				"default":     0,
				"description": "Number of context lines around changes.",
			},
			"file_filter": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of file paths to restrict the diff to.",
			},
		},
	},
	Builtin: builtinCleanDiff,
	Timeout: 30 * time.Second,
}

CleanDiffDef produces compact git diffs as structured JSON. Note: this tool inherently depends on git being installed.

View Source
var ConflictsDef = ToolDef{
	Name:        "conflicts",
	Description: "Parse git merge conflict markers in files. Returns structured JSON with each conflict's ours/theirs content, line numbers, refs, and surrounding context. Supports standard and diff3 styles.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of file paths to parse for conflicts.",
			},
			"context_lines": map[string]any{
				"type":        "integer",
				"default":     1,
				"description": "Number of context lines above and below each conflict.",
			},
		},
		"required": []string{"file"},
	},
	Builtin: builtinConflicts,
	Timeout: 15 * time.Second,
}

ConflictsDef parses git merge conflict markers.

View Source
var DeleteDef = ToolDef{
	Name:        "delete",
	Description: "Move a file or directory to macOS Trash. Safe alternative to rm/rmdir. Handles name collisions. Works on files, directories, and symlinks.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"path": map[string]any{
				"type":        "string",
				"description": "Absolute path to the file or directory to trash.",
			},
		},
		"required": []string{"path"},
	},
	Builtin: builtinDelete,
	Timeout: 10 * time.Second,
}

DeleteDef moves files/directories to macOS Trash.

View Source
var ErrsDef = ToolDef{
	Name:        "errs",
	Description: "Compact error/lint parser. Normalizes verbose compiler/linter output into structured JSON. Strips ANSI codes, deduplicates. Supports Go, GCC/Clang, Rust, TypeScript, ESLint, dotnet/C#, Python, Kotlin.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"input": map[string]any{
				"type":        "string",
				"description": "Raw error/lint text to parse.",
			},
			"format": map[string]any{
				"type":        "string",
				"description": "Format hint: go, eslint, tsc, rust, gcc, dotnet. Auto-detects if omitted.",
			},
		},
		"required": []string{"input"},
	},
	Builtin: builtinErrs,
	Timeout: 15 * time.Second,
}

ErrsDef parses build errors and lint output.

View Source
var ImportsDef = ToolDef{
	Name:        "imports",
	Description: "Map imports and dependencies for files in a directory. Shows which files import which packages and which local files reference each other. Supports Go, Python, JS/TS, Zig, Rust, C/C++, Swift, Java/Kotlin, Ruby, and shell scripts.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"dir": map[string]any{
				"type":        "string",
				"description": "Absolute path to the directory to scan for imports.",
			},
			"ext": map[string]any{
				"type":        "string",
				"description": "Filter by file extension (e.g., '.go'). Only scan files with this extension.",
			},
			"recursive": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Recursively scan subdirectories.",
			},
		},
		"required": []string{"dir"},
	},
	Builtin: builtinImports,
	Timeout: 30 * time.Second,
}

ImportsDef maps imports and dependencies across a directory.

View Source
var NotabDef = ToolDef{
	Name:        "notab",
	Description: "Normalize whitespace in a file. Default: replaces tabs with spaces. Inverse mode (tabs=true): replaces leading spaces with tabs, for files like Makefiles that require tab indentation.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file": map[string]any{
				"type":        "string",
				"description": "Absolute path to the file to normalize.",
			},
			"spaces": map[string]any{
				"type":        "integer",
				"default":     4,
				"description": "Number of spaces per tab (default: 4).",
			},
			"tabs": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Inverse mode: convert leading spaces to tabs.",
			},
		},
		"required": []string{"file"},
	},
	Builtin: builtinNotab,
	Timeout: 10 * time.Second,
}

NotabDef normalizes whitespace (tabs to spaces or vice versa).

View Source
var ReadDef = ToolDef{
	Name:        "read",
	Description: "Read the contents of a file. Returns the file content with line numbers. Supports reading specific line ranges with offset and limit parameters for large files.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file_path": map[string]any{
				"type":        "string",
				"description": "Absolute or relative path to the file to read.",
			},
			"offset": map[string]any{
				"type":        "integer",
				"description": "Line number to start reading from (1-based). Defaults to 1.",
			},
			"limit": map[string]any{
				"type":        "integer",
				"description": "Maximum number of lines to read. Defaults to 2000.",
			},
		},
		"required": []string{"file_path"},
	},
	Builtin: builtinRead,
	Timeout: 10 * time.Second,
}

ReadDef reads file contents with optional line offset and limit.

View Source
var RepforDef = ToolDef{
	Name:        "repfor",
	Description: "Search and replace strings in files across directories. Supports recursive scanning, extension filtering, case-insensitive search, whole-word matching, dry-run preview, and exclude filters.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"search": map[string]any{
				"type":        "string",
				"description": "String to search for. Use \\n to match literal newlines for multi-line patterns.",
			},
			"replace": map[string]any{
				"type":        "string",
				"description": "String to replace matches with. Use \\n to insert literal newlines.",
			},
			"dir": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of directory paths to search. Defaults to current directory.",
			},
			"file": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of file paths to process. Takes precedence over dir.",
			},
			"ext": map[string]any{
				"type":        "string",
				"description": "File extension to filter (e.g., '.go').",
			},
			"case_insensitive": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Perform case-insensitive search.",
			},
			"whole_word": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Match whole words only.",
			},
			"dry_run": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Preview changes without modifying files.",
			},
			"recursive": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Recursively search subdirectories.",
			},
			"exclude": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Array of strings to exclude from replacement.",
			},
		},
		"required": []string{"search", "replace"},
	},
	Builtin: builtinRepfor,
	Timeout: 60 * time.Second,
}

RepforDef performs search-and-replace across files.

View Source
var SigDef = ToolDef{
	Name:        "sig",
	Description: "Extract the public API surface from a source file. Returns function signatures, type/struct/interface definitions, const/var blocks as compact JSON without implementation bodies. Supports Go, TypeScript, and C#.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file": map[string]any{
				"type":        "string",
				"description": "Absolute path to the source file to analyze.",
			},
			"all": map[string]any{
				"type":        "boolean",
				"default":     false,
				"description": "Include unexported/private symbols.",
			},
		},
		"required": []string{"file"},
	},
	Builtin: builtinSig,
	Timeout: 15 * time.Second,
}

SigDef extracts the public API surface from source files.

View Source
var SpliceDef = ToolDef{
	Name:        "splice",
	Description: "Splice file contents into a target file. Supports four modes: append (after last line), prepend (before first line), replace (overwrite target), and insert (at line N).",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"source": map[string]any{
				"type":        "string",
				"description": "Absolute path to the file to read content from.",
			},
			"target": map[string]any{
				"type":        "string",
				"description": "Absolute path to the file to write content to.",
			},
			"mode": map[string]any{
				"type":        "string",
				"enum":        []string{"append", "prepend", "replace", "insert"},
				"description": "Operation mode.",
			},
			"line": map[string]any{
				"type":        "integer",
				"description": "Line number for insert mode. Content is inserted after this line.",
			},
		},
		"required": []string{"source", "target", "mode"},
	},
	Builtin: builtinSplice,
	Timeout: 10 * time.Second,
}

SpliceDef inserts file contents into a target file.

View Source
var SplitDef = ToolDef{
	Name:        "split",
	Description: "Split a file into multiple parts at specified line numbers. Output files are named with sequential suffixes (e.g., file_001.txt, file_002.txt).",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file": map[string]any{
				"type":        "string",
				"description": "Absolute path to the file to split.",
			},
			"lines": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "integer"},
				"description": "Array of line numbers to split at. Each number marks the last line of a part.",
			},
		},
		"required": []string{"file", "lines"},
	},
	Builtin: builtinSplit,
	Timeout: 30 * time.Second,
}

SplitDef splits a file into multiple parts at specified line numbers.

View Source
var StumpDef = ToolDef{
	Name:        "stump",
	Description: "Token-efficient directory tree visualization. Shows directory structure with optional depth limits, extension filtering, size display, and hidden file inclusion.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"dir": map[string]any{
				"type":        "string",
				"description": "Root directory to scan.",
			},
			"depth": map[string]any{
				"type":        "integer",
				"description": "Max traversal depth (-1 for unlimited).",
			},
			"include_ext": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Only include files with these extensions.",
			},
			"exclude_ext": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Exclude files with these extensions.",
			},
			"exclude_patterns": map[string]any{
				"type":        "array",
				"items":       map[string]any{"type": "string"},
				"description": "Exclude paths matching these patterns.",
			},
			"show_size": map[string]any{
				"type":        "boolean",
				"description": "Include file sizes.",
			},
			"show_hidden": map[string]any{
				"type":        "boolean",
				"description": "Include hidden files and directories.",
			},
		},
		"required": []string{"dir"},
	},
	Builtin: builtinStump,
	Timeout: 15 * time.Second,
}

StumpDef produces token-efficient directory tree visualizations.

View Source
var TabcountDef = ToolDef{
	Name:        "tabcount",
	Description: "Count leading tab characters per line. Returns each line number and its tab depth. Useful for verifying indentation in Go files or diagnosing edit failures due to indentation mismatch.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file": map[string]any{
				"type":        "string",
				"description": "Absolute path to the file to analyze.",
			},
			"start_line": map[string]any{
				"type":        "integer",
				"default":     0,
				"description": "Start line number (1-based). Defaults to beginning of file.",
			},
			"end_line": map[string]any{
				"type":        "integer",
				"default":     0,
				"description": "End line number (1-based, inclusive). Defaults to end of file.",
			},
		},
		"required": []string{"file"},
	},
	Builtin: builtinTabcount,
	Timeout: 10 * time.Second,
}

TabcountDef counts leading tab characters per line in a file.

View Source
var TransformDef = ToolDef{
	Name:        "transform",
	Description: "Composable JSON data pipeline. Takes a JSON array (from exec command or file) and runs it through a sequence of operations: group_by, sort_by, filter, count, flatten, format.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"exec": map[string]any{
				"type":        "string",
				"description": "Shell command whose stdout is a JSON array. Use exec OR file, not both.",
			},
			"file": map[string]any{
				"type":        "string",
				"description": "Absolute path to a JSON file containing an array. Use file OR exec, not both.",
			},
			"pipeline": map[string]any{
				"type":        "array",
				"description": "Ordered sequence of operations. Each element: {\"op\": \"<name>\", ...args}.",
			},
		},
		"required": []string{"pipeline"},
	},
	Builtin: builtinTransform,
	Timeout: 30 * time.Second,
}

TransformDef runs composable operations on JSON arrays.

View Source
var UTF8Def = ToolDef{
	Name:        "utf8",
	Description: "Detect and fix corrupted UTF-16 files by converting to clean UTF-8 in-place. Handles null-laced ASCII, missing BOMs, mixed encoding, and unpaired surrogates.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file": map[string]any{
				"type":        "string",
				"description": "Absolute path to the file to fix.",
			},
			"backup": map[string]any{
				"type":        "boolean",
				"default":     true,
				"description": "Create a .bak backup before modifying.",
			},
		},
		"required": []string{"file"},
	},
	Builtin: builtinUTF8,
	Timeout: 10 * time.Second,
}

UTF8Def detects and fixes corrupted UTF-16 files.

View Source
var WriteDef = ToolDef{
	Name:        "write",
	Description: "Write content to a file. Creates the file if it doesn't exist, or overwrites it if it does. Parent directories are created automatically.",
	InputSchema: map[string]any{
		"type": "object",
		"properties": map[string]any{
			"file_path": map[string]any{
				"type":        "string",
				"description": "Absolute or relative path to the file to write.",
			},
			"content": map[string]any{
				"type":        "string",
				"description": "The content to write to the file.",
			},
		},
		"required": []string{"file_path", "content"},
	},
	Builtin: builtinWrite,
	Timeout: 10 * time.Second,
}

WriteDef writes content to a file, creating parent directories as needed.

Functions

func AtomicWrite

func AtomicWrite(path string, content []byte) error

AtomicWrite writes content to a file via temp+rename for crash safety. Preserves the original file's permissions if it exists.

func ContextWithReadTracker

func ContextWithReadTracker(ctx context.Context, rt *ReadTracker) context.Context

ContextWithReadTracker returns a new context carrying the ReadTracker.

func WalkFiles

func WalkFiles(opts WalkOptions) ([]string, error)

WalkFiles yields file paths matching the options. If Files is non-empty, those are returned directly (filtered by Ext). Otherwise, Dirs are scanned.

Types

type APITool

type APITool struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	InputSchema map[string]any `json:"input_schema"`
}

APITool is a generic tool representation for API requests.

type AfterFunc

type AfterFunc func(ctx context.Context, call ToolCall, result ToolResult, elapsed time.Duration)

AfterFunc is called after a tool executes with elapsed time.

type BeforeFunc

type BeforeFunc func(ctx context.Context, call ToolCall)

BeforeFunc is called before a tool executes.

type BuiltinFunc

type BuiltinFunc func(ctx context.Context, input map[string]any, workDir string) Result

BuiltinFunc is the signature for tools that execute in-process rather than spawning a subprocess. The workDir parameter is the executor's working directory.

type CSExtractor

type CSExtractor struct{}

func (*CSExtractor) Extensions

func (e *CSExtractor) Extensions() []string

func (*CSExtractor) Extract

func (e *CSExtractor) Extract(filePath string, exportedOnly bool) (*FileShape, error)

type Executor

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

Executor runs tool binaries as subprocesses, mapping JSON input parameters to CLI flags via the tool's FlagMap.

func NewExecutor

func NewExecutor(registry *Registry, workDir string) *Executor

NewExecutor creates an executor bound to a registry and working directory. The executor owns a ReadTracker that enforces read-before-write safety.

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, name string, input map[string]any) Result

Execute runs a tool by name with the given input parameters. Builtin tools execute in-process; subprocess tools are invoked via exec.Command.

func (*Executor) ExecuteJSON

func (e *Executor) ExecuteJSON(ctx context.Context, name string, rawInput json.RawMessage) Result

ExecuteJSON runs a tool using raw JSON input — the format LLM APIs return in tool_use blocks. It unmarshals the JSON into map[string]any and dispatches.

func (*Executor) ReadTracker

func (e *Executor) ReadTracker() *ReadTracker

ReadTracker returns the executor's read tracker for inspection/testing.

type FieldDef

type FieldDef struct {
	Name string `json:"name,omitempty"`
	Type string `json:"type"`
	Tag  string `json:"tag,omitempty"`
}

FieldDef describes a struct field.

type FileShape

type FileShape struct {
	File      string     `json:"file"`
	Package   string     `json:"package"`
	Imports   []string   `json:"imports,omitempty"`
	Types     []TypeDef  `json:"types,omitempty"`
	Functions []FuncDef  `json:"functions,omitempty"`
	Constants []ValueDef `json:"constants,omitempty"`
	Variables []ValueDef `json:"variables,omitempty"`
}

FileShape is the top-level output.

type FilterFunc

type FilterFunc func(def ToolDef) bool

FilterFunc decides whether a ToolDef should be included in a filtered registry. Return true to include, false to exclude.

func ExcludeDangerous

func ExcludeDangerous() FilterFunc

ExcludeDangerous returns a FilterFunc that removes tools capable of writing, deleting, or executing arbitrary commands.

func ExcludeNames

func ExcludeNames(names ...string) FilterFunc

ExcludeNames returns a FilterFunc that excludes tools with any of the given names.

func IncludeNames

func IncludeNames(names ...string) FilterFunc

IncludeNames returns a FilterFunc that includes only tools with one of the given names.

func ReadOnlyTools

func ReadOnlyTools() FilterFunc

ReadOnlyTools returns a FilterFunc that keeps only read-only, non-destructive tools.

type FlagSpec

type FlagSpec struct {
	// Flag is the CLI flag name (e.g. "--file", "--dir").
	Flag string

	// Type controls serialization: "string", "bool", "int", "array".
	Type string

	// Positional appends the value as a positional arg without a flag prefix.
	Positional bool
}

FlagSpec describes how a single input parameter maps to a CLI flag.

type FuncDef

type FuncDef struct {
	Name      string `json:"name"`
	Receiver  string `json:"receiver,omitempty"`
	Signature string `json:"signature"`
	Line      int    `json:"line"`
}

FuncDef describes a function or method.

type Middleware

type Middleware func(ctx context.Context, call ToolCall, next func(context.Context, ToolCall) ToolResult) ToolResult

Middleware intercepts tool calls before execution. Call next to continue the chain, or return directly to short-circuit.

func DenyListMiddleware

func DenyListMiddleware(names ...string) Middleware

DenyListMiddleware returns middleware that blocks calls to the named tools, returning an error result without executing.

func LogMiddleware

func LogMiddleware(before BeforeFunc, after AfterFunc) Middleware

LogMiddleware returns middleware that calls hooks before and after each tool execution. Either hook may be nil.

type ReadTracker

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

ReadTracker records the mtime of files at the moment they were read. Before a write, the tracker checks whether the file has been modified since the last read — preventing blind overwrites of changed files.

Rules:

  • New files (don't exist on disk) can be written without a prior read.
  • Existing files must be read before writing.
  • If a file was modified after the last read, the write is rejected until the file is re-read.

func NewReadTracker

func NewReadTracker() *ReadTracker

NewReadTracker creates an empty tracker.

func ReadTrackerFromContext

func ReadTrackerFromContext(ctx context.Context) *ReadTracker

ReadTrackerFromContext extracts the ReadTracker from context, or nil if absent.

func (*ReadTracker) CheckWrite

func (rt *ReadTracker) CheckWrite(path string) error

CheckWrite validates that a write to path is safe. Returns nil if the write is allowed, or an error describing why it's blocked.

A write is allowed when:

  • The file does not exist (new file creation).
  • The file was previously read and has not been modified since.

A write is blocked when:

  • The file exists but was never read.
  • The file exists and was modified after the last read.

func (*ReadTracker) Clear

func (rt *ReadTracker) Clear()

Clear removes all read records.

func (*ReadTracker) Len

func (rt *ReadTracker) Len() int

Len returns the number of tracked files.

func (*ReadTracker) RecordRead

func (rt *ReadTracker) RecordRead(path string) error

RecordRead stores the file's current mtime as the last-read timestamp. Call this after a successful file read.

func (*ReadTracker) RecordWrite

func (rt *ReadTracker) RecordWrite(path string) error

RecordWrite updates the tracker after a successful write so that subsequent writes to the same file don't require another read (the write itself constitutes knowledge of the file's state).

func (*ReadTracker) WasRead

func (rt *ReadTracker) WasRead(path string) bool

WasRead returns true if the file has been read at least once.

type Registry

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

Registry holds tool definitions and converts them to API-ready format. All methods are safe for concurrent use via sync.RWMutex.

func DefaultRegistry

func DefaultRegistry() *Registry

DefaultRegistry creates a registry with all builtin tools registered.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates an empty tool registry.

func TerseRegistry

func TerseRegistry() *Registry

TerseRegistry creates a registry with only the 15 terse tools (no read/write/bash). Use this when your agent framework already provides its own file I/O and shell.

func ToolRegistry

func ToolRegistry(toolSets ...[]ToolDef) *Registry

ToolRegistry creates a registry from one or more tool definition slices.

reg := tools.ToolRegistry(tools.CoreTools(), tools.TerseTools())

func (*Registry) APITools

func (r *Registry) APITools() []APITool

APITools converts all registered tool definitions to APITool slices suitable for inclusion in API requests.

func (*Registry) CheckBinaries

func (r *Registry) CheckBinaries() map[string]error

CheckBinaries verifies that each registered subprocess tool's binary is on PATH. Builtin tools are skipped. Returns a map of tool name to error for any missing binaries.

func (*Registry) Defs

func (r *Registry) Defs() []ToolDef

Defs returns all registered ToolDef values in insertion order.

func (*Registry) Filter

func (r *Registry) Filter(fn FilterFunc) *Registry

Filter returns a new Registry containing only tools that pass fn. The original registry is not modified.

func (*Registry) Get

func (r *Registry) Get(name string) (ToolDef, bool)

Get returns a tool definition by name.

func (*Registry) Len

func (r *Registry) Len() int

Len returns the number of registered tools.

func (*Registry) Names

func (r *Registry) Names() []string

Names returns all registered tool names in sorted order.

func (*Registry) Register

func (r *Registry) Register(def ToolDef)

Register adds a tool definition to the registry. If a tool with the same name exists, it is replaced.

func (*Registry) Remove

func (r *Registry) Remove(name string)

Remove removes a tool from the registry.

type Result

type Result struct {
	// Output is the tool's stdout content (JSON).
	Output string

	// IsError is true when the tool exited non-zero or timed out.
	IsError bool

	// Error contains stderr content on failure, or a timeout message.
	Error string
}

Result holds the output of a tool execution.

func (Result) Content

func (r Result) Content() string

Content returns the text that should be sent back to the LLM. On success it returns Output; on error it returns Error.

type TSExtractor

type TSExtractor struct{}

func (*TSExtractor) Extensions

func (e *TSExtractor) Extensions() []string

func (*TSExtractor) Extract

func (e *TSExtractor) Extract(filePath string, exportedOnly bool) (*FileShape, error)

type ToolCall

type ToolCall struct {
	// ID is the round-trip correlation ID from the provider.
	// Empty for providers that don't use call IDs (e.g. Gemini).
	ID string

	// Name is the tool name (must match a registered ToolDef.Name).
	Name string

	// Input is the raw JSON arguments from the LLM.
	Input json.RawMessage
}

ToolCall is the provider-agnostic representation of a tool invocation from an LLM. Each provider adapter converts SDK-specific types to/from ToolCall.

type ToolDef

type ToolDef struct {
	// Name is the tool name (e.g. "checkfor").
	Name string

	// Description is shown to the LLM to help it decide when to use the tool.
	Description string

	// InputSchema is the JSON Schema for the tool's parameters.
	InputSchema map[string]any

	// Builtin, when non-nil, means this tool runs in-process instead of
	// spawning a subprocess. Binary/FlagMap/NeedsCLI/StdinParam are ignored.
	Builtin BuiltinFunc

	// Binary is the executable name resolved via PATH (e.g. "checkfor", "stump-core").
	// Empty for builtin tools.
	Binary string

	// NeedsCLI prepends --cli to the argument list when true.
	NeedsCLI bool

	// FlagMap maps input parameter names to CLI flag specifications.
	FlagMap map[string]FlagSpec

	// StdinParam names the input parameter whose value is piped to stdin
	// instead of passed as a flag. Empty means no stdin piping.
	StdinParam string

	// Timeout is the per-invocation timeout. Zero uses the executor's default.
	Timeout time.Duration
}

ToolDef defines a tool that can be invoked during agentic conversations. A tool is either a subprocess tool (Binary set) or a builtin (Builtin set).

func BuiltinTools

func BuiltinTools() []ToolDef

BuiltinTools returns all tools: core (read/write/bash) + terse tools.

func CoreTools

func CoreTools() []ToolDef

CoreTools returns the agent-infrastructure tools: read, write, bash. These provide basic file I/O and shell access for the LLM.

func TerseTools

func TerseTools() []ToolDef

TerseTools returns the 15 domain-specific terse tools. These are the primary tools for agentic workflows: search, replace, diff, parse, transform, and file manipulation.

func (ToolDef) IsBuiltinTool

func (d ToolDef) IsBuiltinTool() bool

IsBuiltinTool returns true if this tool executes in-process.

type ToolResult

type ToolResult struct {
	// ID is echoed from the originating ToolCall.ID.
	ID string

	// Name is echoed from the originating ToolCall.Name.
	Name string

	// Content is the text output sent back to the LLM.
	Content string

	// IsError indicates the tool execution failed.
	IsError bool
}

ToolResult is the provider-agnostic execution result. Each provider adapter converts ToolResult to its SDK-specific result type.

type ToolRunner

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

ToolRunner wraps an Executor with a middleware chain.

func NewToolRunner

func NewToolRunner(executor *Executor) *ToolRunner

NewToolRunner creates a ToolRunner that dispatches calls through the given Executor.

func (*ToolRunner) Executor

func (r *ToolRunner) Executor() *Executor

Executor returns the underlying Executor for direct access.

func (*ToolRunner) Run

func (r *ToolRunner) Run(ctx context.Context, call ToolCall) ToolResult

Run executes a ToolCall through the middleware chain, then dispatches to the Executor. Returns a ToolResult with the call's ID and Name echoed back.

func (*ToolRunner) Use

func (r *ToolRunner) Use(mw ...Middleware)

Use appends one or more middleware to the chain. Middleware execute in the order they are added (first added = outermost).

type TypeDef

type TypeDef struct {
	Name       string     `json:"name"`
	Kind       string     `json:"kind"` // struct, interface, named, alias
	Line       int        `json:"line"`
	Fields     []FieldDef `json:"fields,omitempty"`
	Methods    []FuncDef  `json:"methods,omitempty"`
	Embeds     []string   `json:"embeds,omitempty"`
	Underlying string     `json:"underlying,omitempty"`
}

TypeDef describes a type declaration.

type ValueDef

type ValueDef struct {
	Name  string `json:"name"`
	Type  string `json:"type,omitempty"`
	Value string `json:"value,omitempty"`
	Line  int    `json:"line"`
}

ValueDef describes a const or var.

type WalkOptions

type WalkOptions struct {
	Dirs       []string // directories to scan
	Files      []string // specific files (bypass directory scan)
	Ext        string   // extension filter (e.g. ".go")
	Recursive  bool     // recurse into subdirectories
	SkipHidden bool     // skip dotfiles/dotdirs
}

WalkOptions controls directory traversal for file discovery.

Jump to

Keyboard shortcuts

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