Help us shape the future of Go! Take the 2026 Go Developer Survey

tools

package
v2.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package tools provides the unified tool abstraction for Celeste CLI. This file implements StreamingToolExecutor, a state machine that accepts tool calls as they arrive during LLM streaming and dispatches them according to their concurrency safety.

Index

Constants

This section is empty.

Variables

View Source
var ErrToolNameTaken = errors.New("tool name already registered")

ErrToolNameTaken is returned by Add when another tool holds the name.

Functions

func CallIDFromContext

func CallIDFromContext(ctx context.Context) string

CallIDFromContext returns the ID WithCallID recorded, or "".

func CallKeyFromContext

func CallKeyFromContext(ctx context.Context) string

CallKeyFromContext returns the key WithCallKey recorded, or "".

func PermissionReadOnly

func PermissionReadOnly(t Tool, input map[string]any) bool

PermissionReadOnly reports whether the permission checker treats this call of t as read-only: t is read-only, or the call only changes internal state (InternalStater).

func PrimaryArg

func PrimaryArg(t Tool) string

PrimaryArg is the input field an argument-scoped permission rule ("bash(git *)", "write_file(src/*)") is matched against, and the one a permission prompt leads with: the tool's own choice (PrimaryArger), else the first of primaryArgKeys its schema declares, else its only declared string property. "" means none: an argument-scoped rule then permits nothing for the tool and still restricts it. Only declared fields count, so a field the model adds that the tool never reads cannot stand in for the real argument.

func RankDocs

func RankDocs(docs []string, query string) []int

RankDocs ranks free-text documents by BM25 against query and returns their indices, best match first. An empty query returns every index in original order (no filtering); a non-empty query returns only documents that score above zero. Shares the tokenizer + BM25 parameters with ToolIndex, so the skills browser ranks with the same relevance as find_tools.

func TimeoutFor

func TimeoutFor(t Tool, def time.Duration) time.Duration

TimeoutFor returns t's own timeout when it has one, else def.

func WithAskAdvisor

func WithAskAdvisor(ctx context.Context, a AskAdvisor) context.Context

WithAskAdvisor makes a consult this call's allowed decision. loop.Loop sets it from Loop.Advisor.

func WithCallID

func WithCallID(ctx context.Context, id string) context.Context

WithCallID records the ID of the tool call ctx runs. The write tools file their checkpoints under it (2.0 F4), so a rewind can map them to messages.

func WithCallKey

func WithCallKey(ctx context.Context, key string) context.Context

WithCallKey records the per-invocation key of the tool call ctx runs (loop.ToolCall.Key). Unlike the model's call ID it never repeats, so a permission prompt can bind its ask to exactly this call.

func WithExecTimeout

func WithExecTimeout(ctx context.Context, timeout time.Duration) context.Context

WithExecTimeout asks the registry to bound the tool's execution with timeout, starting only once the tool is cleared to run. A deadline set on ctx directly also covers the time spent waiting on the user's approval, so a slow answer hands the tool an already-expired context (#172).

func WithPrompt

func WithPrompt(ctx context.Context, fn PromptFunc) context.Context

WithPrompt makes fn answer this call's permission Ask instead of the registry's prompt. loop.Loop uses it so each run brings its own Gate, whose lifetime differs by mode (2.0 F2). A nil fn means no one to ask, the same as WithoutPrompt: it never falls back to the registry's prompt.

func WithoutPrompt

func WithoutPrompt(ctx context.Context) context.Context

WithoutPrompt makes this call's permission Ask deny as headless ("no prompt is configured"), ignoring the registry's own prompt. loop.Loop uses it for a run without a Gate. It shares WithPrompt's key, so the innermost of the two wins (a subagent's run inside a gated parent, or the reverse).

Types

type AdvisedCall

type AdvisedCall struct {
	Name     string
	Input    map[string]any
	ReadOnly bool
}

AdvisedCall is a call the permission policy allows, shown to an AskAdvisor.

