feat(streaming): add EventStreamer for iterating stream events - #1567
Draft
ajbozarth wants to merge 1 commit into
Draft
feat(streaming): add EventStreamer for iterating stream events#1567ajbozarth wants to merge 1 commit into
ajbozarth wants to merge 1 commit into
Conversation
stream(as_events=True) returns an EventStreamer that yields a stream's typed StreamEvent objects via async-for, instead of the Streamer's str chunks. It runs the stream to completion on a background task and delivers events through a queue, so the terminal CompletedEvent and ErrorEvent reach the consumer on every exit path — including an early break — which iterating the driver's generator cannot guarantee. Additive and opt-in: the default stream() chunk contract is unchanged. Events are threaded to an optional per-stream queue in _emit_event; the STREAMING_EVENT hook still fires on all paths, so telemetry is unaffected. stream() is split into _stream() plus a dispatching wrapper with typed overloads on as_events. Tests, typing checks, and docs are deferred to follow-ups. Assisted-by: Claude Code Signed-off-by: Alex Bozarth <ajbozart@us.ibm.com>
Contributor
Author
|
@jakelorocco here's my initial draft POC for a events iterator for streaming. This is the better of the two POCs I wrote and I'll leave this here for your review while I'm out. Once I get back next week we can sync and I'll continue work on this |
jakelorocco
reviewed
Aug 19, 2026
jakelorocco
left a comment
Contributor
There was a problem hiding this comment.
This pretty much looks like exactly what I was looking for; thank you!
Comment on lines
+368
to
+381
| try: | ||
| self._streamer = await _stream( | ||
| action, | ||
| backend, | ||
| ctx, | ||
| chunking=chunking, | ||
| requirements=requirements, | ||
| validation_backend=validation_backend, | ||
| event_queue=self._queue, | ||
| ) | ||
| async for _ in self._streamer: # chunks discarded; events are surfaced | ||
| pass | ||
| finally: | ||
| self._queue.put_nowait(None) |
Contributor
There was a problem hiding this comment.
Since we are awaiting the underlying _stream function, the stream still progresses even when not directly awaiting on this event streamer, right?
Comment on lines
+369
to
+377
| self._streamer = await _stream( | ||
| action, | ||
| backend, | ||
| ctx, | ||
| chunking=chunking, | ||
| requirements=requirements, | ||
| validation_backend=validation_backend, | ||
| event_queue=self._queue, | ||
| ) |
Contributor
There was a problem hiding this comment.
Is it not an option to do the as_events boolean on the streamer itself and remove the need for discarding empty chunks and creating a new queue?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
Follow-up to the design discussion on #1543 (now merged) — streaming event consumption ergonomics. No separate tracking issue yet — flagging for a maintainer to route.
Description
Adds an opt-in
EventStreamer, returned bystream(..., as_events=True), that lets you iterate a stream's typedStreamEventobjects directly withasync for— rather than the only route today, a process-globalSTREAMING_EVENTplugin hook demuxed onstreaming_id.It wraps a
Streamer, drives it to completion on a background task, and delivers events through a queue. Because the queue's transport (put_nowait) is legal from teardown and during exception unwinding, the terminalCompletedEvent/ErrorEventare delivered on every exit path — including an earlybreak— which the alternative of yielding events from the driver generator cannot do (yieldis illegal duringGeneratorExit).Additive and opt-in. The default
stream()chunk-iterating contract is unchanged. Implementation: an optional per-streamevent_queueis threaded through_emit_event; theSTREAMING_EVENThook still fires on all paths (telemetry unaffected).stream()is split into_stream()+ a dispatching wrapper with typed@overloads onas_events.Testing
Deferred — this is a design-review draft. Not yet done: unit tests for
EventStreamer, atest/typing/check_*.pyfor the overloads, a docs example, and two known edge cases (setup-failure event symmetry; external cancellation during setup). Verified locally:ruff format/checkclean,mypyclean, existingtest_streaming.pypasses (37), and an offline harness exercises all four event-mode paths (natural completion, requirement-fail, error re-raise, early break with no task leak).Attribution