This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/tools/registry.go
2026-03-08 16:54:17 +00:00

124 lines
3.5 KiB
Go

package tools
import (
"context"
"encoding/json"
"fmt"
"log"
)
// ── Registry ────────────────────────────────
var registry = map[string]Tool{}
// Register adds a tool to the global registry. Called at init time.
func Register(t Tool) {
def := t.Definition()
if _, exists := registry[def.Name]; exists {
panic("tools.Register: duplicate tool name: " + def.Name)
}
registry[def.Name] = t
log.Printf("🔧 Registered tool: %s", def.Name)
}
// Get returns a tool by name, or nil if not found.
func Get(name string) Tool {
return registry[name]
}
// AllDefinitions returns tool definitions for all registered tools.
// Used to populate the `tools` field in LLM requests.
func AllDefinitions() []ToolDef {
defs := make([]ToolDef, 0, len(registry))
for _, t := range registry {
defs = append(defs, t.Definition())
}
return defs
}
// AllDefinitionsFiltered returns tool definitions excluding any names in the
// disabled set. Used when the frontend sends a disabled_tools list.
//
// Deprecated: use AvailableFor() for context-aware filtering. This wrapper
// remains for call sites that don't yet have a ToolContext.
func AllDefinitionsFiltered(disabled map[string]bool) []ToolDef {
return AvailableFor(ToolContext{}, disabled)
}
// AvailableFor returns tool definitions available in the given context,
// excluding any names in the disabled set. Tools that implement
// ContextualTool have their Availability() predicate evaluated against
// tctx. Tools that only implement Tool (no predicate) are always included.
func AvailableFor(tctx ToolContext, disabled map[string]bool) []ToolDef {
defs := make([]ToolDef, 0, len(registry))
for _, t := range registry {
def := t.Definition()
if disabled[def.Name] {
continue
}
// Check context-aware availability if the tool declares it
if ct, ok := t.(ContextualTool); ok {
if !ct.Availability()(tctx) {
continue
}
}
defs = append(defs, def)
}
return defs
}
// ── Execution ───────────────────────────────
// ExecuteCall runs a single tool call and returns a ToolResult.
// Never returns an error — failures are encoded in the result content
// so the LLM can see what went wrong and adjust.
func ExecuteCall(ctx context.Context, execCtx ExecutionContext, call ToolCall) ToolResult {
tool := Get(call.Name)
if tool == nil {
return ToolResult{
ToolCallID: call.ID,
Name: call.Name,
Content: jsonError(fmt.Sprintf("unknown tool: %s", call.Name)),
IsError: true,
}
}
result, err := tool.Execute(ctx, execCtx, call.Arguments)
if err != nil {
log.Printf("Tool %s error: %v", call.Name, err)
return ToolResult{
ToolCallID: call.ID,
Name: call.Name,
Content: jsonError(err.Error()),
IsError: true,
}
}
return ToolResult{
ToolCallID: call.ID,
Name: call.Name,
Content: result,
}
}
// ExecuteAll runs multiple tool calls and returns all results.
func ExecuteAll(ctx context.Context, execCtx ExecutionContext, calls []ToolCall) []ToolResult {
results := make([]ToolResult, len(calls))
for i, call := range calls {
results[i] = ExecuteCall(ctx, execCtx, call)
}
return results
}
// HasTools returns true if any tools are registered.
func HasTools() bool {
return len(registry) > 0
}
// ── Helpers ─────────────────────────────────
func jsonError(msg string) string {
b, _ := json.Marshal(map[string]string{"error": msg})
return string(b)
}