tools

package
v1.14.0 Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2026 License: MIT Imports: 34 Imported by: 0

README

Tool System Architecture

This package implements the tool execution system for Hex, providing a clean abstraction for executing tools (Read, Write, Bash, etc.) with permission management and API integration.

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                        Tool System                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌──────────────┐      ┌──────────────┐                    │
│  │   Registry   │◄─────┤   Executor   │                    │
│  │              │      │              │                    │
│  │  - Register  │      │  - Execute   │                    │
│  │  - Get       │      │  - Approval  │                    │
│  │  - List      │      └──────┬───────┘                    │
│  └──────┬───────┘             │                            │
│         │                     │                            │
│         │ manages             │ uses                       │
│         │                     │                            │
│         ▼                     ▼                            │
│  ┌─────────────────────────────────────┐                   │
│  │          Tool Interface             │                   │
│  ├─────────────────────────────────────┤                   │
│  │  - Name() string                    │                   │
│  │  - Description() string             │                   │
│  │  - RequiresApproval(params) bool    │                   │
│  │  - Execute(ctx, params) Result      │                   │
│  └─────────────────────────────────────┘                   │
│                      ▲                                      │
│                      │ implements                          │
│         ┌────────────┴────────────┬───────────┐            │
│         │                         │           │            │
│  ┌──────┴──────┐          ┌──────┴──────┐   ┌─┴─────┐     │
│  │  ReadTool   │          │ WriteTool   │   │ Bash  │     │
│  │             │          │             │   │ Tool  │     │
│  │ (Phase 2-9) │          │ (Phase 2-10)│   │(2-11) │     │
│  └─────────────┘          └─────────────┘   └───────┘     │
│                                                             │
└─────────────────────────────────────────────────────────────┘
                            │
                            │ converts to/from
                            ▼
                ┌────────────────────────┐
                │   API Integration      │
                ├────────────────────────┤
                │  ToolUse    (request)  │
                │  ToolResult (response) │
                └────────────────────────┘

Core Components

1. Tool Interface

The Tool interface defines the contract that all tools must implement:

type Tool interface {
    Name() string
    Description() string
    RequiresApproval(params map[string]interface{}) bool
    Execute(ctx context.Context, params map[string]interface{}) (*Result, error)
}

Key Design Decisions:

  • Context-aware: All tools receive a context for cancellation and timeout support
  • Dynamic approval: Tools decide at runtime if they need approval based on parameters
  • Generic parameters: Uses map[string]interface{} for flexibility with different tool types
  • Structured results: Returns Result with success/failure, output, error, and metadata
2. Registry

Thread-safe storage for available tools. Provides:

  • Registration: Add tools to the registry
  • Retrieval: Get tools by name
  • Listing: Get all registered tool names (sorted alphabetically)
  • Thread Safety: Uses sync.RWMutex for concurrent access

Design Pattern: Singleton-style registry that persists for the application lifetime.

3. Executor

Manages tool execution with permission checking:

type Executor struct {
    registry     *Registry
    approvalFunc ApprovalFunc
}

Execution Flow:

  1. Retrieve tool from registry
  2. Check if approval is needed (via RequiresApproval)
  3. If needed, call approvalFunc and block if denied
  4. Execute tool with context and parameters
  5. Return result or error

Permission System:

  • ApprovalFunc: func(toolName string, params map[string]interface{}) bool
  • Called only when RequiresApproval(params) returns true
  • If nil, no approval checks are performed (useful for testing)
  • If returns false, tool is not executed (permission denied)
4. Result

Represents tool execution outcome:

type Result struct {
    ToolName string                 // Tool that was executed
    Success  bool                   // Did it succeed?
    Output   string                 // Standard output/result
    Error    string                 // Error message if failed
    Metadata map[string]interface{} // Additional metadata
}

Design Philosophy:

  • Clear success/failure indication
  • Separate Output and Error fields (only one populated)
  • Extensible Metadata for tool-specific information (exit codes, file paths, etc.)
5. API Integration Types

Maps between internal execution and Anthropic API format:

ToolUse (API → Internal):

type ToolUse struct {
    Type  string                 `json:"type"`  // "tool_use"
    ID    string                 `json:"id"`    // Unique ID
    Name  string                 `json:"name"`  // Tool name
    Input map[string]interface{} `json:"input"` // Parameters
}

ToolResult (Internal → API):

type ToolResult struct {
    Type      string `json:"type"`        // "tool_result"
    ToolUseID string `json:"tool_use_id"` // Original ID
    Content   string `json:"content"`     // Output or error
    IsError   bool   `json:"is_error"`    // Error flag
}

Conversion: ResultToToolResult(result *Result, toolUseID string) ToolResult

Usage Examples

Basic Tool Registration and Execution
// Create registry
registry := tools.NewRegistry()

// Register a tool
tool := &MyTool{}
registry.Register(tool)

// Create executor with approval
executor := tools.NewExecutor(registry, func(name string, params map[string]interface{}) bool {
    // Ask user for approval
    return askUser(name, params)
})

// Execute tool
result, err := executor.Execute(context.Background(), "my_tool", map[string]interface{}{
    "param1": "value1",
})
Implementing a Custom Tool
type MyTool struct{}

func (t *MyTool) Name() string {
    return "my_tool"
}

func (t *MyTool) Description() string {
    return "Does something useful"
}

func (t *MyTool) RequiresApproval(params map[string]interface{}) bool {
    // Check if this operation is dangerous based on params
    path := params["path"].(string)
    return strings.HasPrefix(path, "/etc")
}

