64 lines
1.4 KiB
Go
64 lines
1.4 KiB
Go
// 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
|
|
}
|