tools

package
v1.5.7 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package tools provides HITL (Human-in-the-Loop) tool support.

Package tools provides typed tool handlers and utilities for SDK v2.

This package extends the basic tool registration provided by [sdk.Conversation.OnTool] with additional capabilities:

  • Type-safe handlers using Go generics
  • Handler adapters for the runtime tool registry
  • HTTP tool executors
  • Pending tool management for HITL workflows

Most users will only need [sdk.Conversation.OnTool] from the main sdk package. This sub-package is for advanced use cases.

Typed Handlers

For type safety, use OnTyped with struct arguments:

type GetWeatherArgs struct {
    City    string `map:"city"`
    Country string `map:"country"`
}

tools.OnTyped(conv, "get_weather", func(args GetWeatherArgs) (any, error) {
    return weatherAPI.GetForecast(args.City, args.Country)
})

The struct fields are populated from the args map using the "map" struct tag (or the field name in lowercase if no tag is specified).

Index

Constants

View Source
const (
	// DefaultPendingTTL is the default time-to-live for pending tool calls.
	// Entries older than this are automatically removed (by the in-memory
	// cleanup goroutine, or by the backend's native expiry for durable stores).
	DefaultPendingTTL = 5 * time.Minute

	// DefaultMaxPending is the default maximum number of pending tool calls a
	// MemoryPendingStore holds simultaneously. New adds are rejected when full.
	DefaultMaxPending = 1000
)
View Source
const DefaultMaxAggregateSize = tools.DefaultMaxAggregateSize

DefaultMaxAggregateSize is the default cumulative response size limit (50 MB).

View Source
const DefaultRedisKeyPrefix = "promptkit:pending:"

DefaultRedisKeyPrefix namespaces pending-approval keys in Redis.

Variables

View Source
var (
	NewHTTPExecutor                 = tools.NewHTTPExecutor
	NewHTTPExecutorWithClient       = tools.NewHTTPExecutorWithClient
	NewHTTPExecutorWithMaxAggregate = tools.NewHTTPExecutorWithMaxAggregate
)

NewHTTPExecutor creates a new HTTP executor with default settings. NewHTTPExecutorWithClient creates one with a custom http.Client. NewHTTPExecutorWithMaxAggregate creates one with a custom aggregate size limit.

View Source
var ErrAggregateResponseSizeExceeded = tools.ErrAggregateResponseSizeExceeded

ErrAggregateResponseSizeExceeded is returned when cumulative response size is exceeded.

View Source
var ErrPendingAlreadyResolved = errors.New("pending tool call already resolved or expired")

ErrPendingAlreadyResolved is returned by a resolve/reject when the pending call is no longer present — it was already claimed by another caller (e.g. a second instance of the same agent) or expired. It is the signal that this caller lost the single-winner race and must not act on the call.

View Source
var ErrPendingStoreFull = errors.New("pending store is full")

ErrPendingStoreFull is returned when the store has reached its maximum capacity.

Functions

func OnTyped

func OnTyped[T any](conv ToolRegistrar, name string, handler TypedHandler[T])

OnTyped registers a typed handler for a tool.

The type parameter T must be a struct type. Field values are populated from the args map using:

  • The "map" struct tag value, or
  • The lowercase field name if no tag is present

Example:

type SearchArgs struct {
    Query    string `map:"query"`
    MaxResults int  `map:"max_results"`
}

tools.OnTyped(conv, "search", func(args SearchArgs) (any, error) {
    return search(args.Query, args.MaxResults)
})

Types

type AsyncToolHandler

type AsyncToolHandler func(args map[string]any) PendingResult

AsyncToolHandler is a function that may require approval before execution. Return a non-empty PendingResult to indicate approval is needed. Return an empty PendingResult{} to proceed immediately.

type Closer added in v1.5.6

type Closer interface {
	Close() error
}

Closer is an optional PendingStore capability. In-memory stores expose it to stop their cleanup goroutine; the SDK type-asserts for it on Close. Durable stores that hold no local resources need not implement it.

type ExecFunc added in v1.5.6

type ExecFunc func(args map[string]any) (any, error)

ExecFunc is a registered tool execution handler. On resolve, the Conversation recovers the ExecFunc for a pending call by tool name (a persisted call cannot carry the closure) and runs it with the approved arguments.

type HTTPExecutor

type HTTPExecutor = tools.HTTPExecutor

HTTPExecutor is re-exported from runtime/tools for backward compatibility.

type HTTPToolConfig

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

HTTPToolConfig provides a builder pattern for configuring HTTP tool handlers. This is used with OnToolHTTP to register HTTP-based tools programmatically.

func NewHTTPToolConfig

