Repository navigation
Python: Support ag-ui-protocol 1.0 in agent-framework-ag-ui - #9378
Markus Ecker (mme) wants to merge 4 commits into
Conversation
Allow ag-ui-protocol 1.x (>=0.1.19,<2) and lock 1.0.0. With 1.0 installed the package now passes its whole test suite; 0.1.x keeps its behaviour. - Declare protocolVersion on every RUN_STARTED through one helper, _build_run_started_event, gated on the installed SDK exposing PROTOCOL_VERSION, so 0.1.x keeps emitting the event it does today. - Send media in MESSAGES_SNAPSHOT in the 1.0 shape when 1.0 is installed: 1.0 rejects the legacy binary part, so snapshots with media, including threads saved before an upgrade, failed validation on replay. Stored snapshot messages keep their current shape. - Pass media in a ContentPart[] tool result on as the function result's Content items instead of the text of the raw parts list: for tool messages, for tool media replayed from a stored snapshot, and for TOOL_CALL_RESULT events in AGUIChatClient. With 1.0, AGUIChatClient also sends such media back as content parts in later requests (MCP results keep their own handling). History deduplication and approval snapshot rebuilds treat an image-only result as output and keep its media. - Workflow snapshots: store 1.0 tool-result parts as wire dictionaries, and default a text message without a role to assistant (1.0 no longer defaults it), so the snapshot serializes and replays. - Treat a 1.0 file source like an id reference: its handle is in "value", which was not read, so the part was dropped silently. It now gets the existing unresolved-reference rejection. - Read 1.0's modelled JSON Patch operations in the task-steps example and in the predictive-state tests, and compare replayed snapshots with the wire shape for the installed SDK.
There was a problem hiding this comment.
🟡 Changes recommended
Rich tool-result media is still stripped when relayed through AgentFrameworkAgent.
1 open finding
What changed in this PR
Adds AG-UI protocol 1.0 compatibility while retaining 0.1.x support.
Changes:
- Adds version-aware run-start and snapshot serialization.
- Preserves multimodal tool results and handles 1.0 file sources.
- Updates examples, dependencies, documentation, and compatibility tests.
| File | Description |
|---|---|
python/uv.lock |
Locks AG-UI protocol 1.0. |
tests/ag_ui/test_workflow_agent.py |
Adds version-aware snapshot assertions. |
tests/ag_ui/test_task_steps_example_agent.py |
Tests the updated task-steps example. |
tests/ag_ui/test_run_common.py |
Strengthens tool-result payload assertions. |
tests/ag_ui/test_protocol_1_0.py |
Covers protocol 1.0 compatibility behavior. |
tests/ag_ui/test_predictive_state.py |
Supports modelled JSON Patch operations. |
README.md |
Documents protocol compatibility. |
pyproject.toml |
Expands the supported protocol range. |
AGENTS.md |
Records AG-UI compatibility conventions. |
_workflow.py |
Updates workflow snapshots and run-start events. |
_workflow_run.py |
Centralizes workflow run-start creation. |
_utils.py |
Detects the installed protocol version. |
_snapshot_session.py |
Converts stored snapshots for wire replay. |
_run_common.py |
Adds version-aware run-start construction. |
_predictive_state.py |
Adapts JSON Patch typing. |
_message_adapters.py |
Converts media snapshots and rich tool results. |
_event_converters.py |
Parses content-part tool results. |
_agent_run.py |
Preserves media in snapshots and approvals. |
task_steps_agent.py |
Supports 1.0 patches and snapshot media. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
| if isinstance(result, list): | ||
| # An AG-UI 1.0 ContentPart[] result: pass media on as Content items, as for tool messages. | ||
| converted = _tool_content_parts_to_result(cast(list[Any], result), tool_call_id) | ||
| if converted is not None: | ||
| result = converted |
There was a problem hiding this comment.
Fixed in ba2d486. _emit_tool_result now encodes a media-bearing result with _function_result_to_agui_content and threads the parts through _emit_tool_result_common, so both the outward TOOL_CALL_RESULT (unless a display payload overrides it) and the snapshot message in flow.tool_results carry the content parts. The approval-path events in _make_approval_tool_result_events do the same, matching the snapshot messages _resolved_tool_result_snapshot_messages already rebuilt with parts. MCP results keep the Host-payload handling. Tests: test_agent_run_relays_tool_result_media (stub agent behind AgentFrameworkAgent) and test_agent_relays_tool_result_media_from_an_agui_server (AGUIChatClient fed by a mock AG-UI server, wrapped in Agent and AgentFrameworkAgent).
Resolve conflicts with the AG-UI changes on main: - pyproject.toml: keep main's agent-framework-core floor (>=1.22.0) with the ag-ui-protocol <2 range. - _message_adapters.py: main's per-occurrence result slots in _deduplicate_messages, judging emptiness with _function_result_has_output so an image-only result still replaces an empty placeholder. - _workflow.py: main's live_owner check, with every RUN_STARTED (including the new factory-run site) built by _build_run_started_event. - tests: main's (call_id, occurrence) snapshot keys and import additions.
A function result holding media (for example one AGUIChatClient built from a 1.0 ContentPart[] TOOL_CALL_RESULT) reached _emit_tool_result, which serialized only content.result: the outward TOOL_CALL_RESULT and the snapshot tool message in flow.tool_results were text-only, so an AgentFrameworkAgent in front of an AG-UI server dropped the media. _emit_tool_result now encodes such a result with _function_result_to_agui_content and threads the parts through _emit_tool_result_common to the event (unless a display payload overrides it) and the snapshot message. The approval-path TOOL_CALL_RESULT events do the same, matching the snapshot messages _resolved_tool_result_snapshot_messages already rebuilt with parts. MCP results keep their Host-payload handling. Tests: an AgentFrameworkAgent run over a stub agent yielding a media result, and a relay through AGUIChatClient fed by a mock AG-UI server.
The previous merge commit recorded the resolved tree without main as a parent; this merge records the ancestry (the tree already carries main).
Eduard van Valkenburg (eavanvalkenburg)
left a comment
There was a problem hiding this comment.
The 1.0 media and snapshot compatibility work is thorough, but the client-side version declaration and the promised lower-bound compatibility need durable coverage before the dependency range is widened.
| } | ||
|
|
||
|
|
||
| def _build_run_started_event(run_id: str, thread_id: str) -> RunStartedEvent: |
There was a problem hiding this comment.
This correctly centralizes the producer declaration on RUN_STARTED, but the other half of the 1.0 contract is still missing: AGUIChatClient builds its outbound RunAgentInput through AGUIHttpService.post_run() without protocolVersion. The 1.0 SDK explicitly requires a client to declare PROTOCOL_VERSION on RunAgentInput, just as a producer declares it on RunStartedEvent. Please add the installed version to the outbound request payload when available (and omit it for 0.1.x), with HTTP-payload tests for both branches.
| dependencies = [ | ||
| "agent-framework-core>=1.22.0,<2", | ||
| "ag-ui-protocol>=0.1.19,<0.2", | ||
| "ag-ui-protocol>=0.1.19,<2", |
There was a problem hiding this comment.
This widens the supported dependency surface from one 0.1.x line to 0.1.19 <= version < 2, but the tracked lock and repository CI exercise only 1.0.0. The local 0.1.19/0.1.22 runs are not a durable compatibility guard. Please add a dependency-bounds/targeted matrix that runs the AG-UI tests against the floor (0.1.19), latest 0.1.x, and 1.0.0 so future changes cannot silently break the published lower range.
|
Markus Ecker (@mme), I checked the request construction on Are you already taking care of that part, or would a small patch adding the SDK version when available and HTTP-payload tests for both branches be useful in this PR? I would keep the dependency matrix and your media changes out of that patch. This is source analysis only: I have not reproduced this with SDK 1.0 or prepared a verified patch. AI-assisted analysis and writing. |

Motivation & Context
agent-framework-ag-uipinsag-ui-protocol>=0.1.19,<0.2, so it can't be installed alongside AG-UI 1.0, and with 1.0 installed the package fails 24 of its own tests: snapshots with media fail validation on replay,RUN_STARTEDdoesn't declareprotocolVersion, media in tool results is lost, and a 1.0 file source is dropped silently. Details and a reproduction are in #9377.Description & Review Guide
ag-ui-protocol>=0.1.19,<2and lock 1.0.0. 0.1.x keeps today's behaviour; every version-specific shape is gated on the installed SDK exposingPROTOCOL_VERSION(_AGUI_PROTOCOL_VERSIONin_utils.py).protocolVersionon everyRUN_STARTED, through one helper,_build_run_started_event(all 25 construction sites).MESSAGES_SNAPSHOTmedia in the 1.0 shape. 1.0 rejects the legacybinarypart, so snapshots with media, including threads saved before an upgrade, failed on replay. Stored snapshot messages keep their current shape;snapshot_messages_for_wirerewrites them only where a snapshot event is built.ContentPart[]tool result becomes the function result's Content items instead of the text of the raw parts list. This covers tool messages, tool media replayed from stored snapshots, andTOOL_CALL_RESULTevents inAGUIChatClient. With 1.0,AGUIChatClientalso sends that media back as content parts in later requests; MCP results keep their own handling. History deduplication and approval snapshot rebuilds treat an image-only result as output. In the other direction, a function result holding media (such as oneAGUIChatClientbuilt from a relayedTOOL_CALL_RESULT) goes out as content parts onTOOL_CALL_RESULTand in the snapshot tool message, soAgentFrameworkAgentrelays it intact; approval-path result events do the same, and MCP results keep their Host-payload handling.assistant(1.0 no longer defaults it).{"type": "file", "value": ...}source now gets the same rejection as anidsource. Before,valuewasn't read, so the part was dropped silently.Content.resultin the package; the ones that only see results of tools the server runs itself are unchanged.Tests (new in
tests/ag_ui/test_protocol_1_0.py, plus version-aware assertions intest_workflow_agent.py,test_predictive_state.pyandtest_run_common.py; the task-steps example has its own test file; two relay tests run anAgentFrameworkAgentover a stub agent and over anAGUIChatClientfed by a mock AG-UI server):ag-ui-protocolpytest packages/ag-ui/testspoe check -P ag-uipasses on 1.0.0 (ruff format and check, pyrefly, mypy, zuban, ty, pyright, tests). The new tests fail onmainwith 1.0 installed.Related Issue
Fixes #9377
AI Assistance
AI assistance details: implementation, tests and this PR description were written with Claude Code; the change was also reviewed with Codex.
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.