type AskAdvisor

type AskAdvisor func(ctx context.Context, call AdvisedCall) (ask bool, reason string)

AskAdvisor may turn a call the policy allows into an Ask (jev_gate, 2.0 W3). It can never allow a call or deny one outright: Deny stays Deny, and an Ask it adds goes to the prompt, or is denied headless. reason is shown with the prompt and in a headless denial.

type AskFunc

type AskFunc func(ctx context.Context, req AskRequest) (AskResponse, error)

AskFunc is a blocking callback that presents an AskRequest and returns the user's answer. Installed only in interactive (TUI) mode; nil means headless, in which case Ask returns an error rather than deadlocking.

type AskOption

type AskOption struct {
	Label       string `json:"label"`
	Description string `json:"description,omitempty"`
}

AskOption is one selectable choice presented by the ask tool.

type AskRequest

type AskRequest struct {
	Question    string      `json:"question"`
	Options     []AskOption `json:"options"`
	MultiSelect bool        `json:"multi_select"`
}

AskRequest is a structured question the model asks the user mid-turn.

type AskResponse

type AskResponse struct {
	Selected  []string // chosen option labels
	Cancelled bool
}

AskResponse carries the user's selection.

type ExecFunc

type ExecFunc func(ctx context.Context, callID string, tool Tool, input map[string]any) (ToolResult, error)

ExecFunc runs one call for the executor. The default calls tool.Execute directly; loop.Loop installs one that goes through the registry so the permission checker, timeouts and hooks apply.

type ExecutorResult

type ExecutorResult struct {
	CallID   string     // The tool call ID from the LLM
	ToolName string     // The tool name
	State    ToolState  // Final state
	Result   ToolResult // Tool output (content and/or error)
	Err      error      // Go error if tool execution returned one
}

ExecutorResult holds the result of a single tool execution, including the call metadata and final state.

type HookRunner

type HookRunner interface {
	PreToolUse(ctx context.Context, toolName string, input map[string]any) PreToolHookResult
	PostToolUse(ctx context.Context, toolName string, input map[string]any, result ToolResult) (additionalContext string)
}

HookRunner runs tool hooks (2.0 F0). It is an interface so tools does not import hooks; hooks.Runner.ToolHooks returns one.

type InternalStater

type InternalStater interface {
	InternalState(input map[string]any) bool
}

InternalStater is implemented by tools whose call, for some inputs, changes only the session's own bookkeeping (the todo list), never the user's files or the outside world. The permission checker treats such a call as read-only: it is auto-allowed in the default mode without a prompt. Hooks still see it, and the tool stays non-read-only everywhere else (plan mode does not offer it).

type InterruptBehavior

type InterruptBehavior int

InterruptBehavior defines how a tool responds to cancellation signals.

const (
	// InterruptCancel means the tool should be cancelled immediately.
	InterruptCancel InterruptBehavior = iota
	// InterruptBlock means the tool should block until completion.
	InterruptBlock
)

type PermissionRequest

type PermissionRequest struct {
	ToolName     string
	InputSummary string // short human-readable summary of what the tool will do
	RiskLevel    string // "read", "write", or "destructive"
	// Forced is set when a PreToolUse hook or an AskAdvisor forced this
	// ask over a policy Allow: a prompt must ask the user, never answer
	// from a remembered "always" of its own.
	Forced bool
	// Context is the asking call's context (loop.PromptGate sets it): its
	// Done and values tell an interactive prompt which run asked and
	// whether that run has ended (2.0 F2e). nil: unknown.
	Context context.Context
}

PermissionRequest describes a pending tool invocation that requires user approval. It is passed to PromptFunc when the permission checker returns Ask.

type PermissionResponse

type PermissionResponse struct {
	Decision string // "allow_once", "always_allow", "deny", "always_deny"
	Pattern  string // rule pattern for "always" decisions
}

PermissionResponse carries the user's decision from an interactive prompt.

type PreToolHookResult