func NewHTTPToolConfig(url string, opts ...HTTPToolOption) *HTTPToolConfig

NewHTTPToolConfig creates a new HTTP tool configuration.

func (*HTTPToolConfig) Handler

func (c *HTTPToolConfig) Handler() func(args map[string]any) (any, error)

Handler returns a tool handler function that makes the HTTP request. This is used with OnTool to register an HTTP-based tool.

func (*HTTPToolConfig) HandlerCtx added in v1.3.6

func (c *HTTPToolConfig) HandlerCtx() func(ctx context.Context, args map[string]any) (any, error)

HandlerCtx returns a context-aware tool handler function that makes the HTTP request. The context is propagated to the underlying HTTP executor for tracing and cancellation.

func (*HTTPToolConfig) ToDescriptorConfig

func (c *HTTPToolConfig) ToDescriptorConfig() *tools.HTTPConfig

ToDescriptorConfig converts the HTTPToolConfig to a runtime HTTPConfig that can be used with a tool descriptor.

type HTTPToolOption

type HTTPToolOption func(*HTTPToolConfig)

HTTPToolOption configures an HTTPToolConfig.

func WithHeader

func WithHeader(key, value string) HTTPToolOption

WithHeader adds a static header.

func WithHeaderFromEnv

func WithHeaderFromEnv(headerEnv string) HTTPToolOption

WithHeaderFromEnv adds a header that reads its value from an environment variable. Format: "Header-Name=ENV_VAR_NAME"

func WithMethod

func WithMethod(method string) HTTPToolOption

WithMethod sets the HTTP method.

func WithPostProcess

func WithPostProcess(fn func(resp []byte) ([]byte, error)) HTTPToolOption

WithPostProcess adds a function to process the response after receiving.

func WithPreRequest

func WithPreRequest(fn func(req *http.Request) error) HTTPToolOption

WithPreRequest adds a function to modify the request before sending.

func WithRedact

func WithRedact(fields ...string) HTTPToolOption

WithRedact specifies fields to redact from the response.

func WithTimeout

func WithTimeout(ms int) HTTPToolOption

WithTimeout sets the request timeout in milliseconds.

func WithTransform

func WithTransform(transform func(args map[string]any) (map[string]any, error)) HTTPToolOption

WithTransform adds a function to transform arguments before the request.

type HandlerAdapter

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

HandlerAdapter adapts an SDK handler to the runtime's tools.Executor interface.

func NewHandlerAdapter

func NewHandlerAdapter(name string, handler func(args map[string]any) (any, error)) *HandlerAdapter

NewHandlerAdapter creates a new adapter for the given handler.

func (*HandlerAdapter) Execute

Execute runs the handler with the given arguments.

func (*HandlerAdapter) Name

func (a *HandlerAdapter) Name() string

Name returns the tool name.

type MemoryPendingStore added in v1.5.6

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

MemoryPendingStore is the default in-process PendingStore. It keys pending calls by (conversationID, id), bounds total entries, and sweeps expired entries on a background goroutine. It is the zero-config default when no durable store is injected via WithPendingStore.

func NewMemoryPendingStore added in v1.5.6

func NewMemoryPendingStore(opts ...PendingStoreOption) *MemoryPendingStore

NewMemoryPendingStore creates an in-memory pending store with TTL-based cleanup. Call Close() when the store is no longer needed to stop the cleanup goroutine.

func (*MemoryPendingStore) Add added in v1.5.6

Add stores a pending tool call. Returns ErrPendingStoreFull if the store has reached its maximum capacity.

func (*MemoryPendingStore) Claim added in v1.5.6

func (s *MemoryPendingStore) Claim(_ context.Context, convID, id string) (*PendingToolCall, bool, error)

Claim atomically removes and returns a pending tool call. The delete-under-lock makes it single-winner: concurrent claimers for the same id see at most one ok=true.

func (*MemoryPendingStore) Close added in v1.5.6

func (s *MemoryPendingStore) Close() error

Close stops the background cleanup goroutine and waits for it to finish. It satisfies the optional Closer capability.

func (*MemoryPendingStore) Get added in v1.5.6

func (s *MemoryPendingStore) Get(_ context.Context, convID, id string) (*PendingToolCall, bool, error)

Get retrieves a read-only view of a pending tool call. It returns a copy so a caller cannot mutate the stored record — matching RedisPendingStore, which decodes a fresh value.

func (*MemoryPendingStore) List added in v1.5.6

func (s *MemoryPendingStore) List(_ context.Context, convID string) ([]*PendingToolCall, error)

List returns copies of all pending tool calls for a conversation.

type PendingResult

