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