// Package plain ist der stdout-Renderer für den Print-Modus (5.8): Textdeltas // nach stdout, Tool-Aktivität nach stderr. Reiner Consumer von tool.UIEvent. package plain import ( "fmt" "io" "nub/internal/tool" ) type Renderer struct { Out io.Writer Err io.Writer } // Render konsumiert den Event-Channel, bis er schließt, und liefert den // zuletzt gesehenen Fehler zurück (für die Exit-Code-Entscheidung in main). func (r *Renderer) Render(events <-chan tool.UIEvent) error { var lastErr error openText := false for ev := range events { switch e := ev.(type) { case tool.TextDelta: fmt.Fprint(r.Out, e.Text) openText = true case tool.ThinkingDelta: // v1: kein separates Rendering für Thinking im Print-Modus. case tool.ToolCallStart: if openText { fmt.Fprintln(r.Out) openText = false } fmt.Fprintf(r.Err, "→ %s %s\n", e.Name, string(e.Input)) case tool.ToolStream: fmt.Fprint(r.Err, e.Text) case tool.ToolCallOutput: status := "ok" if e.Result.IsError { status = "error" } fmt.Fprintf(r.Err, "← %s [%s]\n", e.Name, status) case tool.TurnDone: if openText { fmt.Fprintln(r.Out) openText = false } if e.Usage.InputTokens > 0 || e.Usage.OutputTokens > 0 { fmt.Fprintf(r.Err, "usage: input=%d output=%d cache_read=%d\n", e.Usage.InputTokens, e.Usage.OutputTokens, e.Usage.CacheReadTokens) } case tool.ErrorEvent: fmt.Fprintf(r.Err, "error: %v\n", e.Err) lastErr = e.Err case tool.CompactionEvent: fmt.Fprintf(r.Err, "compacted %d nodes into a %d-token summary\n", e.ReplacedNodes, e.SummaryTokens) } } return lastErr }