type PendingResult struct {
	// Reason is a machine-readable code for why approval is needed.
	// Examples: "high_value", "sensitive_action", "rate_limited"
	Reason string

	// Message is a human-readable explanation for the approval requirement.
	// This should be suitable for display to an approver.
	Message string
}

PendingResult is returned by async tool handlers to indicate that the tool execution requires external approval.

Example:

conv.OnToolAsync("process_refund", func(args map[string]any) PendingResult {
    amount := args["amount"].(float64)
    if amount > 1000 {
        return PendingResult{
            Reason:  "high_value_refund",
            Message: fmt.Sprintf("Refund of $%.2f requires approval", amount),
        }
    }
    // Return empty to proceed immediately
    return PendingResult{}
})

func (PendingResult) IsPending

func (p PendingResult) IsPending() bool

IsPending returns true if this result requires approval.

type PendingStore

type PendingStore interface {
	// Add persists a pending call. Returns ErrPendingStoreFull if a bounded
	// store is at capacity.
	Add(ctx context.Context, call *PendingToolCall) error

	// Get returns a read-only view of a pending call, or ok=false if absent.
	Get(ctx context.Context, convID, id string) (*PendingToolCall, bool, error)

	// List returns all pending calls for a conversation.
	List(ctx context.Context, convID string) ([]*PendingToolCall, error)

	// Claim atomically removes and returns a pending call. ok=false means the
	// call was already claimed by another caller or never existed. This is the
	// single-winner resolution gate for both approve and reject.
	Claim(ctx context.Context, convID, id string) (*PendingToolCall, bool, error)
}

PendingStore persists tool calls awaiting human approval.

It mirrors statestore.Store: read-shaped for inspection, with a single atomic take primitive for resolution. Implementations MUST make Claim atomic — at most one caller may successfully claim a given id — so that concurrent instances of the same agent cannot double-resolve a call. Get and List are read-only and carry no such guarantee.

The store never executes tool handlers; resolution (running the recovered handler) happens in the Conversation. This keeps memory and durable backends on one identical resolution path.

type PendingStoreOption added in v1.3.10

type PendingStoreOption func(*MemoryPendingStore)

PendingStoreOption configures a MemoryPendingStore during construction.

func WithMaxPending added in v1.3.10

func WithMaxPending(limit int) PendingStoreOption

WithMaxPending sets the maximum number of pending tool calls allowed.

func WithPendingTTL added in v1.3.10

func WithPendingTTL(ttl time.Duration) PendingStoreOption

WithPendingTTL sets the time-to-live for pending tool calls. Entries older than this are removed during periodic cleanup.

type PendingToolCall

type PendingToolCall struct {
	// Unique identifier for this pending call (the provider tool call ID).
	ID string `json:"id"`

	// ConversationID scopes the call so a store shared across conversations can
	// isolate queues and list one conversation's pending calls.
	ConversationID string `json:"conversation_id"`

	// Tool name — also the key used to recover the execution handler on resolve.
	Name string `json:"name"`

	// Arguments the model proposed for the call.
	Arguments map[string]any `json:"arguments"`

	// Reason the tool requires approval (from PendingResult).
	Reason string `json:"reason"`

	// Human-readable message (from PendingResult).
	Message string `json:"message"`

	// CreatedAt tracks when this entry was added, for TTL expiration.
	CreatedAt time.Time `json:"created_at"`
}

PendingToolCall represents a tool call awaiting approval. It is pure data: it carries no execution closure, so it can be serialized into a durable store and resolved by a different process. The execution handler is recovered by Name at resolve time (see Conversation.ResolveTool).

type RedisOption added in v1.5.6

type RedisOption func(*RedisPendingStore)

RedisOption configures a RedisPendingStore.

func WithRedisKeyPrefix added in v1.5.6

func WithRedisKeyPrefix(prefix string) RedisOption

WithRedisKeyPrefix overrides the key namespace (default DefaultRedisKeyPrefix).

func WithRedisTTL added in v1.5.6

func WithRedisTTL(ttl time.Duration) RedisOption

WithRedisTTL overrides the per-entry TTL (default DefaultPendingTTL).

type RedisPendingStore added in v1.5.6

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

RedisPendingStore is a durable PendingStore backed by Redis. Pending calls survive process restarts and can be resolved by a different instance of the same agent. Claim is atomic across processes via GETDEL, so exactly one instance wins a concurrent resolve.

Records are stored one key per call under {prefix}{convID}:{id} with a native TTL, so expiry is handled by Redis (no cleanup goroutine). The caller owns the *redis.Client lifecycle; this store does not close it.