func (t *MyTool) Execute(ctx context.Context, params map[string]interface{}) (*tools.Result, error) {
    // Do the work
    output, err := doWork(params)
    if err != nil {
        return &tools.Result{
            ToolName: "my_tool",
            Success:  false,
            Error:    err.Error(),
        }, nil
    }

    return &tools.Result{
        ToolName: "my_tool",
        Success:  true,
        Output:   output,
        Metadata: map[string]interface{}{
            "path": params["path"],
        },
    }, nil
}
Handling API Integration
// Receive from API
var toolUse tools.ToolUse
json.Unmarshal(apiData, &toolUse)

// Execute
result, err := executor.Execute(ctx, toolUse.Name, toolUse.Input)
if err != nil {
    // Handle error
}

// Convert back to API format
toolResult := tools.ResultToToolResult(result, toolUse.ID)
apiResponse, _ := json.Marshal(toolResult)

Testing

Unit Testing

Each component has comprehensive unit tests:

  • tool_test.go: Tool interface and mock implementations
  • registry_test.go: Registration, retrieval, thread safety
  • executor_test.go: Execution flow, approval, error handling
  • result_test.go: Result structures and metadata
  • types_test.go: API integration types and JSON marshaling

Coverage: 100% statement coverage

Mock Tool

The MockTool type provides configurable behavior for testing:

mock := &tools.MockTool{
    NameValue: "test_tool",
    RequiresApprovalValue: true,
    ExecuteFunc: func(ctx context.Context, params map[string]interface{}) (*tools.Result, error) {
        // Custom behavior
        return &tools.Result{Success: true, Output: "test"}, nil
    },
}

Thread Safety

  • Registry: Uses sync.RWMutex for concurrent access

    • Multiple readers can access simultaneously
    • Writers get exclusive access
    • Tested with concurrent registration and retrieval
  • Executor: Stateless, safe for concurrent use

    • Each execution is independent
    • Context propagation for cancellation

Error Handling

Two levels of errors:

  1. Execution errors (returned as error):

    • Tool not found in registry
    • Catastrophic failures during tool execution
    • Context cancellation
  2. Tool errors (in Result.Error):

    • Expected failures (file not found, permission denied, etc.)
    • User-facing error messages
    • Tool still executed, but operation failed

Philosophy: Distinguish between "the tool ran but failed" vs "we couldn't run the tool."

Next Steps (Phase 2 Tasks 9-11)

This architecture provides the foundation for:

  • Task 9: Read Tool - File reading with safety checks
  • Task 10: Write Tool - File writing with confirmation
  • Task 11: Bash Tool - Sandboxed command execution

Each will implement the Tool interface and integrate with this system.

Design Principles

  1. Separation of Concerns: Registry, execution, and approval are separate
  2. Testability: Clean interfaces, mockable components
  3. Extensibility: Easy to add new tool types
  4. Safety: Permission system prevents dangerous operations
  5. Thread Safety: Concurrent tool execution support
  6. API Integration: Clean mapping to/from Anthropic API format

Files

  • tool.go: Tool interface definition
  • registry.go: Tool registry implementation
  • executor.go: Tool executor with approval
  • result.go: Result structures
  • types.go: API integration types
  • mock_tool.go: Testing utilities
  • *_test.go: Comprehensive test coverage
  • example_test.go: Usage examples
  • README.md: This documentation

Documentation

Overview

Package tools provides the tool system for extending Hex with external capabilities. ABOUTME: AskUserQuestion tool for interactive decision-making ABOUTME: Presents multiple-choice questions to users and collects answers

Example

Example demonstrates the complete tool system workflow

package main

import (
	"context"
	"fmt"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	// 1. Create a registry
	registry := tools.NewRegistry()

	// 2. Create and register a safe tool (no approval needed)
	safeTool := &tools.MockTool{
		NameValue:             "echo",
		DescriptionValue:      "Echoes back the input",
		RequiresApprovalValue: false,
		ExecuteFunc: func(_ context.Context, params map[string]interface{}) (*tools.Result, error) {
			message := params["message"].(string)
			return &tools.Result{
				ToolName: "echo",
				Success:  true,
				Output:   message,
			}, nil
		},
	}
	_ = registry.Register(safeTool)

	// 3. Create and register a dangerous tool (requires approval)
	dangerousTool := &tools.MockTool{
		NameValue:             "delete_file",
		DescriptionValue:      "Deletes a file",
		RequiresApprovalValue: true,
		ExecuteFunc: func(_ context.Context, params map[string]interface{}) (*tools.Result, error) {
			path := params["path"].(string)
			return &tools.Result{
				ToolName: "delete_file",
				Success:  true,
				Output:   fmt.Sprintf("Deleted %s", path),
			}, nil
		},
	}
	_ = registry.Register(dangerousTool)

	// 4. Create an executor with approval function
	approvalFunc := func(toolName string, params map[string]interface{}) bool {
		// In a real application, this would prompt the user
		// For this example, we'll approve delete operations on /tmp
		if toolName == "delete_file" {
			path := params["path"].(string)
			return path == "/tmp/test.txt"
		}
		return true
	}
	executor := tools.NewExecutor(registry, approvalFunc)

	// 5. Execute the safe tool (no approval needed)
	ctx := context.Background()
	result1, _ := executor.Execute(ctx, "echo", map[string]interface{}{
		"message": "Hello, World!",
	})
	fmt.Printf("Echo result: %s\n", result1.Output)

	// 6. Execute dangerous tool with approved path
	result2, _ := executor.Execute(ctx, "delete_file", map[string]interface{}{
		"path": "/tmp/test.txt",
	})
	fmt.Printf("Delete result (approved): %s\n", result2.Output)

	// 7. Execute dangerous tool with denied path
	result3, _ := executor.Execute(ctx, "delete_file", map[string]interface{}{
		"path": "/etc/passwd",
	})
	fmt.Printf("Delete result (denied): success=%v, error=%s\n", result3.Success, result3.Error)

	// 8. List all registered tools
	tools := registry.List()
	fmt.Printf("Registered tools: %v\n", tools)

}
Output:
Echo result: Hello, World!
Delete result (approved): Deleted /tmp/test.txt
Delete result (denied): success=false, error=user denied permission
Registered tools: [delete_file echo]
Example (CompleteWorkflow)

