56 lines
1.3 KiB
Go
56 lines
1.3 KiB
Go
package agentruntime
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
)
|
|
|
|
// IsTerminalEvent reports whether an event closes one runtime execution.
|
|
func IsTerminalEvent(eventType EventType) bool {
|
|
switch eventType {
|
|
case EventTypeComplete, EventTypeError, EventTypeCancelled:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// TerminalEmitter enforces at-most-one terminal event while preserving the
|
|
// wrapped sink's ordering. Duplicate terminal and post-terminal events are
|
|
// ignored so a host cannot expose more than one terminal boundary.
|
|
type TerminalEmitter struct {
|
|
sink EventSink
|
|
|
|
emitMu sync.Mutex
|
|
mu sync.Mutex
|
|
terminal bool
|
|
}
|
|
|
|
// NewTerminalEmitter wraps sink with terminal exactly-once protection.
|
|
func NewTerminalEmitter(sink EventSink) *TerminalEmitter {
|
|
return &TerminalEmitter{sink: sink}
|
|
}
|
|
|
|
// Emit forwards the event unless the terminal boundary was already observed.
|
|
func (e *TerminalEmitter) Emit(ctx context.Context, event RuntimeEvent) error {
|
|
e.emitMu.Lock()
|
|
defer e.emitMu.Unlock()
|
|
|
|
e.mu.Lock()
|
|
if e.terminal {
|
|
e.mu.Unlock()
|
|
return nil
|
|
}
|
|
if IsTerminalEvent(event.Type) {
|
|
e.terminal = true
|
|
}
|
|
e.mu.Unlock()
|
|
return e.sink.Emit(ctx, event)
|
|
}
|
|
|
|
// TerminalObserved reports whether a terminal event was accepted.
|
|
func (e *TerminalEmitter) TerminalObserved() bool {
|
|
e.mu.Lock()
|
|
defer e.mu.Unlock()
|
|
return e.terminal
|
|
}
|