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 ¶
- Variables
- func CallIDFromContext(ctx context.Context) string
- func CallKeyFromContext(ctx context.Context) string
- func PermissionReadOnly(t Tool, input map[string]any) bool
- func PrimaryArg(t Tool) string
- func RankDocs(docs []string, query string) []int
- func TimeoutFor(t Tool, def time.Duration) time.Duration
- func WithAskAdvisor(ctx context.Context, a AskAdvisor) context.Context
- func WithCallID(ctx context.Context, id string) context.Context
- func WithCallKey(ctx context.Context, key string) context.Context
- func WithExecTimeout(ctx context.Context, timeout time.Duration) context.Context
- func WithPrompt(ctx context.Context, fn PromptFunc) context.Context
- func WithoutPrompt(ctx context.Context) context.Context
- type AdvisedCall
- type AskAdvisor
- type AskFunc
- type AskOption
- type AskRequest
- type AskResponse
- type ExecFunc
- type ExecutorResult
- type HookRunner
- type InternalStater
- type InterruptBehavior
- type PermissionRequest
- type PermissionResponse
- type PreToolHookResult
- type PrimaryArger
- type ProgressEvent
- type PromptFunc
- type Registry
- func (r *Registry) Activate(names ...string)
- func (r *Registry) Activated(name string) bool
- func (r *Registry) ActivatedNames() []string
- func (r *Registry) Add(tool Tool, modes ...RuntimeMode) error
- func (r *Registry) Ask(ctx context.Context, req AskRequest) (AskResponse, error)
- func (r *Registry) Count() int
- func (r *Registry) DiscoveryMode() bool
- func (r *Registry) Execute(ctx context.Context, name string, input map[string]any) (ToolResult, error)
- func (r *Registry) ExecuteWithProgress(ctx context.Context, name string, input map[string]any, ...) (ToolResult, error)
- func (r *Registry) Get(name string) (Tool, bool)
- func (r *Registry) GetAll() []Tool
- func (r *Registry) GetToolDefinitions() []map[string]any
- func (r *Registry) GetTools(mode RuntimeMode) []Tool
- func (r *Registry) LoadCustomTools(dir string) error
- func (r *Registry) Overwritten() []string
- func (r *Registry) Prompt() PromptFunc
- func (r *Registry) Register(tool Tool)
- func (r *Registry) RegisterWithModes(tool Tool, modes ...RuntimeMode)
- func (r *Registry) Retain(keep func(Tool) bool) int
- func (r *Registry) SetAskFunc(fn AskFunc)
- func (r *Registry) SetDiscoveryMode(on bool)
- func (r *Registry) SetHidden(name string, hidden bool)
- func (r *Registry) SetHookRunner(runner HookRunner)
- func (r *Registry) SetPermissionChecker(checker *permissions.Checker)
- func (r *Registry) SetPromptFunc(fn PromptFunc)
- func (r *Registry) Unregister(name string)
- func (r *Registry) UnregisterByPrefix(prefix string) int
- type RiskRater
- type RuntimeMode
- type StreamingToolExecutor
- type Timeouter
- type Tool
- type ToolIndex
- type ToolResult
- type ToolState
Constants ¶
This section is empty.
Variables ¶
var ErrToolNameTaken = errors.New("tool name already registered")
ErrToolNameTaken is returned by Add when another tool holds the name.
Functions ¶
func CallIDFromContext ¶
CallIDFromContext returns the ID WithCallID recorded, or "".
func CallKeyFromContext ¶
CallKeyFromContext returns the key WithCallKey recorded, or "".
func PermissionReadOnly ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 (*Registry) Activate ¶
Activate re-exposes hidden tools for the rest of the session (called by find_tools after a BM25 match).
func (*Registry) Activated ¶
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 ¶
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) DiscoveryMode ¶
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) GetToolDefinitions ¶
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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
SetAskFunc installs the interactive ask callback (TUI-only).
func (*Registry) SetDiscoveryMode ¶
SetDiscoveryMode toggles dynamic tool discovery. When off (default), the hidden/activated maps are ignored and every registered tool is offered.
func (*Registry) SetHidden ¶
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 ¶
Unregister removes a tool by name. No-op if the tool is not present.
func (*Registry) UnregisterByPrefix ¶
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 ¶
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 ¶
func (e *StreamingToolExecutor) Wait() []ExecutorResult
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 ¶
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 ¶
BuildToolIndex indexes each tool's name+description for BM25 search.
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 )