Skip to content

Events

Everything the processor sees becomes an event: stream lifecycle, text outside blocks, and every stage of a block's life. This page is the authoritative map of the event model.

Consuming events

All events are immutable Pydantic models deriving from BaseEvent. The Event type is a discriminated union (on the type field) of every concrete event class, so the idiomatic consumption pattern is one isinstance check per class you care about:

async for event in processor.process_stream(example_stream()):
    # Handle lifecycle events
    if isinstance(event, BlockStartEvent):
        print(f"[START] Block opened at line {event.start_line}")
        print(f"        Syntax: {event.syntax}")
        print(f"        Block ID: {event.block_id}")
        print()

    # Handle section-specific delta events with type safety
    elif isinstance(event, BlockHeaderDeltaEvent):
        # Type-safe: isinstance check works!
        header_count += 1
        print(f"[HEADER] Line {event.current_line}: {event.delta!r}")

        # Header-specific field: inline_metadata
        if event.inline_metadata:
            print(f"         Inline metadata: {event.inline_metadata}")

    elif isinstance(event, BlockMetadataDeltaEvent):
        # Type-safe handling of metadata section
        metadata_count += 1
        metadata_lines.append(event.delta)

        # Metadata-specific field: is_boundary
        boundary_marker = " [BOUNDARY]" if event.is_boundary else ""
        print(f"[META]   Line {event.current_line}: {event.delta!r}{boundary_marker}")

    elif isinstance(event, BlockContentDeltaEvent):
        # Type-safe handling of content section
        content_count += 1
        content_lines.append(event.delta)
        print(f"[CONTENT] Line {event.current_line}: {event.delta!r}")
        print(f"          Accumulated size: {event.accumulated_size} bytes")

    # Handle completion
    elif isinstance(event, BlockEndEvent):
        print()
        print(f"[END] Block completed: {event.block_id}")
        print(f"      Lines: {event.start_line}-{event.end_line}")
        print(f"      Type: {event.block_type}")

    elif isinstance(event, TextContentEvent):
        # Regular text outside blocks
        if event.content.strip():
            print(f"[TEXT] {event.content.strip()}")

View source on GitHub

Every event shares three fields from BaseEvent:

Field Type Description
timestamp int Unix timestamp in milliseconds, auto-generated
event_id str Unique identifier (UUID), auto-generated
raw_event Any \| None Original provider event, preserved by adapters

The per-event tables below list only the fields each event adds.

Native provider events in the stream

StreamBlockProcessor.process_stream() yields TChunk | Event: when emit_original_events=True (the default), the original provider chunks are interleaved with StreamBlocks events. Use processor.is_native_event(event) to tell them apart without coupling to a provider.

Event flow for one block

For each block, events arrive in a fixed order. Which sections appear depends on the syntax: frontmatter syntaxes have a metadata section, the preamble syntax carries metadata inline in the header.

LLM streamProcessorYour app !!plan01:taskBLOCK_STARTBLOCK_HEADER_DELTAmetadata linesBLOCK_METADATA_DELTABLOCK_METADATA_END (parsed, validated)content linesBLOCK_CONTENT_DELTABLOCK_CONTENT_END!!endBLOCK_END (typed block)
LLM streamProcessorYour app !!plan01:taskBLOCK_STARTBLOCK_HEADER_DELTAmetadata linesBLOCK_METADATA_DELTABLOCK_METADATA_END (parsed, validated)content linesBLOCK_CONTENT_DELTABLOCK_CONTENT_END!!endBLOCK_END (typed block)

A block can also fail earlier, for example BLOCK_ERROR with UNCLOSED_BLOCK at end of stream, or with VALIDATION_FAILED right after BLOCK_METADATA_END when early metadata validation aborts the block.

Overview

