first commit

This commit is contained in:
2026-08-14 23:41:57 +08:00
commit 086803a8dd
471 changed files with 91938 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
package agentcore
import (
"context"
"errors"
"sync"
)
// EventStream is the Go equivalent of pi's EventStream<T,R>: a producer pushes
// events onto a channel while a consumer ranges over them, and a terminal event
// yields a final result R. It replaces pi's async generator (event-stream.ts).
//
// Design (research §2.2):
// - Iteration: Events() returns <-chan T for `for ev := range s.Events()`.
// - Result: Result(ctx) blocks until the producer sets a result (or the
// stream fails/cancels). The result is NOT sent on the event channel, so a
// consumer that stops reading events can still obtain it.
// - Cancellation: the producer selects on ctx.Done() when sending, so a
// consumer that stops reading never leaks the producer goroutine.
//
// pi's isComplete/extractResult callbacks are retained as optional fields so a
// producer can let the stream detect the terminal event itself; a producer may
// instead call SetResult explicitly (more Go-idiomatic). Either path resolves
// Result exactly once.
type EventStream[T any, R any] struct {
ch chan T
// IsComplete reports whether an event is the terminal one. Optional: if
// set, Emit auto-captures the result via ExtractResult when it returns true.
IsComplete func(event T) bool
// ExtractResult derives the final result from the terminal event. Required
// when IsComplete is set.
ExtractResult func(event T) R
resultOnce sync.Once
result R
resultErr error
resultCh chan struct{} // closed once result (or resultErr) is set
}
// ErrStreamIncomplete is returned by Result when the event channel closed
// without any result being set (the producer ended abnormally without a
// terminal event).
var ErrStreamIncomplete = errors.New("agent: event stream ended without a result")
// NewEventStream constructs an EventStream with the given channel buffer size.
// A buffer of 0 gives fully synchronous back-pressure (each Emit blocks until a
// consumer receives), matching pi's sequential `await emit(...)`.
func NewEventStream[T any, R any](buffer int) *EventStream[T, R] {
if buffer < 0 {
buffer = 0
}
return &EventStream[T, R]{
ch: make(chan T, buffer),
resultCh: make(chan struct{}),
}
}
// Events returns the receive-only event channel. Ranging over it terminates
// when the producer calls Close.
func (s *EventStream[T, R]) Events() <-chan T { return s.ch }
// Emit sends an event to consumers, honoring cancellation. If ctx is cancelled
// before the event is received, Emit returns ctx.Err() and the event is
// dropped. When IsComplete is configured and reports true for the event, the
// result is captured (once) before the send.
func (s *EventStream[T, R]) Emit(ctx context.Context, event T) error {
if s.IsComplete != nil && s.IsComplete(event) && s.ExtractResult != nil {
s.SetResult(s.ExtractResult(event))
}
select {
case s.ch <- event:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
// SetResult records the final result. Only the first call wins; later calls
// (including SetError) are no-ops. Safe to call before Close.
func (s *EventStream[T, R]) SetResult(result R) {
s.resultOnce.Do(func() {
s.result = result
close(s.resultCh)
})
}
// SetError records a terminal error as the stream's outcome. Only the first
// call among SetResult/SetError wins.
func (s *EventStream[T, R]) SetError(err error) {
s.resultOnce.Do(func() {
s.resultErr = err
close(s.resultCh)
})
}
// Close closes the event channel, ending consumer iteration. If no result was
// set, Result will report ErrStreamIncomplete. Call exactly once from the
// producer after the last Emit.
func (s *EventStream[T, R]) Close() {
// Ensure a waiting Result never blocks forever if the producer forgot to
// set a result.
s.resultOnce.Do(func() {
s.resultErr = ErrStreamIncomplete
close(s.resultCh)
})
close(s.ch)
}
// Result blocks until the producer sets a result/error, ctx is cancelled, or
// the stream closes without a result. It is safe to call concurrently and
// returns the same outcome on every call.
func (s *EventStream[T, R]) Result(ctx context.Context) (R, error) {
select {
case <-s.resultCh:
return s.result, s.resultErr
case <-ctx.Done():
var zero R
return zero, ctx.Err()
}
}