Example_completeWorkflow demonstrates a complete tool system workflow

package main

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	// Simulate a simple file system tool
	type FileSystemTool struct {
		name string
	}

	readTool := &FileSystemTool{name: "read_file"}
	writeTool := &FileSystemTool{name: "write_file"}

	// Implement Tool interface for FileSystemTool
	getName := func(t *FileSystemTool) string { return t.name }
	getDesc := func(t *FileSystemTool) string { return fmt.Sprintf("Tool: %s", t.name) }
	requiresApproval := func(t *FileSystemTool, _ map[string]interface{}) bool {
		// Write operations require approval
		return t.name == "write_file"
	}
	execute := func(t *FileSystemTool, _ context.Context, params map[string]interface{}) (*tools.Result, error) {
		path := params["path"].(string)
		if t.name == "read_file" {
			return &tools.Result{
				ToolName: t.name,
				Success:  true,
				Output:   fmt.Sprintf("Contents of %s", path),
				Metadata: map[string]interface{}{"path": path},
			}, nil
		}
		// write_file
		content := params["content"].(string)
		return &tools.Result{
			ToolName: t.name,
			Success:  true,
			Output:   fmt.Sprintf("Wrote %d bytes to %s", len(content), path),
			Metadata: map[string]interface{}{"path": path, "bytes": len(content)},
		}, nil
	}

	// Wrap in MockTool for demonstration
	readMock := &tools.MockTool{
		NameValue:        getName(readTool),
		DescriptionValue: getDesc(readTool),
		ExecuteFunc: func(ctx context.Context, params map[string]interface{}) (*tools.Result, error) {
			return execute(readTool, ctx, params)
		},
	}

	writeMock := &tools.MockTool{
		NameValue:             getName(writeTool),
		DescriptionValue:      getDesc(writeTool),
		RequiresApprovalValue: requiresApproval(writeTool, nil),
		ExecuteFunc: func(ctx context.Context, params map[string]interface{}) (*tools.Result, error) {
			return execute(writeTool, ctx, params)
		},
	}

	// 1. Setup: Create registry and register tools
	registry := tools.NewRegistry()
	_ = registry.Register(readMock)
	_ = registry.Register(writeMock)

	// 2. Create executor with approval logic
	approvalFunc := func(toolName string, params map[string]interface{}) bool {
		// Approve writes to /tmp, deny others
		if toolName == "write_file" {
			path := params["path"].(string)
			return strings.HasPrefix(path, "/tmp/")
		}
		return true
	}

	executor := tools.NewExecutor(registry, approvalFunc)

	// 3. Simulate API request (read_file)
	apiRequest1 := `{
		"type": "tool_use",
		"id": "toolu_read_123",
		"name": "read_file",
		"input": {
			"path": "/tmp/config.json"
		}
	}`

	var toolUse1 tools.ToolUse
	_ = json.Unmarshal([]byte(apiRequest1), &toolUse1)

	// 4. Execute tool
	ctx := context.Background()
	result1, _ := executor.Execute(ctx, toolUse1.Name, toolUse1.Input)

	// 5. Convert to API response
	toolResult1 := tools.ResultToToolResult(result1, toolUse1.ID)
	fmt.Printf("Read operation: success=%v\n", !toolResult1.IsError)

	// 6. Simulate API request (write_file with approval)
	apiRequest2 := `{
		"type": "tool_use",
		"id": "toolu_write_456",
		"name": "write_file",
		"input": {
			"path": "/tmp/output.txt",
			"content": "Hello, World!"
		}
	}`

	var toolUse2 tools.ToolUse
	_ = json.Unmarshal([]byte(apiRequest2), &toolUse2)

	result2, _ := executor.Execute(ctx, toolUse2.Name, toolUse2.Input)
	toolResult2 := tools.ResultToToolResult(result2, toolUse2.ID)
	fmt.Printf("Write operation (approved): success=%v\n", !toolResult2.IsError)

	// 7. Simulate denied write operation
	apiRequest3 := `{
		"type": "tool_use",
		"id": "toolu_write_789",
		"name": "write_file",
		"input": {
			"path": "/etc/passwd",
			"content": "malicious"
		}
	}`

	var toolUse3 tools.ToolUse
	_ = json.Unmarshal([]byte(apiRequest3), &toolUse3)

	result3, _ := executor.Execute(ctx, toolUse3.Name, toolUse3.Input)
	toolResult3 := tools.ResultToToolResult(result3, toolUse3.ID)
	fmt.Printf("Write operation (denied): success=%v\n", !toolResult3.IsError)

	// 8. List all available tools
	availableTools := registry.List()
	fmt.Printf("Available tools: %v\n", availableTools)

}
Output:
Read operation: success=true
Write operation (approved): success=true
Write operation (denied): success=false
Available tools: [read_file write_file]

Index

Examples

Constants