type PreToolHookResult struct {
	Decision          string // "allow", "deny" or "ask"
	Reason            string
	AdditionalContext string
	UpdatedInput      map[string]any // nil = unchanged
}

PreToolHookResult is the combined verdict of the PreToolUse hooks.

type PrimaryArger

type PrimaryArger interface {
	PrimaryArg() string
}

PrimaryArger is implemented by a tool that names its primary argument itself; PrimaryArg otherwise reads it from the tool's schema.

type ProgressEvent

type ProgressEvent struct {
	ToolName string  `json:"tool_name"`
	Message  string  `json:"message"`
	Percent  float64 `json:"percent"` // -1 for indeterminate
}

ProgressEvent represents a progress update from a running tool.

type PromptFunc

type PromptFunc func(req PermissionRequest) PermissionResponse

PromptFunc is a blocking callback invoked when a tool execution requires interactive approval. It runs in the tool-execution goroutine (off the Bubble Tea Update loop), so it may safely block until the user responds. Returning a zero-value PermissionResponse (empty Decision) is treated as deny.

type Registry

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

Registry manages the collection of available tools and their mode associations.

func NewRegistry

func NewRegistry() *Registry

NewRegistry creates a new empty tool registry.

func (*Registry) Activate

func (r *Registry) Activate(names ...string)

Activate re-exposes hidden tools for the rest of the session (called by find_tools after a BM25 match).

func (*Registry) Activated

func (r *Registry) Activated(name string) bool

Activated reports whether find_tools activated name this session. It holds whether or not discovery mode is on: the small-window tool fit (compact.FitTools) keeps an activated tool even when it drops others.

func (*Registry) ActivatedNames

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

ActivatedNames lists the registered tools find_tools activated this session, oldest activation first (activating one again makes it the newest). The small-window tool fit keeps the newest when not all fit.

func (*Registry) Add

func (r *Registry) Add(tool Tool, modes ...RuntimeMode) error

Add registers an external tool (an MCP server's, a JSON custom tool) without ever replacing another (2.0 W4): a name held by a different tool is ErrToolNameTaken, wrapped with the name. Re-adding the very same tool (a reconnect) is a no-op success. Modes as RegisterWithModes.

func (*Registry) Ask

func (r *Registry) Ask(ctx context.Context, req AskRequest) (AskResponse, error)

Ask presents a structured question to the user and blocks for the answer. Returns an error when no callback is installed (headless / one-shot / serve), so the caller can degrade gracefully instead of deadlocking.

func (*Registry) Count

func (r *Registry) Count() int

Count returns the number of registered tools.

func (*Registry) DiscoveryMode

func (r *Registry) DiscoveryMode() bool

DiscoveryMode reports whether dynamic tool discovery is on.

func (*Registry) Execute

func (r *Registry) Execute(ctx context.Context, name string, input map[string]any) (ToolResult, error)

Execute runs a tool by name with input validation.

func (*Registry) ExecuteWithProgress

func (r *Registry) ExecuteWithProgress(ctx context.Context, name string, input map[string]any, progress chan<- ProgressEvent) (ToolResult, error)

ExecuteWithProgress runs a tool by name with input validation and a progress channel.

Order: validate → deny-only permission pass on the model's input (a hard denial never reaches a hook process) → PreToolUse hooks → re-validate a rewritten input → full permission gate on the final input (a hook's ask forces the prompt; a hook can never skip one) → execute → PostToolUse.

func (*Registry) Get

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

Get returns a tool by name.

func (*Registry) GetAll

func (r *Registry) GetAll() []Tool

GetAll returns all registered tools sorted by name.

func (*Registry) GetToolDefinitions

func (r *Registry) GetToolDefinitions() []map[string]any

GetToolDefinitions returns all tools in OpenAI function-calling format.

func (*Registry) GetTools

func (r *Registry) GetTools(mode RuntimeMode) []Tool

GetTools returns tools available for the given mode, sorted by name.

