// Package tools contains the tool registry and built-in safe tools. package tools import ( "context" "encoding/json" "fmt" "sort" "github.com/crypto/byteclaw/internal/llm" ) type Tool interface { Spec() llm.ToolSpec Call(ctx context.Context, args json.RawMessage) (string, error) } type Registry struct{ tools map[string]Tool } func NewRegistry(ts ...Tool) *Registry { r := &Registry{tools: map[string]Tool{}} for _, t := range ts { r.Register(t) } return r } func (r *Registry) Register(t Tool) { r.tools[t.Spec().Name] = t } func (r *Registry) Specs() []llm.ToolSpec { out := make([]llm.ToolSpec, 0, len(r.tools)) for _, t := range r.tools { out = append(out, t.Spec()) } sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name }) return out } // Call runs a tool; errors are returned as text so the model can react to them. func (r *Registry) Call(ctx context.Context, name, args string) string { t, ok := r.tools[name] if !ok { return fmt.Sprintf("error: unknown tool %q", name) } if args == "" { args = "{}" } if !json.Valid([]byte(args)) { return "error: arguments are not valid JSON" } res, err := t.Call(ctx, json.RawMessage(args)) if err != nil { return "error: " + err.Error() } return res } func obj(props map[string]any, required ...string) map[string]any { m := map[string]any{"type": "object", "properties": props} if len(required) > 0 { m["required"] = required } return m }