View Source
const (
	// DefaultCommandTimeout is the default timeout for command execution
	DefaultCommandTimeout = 30 * time.Second

	// MaxCommandTimeout is the maximum allowed timeout (5 minutes)
	MaxCommandTimeout = 5 * time.Minute

	// MaxOutputSize is the maximum combined output size (1MB)
	MaxOutputSize = 1024 * 1024
)
View Source
const (
	// DefaultTaskTimeout is the default timeout for task execution
	DefaultTaskTimeout = 5 * time.Minute

	// MaxTaskTimeout is the maximum allowed timeout (30 minutes)
	MaxTaskTimeout = 30 * time.Minute

	// DefaultMaxAgentDepth is the default maximum recursion depth for sub-agents
	DefaultMaxAgentDepth = 5
)
View Source
const (
	// DefaultMaxContentSize is the default maximum content size to write (10MB)
	DefaultMaxContentSize = 10 * 1024 * 1024

	// ModeCreate creates a new file, failing if it already exists
	ModeCreate = "create"
	// ModeOverwrite overwrites an existing file or creates a new one
	ModeOverwrite = "overwrite"
	// ModeAppend appends to an existing file or creates a new one
	ModeAppend = "append"
)
View Source
const (
	// DefaultMaxFileSize is the default maximum file size to read (1MB)
	DefaultMaxFileSize = 1024 * 1024
)

Variables

This section is empty.

Functions

func GetBackgroundProcess

func GetBackgroundProcess(shellID string) *os.Process

GetBackgroundProcess retrieves a process from the global registry (legacy)

func GetToolSchema added in v1.7.0

func GetToolSchema(toolName string) map[string]interface{}

GetToolSchema returns the JSON Schema for a specific tool's input parameters. Exported for use by the mux adapter to implement SchemaProvider.

func IsCacheable added in v1.6.0

func IsCacheable(toolName string) bool

IsCacheable determines if a tool's results should be cached

func ListBackgroundProcesses

func ListBackgroundProcesses() []string

ListBackgroundProcesses returns a list of all registered shell IDs (legacy)

func RegisterBackgroundProcess

func RegisterBackgroundProcess(shellID string, process *os.Process)

RegisterBackgroundProcess adds a process to the global registry (legacy)

func UnregisterBackgroundProcess

func UnregisterBackgroundProcess(shellID string)

UnregisterBackgroundProcess removes a process from the global registry (legacy)

Types

type ApprovalFunc

type ApprovalFunc func(toolName string, params map[string]interface{}) bool

ApprovalFunc is called when a tool needs user approval Returns true if approved, false if denied

type AskUserQuestionTool

type AskUserQuestionTool struct{}

AskUserQuestionTool prompts users with multiple-choice questions

func (*AskUserQuestionTool) Description

func (t *AskUserQuestionTool) Description() string

Description returns what the tool does

func (*AskUserQuestionTool) Execute

