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()}")
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.
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))
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")
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}")
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
- Adapters: how provider chunks become text and events.
- Error Handling: reacting to
BlockErrorEventcodes. - Events reference: full API of every event class.