89 lines
2.5 KiB
Go
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)
|
|
}
|