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 } // 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) }