Documentation
¶
Overview ¶
Package streamaccumulator folds streaming content chunks into complete content blocks. It is a pure converter shared by the loop and the TUI/CLI live display path:
ThinkingChunk -> ThinkingBlock TextChunk -> TextBlock ToolUseChunk -> ToolUseBlock RefusalChunk -> RefusalBlock ImageChunk -> ImageBlock
It does NOT send events, validate tool permissions, decide turn failure, or know about the loop. Policy stays in the loop; this package only converts. It deliberately imports nothing beyond the standard library and internal/content (in particular, never internal/agent/loop or its event package) so it carries no dependency cycle.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Images ¶
type Images struct {
// contains filtered or unexported fields
}
Images folds streaming ImageChunk deltas into complete ImageBlocks. Like ToolUses it is keyed by the provider-supplied Index with a map rather than slice indexing, so a negative or huge Index — the value is provider/attacker-influenced — can NEVER panic or allocate an unbounded slice.
Per-image keying is load-bearing for correctness, not ordering: appending one image's bytes to another's produces a corrupt file that nothing downstream can detect or recover. Data fragments for an Index append in arrival order; URL and MediaType take the last non-empty value, since they arrive whole rather than fragmented. Blocks() emits the assembled blocks in ASCENDING Index order (the deterministic response order). The zero value is ready to use.
func (*Images) Add ¶
func (a *Images) Add(chunk *content.ImageChunk)
Add folds one delta into the accumulator, bounds-safe on any Index value. The delta's bytes are appended into the part's own buffer, so the caller may reuse or mutate its slice after the call without rewriting accumulated bytes.
func (Images) Blocks ¶
func (a Images) Blocks() []content.ImageBlock
Blocks returns the assembled ImageBlocks in ascending Index order, or nil if no chunk was received. Each block gets its OWN copy of the accumulated bytes, so mutating a returned block cannot corrupt the accumulator or a block handed to another caller. The bytes are used verbatim; this package never validates that they decode as the claimed MediaType.
type Refusal ¶
type Refusal struct {
// contains filtered or unexported fields
}
Refusal folds streamed RefusalChunk deltas into a single RefusalBlock. It mirrors Text because RefusalChunk mirrors TextChunk: refusal deltas carry no Index and concatenate losslessly (see content.RefusalChunk for why).
The received flag, not the accumulated string, decides whether a block exists. A provider may refuse with no explanation at all, and an empty refusal must still materialize a RefusalBlock: dropping it would restore the exact bug this type was added to fix — a refusal decoding as a successful empty reply. The zero value is ready to use.
func (*Refusal) Add ¶
func (a *Refusal) Add(chunk *content.RefusalChunk)
Add appends one refusal delta to the accumulator.
func (Refusal) Block ¶
func (a Refusal) Block() *content.RefusalBlock
Block returns the accumulated RefusalBlock, or nil if no chunk was received.
type Text ¶
type Text struct {
// contains filtered or unexported fields
}
Text folds streamed TextChunk deltas into a single TextBlock. The zero value is ready to use.
type Thinking ¶
type Thinking struct {
// contains filtered or unexported fields
}
Thinking folds streaming ThinkingChunk deltas into complete ThinkingBlocks. It is keyed by the provider-supplied Index the same way ToolUses is, and for the same two reasons: a response may legitimately contain SEVERAL reasoning blocks (Anthropic interleaved thinking opens a fresh thinking / redacted_thinking block around every tool call), and the Index is provider/attacker-influenced, so a map — never slice indexing — keeps a negative or huge Index from panicking or allocating an unbounded slice.
Per-block keying is load-bearing for continuation state, not a nicety. Each reasoning block carries its OWN signature or opaque provider state, and the replayed sequence must match the sequence the model generated block-for-block. Folding N blocks into one builder with a last-write-wins signature would silently rebind the last signature to the concatenation of every block's text, which the provider rejects and which the non-streaming decoders never produce — breaking the invariant that streaming preserves the same continuation state as non-streaming.
Producers that emit a single reasoning block leave Index at zero, so every delta accumulates into that one block exactly as before. Blocks() emits the assembled blocks in ASCENDING Index order (the deterministic response order). The zero value is ready to use.
func (*Thinking) Add ¶
func (a *Thinking) Add(chunk *content.ThinkingChunk)
Add folds one delta into the accumulator, bounds-safe on any Index value.
func (Thinking) Block ¶
func (a Thinking) Block() *content.ThinkingBlock
Block returns the first assembled thinking block, or nil when empty. Deprecated: use Blocks to preserve multiple provider reasoning blocks. Single-block streams retain the exact behavior of the original API.
func (Thinking) Blocks ¶
func (a Thinking) Blocks() []content.ThinkingBlock
Blocks returns the assembled ThinkingBlocks in ascending Index order, or nil if no chunk was received. Gaps in the Index sequence order the blocks but never materialize filler blocks: only indexes that actually received a delta appear.
type ToolUses ¶
type ToolUses struct {
// contains filtered or unexported fields
}
ToolUses folds streaming ToolUseChunk deltas into complete ToolUseBlocks. It is keyed by the provider-supplied Index (which is provider/attacker-influenced), so it uses a map rather than slice indexing: a negative or huge Index can NEVER panic or allocate an unbounded slice. The first delta for an Index typically carries ID/Name; later deltas carry InputJSON fragments to concatenate. Blocks() emits the assembled blocks in ASCENDING Index order (the deterministic response order). The zero value is ready to use.
func (*ToolUses) Add ¶
func (a *ToolUses) Add(chunk *content.ToolUseChunk)
Add folds one delta into the accumulator, bounds-safe on any Index value.
func (ToolUses) Blocks ¶
func (a ToolUses) Blocks() []content.ToolUseBlock
Blocks returns the assembled ToolUseBlocks in ascending Index order, or nil if no chunk was received. The raw concatenated Input is used verbatim; any validation or sanitization happens in the caller.