func (*Registry) LoadCustomTools

func (r *Registry) LoadCustomTools(dir string) error

LoadCustomTools loads JSON tool definitions from a directory. This provides backwards compatibility with ~/.celeste/skills/*.json files.

dir must be absolute: a relative one comes from an unset or empty home directory, and would load (and later run) commands from the current directory, which may be an untrusted checkout.

func (*Registry) Overwritten

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

Overwritten lists the names Register or RegisterWithModes replaced with a different tool, in order; builtins must never do that.

func (*Registry) Prompt

func (r *Registry) Prompt() PromptFunc

Prompt returns the prompt SetPromptFunc installed, or nil. The chat's loop Gate reads it at ask time (2.0 F2d), so a prompt installed after the Gate was built (runChatTUI, tests) still answers.

func (*Registry) Register

func (r *Registry) Register(tool Tool)

Register adds a tool that is available in all modes. It is for builtins, whose names the code controls; an existing tool of that name is replaced. External tools (MCP servers, JSON custom tools) use Add.

func (*Registry) RegisterWithModes

func (r *Registry) RegisterWithModes(tool Tool, modes ...RuntimeMode)

RegisterWithModes adds a tool that is only available in the specified modes (none: all modes). Like Register, it replaces a tool of that name.

func (*Registry) Retain

func (r *Registry) Retain(keep func(Tool) bool) int

Retain removes every tool keep rejects and returns how many it removed. A nil keep removes nothing. A typed subagent (2.0 W4e) trims its own registry with it; the tools' hidden/activated marks go with them.

func (*Registry) SetAskFunc

func (r *Registry) SetAskFunc(fn AskFunc)

SetAskFunc installs the interactive ask callback (TUI-only).

func (*Registry) SetDiscoveryMode

func (r *Registry) SetDiscoveryMode(on bool)

SetDiscoveryMode toggles dynamic tool discovery. When off (default), the hidden/activated maps are ignored and every registered tool is offered.

func (*Registry) SetHidden

func (r *Registry) SetHidden(name string, hidden bool)

SetHidden marks a tool as hidden-until-activated. Only takes effect while discovery mode is on. find_tools itself must never be hidden.

func (*Registry) SetHookRunner

func (r *Registry) SetHookRunner(runner HookRunner)

SetHookRunner sets the hook runner used for pre/post tool hooks. If runner is nil, no hooks are executed (default behavior).

func (*Registry) SetPermissionChecker

func (r *Registry) SetPermissionChecker(checker *permissions.Checker)

SetPermissionChecker sets the permission checker used to gate tool execution. If checker is nil, all tools are allowed (default behavior).

func (*Registry) SetPromptFunc

func (r *Registry) SetPromptFunc(fn PromptFunc)

SetPromptFunc configures the interactive permission prompt callback. When the permission checker returns Ask for a tool, the registry calls fn to get the user's decision. fn runs in the tool-execution goroutine (off the Bubble Tea Update loop), so it may safely block. If fn is nil (the default), any Ask decision is treated as Deny — the gate cannot be silently bypassed in headless or non-TUI contexts.

func (*Registry) Unregister

func (r *Registry) Unregister(name string)

Unregister removes a tool by name. No-op if the tool is not present.

func (*Registry) UnregisterByPrefix

func (r *Registry) UnregisterByPrefix(prefix string) int

UnregisterByPrefix removes every tool whose name starts with prefix and returns the count removed. Used to drop all tools an MCP server contributed.

type RiskRater

type RiskRater interface {
	RiskLevel(input map[string]any) string
}

RiskRater is implemented by tools that rate a call's risk from its input (bash rates its command). It returns "read", "write" or "destructive"; anything else ("" included) falls back to the name heuristic. The permission prompt shows the rating.

type RuntimeMode

type RuntimeMode int

RuntimeMode selects which registered tools a run is offered. Only chat and agent exist: the other two historical values were never queried and went with the runtime mode in 2.0 (#144).

const (
	ModeChat RuntimeMode = iota
	ModeAgent
)

type StreamingToolExecutor

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

StreamingToolExecutor accepts tool calls as they arrive during LLM streaming and dispatches them for execution. Concurrency-safe tools run in parallel goroutines; the rest run one at a time, in order. Results come back in call order from Wait. loop.Loop is its only user.

func NewStreamingToolExecutorWithContext

func NewStreamingToolExecutorWithContext(ctx context.Context, registry *Registry) *StreamingToolExecutor

NewStreamingToolExecutorWithContext creates a new executor with a parent context. Cancelling the parent context will abort all running tools.

func (*StreamingToolExecutor) AddTool

func (e *StreamingToolExecutor) AddTool(callID, toolName, inputJSON string)

AddTool submits a tool call for execution. This method is non-blocking. It parses the input JSON, looks up the tool in the registry, determines concurrency safety, and either launches a goroutine or enqueues for serial execution. If the tool is not found, the entry is immediately marked Failed.

inputJSON is the raw JSON arguments string from the LLM.

func (*StreamingToolExecutor) Cancel

func (e *StreamingToolExecutor) Cancel()

Cancel aborts all running and queued tools.

func (*StreamingToolExecutor) Done

func (e *StreamingToolExecutor) Done()

Done signals that no more tool calls will be added. Must be called before Wait() will return.

func (*StreamingToolExecutor) SetExecFunc

func (e *StreamingToolExecutor) SetExecFunc(fn ExecFunc)

SetExecFunc replaces how each call runs. Call it before AddTool.

func (*StreamingToolExecutor) Wait

Wait blocks until all tool calls have completed and returns results in the original call order. Callers must call Done() before or concurrently with Wait(), otherwise Wait() will block forever.

type Timeouter

type Timeouter interface {
	Timeout() time.Duration
}

Timeouter is implemented by tools that need their own execution timeout (a long build, a subagent, TTS). Zero means "use the caller's default".

type Tool

type Tool interface {
	Name() string
	Description() string
	Parameters() json.RawMessage
	IsConcurrencySafe(input map[string]any) bool
	IsReadOnly() bool
	ValidateInput(input map[string]any) error
	Execute(ctx context.Context, input map[string]any, progress chan<- ProgressEvent) (ToolResult, error)
	InterruptBehavior() InterruptBehavior
}

Tool defines the interface that all tools must implement.

type ToolIndex

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

ToolIndex is a BM25 index over tool name+description text.

func BuildToolIndex

func BuildToolIndex(ts []Tool) *ToolIndex

BuildToolIndex indexes each tool's name+description for BM25 search.

func (*ToolIndex) Search

func (ix *ToolIndex) Search(query string, topN int) []string

Search returns up to topN tool names ranked by BM25 score. Zero-score documents are excluded, so a query with no term overlap returns nothing.

type ToolResult

type ToolResult struct {
	Content  string         `json:"content"`
	Error    bool           `json:"error,omitempty"`
	Metadata map[string]any `json:"metadata,omitempty"`
}

ToolResult represents the output of a tool execution.

type ToolState

type ToolState int

ToolState represents the lifecycle state of a tool execution.

const (
	// ToolStateQueued means the tool is waiting to be executed.
	ToolStateQueued ToolState = iota
	// ToolStateExecuting means the tool is currently running.
	ToolStateExecuting
	// ToolStateCompleted means the tool finished successfully.
	ToolStateCompleted
	// ToolStateFailed means the tool finished with an error.
	ToolStateFailed
	// ToolStateAborted means the tool was cancelled (cascading failure or interrupt).
	ToolStateAborted
)

Directories

Path Synopsis
Package builtin provides Alchemy blockchain API handler implementation
Package builtin provides Alchemy blockchain API handler implementation
cmd/celeste/tools/mcp/adapter.go
cmd/celeste/tools/mcp/adapter.go

Jump to

Keyboard shortcuts

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