package tools
import (
"context"
"io"
)
// Progress is where a long-running tool reports output it has produced but not
// finished producing. It carries deltas, not the whole output so far: the
// consumer accumulates if it needs to, and a front end that only wants to show
// the last line does not have to diff two strings to find it.
//
// It may be called from more than one goroutine — a command's stdout and
// stderr are copied by separate ones — so an implementation must be safe for
// concurrent use.
type Progress func(chunk string)
type progressKey struct{}
// WithProgress attaches a sink to ctx. The agent loop installs one around each
// tool call so the tool does not have to know who is watching, or whether
// anyone is.
func WithProgress(ctx context.Context, p Progress) context.Context {
if p == nil {
return ctx
}
return context.WithValue(ctx, progressKey{}, p)
}
// ProgressFrom returns the sink on ctx, or nil when nothing is watching.
// A tool that finds nothing here should do no extra work at all: the cost of
// streaming is only worth paying when it is going somewhere.
func ProgressFrom(ctx context.Context) Progress {
p, _ := ctx.Value(progressKey{}).(Progress)
return p
}
// progressWriter feeds an io.Writer chain into a sink.
type progressWriter struct{ p Progress }
func (w progressWriter) Write(b []byte) (int, error) {
if len(b) > 0 {
w.p(string(b))
}
return len(b), nil
}
// tee returns w, plus a copy into the sink when there is one. A nil sink gives
// back exactly what was passed in, so the no-observer path allocates nothing
// and copies nothing.
func tee(w io.Writer, p Progress) io.Writer {
if p == nil {
return w
}
return io.MultiWriter(w, progressWriter{p})
}