Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the A2UI parsing and streaming architecture to be stateless, introducing ResponseStream and DirectJsonResponseStream for incremental chunk-by-chunk processing and utilizing RendererState for validation. It also removes several deprecated streaming APIs across the a2ui_a2a and a2ui_adk packages. Feedback on the changes highlights a bug in clean_start where single backticks are incorrectly handled compared to triple backticks, and suggests a performance optimization in possible_tag_start to avoid O(N^2) string slicing by leveraging the pos parameter in re.match without the redundant ^ anchor.
21b7b8f to
bbadfc0
Compare
…ResponseStream The `Parser` read whole responses, which needs no state, and streamed responses, which needs the buffers of one response, on the same object. It also validated inside `compile`, so translation and validation could not be separated, and some paths skipped validation while others repeated it. 1. The parser holds no state. `compile`, `decompile`, `wrap` and `unwrap` only translate. `parse_response(content, wrapped, state=)` compiles every block, applies them to a `RendererState` inside a transaction, and checks the trees of the surfaces they touch once every block is applied. Without a state, the response is checked against a new, empty one, as a chat turn that shows new UI. 2. `open_stream(wrapped=, state=)` returns a `ResponseStream` for one response, with `feed` and `close`. Each message it emits is applied to the state, so the state matches what the client received even when the response fails part way. `close` emits text still held back as a possible tag, raises if a block is still open, and checks the tree of an unwrapped stream. 3. The direct JSON stream checks each message against its state instead of calling `validate_payload` for every message on every chunk, and no longer keeps its own map of surfaces to catalogs. `validate_payload` loses `surface_catalog_ids`. 4. Express, Elemental and Atom output is checked the same way as direct JSON output. 5. `parse_chunk`, `validates_output` and the formats' `parser` property are removed. 6. `a2ui.a2a`: `stream_response_to_parts` takes a `ResponseStream`, and `to_a2a_parts` converts parsed parts. `parse_stream_chunk_to_parts` and the deprecated `parse_response_to_parts` are removed. 7. The shared conformance cases gain `args.history`, the messages the renderer received before a case starts, and the streaming cases expect an update to a surface the renderer doesn't hold to be held back. The evaluation bridge parses once, without a second check. Part of #3262 and #3263. Signed-off-by: Nan Yu <nanyu@google.com>
The cases no longer describe what a renderer received before they start. 1. Streaming cases that update a surface they don't create start with a block that creates it, since a stream is read as one chat turn against a renderer that holds no surfaces. 2. The parse_response case creates its surface in the same block. 3. Compile and decompile cases check a block on its own, the way `validate_payload` does, so a surface the block only updates is assumed to exist and needs no history. The Python runner compiles the block and calls `validate_payload` instead of parsing it against a seeded `RendererState`. Signed-off-by: Nan Yu <nanyu@google.com>
… tags in linear time 1. `clean_start` strips an opening fence only when the block starts with three backticks, as `clean` does, and returns nothing while only the start of a fence has arrived. A block that starts with one backtick is no longer cut short while streaming and then read differently when it closes. 2. `possible_tag_start` and `scan_block` match the start of a tag at its position instead of copying the rest of the text for every `<`. Signed-off-by: Nan Yu <nanyu@google.com>
Seven streaming cases now create their surface in a block of their own instead of relying on `args.history`, which the TypeScript runner ignored. The current stream processor passes them, so they come off the known-failure list, and KNOWN_GAPS.md counts 40 failing cases instead of 47. Signed-off-by: Nan Yu <nanyu@google.com>
A chunk could apply messages, for example by closing a block, and then fail later in the same chunk. feed raised without returning their parts, but the state kept them. feed now runs in a transaction, so the state holds only what the stream emitted.
bbadfc0 to
2f6c405
Compare
…t-response-stream # Conflicts: # python/a2ui_agent/src/a2ui/utils/renderer_state.py
… unclosed direct JSON blocks
gspencergoog
left a comment
There was a problem hiding this comment.
This PR makes the Python Parser stateless, moves catalog and topology checks out of compile into parse_response and the new open_stream/ResponseStream backed by a RendererState, and updates the a2ui.a2a helpers, the ADK converter, the eval bridge, the shared conformance suites and the tests. The streaming state machine and the transactional whole-response path hold up under review: a failed chunk rolls back to what was emitted, createSurface/deleteSurface are held until their object closes, and the open review threads from @ditman are addressed at 38c643f.
Requesting changes for one reason: the two rewritten "update before create" cases in response_streaming.yaml change the shared contract, and the Dart runner does not implement the hold-back, so dart_a2ui_agent (stable) fails on this PR and would stay red after the rebase onto main (the body says the Dart runner passes the updated cases). The sample breakage the body acknowledges is confirmed to be fixed by #3272 for the two workspace samples; the remaining notes below are about the gap between the two merges and one question on truncated blocks in canvas mode.
Broader Context & Non-Diff Observations
The following observations relate to surrounding or referenced code outside the active diff hunks:
samples/agent/adk/restaurant_finder/agent.py:304— Workspace samples import a removed symbol until #3272 landssamples/agent/adk/restaurant_finder/agent.py:42,304andsamples/agent/adk/custom-components-example/agent.py:42,528import the removedparse_response_to_partsand pass aParsertostream_response_to_parts, so they fail at import, and streaming would raiseAttributeError, from the moment this PR merges until #3272 merges.ci-sampleswas skipped on this PR, so CI does not show it. Thegemini_enterprisesamples depend on published releases and are unaffected.
🤖 Agent-generated
ditman
left a comment
There was a problem hiding this comment.
More nits. I think I've seen the update to the samples coming later in the stack, so that's probably not too bad!
…' into feat/python-agent-response-stream Signed-off-by: Nan Yu <nanyu@google.com> # Conflicts: # python/a2ui_agent/CHANGELOG.md
- RendererState.mark_checked stops holding a surface's changed components against later checks. A stream that fails after emitting messages, and a block cut off at the end of a parse_response, mark their surfaces checked, so a later turn isn't required to make components the renderer already holds reachable. Dangling references and a missing root are still reported. - When parse_response's tree check fails, partial_results holds only the text, since the check spans every block; document that partial_results isn't applied to the state. - parse_content_to_parts takes a keyword-only state for canvas agents. - Fix the macros README to call create_parser().compile. - Drop unused imports from test_renderer_state_parsing.py. Signed-off-by: Nan Yu <nanyu@google.com>
…' into feat/python-agent-response-stream Signed-off-by: Nan Yu <nanyu@google.com>
…s known gaps The Dart stream emits an update for a surface the response hasn't created yet, where response_streaming.yaml now holds it back. Skip both cases until the Dart ResponseStream implements the hold-back. Signed-off-by: Nan Yu <nanyu@google.com>
…ecked Finishing a block in a chunk clears the stream's touched surfaces, but if the chunk then fails in a second block, the rollback restores that block's earlier-emitted messages unchecked. feed now marks the surfaces touched before the chunk. Signed-off-by: Nan Yu <nanyu@google.com>
…t-response-stream Port test_streaming_delete_surface.py (added on main) to open_stream, and begin rendering the v0.8 surfaces in the ADK converter tests and the two v0.8 streaming conformance cases, since a v0.8 response that never sends beginRendering is now rejected. Signed-off-by: Nan Yu <nanyu@google.com>
…seStream The stream no longer scans the open block from its start and reads it whole on every chunk: - BlockScanner resumes the close-tag scan where the previous chunk stopped. - read_partial_message_items takes a PartialReadCache: a list or map that closed is taken from the cache, and an open list (the list of messages included) is read on from its last closed item. - A message that has closed is not read, applied or checked again. A growing updateComponents applies only the components it gained (and the last one again while it is healed before v1.0); a repeated id across those batches is rejected. A growing updateDataModel is emitted as its value changes and applied again once it closes. - Only updateComponents and updateDataModel (surfaceUpdate and dataModelUpdate in v0.8) are emitted while they grow. Any other message, callRendererFunction included, waits for its object to close. - At block close the read goes on from the cache. Only a block that does not read as complete JSON is compiled whole, so that it raises as compile does. Streaming one message of n components in 40-character chunks drops from 10.3 s to 0.19 s at n=400 (ratio 400/100 from 15.4 to 4.6). DirectJsonParser.compile now returns AgentToRendererMessage models, as the blueprint and the other formats' compile do. The agent conformance harness runs agent/direct_json/scaling.yaml and agent/express/scaling.yaml under the timing harness (marked performance); test_scaling_cases_have_handlers runs in the default run. Signed-off-by: Nan Yu <nanyu@google.com>
Signed-off-by: Nan Yu <nanyu@google.com>
…load on it RendererState wraps one web_core MessageProcessor, created once with the linked catalogs, with dangling references, a missing root and orphans allowed while messages are applied. - apply(payload) checks and applies messages atomically, and transaction(fn) extends that to everything fn applies. Transactions nest. A callback that returns a promise is rejected at compile time and at run time. - checkTopology(surfaceIds) checks the root and references of each surface, and that every component added or replaced since the last check is reachable. - Rollback uses an undo log rather than copying a surface. Each message records what it replaced: the components an updateComponents adds or replaces (with a copy of the properties of one updated in place), the value an updateDataModel overwrites, and a deleted surface, which is restored by replaying it with each component's own catalogId. The state's bookkeeping is logged too. A transaction is a position in the log, so a failed inner transaction undoes only what it applied. - validatePayload runs on a short-lived subclass that assumes a surface the payload only updates exists, and accepts one message on its own. The conformance cases under conformance/agent/ are updated to the shared cases this SDK and the Python SDK (#3269) run: args.history is dropped, and the streaming cases that now pass are no longer known gaps. Applying 400 one-component messages to one surface, each in a transaction, takes 2.2 times as long as 100 (9.1 with surface copies). Signed-off-by: Nan Yu <nanyu@google.com>
…ach chunk once The parser holds no state. parseResponse and openStream check a response against a RendererState, a new one per call unless the caller passes one (or binds one to A2uiRequestProcessor) for a canvas conversation. A2uiRequestProcessor gains createState and acceptsState, which compares the catalogs' ids and release lines. DirectJsonResponseStream reads each chunk once: 1. BlockScanner resumes the close-tag scan where the last chunk left it. 2. readPartialMessages reads through a PartialReadCache, ported from the Dart reader: a list or map that closed is kept by offset, and a list of messages goes on after the last message that closed. 3. The stream keeps the closed messages and applies each once. Only updateComponents and updateDataModel are emitted while they grow; any other message, callRendererFunction included, waits for its object to close. A growing updateComponents is applied for the components it gained, with ids repeated across chunks rejected. A growing updateDataModel is checked for the lists and maps its value gained, and applied again once it closes. 4. tests/timing/scaling_test.mjs runs conformance/agent/direct_json/ scaling.yaml and conformance/agent/express/scaling.yaml with `yarn test:timing`, using web_core's timing helper, and the ci-timing job runs it for @a2ui/agent. Streaming one updateComponents of 400 components in 40-character chunks drops from 7.0 s to 0.13 s (ratio 400/100 from 16.0 to 3.9). The Node restaurant finder sample and the eval TypeScript bridge use the new API. The conformance cases under conformance/agent/ are updated to the shared cases this SDK and the Python SDK (#3269) run: args.history is dropped, and the streaming cases this stream now passes leave the known failures. conformance/README.md says the TypeScript agent SDK runs the agent timing suites with `yarn test:timing`. Signed-off-by: Nan Yu <nanyu@google.com>
…d RendererState to A2uiRequestProcessor - A parser holds no state. Parser.parseChunk is removed: openStream returns a ResponseStream (DirectJsonResponseStream for direct JSON) whose feed and close read one response against a RendererState, applying each message as it is emitted and checking the trees a block touched when it closes. A failed stream keeps what it emitted and marks it checked (RendererState.markChecked, undone with a failed transaction like any other change). - Parser.compile only translates; parseResponse checks a response as a whole against a RendererState. A2uiRequestProcessor binds a RendererState and gains acceptsState. - DirectJsonResponseStream reads each chunk once: the close-tag scan and the read of the open block resume from the previous chunk (#3314's PartialReadCache), a closed message is applied once, a growing updateComponents applies only the components it gained (and the last one again if it grew) and rejects ids repeated across its chunks, and a growing updateDataModel is checked for the lists and maps its value gained and applied again once it closes. Only updateComponents and updateDataModel are emitted while they grow. - The shared conformance cases for the stateless parser and stream, as in #3269: compile cases assume the surfaces a block only updates, response parser cases create the surfaces they update, and the streaming cases hold back an update to a surface the response has not created. The Dart known gaps for those cases are removed. - The scaling conformance harness streams through openStream into a state that holds the surfaces a payload updates first, and times compile as parseResponse(payload, wrapped: false), as the Python harness does. Signed-off-by: Nan Yu <nanyu@google.com>
…until #3269 #3269 changes test_stream_update_before_create_surface_fails_v09 and test_stream_data_model_before_create_surface_fails_v09 to hold back a message for a surface that does not exist yet. The Python and Dart SDKs on main still emit it, so carrying the change here failed their CI. The cases stay as main has them, and the TypeScript harness lists them as known failures until #3269 lands. Signed-off-by: Nan Yu <nanyu@google.com>
…ases The shared conformance cases this PR carries from #3269 failed CI for the SDKs on main: - test_stream_update_before_create_surface_fails_v09 and test_stream_data_model_before_create_surface_fails_v09 now expect a message for a surface that does not exist yet to be held back, which the Python SDK on main does not do. They stay as main has them until #3269 lands, and the Dart harness skips them until then, since this stream already holds the message back. - With the updated cases, seven TypeScript streaming cases pass, so they leave the TypeScript harness's known failures, as #3279 does. Signed-off-by: Nan Yu <nanyu@google.com>
…d RendererState to A2uiRequestProcessor - A parser holds no state. Parser.parseChunk is removed: openStream returns a ResponseStream (DirectJsonResponseStream for direct JSON) whose feed and close read one response against a RendererState, applying each message as it is emitted and checking the trees a block touched when it closes. A failed stream keeps what it emitted and marks it checked (RendererState.markChecked, undone with a failed transaction like any other change). - Parser.compile only translates; parseResponse checks a response as a whole against a RendererState. A2uiRequestProcessor binds a RendererState and gains acceptsState. - DirectJsonResponseStream reads each chunk once: the close-tag scan and the read of the open block resume from the previous chunk (#3314's PartialReadCache), a closed message is applied once, a growing updateComponents applies only the components it gained (and the last one again if it grew) and rejects ids repeated across its chunks, and a growing updateDataModel is checked for the lists and maps its value gained and applied again once it closes. Only updateComponents and updateDataModel are emitted while they grow. - The shared conformance cases for the stateless parser and stream, as in #3269: compile cases assume the surfaces a block only updates, response parser cases create the surfaces they update, and the streaming cases hold back an update to a surface the response has not created. The Dart known gaps for those cases are removed. - The scaling conformance harness streams through openStream into a state that holds the surfaces a payload updates first, and times compile as parseResponse(payload, wrapped: false), as the Python harness does. Signed-off-by: Nan Yu <nanyu@google.com>
…ases The shared conformance cases this PR carries from #3269 failed CI for the SDKs on main: - test_stream_update_before_create_surface_fails_v09 and test_stream_data_model_before_create_surface_fails_v09 now expect a message for a surface that does not exist yet to be held back, which the Python SDK on main does not do. They stay as main has them until #3269 lands, and the Dart harness skips them until then, since this stream already holds the message back. - With the updated cases, seven TypeScript streaming cases pass, so they leave the TypeScript harness's known failures, as #3279 does. Signed-off-by: Nan Yu <nanyu@google.com>
…d RendererState to A2uiRequestProcessor - A parser holds no state. Parser.parseChunk is removed: openStream returns a ResponseStream (DirectJsonResponseStream for direct JSON) whose feed and close read one response against a RendererState, applying each message as it is emitted and checking the trees a block touched when it closes. A failed stream keeps what it emitted and marks it checked (RendererState.markChecked, undone with a failed transaction like any other change). - Parser.compile only translates; parseResponse checks a response as a whole against a RendererState. A2uiRequestProcessor binds a RendererState and gains acceptsState. - DirectJsonResponseStream reads each chunk once: the close-tag scan and the read of the open block resume from the previous chunk (#3314's PartialReadCache), a closed message is applied once, a growing updateComponents applies only the components it gained (and the last one again if it grew) and rejects ids repeated across its chunks, and a growing updateDataModel is checked for the lists and maps its value gained and applied again once it closes. Only updateComponents and updateDataModel are emitted while they grow. - The shared conformance cases for the stateless parser and stream, as in #3269: compile cases assume the surfaces a block only updates, response parser cases create the surfaces they update, and the streaming cases hold back an update to a surface the response has not created. The Dart known gaps for those cases are removed. - The scaling conformance harness streams through openStream into a state that holds the surfaces a payload updates first, and times compile as parseResponse(payload, wrapped: false), as the Python harness does. Signed-off-by: Nan Yu <nanyu@google.com>
…ases The shared conformance cases this PR carries from #3269 failed CI for the SDKs on main: - test_stream_update_before_create_surface_fails_v09 and test_stream_data_model_before_create_surface_fails_v09 now expect a message for a surface that does not exist yet to be held back, which the Python SDK on main does not do. They stay as main has them until #3269 lands, and the Dart harness skips them until then, since this stream already holds the message back. - With the updated cases, seven TypeScript streaming cases pass, so they leave the TypeScript harness's known failures, as #3279 does. Signed-off-by: Nan Yu <nanyu@google.com>
…d RendererState to A2uiRequestProcessor (#3277) * feat(dart/agent)!: make the Parser stateless; add ResponseStream; bind RendererState to A2uiRequestProcessor - A parser holds no state. Parser.parseChunk is removed: openStream returns a ResponseStream (DirectJsonResponseStream for direct JSON) whose feed and close read one response against a RendererState, applying each message as it is emitted and checking the trees a block touched when it closes. A failed stream keeps what it emitted and marks it checked (RendererState.markChecked, undone with a failed transaction like any other change). - Parser.compile only translates; parseResponse checks a response as a whole against a RendererState. A2uiRequestProcessor binds a RendererState and gains acceptsState. - DirectJsonResponseStream reads each chunk once: the close-tag scan and the read of the open block resume from the previous chunk (#3314's PartialReadCache), a closed message is applied once, a growing updateComponents applies only the components it gained (and the last one again if it grew) and rejects ids repeated across its chunks, and a growing updateDataModel is checked for the lists and maps its value gained and applied again once it closes. Only updateComponents and updateDataModel are emitted while they grow. - The shared conformance cases for the stateless parser and stream, as in #3269: compile cases assume the surfaces a block only updates, response parser cases create the surfaces they update, and the streaming cases hold back an update to a surface the response has not created. The Dart known gaps for those cases are removed. - The scaling conformance harness streams through openStream into a state that holds the surfaces a payload updates first, and times compile as parseResponse(payload, wrapped: false), as the Python harness does. Signed-off-by: Nan Yu <nanyu@google.com> * test(conformance): keep main's SDKs green with the shared streaming cases The shared conformance cases this PR carries from #3269 failed CI for the SDKs on main: - test_stream_update_before_create_surface_fails_v09 and test_stream_data_model_before_create_surface_fails_v09 now expect a message for a surface that does not exist yet to be held back, which the Python SDK on main does not do. They stay as main has them until #3269 lands, and the Dart harness skips them until then, since this stream already holds the message back. - With the updated cases, seven TypeScript streaming cases pass, so they leave the TypeScript harness's known failures, as #3279 does. Signed-off-by: Nan Yu <nanyu@google.com> --------- Signed-off-by: Nan Yu <nanyu@google.com>
…t-response-stream Signed-off-by: Nan Yu <nanyu@google.com> # Conflicts: # conformance/README.md # python/a2ui_agent/tests/utils/test_renderer_state.py
…t-response-stream Signed-off-by: Nan Yu <nanyu@google.com> # Conflicts: # dart/a2ui_agent/test/conformance/direct_json_conformance_test.dart
…t-response-stream Signed-off-by: Nan Yu <nanyu@google.com>
Part of #3262 and #3263. Stacked on #3268.
Important
Like #3268, this PR needs the core change in #3326, whose branch is merged in through #3268. The core part of the diff disappears once #3326 lands.
The
Parserread whole responses, which needs no state, and streamed responses, which needs the buffers of one response, on the same object. It also validated insidecompile, so translation and validation could not be separated, and some paths skipped validation while others repeated it.Parser
compile,decompile,wrapandunwraponly translate.parse_response(content, wrapped, state=)compiles every block, applies them to aRendererStateinside a transaction, and checks the trees of the surfaces they touch once every block is applied. A component may name a child that a later block adds. Without a state, the response is checked against a new, empty one, as a chat turn that shows new UI.open_stream(wrapped=, state=)returns aResponseStreamfor one response, withfeedandclose. Each message it emits is applied to the state, so the state matches what the client received even when the response fails part way.closeemits text still held back as a possible tag, raisesA2uiParseErrorif a block is still open, and checks the tree of an unwrapped stream.direct_json/response_stream.py) checks each message against its state instead of callingvalidate_payloadfor every message on every chunk, and no longer keeps its own map of surfaces to catalogs. The block reading it shares with the parser moves todirect_json/blocks.py.Reading each chunk once
A stream must take time in proportion to the response (blueprint, "Cost of a response"). The direct JSON stream used to scan the open block for the close tag from its start, read the whole open block again, and apply a growing message again whole, on every chunk. Now:
BlockScannerresumes the close-tag scan where the previous chunk stopped.read_partial_message_itemstakes aPartialReadCache: a list or map that closed in an earlier read is taken from the cache, and an open list, the list of messages included, is read on from its last closed item. The reader ports the Dart reader from Fix performance and create conformance tests. #3314.updateComponentsapplies only the components it gained, plus its last component again while that one is healed before v1.0 (_check_added); an id repeated across those batches is rejected as a duplicate. A growingupdateDataModelis emitted each time its value changes and applied again once it closes (_check_grown).compiledoes (_final_messages).Only
updateComponentsandupdateDataModel(v0.8'ssurfaceUpdateanddataModelUpdate) are emitted while they grow. Any other message,callRendererFunctionincluded, now waits for its object to close, and the messages after it wait with it, as the blueprint'sDirectJsonResponseStreamdescribes. Before, onlycreateSurface,beginRenderinganddeleteSurfacewaited.Streaming 40-character chunks with a v1.0 basic catalog, best of three:
The "before" numbers are this branch with #3268 and #3326 merged, so they measure the stream alone.
DirectJsonParser.compilenow returnsAgentToRendererMessagemodels rather than decoded mappings, as the blueprint'scompileand the other formats do. A message that matches no protocol version's message schema raisesA2uiValidationErrorthere. Every caller in the stack already accepted models.Removed
Parser.parse_chunkandvalidates_output, and the formats'parserproperty (usecreate_parser()).validate_payload(..., surface_catalog_ids=...).a2ui-a2a,parse_stream_chunk_to_partsand the deprecatedparse_response_to_parts.stream_response_to_partstakes aResponseStream, and the newto_a2a_partsconverts parsed parts. These move with the parser because they calledparse_chunk.Behaviour changes
A2uiRequestProcessor.parse_responseandA2uiPartConvertertoo. A canvas agent passes theRendererStateit keeps; the next PR lets the processor hold it.validate_payloadstill assumes such a surface exists.id="root".Conformance and evaluation
parse_responsecase is read as one chat turn against a renderer that holds no surfaces, so a case that updates a surface creates it in its input: streaming cases start with a block that creates the surface, and theparse_responsecase creates it in the same block. The Python runner drives streaming cases throughopen_stream,feedandclose.validate_payloaddoes, so a surface the block only updates is assumed to exist. The Python runner compiles the block and callsvalidate_payload.beginRenderingis now rejected (feat(python/agent): add RendererState; build validate_payload on it #3268).agent/direct_json/scaling.yamlandagent/express/scaling.yamlintest_scaling_conformance.py, markedperformance, under the timing harness from perf(python/core): check updateComponents incrementally under deferred graph checks #3326.test_scaling_cases_have_handlersruns in the default run. A payload that updates a surface it does not create (the v0.9 healed case) streams into a state that holds that surface.MessageProcessor.The Dart runner passes the updated cases as it is today. The current TypeScript stream processor now passes seven streaming cases it listed as known failures, so they come off its list.
The samples under
samples/still callparse_chunkandparse_response_to_parts; the sample PRs at the top of this stack move them over.Testing
test_response_stream.pyandtest_renderer_state_parsing.pycover chat and canvas turns, rollback of a failed response, streams that fail part way,close, and a block that names a child a later block adds.test_parse_chunk.pyis replaced by them. Newpython/a2ui_a2a/tests/test_parts.py.uv run pytest python/a2ui_agent/tests python/a2ui_a2a/tests python/a2ui_adk/tests samples/agent/adk/tests eval/tests: all pass except the TypeScript evaluation bridge tests, which fail the same way onmainin my checkout (@a2ui/agentnot resolvable fromnode_modules).uv run mypy python/a2ui_agent/src python/a2ui_a2a/src: no issues.yarn testand Dartdart teststill pass with the updated conformance YAML.test_response_stream_reads.pychecks that each closed message is applied once, that a growing message applies only what it gains, that a healed component is applied again only while it grows, that a data model update is applied when it first reads and once it closes, that a function call waits for its object to close, that a repeated id in a growing message is rejected, and that a close tag inside a string does not end the block however the response is cut.uv run pytest python/ samples/agent/adk/tests: 2855 passed.uv run pytest -m performance python/a2ui_core/tests python/a2ui_agent/tests: 12 passed, including the 8 agent scaling cases. The old stream's ratios in the table above are over their limit of 8.test_streaming_delete_surface.py, which main added againstparse_chunk, is ported toopen_stream.