feat: add run_with_events() for a direct event stream - #4
Merged
Conversation
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.
Summary
Adds
AgentRunner.run_with_events()— a streaming entry point that yields normalized runtime events directly to a bare consumer, with no extension required.run_stream()forwards only chunks yielded by extensions implementingon_agent_runner_event_stream; a bare consumer receives onlyrun_end.run_with_events()solves this by internally installing a private bridge extension (_EventBridge) that captures chain-only tool events and forwards them alongside streamed tokens.Event shapes
token{"type": "token", "chunk": str}— one streamed text chunktool_call{"type": "tool_call", "tool_name": str, "tool_call_id": str, "args": dict}tool_result{"type": "tool_result", "tool_name": str, "tool_call_id": str, "content": Any, "is_error": bool}run_end{"type": "run_end", "session_id": str, "output": str, "new_messages": list, "usage": ...}Ordering: tool events precede the token chunk of the text that follows them; the stream always ends with
run_end. User extensions keep observing events underrun_with_events().Implementation
run_stream()body extracted into a private_run_stream_impl(streamers)shared by both entry points (no code duplication; the thinking warning is inherited)._EventBridge(_internals.py): chain hook queues normalizedtool_call/tool_resultevents; stream hook drains the queue (preserving order) then yieldstokenforTOKEN_STREAM. Never mutates chain data.run_stream()behavior unchanged — existing tests untouched.Known limitations (documented)
thinking_enabled=True;run_end.outputnot a reliable fallback). Warning emitted once on first iteration; preferrun()when thinking is enabled. See README Known issues.tool_call.args/tool_result.contentshow pre-rewrite values;tool_result.is_erroris alwaysFalse(hooks never set it).Verification
make check: ruff ✅ mypy strict ✅ 157 passed, 7 skipped (7 = Postgres, run in CI).run_stream()unchanged, extension coexistence, early-abort restore, snapshot semantics.