Conversation IDs and call IDs must not contain the key separator ':' — the SDK's generated IDs (UUID conversation IDs, provider call IDs) do not. List additionally filters on the exact decoded ConversationID, so a ':' in an id cannot leak one conversation's calls into another's listing.

func NewRedisPendingStore added in v1.5.6

func NewRedisPendingStore(client *redis.Client, opts ...RedisOption) *RedisPendingStore

NewRedisPendingStore creates a durable pending store over the given Redis client:

store := tools.NewRedisPendingStore(
    redis.NewClient(&redis.Options{Addr: "localhost:6379"}),
)
conv, _ := sdk.Open(pack, "assist", sdk.WithPendingStore(store))

func (*RedisPendingStore) Add added in v1.5.6

Add persists a pending call with a native TTL.

func (*RedisPendingStore) Claim added in v1.5.6

func (s *RedisPendingStore) Claim(ctx context.Context, convID, id string) (*PendingToolCall, bool, error)

Claim atomically removes and returns a pending call via GETDEL, guaranteeing a single winner across processes.

func (*RedisPendingStore) Get added in v1.5.6

func (s *RedisPendingStore) Get(ctx context.Context, convID, id string) (*PendingToolCall, bool, error)

Get returns a read-only view of a pending call.

func (*RedisPendingStore) List added in v1.5.6

func (s *RedisPendingStore) List(ctx context.Context, convID string) ([]*PendingToolCall, error)

List returns all pending calls for a conversation via a scoped SCAN.

type ResolvedStore added in v1.1.9

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

ResolvedStore tracks tool call resolutions that haven't been processed by Continue(). This allows the Continue() method to send proper tool result messages to the LLM. It is transient and per-conversation — resolutions are consumed immediately by Continue/ContinueDuplex and never persisted.

func NewResolvedStore added in v1.1.9

func NewResolvedStore() *ResolvedStore

NewResolvedStore creates a new resolved tool store.

func (*ResolvedStore) Add added in v1.1.9

func (s *ResolvedStore) Add(resolution *ToolResolution)

Add stores a resolved tool call.

func (*ResolvedStore) Len added in v1.1.9

func (s *ResolvedStore) Len() int

Len returns the number of stored resolutions.

func (*ResolvedStore) PopAll added in v1.1.9

func (s *ResolvedStore) PopAll() []*ToolResolution

PopAll returns all resolutions and clears the store. Used by Continue() to get all pending tool results.

type ToolHandler

type ToolHandler = func(args map[string]any) (any, error)

ToolHandler is a function type for tool handlers. This mirrors the definition in the main sdk package.

type ToolRegistrar

type ToolRegistrar interface {
	OnTool(name string, handler ToolHandler)
}

ToolRegistrar is the interface for registering tool handlers. This is implemented by [sdk.Conversation].

type ToolResolution

type ToolResolution struct {
	// The resolved tool call ID
	ID string

	// Result if approved and executed
	Result any

	// ResultJSON is the JSON-encoded result
	ResultJSON json.RawMessage

	// Parts contains multimodal content parts for the tool result.
	// When set, these are used directly as MessageToolResult.Parts,
	// taking precedence over ResultJSON.
	Parts []types.ContentPart

	// Error if execution failed
	Error error

	// Rejected is true if the tool was rejected
	Rejected bool

	// RejectionReason explains why the tool was rejected
	RejectionReason string

	// Arguments are the effective arguments the handler executed with. When the
	// call was approved with edits, this is the original arguments with the
	// reviewer's overrides merged in.
	Arguments map[string]any

	// Edited is true when the call was approved with reviewer-supplied argument
	// overrides (see ResolveApproved). Provides an audit trail distinguishing an
	// as-proposed approval from an edited one.
	Edited bool
}

ToolResolution represents the result of resolving a pending tool.

func ResolveApproved added in v1.5.6

func ResolveApproved(call *PendingToolCall, handler ExecFunc, overrides map[string]any) *ToolResolution

ResolveApproved runs handler with the call's arguments (shallow-merged with overrides) and returns the ToolResolution. It is pure — it touches no store — so the Conversation calls it after atomically claiming the call and recovering the handler by name.

Overrides are shallow-merged over a copy of the original arguments: keys in overrides replace the originals, absent keys are preserved. A nil or empty overrides map executes the call exactly as proposed. The original call is never mutated.

func ResolveRejected added in v1.5.6

func ResolveRejected(id, reason string) *ToolResolution

ResolveRejected builds a rejected ToolResolution for a claimed call.

type TypedHandler

type TypedHandler[T any] func(args T) (any, error)

TypedHandler is a function that executes a tool call with typed arguments. T must be a struct type with fields tagged with `map:"fieldname"`.

Jump to

Keyboard shortcuts

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