Documentation
¶
Overview ¶
Package streamemit provides a decoupled emission layer on top of the Anthropic stream assemblers in internal/protocol/assembler.
A StreamEmitter routes incoming Anthropic stream events to per-kind sub-buffers and applies a configurable EmissionPolicy per kind. The supported policies are EmitImmediate (the default — events flow out 1:1 as they arrive) and EmitOnComplete (events for a content block are buffered from content_block_start through content_block_stop and flushed as a single ordered slice on stop).
The primary use case is "hold tool_use blocks until the assembler has the complete tool call" while still streaming text and thinking to the consumer live. This mirrors the buffer/decision pattern used by internal/guardrails/mutate for credential masking, but generalizes it so non-guardrails callers can compose the same shape.
The emitter owns its inner *assembler.AnthropicStreamAssembler. It always feeds every event to the inner assembler, regardless of any buffering, so MessageAssembler() and Finish() always reflect the full stream so far. Callers that today drive their own assembler in parallel (e.g. internal/server/scenario_recording.go) should pick one or the other to avoid double-feeding.
Scope: this package supports Anthropic v1 (MessageStreamEventUnion) and v1beta (BetaRawMessageStreamEventUnion). OpenAI Chat and OpenAI Responses streams are out of scope.
Index ¶
- Variables
- type BlockKind
- type BufferedEvent
- type Config
- type EmissionPolicy
- type StreamEmitter
- func (e *StreamEmitter) Drain() []BufferedEvent
- func (e *StreamEmitter) Feed(event interface{}) ([]BufferedEvent, error)
- func (e *StreamEmitter) FeedV1(evt *anthropic.MessageStreamEventUnion) ([]BufferedEvent, error)
- func (e *StreamEmitter) FeedV1Beta(evt *anthropic.BetaRawMessageStreamEventUnion) ([]BufferedEvent, error)
- func (e *StreamEmitter) Finish(model string, inputTokens, outputTokens int) ([]BufferedEvent, *anthropic.Message)
- func (e *StreamEmitter) MessageAssembler() *assembler.AnthropicStreamAssembler
- func (e *StreamEmitter) ToolBuffer(index int) ([]BufferedEvent, bool)
- type ToolDecision
Constants ¶
This section is empty.
Variables ¶
var ErrMixedVersions = errors.New("streamemit: cannot mix v1 and v1beta events on a single emitter")
ErrMixedVersions is returned by Feed when an emitter that has already processed v1 events receives a v1beta event (or vice versa).
Functions ¶
This section is empty.
Types ¶
type BlockKind ¶
type BlockKind uint8
BlockKind is the assembler-level classification of a content block. We collapse the SDK's many tool-result variants into a single ToolUse kind because the routing decision is the same for all of them: hold until complete, or pass through.
type BufferedEvent ¶
type BufferedEvent = protocol.GuardrailsBufferedEvent
BufferedEvent is one Anthropic SSE event ready to be sent to the consumer.
It is a type alias of protocol.GuardrailsBufferedEvent so the output of this package is byte-compatible with the guardrails rewrite layer's buffered events: callers can append slices from both sources and feed them into the same emitter without conversion.
type Config ¶
type Config struct {
// TextPolicy governs "text" content blocks.
TextPolicy EmissionPolicy
// ThinkingPolicy governs "thinking" and "redacted_thinking" content blocks.
ThinkingPolicy EmissionPolicy
// ToolPolicy governs "tool_use" content blocks. Setting this to
// EmitOnComplete is the primary use case for this package: tool events
// are held until the tool's content_block_stop arrives.
ToolPolicy EmissionPolicy
// OnToolBlockComplete is invoked when a tool_use block has finished
// buffering under EmitOnComplete and is about to be released. The hook
// receives the tool_use id, the content block index, and the buffered
// events in arrival order. It may return a *ToolDecision to replace or
// drop the buffered events; returning nil flushes them as-is. An error
// short-circuits Feed and is returned to the caller.
OnToolBlockComplete func(toolID string, index int, buffered []BufferedEvent) (*ToolDecision, error)
}
Config configures a StreamEmitter. The zero value emits everything immediately and installs no hooks.
type EmissionPolicy ¶
type EmissionPolicy int
EmissionPolicy controls when events for a given block kind are released from the emitter to the caller.
const ( // EmitImmediate releases each event as soon as it is fed to the emitter. // This is the zero value and matches today's passthrough behavior. EmitImmediate EmissionPolicy = iota // EmitOnComplete buffers all events for a single content block from // content_block_start through content_block_stop, then releases them // as one ordered slice when the stop event arrives. EmitOnComplete )
type StreamEmitter ¶
type StreamEmitter struct {
// contains filtered or unexported fields
}
StreamEmitter routes Anthropic stream events through a configurable emission policy. See package doc for usage.
func New ¶
func New(cfg Config) *StreamEmitter
New constructs a StreamEmitter with the given configuration.
func (*StreamEmitter) Drain ¶
func (e *StreamEmitter) Drain() []BufferedEvent
Drain flushes every still-open tool buffer, returning the buffered events in ascending block-index order, and clears the buffers. Useful at end of stream or on error when the caller wants whatever has been accumulated.
Drain does NOT call OnToolBlockComplete — it is a salvage path, not a completion signal.
func (*StreamEmitter) Feed ¶
func (e *StreamEmitter) Feed(event interface{}) ([]BufferedEvent, error)
Feed accepts either a *anthropic.MessageStreamEventUnion (v1) or a *anthropic.BetaRawMessageStreamEventUnion (v1beta) and returns the events the caller should send to the consumer right now.
An emitter is pinned to its first version; mixing versions returns ErrMixedVersions.
func (*StreamEmitter) FeedV1 ¶
func (e *StreamEmitter) FeedV1(evt *anthropic.MessageStreamEventUnion) ([]BufferedEvent, error)
FeedV1 routes a v1 Anthropic stream event through the emitter and returns the (possibly empty) events to send to the consumer right now.
func (*StreamEmitter) FeedV1Beta ¶
func (e *StreamEmitter) FeedV1Beta(evt *anthropic.BetaRawMessageStreamEventUnion) ([]BufferedEvent, error)
FeedV1Beta routes a v1beta Anthropic stream event through the emitter and returns the events to send to the consumer right now.
func (*StreamEmitter) Finish ¶
func (e *StreamEmitter) Finish(model string, inputTokens, outputTokens int) ([]BufferedEvent, *anthropic.Message)
Finish drains any remaining tool buffers and returns the assembled *anthropic.Message produced by the inner assembler.
model, inputTokens, outputTokens are forwarded to AnthropicStreamAssembler.Finish.
func (*StreamEmitter) MessageAssembler ¶
func (e *StreamEmitter) MessageAssembler() *assembler.AnthropicStreamAssembler
MessageAssembler returns the inner *AnthropicStreamAssembler so callers can inspect message-side accumulation independently of tool buffering. Callers should treat it as read-only — feeding events into it directly will desync it from the emitter's routing state.
func (*StreamEmitter) ToolBuffer ¶
func (e *StreamEmitter) ToolBuffer(index int) ([]BufferedEvent, bool)
ToolBuffer returns a copy of the events currently buffered for the given content block index. The bool is false when no buffer exists for that index (either it was never tool_use, it was emitted immediately, or it has already been flushed).
type ToolDecision ¶
type ToolDecision struct {
Replace []BufferedEvent
Drop bool
}
ToolDecision is the return value of Config.OnToolBlockComplete.
If Drop is true the buffered events are suppressed entirely. Otherwise, if Replace is non-nil those events are emitted instead of the buffered ones. A nil ToolDecision (or a zero-value one) means "flush the buffered events unchanged".