nub/internal/tool/tool.go
Tom a97013d876 initial commit
- v 0.1.0 siehe CHANGELOG.md
2026-07-25 11:08:01 +02:00

89 lines
2.5 KiB
Go

// Package tool definiert das Tool-Interface, Ergebnis-Typen und die Registry,
// über die der Agent-Loop Tools parallel ausführt.
package tool
import (
"context"
"encoding/json"
"fmt"
"sort"
)
type Tool interface {
Name() string
Description() string
Schema() json.RawMessage
Run(ctx context.Context, input json.RawMessage, env Env) (Result, error)
}
// Result ist zweigeteilt (E-05): ForModel geht knapp in den tool_result-Block,
// ForUI ist strukturiert (z.B. ein Diff) und für den jeweiligen Renderer gedacht.
type Result struct {
ForModel string
ForUI any
IsError bool
}
type Env struct {
Cwd string
RepoRoot string
Emit func(UIEvent) // für streamende Tools (bash)
// Ask stellt dem Nutzer eine Rückfrage und blockiert auf die Antwort.
// nil (typischerweise im Print-Modus) bedeutet: interaktive Rückfragen
// werden hier nicht unterstützt — das question-Tool meldet das dann als
// klaren Fehler statt zu hängen.
Ask func(ctx context.Context, question string, options []string) (string, error)
}
// Registry hält die zur Laufzeit verfügbaren Tools (built-in + MCP-Bridge).
type Registry struct {
tools map[string]Tool
}
func NewRegistry() *Registry {
return &Registry{tools: make(map[string]Tool)}
}
func (r *Registry) Register(t Tool) {
r.tools[t.Name()] = t
}
func (r *Registry) Get(name string) (Tool, bool) {
t, ok := r.tools[name]
return t, ok
}
// Defs liefert die Tool-Definitionen in stabiler (nach Name sortierter)
// Reihenfolge. Wichtig für Prompt-Caching (E-06): tools steht im Request vor
// messages und ist Teil des gecachten Prefix — Go-Map-Iteration ist pro
// Aufruf zufällig, eine unsortierte Ausgabe hätte den Cache bei jedem
// einzelnen Turn neu gebrochen.
func (r *Registry) Defs() []Def {
defs := make([]Def, 0, len(r.tools))
for _, t := range r.tools {
defs = append(defs, Def{
Name: t.Name(),
Description: t.Description(),
Schema: t.Schema(),
})
}
sort.Slice(defs, func(i, j int) bool { return defs[i].Name < defs[j].Name })
return defs
}
type Def struct {
Name string
Description string
Schema json.RawMessage
}
// Run führt ein registriertes Tool aus; unbekannte Namen liefern ein
// Fehlerergebnis statt eines Panics (jeder tool_use braucht ein Ergebnis).
func (r *Registry) Run(ctx context.Context, name string, input json.RawMessage, env Env) (Result, error) {
t, ok := r.tools[name]
if !ok {
return Result{ForModel: fmt.Sprintf("unknown tool: %s", name), IsError: true}, nil
}
return t.Run(ctx, input, env)
}