func (t *AskUserQuestionTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute runs the tool with the given parameters

func (*AskUserQuestionTool) Name

func (t *AskUserQuestionTool) Name() string

Name returns the tool's identifier

func (*AskUserQuestionTool) RequiresApproval

func (t *AskUserQuestionTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval returns whether this tool needs user confirmation

type BackgroundProcess

type BackgroundProcess struct {
	ID         string      // Unique identifier for the process
	Command    string      // The command being executed
	StartTime  time.Time   // When the process started
	Stdout     []string    // Lines of stdout output
	Stderr     []string    // Lines of stderr output
	ReadOffset int         // Number of lines already read by BashOutput
	Done       bool        // Whether the process has finished
	ExitCode   int         // Exit code (valid only if Done is true)
	Process    *os.Process // The actual OS process
	// contains filtered or unexported fields
}

BackgroundProcess represents a running or completed background bash process

func (*BackgroundProcess) AppendStderr

func (bp *BackgroundProcess) AppendStderr(line string)

AppendStderr adds a line to stderr in a thread-safe manner

func (*BackgroundProcess) AppendStdout

func (bp *BackgroundProcess) AppendStdout(line string)

AppendStdout adds a line to stdout in a thread-safe manner

func (*BackgroundProcess) GetExitCode

func (bp *BackgroundProcess) GetExitCode() int

GetExitCode returns the exit code (only valid if Done is true)

func (*BackgroundProcess) GetNewOutput

func (bp *BackgroundProcess) GetNewOutput() (stdout []string, stderr []string)

GetNewOutput returns output since the last read and updates the read offset

func (*BackgroundProcess) IsDone

func (bp *BackgroundProcess) IsDone() bool

IsDone returns whether the process has finished

func (*BackgroundProcess) MarkDone

func (bp *BackgroundProcess) MarkDone(exitCode int)

MarkDone marks the process as completed with the given exit code

type BackgroundProcessRegistry

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

BackgroundProcessRegistry stores running background processes

func GetBackgroundRegistry

func GetBackgroundRegistry() *BackgroundProcessRegistry

GetBackgroundRegistry returns the global background process registry

func (*BackgroundProcessRegistry) Get

Get retrieves a background process by ID

func (*BackgroundProcessRegistry) List

func (r *BackgroundProcessRegistry) List() []string

List returns all background process IDs

func (*BackgroundProcessRegistry) Register

Register adds a background process to the registry

func (*BackgroundProcessRegistry) Remove

func (r *BackgroundProcessRegistry) Remove(id string) error

Remove removes a background process from the registry

type BashOutputTool

type BashOutputTool struct{}

BashOutputTool retrieves output from background bash processes

func NewBashOutputTool

func NewBashOutputTool() *BashOutputTool

NewBashOutputTool creates a new bash output tool

func (*BashOutputTool) Description

func (t *BashOutputTool) Description() string

Description returns the tool description

func (*BashOutputTool) Execute

func (t *BashOutputTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute retrieves output from a background process

func (*BashOutputTool) Name

func (t *BashOutputTool) Name() string

Name returns the tool name

func (*BashOutputTool) RequiresApproval

func (t *BashOutputTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval always returns false - this is a read-only tool

type BashTool

type BashTool struct {
	DefaultTimeout time.Duration // Default timeout for commands
	MaxOutputSize  int           // Maximum output size (bytes)
}

BashTool implements shell command execution functionality

Example
// Create a new Bash tool
bashTool := NewBashTool()

// Execute a simple command
result, err := bashTool.Execute(context.Background(), map[string]interface{}{
	"command": "echo 'Hello from Bash tool!'",
})
if err != nil {
	log.Fatal(err)
}

fmt.Println("Success:", result.Success)
fmt.Println("Exit Code:", result.Metadata["exit_code"])
Output:
Success: true
Exit Code: 0
Example (RequiresApproval)
bashTool := NewBashTool()

// ALL bash commands require approval for safety
params := map[string]interface{}{
	"command": "ls /",
}

requiresApproval := bashTool.RequiresApproval(params)
fmt.Println("Requires approval:", requiresApproval)
Output:
Requires approval: true
Example (WithExecutor)
// Create a registry and register the bash tool
registry := NewRegistry()
bashTool := NewBashTool()
_ = registry.Register(bashTool)

// Create an executor with approval function
approvalFunc := func(_ string, params map[string]interface{}) bool {
	// In real use, prompt the user for approval
	command := params["command"].(string)
	fmt.Printf("Approve command '%s'? ", command)
	return true // Auto-approve for demo
}

executor := NewExecutor(registry, approvalFunc)

// Execute the tool through the executor
result, err := executor.Execute(context.Background(), "bash", map[string]interface{}{
	"command": "echo 'Executed via executor'",
})
if err != nil {
	log.Fatal(err)
}

fmt.Println("Success:", result.Success)
Output:
Approve command 'echo 'Executed via executor''? Success: true
Example (WithTimeout)
bashTool := NewBashTool()

// Execute command with custom timeout (2 seconds)
result, err := bashTool.Execute(context.Background(), map[string]interface{}{
	"command": "sleep 1; echo 'Done sleeping'",
	"timeout": float64(2),
})
if err != nil {
	log.Fatal(err)
}

fmt.Println("Success:", result.Success)
Output:
Success: true
Example (WithWorkingDirectory)
bashTool := NewBashTool()

// Execute command in a specific directory
result, err := bashTool.Execute(context.Background(), map[string]interface{}{
	"command":     "pwd",
	"working_dir": "/tmp",
})
if err != nil {
	log.Fatal(err)
}

fmt.Println("Working directory set:", result.Metadata["working_dir"])
Output:
Working directory set: /tmp

func NewBashTool

func NewBashTool() *BashTool

NewBashTool creates a new bash tool with default settings

func (*BashTool) Description

func (t *BashTool) Description() string

Description returns the tool description

func (*BashTool) Execute

func (t *BashTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute runs a shell command and returns its output

func (*BashTool) Name

func (t *BashTool) Name() string

Name returns the tool name

func (*BashTool) RequiresApproval

func (t *BashTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval always returns true for command execution

type CacheEntry added in v1.6.0

type CacheEntry struct {
	Result    *Result
	ExpiresAt time.Time
}

CacheEntry represents a cached tool result

func (*CacheEntry) IsExpired added in v1.6.0

func (e *CacheEntry) IsExpired() bool

IsExpired checks if the cache entry has expired

type CacheStats added in v1.6.0

type CacheStats struct {
	Hits      int64
	Misses    int64
	Evictions int64
	Size      int
	Capacity  int
	HitRate   float64
}

CacheStats contains cache statistics

type EditTool

type EditTool struct{}

EditTool performs exact string replacements in files

func NewEditTool

func NewEditTool() *EditTool

NewEditTool creates a new Edit tool instance

func (*EditTool) Description

func (t *EditTool) Description() string

Description returns the tool's purpose

func (*EditTool) Execute

func (t *EditTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute performs the string replacement

func (*EditTool) Name

func (t *EditTool) Name() string

Name returns the tool identifier

func (*EditTool) RequiresApproval

func (t *EditTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval always returns true - editing files is destructive

type Executor

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

Executor runs tools with permission management

func NewExecutor

func NewExecutor(registry *Registry, approvalFunc ApprovalFunc) *Executor

NewExecutor creates a new tool executor

func (*Executor) DisableCache added in v1.6.0

func (e *Executor) DisableCache()

DisableCache disables result caching

func (*Executor) EnableCache added in v1.6.0

func (e *Executor) EnableCache(capacity int, ttl time.Duration)

EnableCache enables result caching with custom capacity and TTL

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, toolName string, params map[string]interface{}) (*Result, error)

Execute runs a tool by name with given parameters

func (*Executor) GetCacheStats added in v1.6.0

func (e *Executor) GetCacheStats() *CacheStats

GetCacheStats returns cache statistics

func (*Executor) GetPermissionChecker

func (e *Executor) GetPermissionChecker() *permissions.Checker

GetPermissionChecker returns the current permission checker (may be nil)

func (*Executor) SetHookEngine

func (e *Executor) SetHookEngine(engine *hooks.Engine)

SetHookEngine sets the hook engine for this executor

func (*Executor) SetPermissionChecker

func (e *Executor) SetPermissionChecker(checker *permissions.Checker)

SetPermissionChecker sets the permission checker for this executor

func (*Executor) SetPermissionHook

func (e *Executor) SetPermissionHook(hook PermissionHook)

SetPermissionHook sets a hook that fires before permission checking

type GlobTool

type GlobTool struct{}

GlobTool finds files matching glob patterns

func NewGlobTool

func NewGlobTool() *GlobTool

NewGlobTool creates a new Glob tool instance

func (*GlobTool) Description

func (t *GlobTool) Description() string

Description returns the tool's purpose

func (*GlobTool) Execute

func (t *GlobTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute performs the glob search

func (*GlobTool) Name

func (t *GlobTool) Name() string

Name returns the tool identifier

func (*GlobTool) RequiresApproval

func (t *GlobTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval returns false (glob is read-only)

type GrepTool

type GrepTool struct{}

GrepTool searches code using ripgrep patterns

func NewGrepTool

func NewGrepTool() *GrepTool

NewGrepTool creates a new Grep tool instance

func (*GrepTool) Description

func (t *GrepTool) Description() string

Description returns the tool's purpose

func (*GrepTool) Execute

func (t *GrepTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute performs the grep search

func (*GrepTool) Name

func (t *GrepTool) Name() string

Name returns the tool identifier

func (*GrepTool) RequiresApproval

func (t *GrepTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval returns false for normal paths (grep is read-only)

type KillShellTool

type KillShellTool struct{}

KillShellTool implements background process termination

func NewKillShellTool

func NewKillShellTool() *KillShellTool

NewKillShellTool creates a new kill shell tool

func (*KillShellTool) Description

func (t *KillShellTool) Description() string

Description returns the tool description

func (*KillShellTool) Execute

func (t *KillShellTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute terminates a background process

func (*KillShellTool) Name

func (t *KillShellTool) Name() string

Name returns the tool name

func (*KillShellTool) RequiresApproval

func (t *KillShellTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval always returns true (killing processes is destructive)

type MockTool

type MockTool struct {
	NameValue             string
	DescriptionValue      string
	RequiresApprovalValue bool
	ExecuteFunc           func(context.Context, map[string]interface{}) (*Result, error)
}

MockTool is a simple tool for testing

func (*MockTool) Description

func (m *MockTool) Description() string

Description returns the tool description

func (*MockTool) Execute

func (m *MockTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute runs the mock tool

func (*MockTool) Name

func (m *MockTool) Name() string

Name returns the tool name

func (*MockTool) RequiresApproval

func (m *MockTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval returns whether approval is needed

type MuxAgentRunner added in v1.10.0

type MuxAgentRunner interface {
	// RunAgent executes a subagent with the given configuration and returns the output.
	RunAgent(ctx context.Context, agentID, prompt, systemPrompt string, allowedTools []string) (output string, err error)
}

MuxAgentRunner is an interface for running a mux agent. This interface breaks the import cycle between tools and adapter packages.

type MuxConfig added in v1.10.0

type MuxConfig struct {
	AgentRunner MuxAgentRunner // Runner that creates and executes mux agents
}

MuxConfig holds configuration for spawning mux agents

type PermissionHook

type PermissionHook func(toolName string, params map[string]interface{}, checkResult permissions.CheckResult)

PermissionHook is called before permission check, allowing external systems to intercept

type ReadTool

type ReadTool struct {
	MaxFileSize int64 // Maximum file size to read (bytes)
}

ReadTool implements file reading functionality

Example

ExampleReadTool demonstrates basic file reading

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	// Create a test file
	tmpFile, _ := os.CreateTemp("", "example-*.txt")
	defer func() { _ = os.Remove(tmpFile.Name()) }()
	_, _ = tmpFile.WriteString("Hello, World!")
	_ = tmpFile.Close()

	// Create and use the Read tool
	tool := tools.NewReadTool()
	result, _ := tool.Execute(context.Background(), map[string]interface{}{
		"path": tmpFile.Name(),
	})

	fmt.Println("Success:", result.Success)
	fmt.Println("Output:", result.Output)
}
Output:
Success: true
Output: Hello, World!
Example (RequiresApproval)

ExampleReadTool_requiresApproval demonstrates approval checking

package main

import (
	"fmt"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	tool := tools.NewReadTool()

	// Sensitive path requires approval
	sensitive := tool.RequiresApproval(map[string]interface{}{
		"path": "/etc/passwd",
	})
	fmt.Println("Sensitive path:", sensitive)

	// Regular path does not require approval
	regular := tool.RequiresApproval(map[string]interface{}{
		"path": "/tmp/test.txt",
	})
	fmt.Println("Regular path:", regular)

}
Output:
Sensitive path: true
Regular path: false
Example (WithExecutor)

ExampleReadTool_withExecutor demonstrates using the Read tool with Executor

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	tmpFile, _ := os.CreateTemp("", "example-*.txt")
	defer func() { _ = os.Remove(tmpFile.Name()) }()
	_, _ = tmpFile.WriteString("Hello from Executor!")
	_ = tmpFile.Close()

	// Create registry and register the Read tool
	registry := tools.NewRegistry()
	_ = registry.Register(tools.NewReadTool())

	// Create executor with auto-approval
	executor := tools.NewExecutor(registry, func(_ string, _ map[string]interface{}) bool {
		return true // Auto-approve for this example
	})

	// Execute the tool through the executor
	result, _ := executor.Execute(context.Background(), "read_file", map[string]interface{}{
		"path": tmpFile.Name(),
	})

	fmt.Println("Success:", result.Success)
	fmt.Println("Output:", result.Output)
}
Output:
Success: true
Output: Hello from Executor!
Example (WithLimit)

ExampleReadTool_withLimit demonstrates reading with a limit

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	tmpFile, _ := os.CreateTemp("", "example-*.txt")
	defer func() { _ = os.Remove(tmpFile.Name()) }()
	_, _ = tmpFile.WriteString("0123456789")
	_ = tmpFile.Close()

	tool := tools.NewReadTool()
	result, _ := tool.Execute(context.Background(), map[string]interface{}{
		"path":  tmpFile.Name(),
		"limit": float64(5),
	})

	fmt.Println(result.Output)
}
Output:
01234
Example (WithOffset)

ExampleReadTool_withOffset demonstrates reading with an offset

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	tmpFile, _ := os.CreateTemp("", "example-*.txt")
	defer func() { _ = os.Remove(tmpFile.Name()) }()
	_, _ = tmpFile.WriteString("0123456789")
	_ = tmpFile.Close()

	tool := tools.NewReadTool()
	result, _ := tool.Execute(context.Background(), map[string]interface{}{
		"path":   tmpFile.Name(),
		"offset": float64(5),
	})

	fmt.Println(result.Output)
}
Output:
56789

func NewReadTool

func NewReadTool() *ReadTool

NewReadTool creates a new read tool with default settings

func (*ReadTool) Description

func (t *ReadTool) Description() string

Description returns the tool description

func (*ReadTool) Execute

func (t *ReadTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute reads the file and returns its contents

func (*ReadTool) Name

func (t *ReadTool) Name() string

Name returns the tool name

func (*ReadTool) RequiresApproval

func (t *ReadTool) RequiresApproval(params map[string]interface{}) bool

RequiresApproval returns true if the path is sensitive

type Registry

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

Registry manages available tools

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new tool registry

func (*Registry) Get

func (r *Registry) Get(name string) (Tool, error)

Get retrieves a tool by name

func (*Registry) GetDefinitions

func (r *Registry) GetDefinitions() []core.ToolDefinition

GetDefinitions returns tool definitions for all registered tools in API format

func (*Registry) List

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

List returns all registered tool names sorted alphabetically

func (*Registry) Register

func (r *Registry) Register(tool Tool) error

Register adds a tool to the registry

type Result

type Result struct {
	ToolName string                 // Tool that was executed
	Success  bool                   // Did it succeed?
	Output   string                 // Standard output/result
	Error    string                 // Error message if failed
	Metadata map[string]interface{} // Additional metadata (file path, exit code, etc.)
}

Result represents the output of a tool execution

type ResultCache added in v1.6.0

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

ResultCache is an LRU cache for tool results

func NewResultCache added in v1.6.0

func NewResultCache(capacity int, ttl time.Duration) *ResultCache

NewResultCache creates a new LRU cache with the given capacity and TTL

func (*ResultCache) Clear added in v1.6.0

func (c *ResultCache) Clear()

Clear removes all entries from the cache

func (*ResultCache) Get added in v1.6.0

func (c *ResultCache) Get(toolName string, params map[string]interface{}) (*Result, bool)

Get retrieves a cached result if available and not expired

func (*ResultCache) Set added in v1.6.0

func (c *ResultCache) Set(toolName string, params map[string]interface{}, result *Result)

Set stores a result in the cache

func (*ResultCache) Stats added in v1.6.0

func (c *ResultCache) Stats() CacheStats

Stats returns cache statistics

type SearchResult

type SearchResult struct {
	Title   string
	URL     string
	Snippet string
}

SearchResult represents a single web search result

type TaskTool

type TaskTool struct {
	DefaultTimeout time.Duration       // Default timeout for tasks
	HexBinPath     string              // Path to hex binary (empty = search PATH)
	Executor       *subagents.Executor // Legacy subagent executor
	UseFramework   bool                // If true, use new subagent framework instead of direct subprocess
	UseMux         bool                // If true, use mux agents (Phase 2)
	MuxConfig      *MuxConfig          // Configuration for mux agents
}

TaskTool implements sub-agent task delegation functionality

func NewTaskTool

func NewTaskTool() *TaskTool

NewTaskTool creates a new task tool with default settings

func NewTaskToolWithFramework

func NewTaskToolWithFramework() *TaskTool

NewTaskToolWithFramework creates a task tool that uses the subagent framework

func NewTaskToolWithMux added in v1.10.0

func NewTaskToolWithMux(runner MuxAgentRunner) *TaskTool

NewTaskToolWithMux creates a task tool that uses mux agents (in-process)

func (*TaskTool) Description

func (t *TaskTool) Description() string

Description returns the tool description

func (*TaskTool) Execute

func (t *TaskTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute launches a sub-agent and returns its output

func (*TaskTool) ExecuteStreaming

func (t *TaskTool) ExecuteStreaming(ctx context.Context, params map[string]interface{}) (<-chan *Result, error)

ExecuteStreaming launches a sub-agent and streams incremental output updates. Returns a channel that emits Result objects as output becomes available. The channel is closed when execution completes or fails.

func (*TaskTool) Name

func (t *TaskTool) Name() string

Name returns the tool name

func (*TaskTool) RequiresApproval

func (t *TaskTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval always returns true for task execution

func (*TaskTool) SetMuxRunner added in v1.10.0

func (t *TaskTool) SetMuxRunner(runner MuxAgentRunner)

SetMuxRunner enables mux agent execution with the given runner. This allows setting mux config after construction (useful when tools have circular dependencies).

type TodoWriteTool

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

TodoWriteTool implements the todo_write tool for managing todo lists

func (*TodoWriteTool) Description

func (t *TodoWriteTool) Description() string

Description returns a human-readable description

func (*TodoWriteTool) Execute

func (t *TodoWriteTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute creates and formats a todo list

func (*TodoWriteTool) Name

func (t *TodoWriteTool) Name() string

Name returns the tool identifier

func (*TodoWriteTool) RequiresApproval

func (t *TodoWriteTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval returns false (TodoWrite is read-only display)

type Tool

type Tool interface {
	// Name returns the unique identifier for this tool (e.g., "read_file", "bash")
	Name() string

	// Description returns a human-readable description of what this tool does
	Description() string

	// RequiresApproval returns true if this specific tool invocation needs user approval.
	// The decision can be based on the parameters (e.g., writing to /etc requires approval).
	// params is guaranteed to be non-nil but may be empty or have unexpected types.
	RequiresApproval(params map[string]interface{}) bool

	// Execute runs the tool with the given parameters.
	// Implementations must:
	//   - Validate required parameters exist
	//   - Type assert parameters safely (return error on wrong type)
	//   - Respect context cancellation
	//   - Return a Result (not error) for expected failures like "file not found"
	//   - Return an error only for unexpected/catastrophic failures
	// params is guaranteed to be non-nil but may be empty.
	Execute(ctx context.Context, params map[string]interface{}) (*Result, error)
}

Tool represents an executable tool that can be invoked by the assistant. Implementations must:

  • Validate their own parameters in Execute() and return appropriate errors
  • Handle type assertions safely (params may have incorrect types)
  • Be safe for concurrent execution from multiple goroutines

func NewAskUserQuestionTool

func NewAskUserQuestionTool() Tool

NewAskUserQuestionTool creates a new AskUserQuestion tool instance

func NewTodoWriteTool

func NewTodoWriteTool() Tool

NewTodoWriteTool creates a new TodoWrite tool instance

func NewTodoWriteToolWithDB

func NewTodoWriteToolWithDB(db *sql.DB) Tool

NewTodoWriteToolWithDB creates a new TodoWrite tool with database persistence

func NewWebFetchTool

func NewWebFetchTool() Tool

NewWebFetchTool creates a new WebFetch tool instance

func NewWebSearchTool

func NewWebSearchTool() Tool

NewWebSearchTool creates a new web search tool instance

type ToolResult

type ToolResult struct {
	Type      string `json:"type"`        // Always "tool_result"
	ToolUseID string `json:"tool_use_id"` // ID from the ToolUse request
	Content   string `json:"content"`     // Tool output or error message
	IsError   bool   `json:"is_error"`    // true if this represents an error
}

ToolResult represents a tool execution result to send back to the Anthropic API. This is sent as a content block in a user message after tool execution completes. See: https://docs.anthropic.com/claude/docs/tool-use#returning-tool-results

func ResultToToolResult

func ResultToToolResult(result *Result, toolUseID string) ToolResult

ResultToToolResult converts an internal Result to a ToolResult for the API

Example

ExampleResultToToolResult demonstrates converting internal results to API format

package main

import (
	"fmt"

	"github.com/2389-research/hex/internal/tools"
)

func main() {
	// Success result
	successResult := &tools.Result{
		ToolName: "read_file",
		Success:  true,
		Output:   "file contents",
	}
	apiResult1 := tools.ResultToToolResult(successResult, "toolu_123")
	fmt.Printf("API result 1: type=%s, error=%v\n", apiResult1.Type, apiResult1.IsError)

	// Error result
	errorResult := &tools.Result{
		ToolName: "write_file",
		Success:  false,
		Error:    "permission denied",
	}
	apiResult2 := tools.ResultToToolResult(errorResult, "toolu_456")
	fmt.Printf("API result 2: type=%s, error=%v, content=%s\n",
		apiResult2.Type, apiResult2.IsError, apiResult2.Content)

}
Output:
API result 1: type=tool_result, error=false
API result 2: type=tool_result, error=true, content=Error: permission denied

type ToolUse

type ToolUse = core.ToolUse

ToolUse is an alias for core.ToolUse for convenience Use core.ToolUse directly in new code

type WebFetchTool

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

WebFetchTool fetches content from a URL and optionally processes it

func (*WebFetchTool) Description

func (t *WebFetchTool) Description() string

Description returns the tool description

func (*WebFetchTool) Execute

func (t *WebFetchTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute fetches the URL and returns its content

func (*WebFetchTool) Name

func (t *WebFetchTool) Name() string

Name returns the tool name

func (*WebFetchTool) RequiresApproval

func (t *WebFetchTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval always returns true since this makes network requests

type WebSearchTool

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

WebSearchTool searches the web using DuckDuckGo and returns formatted results

func (*WebSearchTool) Description

func (t *WebSearchTool) Description() string

Description returns a human-readable description of the tool

func (*WebSearchTool) Execute

func (t *WebSearchTool) Execute(ctx context.Context, params map[string]interface{}) (*Result, error)

Execute performs the web search and returns formatted results

func (*WebSearchTool) Name

func (t *WebSearchTool) Name() string

Name returns the tool's identifier

func (*WebSearchTool) RequiresApproval

func (t *WebSearchTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval returns true since this tool makes network requests

type WriteTool

type WriteTool struct {
	MaxContentSize int64 // Maximum content size to write (bytes)
}

WriteTool implements file writing functionality

func NewWriteTool

func NewWriteTool() *WriteTool

NewWriteTool creates a new write tool with default settings

func (*WriteTool) Description

func (t *WriteTool) Description() string

Description returns the tool description

func (*WriteTool) Execute

func (t *WriteTool) Execute(_ context.Context, params map[string]interface{}) (*Result, error)

Execute writes content to a file

func (*WriteTool) Name

func (t *WriteTool) Name() string

Name returns the tool name

func (*WriteTool) RequiresApproval

func (t *WriteTool) RequiresApproval(_ map[string]interface{}) bool

RequiresApproval always returns true for write operations

Jump to

Keyboard shortcuts

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