EventType Event class Emitted when
STREAM_STARTED StreamStartedEvent Stream processing begins
STREAM_FINISHED StreamFinishedEvent Stream completes
STREAM_ERROR StreamErrorEvent Stream processing fails
TEXT_CONTENT TextContentEvent A complete line outside any block
TEXT_DELTA TextDeltaEvent A raw text chunk, before line completion
BLOCK_START BlockStartEvent Block opening marker detected
BLOCK_HEADER_DELTA BlockHeaderDeltaEvent Content added to the header section
BLOCK_METADATA_DELTA BlockMetadataDeltaEvent Content added to the metadata section
BLOCK_CONTENT_DELTA BlockContentDeltaEvent Content added to the content section
BLOCK_METADATA_END BlockMetadataEndEvent Metadata section complete and parsed
BLOCK_CONTENT_END BlockContentEndEvent Content section complete and parsed
BLOCK_END BlockEndEvent Block extracted and validated
BLOCK_ERROR BlockErrorEvent Block extraction failed
CUSTOM CustomEvent Application-specific events

Lifecycle events

StreamStartedEvent

First event of every stream.

Field Type Description
stream_id str Identifier of this stream
registry_name str \| None Name of the registry in use

StreamFinishedEvent

Last event of a successful stream, with summary counters.

Field Type Description
stream_id str Identifier of this stream
blocks_extracted int Successfully extracted blocks
blocks_rejected int Rejected blocks
total_events int Total events emitted
duration_ms int \| None Processing duration

StreamErrorEvent

Stream-level failure (not a single block failing).

Field Type Description
stream_id str Identifier of this stream
error str Error description
error_code str \| None Optional machine-readable code

Text events

TextContentEvent

A complete line of text outside any block. Nothing in the stream is lost: prose between blocks arrives here.

Field Type Description
content str The line content
line_number int Line position in the stream

TextDeltaEvent

Emitted immediately as text arrives, before lines complete. Each delta knows whether it is inside a block and which section it belongs to, ideal for typewriter effects and live UIs:

Field Type Description
delta str The raw text chunk
inside_block bool Whether the chunk falls inside a block
block_id str \| None Owning block, if inside one
section str \| None "header", "metadata", or "content"
async for event in processor.process_stream(character_stream()):
    if isinstance(event, TextDeltaEvent):
        # Print each character immediately (typewriter effect)
        sys.stdout.write(event.delta)
        sys.stdout.flush()

        # Show context
        if event.inside_block:
            context = f" [{event.section}]"
            # Clear the context indicator on newlines
            if event.delta == "\n":
                sys.stdout.write(f"  {context}\n")

    elif isinstance(event, BlockStartEvent):
        sys.stdout.write(f"\n[Block starting: {event.syntax}]\n")

    elif isinstance(event, BlockEndEvent):
        block = event.get_block()
        if block is None:
            continue
        print("\n[Block extracted:]")
        print(block.model_dump_json(indent=2))

View source on GitHub

Block events

BlockStartEvent

Fires as soon as the opening marker is detected, before any content. Use it to create UI elements or allocate resources early:

Field Type Description
block_id str Block identifier
block_type str \| None Type, if already known from the opening line
syntax str Detecting syntax name
start_line int Line where the block opened
inline_metadata dict \| None Metadata parsed from the opening line (preamble syntax)
async for event in processor.process_stream(delayed_stream()):
    if isinstance(event, BlockStartEvent):
        print("🟢 BlockOpened!")
        print(f"   Syntax: {event.syntax}")
        print(f"   Line: {event.start_line}")

        if event.inline_metadata:
            print(f"   Metadata: {event.inline_metadata}")

        # Prepare resources (e.g., create UI widget)
        active_blocks[event.start_line] = {
            "syntax": event.syntax,
            "content": [],
        }

        print("   → UI widget created")
        print("   → Waiting for content...")
        print()

    elif isinstance(event, TextDeltaEvent):
        # Accumulate content for active blocks
        if event.inside_block and active_blocks:
            line = next(iter(active_blocks.keys()))
            active_blocks[line]["content"].append(event.delta)
            print(f"   📝 Accumulating: {repr(event.delta)[:30]}")

    elif isinstance(event, BlockEndEvent):
        block = event.get_block()
        if block is None:
            continue
        print("\n✅ BlockExtracted:")
        print(block.model_dump_json(indent=2))

        # Clean up
        active_blocks.clear()
        print("   → UI widget finalized\n")

View source on GitHub

Section delta events

BlockHeaderDeltaEvent, BlockMetadataDeltaEvent, and BlockContentDeltaEvent stream a block's three sections as they accumulate. All three share:

Field Type Description
block_id str Owning block
delta str Text added to the section
syntax str Detecting syntax name
current_line int Current line number
accumulated_size int Bytes accumulated so far (drives max_block_size)

Per-event extras:

Event Extra field Description
BlockHeaderDeltaEvent inline_metadata: dict \| None Metadata parsed from the header line
BlockMetadataDeltaEvent is_boundary: bool Whether this delta is a --- boundary line
BlockContentDeltaEvent none

BlockMetadataEndEvent

Fires when the metadata section completes, before content begins. This enables early validation: combined with MetadataValidationFailureMode.ABORT_BLOCK, a failing metadata validator aborts the block before its content streams in (see the Validation guide).

Field Type Description
block_id str Owning block
syntax str Detecting syntax name
start_line / end_line int Section bounds
raw_metadata str Raw metadata text
parsed_metadata dict \| None Parsed metadata
validation_passed bool Early validation outcome
validation_error str \| None Failure detail
async for event in processor.process_stream(simulated_stream(text, preset="fast")):
    if isinstance(event, BlockStartEvent):
        print("  [BlockStart] Block opened")
    elif isinstance(event, BlockMetadataEndEvent):
        print("  [MetadataEnd] Metadata section complete!")
        print(f"    - Parsed metadata: {event.parsed_metadata}")
        print(f"    - Validation passed: {event.validation_passed}")
    elif isinstance(event, BlockContentEndEvent):
        print("  [ContentEnd] Content section complete")
    elif isinstance(event, BlockEndEvent):
        print(f"  [BlockEnd] Block extracted: {event.block_type}")

View source on GitHub

BlockContentEndEvent

Mirror of the metadata end event for the content section, emitted just before final extraction.

Field Type Description
block_id str Owning block
syntax str Detecting syntax name
start_line / end_line int Section bounds
raw_content str Raw content text
parsed_content dict \| None Parsed content
validation_passed bool Early validation outcome
validation_error str \| None Failure detail

BlockEndEvent

The block was extracted and validated. get_block() returns the typed ExtractedBlock.

Field Type Description
block_id str Block identifier
block_type str Registered block type
syntax str Detecting syntax name
start_line / end_line int Block bounds in the stream
metadata dict Extracted metadata (serializable form)
content dict Extracted content (serializable form)
raw_content str Original unparsed body
hash_id str Stable hash for deduplication/caching

BlockErrorEvent

Block extraction failed; the stream keeps processing. error_code values are covered in the Error Handling guide.

Field Type Description
block_id str \| None Block identifier, if known
reason str Human-readable failure reason
error_code BlockErrorCode \| None VALIDATION_FAILED, SIZE_EXCEEDED, UNCLOSED_BLOCK, ...
syntax str Detecting syntax name
start_line / end_line int / int \| None Block bounds
exception Exception \| None Original exception (excluded from serialization)

Custom events

CustomEvent

Application-specific events, also used by the AG-UI output adapter to wrap block events.

Field Type Description
name str Event name (e.g. streamblocks.block_end)
value dict Arbitrary payload

Controlling event volume

Three ProcessorConfig flags gate event emission (all default to True):

Flag Gates Disable when
emit_original_events Passthrough of native provider chunks You only need StreamBlocks events
emit_text_deltas TextDeltaEvent Batch processing; line-level events suffice
emit_section_end_events BlockMetadataEndEvent, BlockContentEndEvent No early validation needed
# Disable section end events for maximum performance
config = ProcessorConfig(emit_section_end_events=False)  # Opt-out
processor = StreamBlockProcessor(registry, config=config)

See Performance Tuning for the trade-offs, and the configuration flags example for all combinations in action.

Next steps