Skip to content

Update React Shell to A2UI v0.9 and Add A2A Middleware#1186

Closed
jacobsimionato wants to merge 3 commits intogoogle:mainfrom
jacobsimionato:react-samples-4
Closed

Update React Shell to A2UI v0.9 and Add A2A Middleware#1186
jacobsimionato wants to merge 3 commits intogoogle:mainfrom
jacobsimionato:react-samples-4

Conversation

@jacobsimionato
Copy link
Copy Markdown
Collaborator

Description of Changes

This pull request updates the React Shell sample to support A2UI version 0.9. Key changes include:

  • Protocol Upgrade: Migrated the React Shell from v0.8 to v0.9, utilizing the A2uiSurface component and MessageProcessor from @a2ui/web_core.
  • A2A Middleware: Added a new Vite middleware (samples/client/react/shell/middleware/a2a.ts) to handle communication with A2A agents. This middleware supports both standard POST requests and Server-Sent Events (SSE) for streaming responses.
  • Streaming Support: Refactored A2UIClient in the React Shell to support streaming A2UI messages from the agent via the new middleware.
  • Agent Updates: Updated the Restaurant Finder agent (ADK) with v0.9 examples and improved prompt instructions for relative data binding paths in templates.
  • Python SDK Fix: Updated the Python A2uiStreamParser to correctly handle path normalization based on the protocol version (ensuring leading slashes only for v0.8).
  • Mock Data Update: Updated the React Shell's mock response system to produce valid v0.9 messages.

Rationale

The A2UI protocol is evolving to v0.9, which introduces a more streamlined message format and better support for dynamic UI updates. Updating the React Shell ensures it remains a reference implementation for the latest version of the protocol. The addition of the A2A middleware simplifies client-side development by providing a unified way to talk to A2A agents with built-in streaming support.

Testing/Running Instructions

Prerequisites

  • Ensure you have uv installed for running the Python agent.
  • Ensure you have npm installed.

Steps

  1. Start the Restaurant Finder Agent:
    cd samples/agent/adk/restaurant_finder
    # Make sure you have a .env file with GEMINI_API_KEY
    uv run .
  2. Build Dependencies:
    # From the root of the repo
    cd renderers/web_core
    npm install && npm run build
    cd ../react
    npm install && npm run build
  3. Run the React Shell:
    cd samples/client/react/shell
    npm install
    npm run dev
  4. Verify:
    • Open http://localhost:5003 in your browser.
    • Enter a query like "Find Chinese restaurants in New York".
    • Verify that the UI renders correctly using v0.9 components and that the responses are streamed.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request migrates the React shell and restaurant finder sample to the A2UI v0.9 protocol. Key changes include refactoring the React application to use the new MessageProcessor and A2uiSurface components, updating mock data to the v0.9 schema, and introducing a Vite middleware to proxy A2A agent communication with SSE streaming support. Several high-severity issues were identified, including a protocol mismatch when handling user actions in App.tsx and an incorrect SSE error format in the middleware that will cause client-side runtime errors. Additionally, the middleware lacks request body size limits, the agent card URL is hardcoded, and a TypeScript build artifact was accidentally committed.

console.log('User action:', action);
if (sendAndProcessRef.current) {
sendAndProcessRef.current(actionMessage);
sendAndProcessRef.current(action as any);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The action object is being passed directly to sendAndProcess. However, getMockResponse (line 147) and the A2UI v0.9 protocol expect a message wrapper containing the action (i.e., { action: ... }). This mismatch will cause mock actions to fail and may lead to issues with the agent middleware.

Suggested change
sendAndProcessRef.current(action as any);
sendAndProcessRef.current({ action } as A2uiClientMessage);

res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ error: e.message || String(e) }));
} else {
res.write(`data: ${JSON.stringify({ error: e.message || String(e) })}\n\n`);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The SSE error response is sent as a single object, but the client-side parser in client.ts (line 69) expects an array of Part objects. Attempting to iterate over a non-array object using for...of will throw a runtime error on the client.

Suggested change
res.write(`data: ${JSON.stringify({ error: e.message || String(e) })}\n\n`);
res.write(`data: ${JSON.stringify([{ kind: "error", text: e.message || String(e) }])}\n\n`);

Comment on lines +75 to +77
req.on("data", (chunk) => {
originalBody += chunk.toString();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

The middleware accumulates the request body in memory without any size limit. This is a potential security risk as it could lead to memory exhaustion (DoS) if a malicious user sends an extremely large payload.

// Create a client pointing to the agent's Agent Card URL.
// Default to localhost:10002 for the restaurant agent.
client = await A2AClient.fromCardUrl(
"http://localhost:10002/.well-known/agent-card.json",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The agent card URL is hardcoded to localhost:10002. For better maintainability and flexibility, this should be configurable via an environment variable.

Suggested change
"http://localhost:10002/.well-known/agent-card.json",
process.env["AGENT_CARD_URL"] || "http://localhost:10002/.well-known/agent-card.json",

if (done) break;
buffer += decoder.decode(value, { stream: true });

const lines = buffer.split('\n\n');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Splitting the SSE stream strictly on \n\n is fragile. While it matches the current middleware implementation, standard SSE can use different line endings (like \r\n\r\n) or even single newlines between data fields if other fields (like id) are present. Consider a more robust SSE parsing approach.

@@ -0,0 +1 @@
{"root":["./src/app.tsx","./src/client.ts","./src/main.tsx","./src/configs/index.ts","./src/configs/restaurant.ts","./src/configs/types.ts","./src/mock/index.ts","./src/mock/restaurantmessages.ts","./src/theme/default-theme.ts"],"version":"5.9.3"} No newline at end of file
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The tsconfig.tsbuildinfo file is a build artifact generated by TypeScript and should not be committed to the repository. Please add it to your .gitignore and remove it from the PR.

andrewkolos added a commit to andrewkolos/A2UI that referenced this pull request Apr 16, 2026
NOTE: This is a library-level fix, not a shell-sample fix, and is NOT
required for PR google#1186 to merge. Including it on this branch to move
quickly — surfaced while smoke-testing google#1186. Cherry-pick or drop
independently of the shell commits.

Three small things in basic catalog v0.9:

1. The 'weight' schema property (CommonProps in basic_components.ts) was
   not implemented in any React v0.9 component. Agents emitting weight: 1
   on Image and weight: 2 on a sibling Column got default flex behavior
   (effectively no proportional sizing).

2. Image used width: 100% with no min-width: 0, so as a flex child it
   couldn't shrink below its intrinsic width. With full-resolution agent-
   served images this pushed sibling columns out of view.

3. Image inherited align-items: stretch from its parent Row, then
   objectFit: 'fill' (the default) distorted the image vertically.

Fix: in Image.tsx and Column.tsx, when props.weight is a number apply it
as `flex: <w> <w> 0`; add `min-width: 0`; and on Image, `align-self:
flex-start` so the row's stretch alignment doesn't deform photos.

Default objectFit / max-height for Image and a sensible card max-width
in the shell are still open design questions; not addressed here.
Copy link
Copy Markdown
Collaborator

@andrewkolos andrewkolos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I didn't use GitHub's review UI for my last comment.

Anyway, it looks like a bunch of changes landed upstream since I cloned yesterday. I tested against the in-repo alpha.1 versions (whatever your branch had checked in), which might be out of date. I'll try to check whether any of this or not addressed the problems I ran into.

@andrewkolos
Copy link
Copy Markdown
Collaborator

andrewkolos commented Apr 16, 2026

Oops, I didn't use GitHub's review UI for my last comment.

Anyway, it looks like a bunch of changes landed upstream since I cloned yesterday. I tested against the in-repo alpha.1 versions (whatever your branch had checked in), which might be out of date. I'll try to check whether any of this or not addressed the problems I ran into.

My original comment was on the wrong thread. Oops. Re-posting it here:

I checked this out locally and tried to run the react shell demo with the restaurant finder agent, but ran into issues.

What I did (more or less):

gh pr checkout 1186 
cd samples/agent/adk/restaurant_finder
uv run .

cd renderers/web_core && npm install && npm run build
cd ../react  && npm install && npm run build

cd samples/client/react/shell
npm install
npm run dev

# open http://localhost:5003
# submit the default prompt tex

Here's what I found:

  1. Unformatted markdown text (the heading is unformatted, starts with "#").
  2. "Surface default already exists" errors reported by the browser.
  3. The images in the restaurant cards are way too big (and the cards' widths are needlessly over-constrained for a non-mobile display). This crops off all the text and action buttons. This is partly due to issues in the react app's CSS, but the weight schema property also might not be implemented properly in the react v0.9 catalog.

I used an agent to produce this branch off of this PR with atomic fixes to individual issues it reported. On this branch I was able to go through the restaurant finder experience without any noticeable issues. I've published it and you can view individual commit messages and diffs here: jacobsimionato/A2UI@react-samples-4...andrewkolos:A2UI:fix/react-shell-v0.9-smoke-test-fixes.

Normally, I would do a full walkthrough and validation of each change, file pre-existing issues separately, document remaining bugs in detail here, etc.; but I'm guessing 1) we would like to move as quickly as possible here and 2) if you happen to be more experienced with the code files under change, you could more quickly swat some of the changes on my branch as hallucinated red-herrings (or hacky "fixes") if they happen to be so.

@jacobsimionato
Copy link
Copy Markdown
Collaborator Author

jacobsimionato commented Apr 16, 2026

Oops, I didn't use GitHub's review UI for my last comment.
Anyway, it looks like a bunch of changes landed upstream since I cloned yesterday. I tested against the in-repo alpha.1 versions (whatever your branch had checked in), which might be out of date. I'll try to check whether any of this or not addressed the problems I ran into.

My original comment was on the wrong thread. Oops. Re-posting it here:

I checked this out locally and tried to run the react shell demo with the restaurant finder agent, but ran into issues.

What I did (more or less):

gh pr checkout 1186 
cd samples/agent/adk/restaurant_finder
uv run .

cd renderers/web_core && npm install && npm run build
cd ../react  && npm install && npm run build

cd samples/client/react/shell
npm install
npm run dev

# open http://localhost:5003
# submit the default prompt tex

Here's what I found:

  1. Unformatted markdown text (the heading is unformatted, starts with "#").
  2. "Surface default already exists" errors reported by the browser.
  3. The images in the restaurant cards are way too big (and the cards' widths are needlessly over-constrained for a non-mobile display). This crops off all the text and action buttons. This is partly due to issues in the react app's CSS, but the weight schema property also might not be implemented properly in the react v0.9 catalog.

I used an agent to produce this branch off of this PR with atomic fixes to individual issues it reported. On this branch I was able to go through the restaurant finder experience without any noticeable issues. I've published it and you can view individual commit messages and diffs here: jacobsimionato/A2UI@react-samples-4...andrewkolos:A2UI:fix/react-shell-v0.9-smoke-test-fixes.

Normally, I would do a full walkthrough and validation of each change, file pre-existing issues separately, document remaining bugs in detail here, etc.; but I'm guessing 1) we would like to move as quickly as possible here and 2) if you happen to be more experienced with the code files under change, you could more quickly swat some of the changes on my branch as hallucinated red-herrings (or hacky "fixes") if they happen to be so.

Hey thanks so much for investigating this - your changes LGTM! Could you please package up my original PR and your tweaks into a new PR and mail it to David and Jose tomorrow? If you're able to reduce the diff against main where possible, that would be great too e.g. maybe we can revert some of the changes to the example JSON in the restaurant finder agent.

Re the management of surfaces - it's true that the way the restaurant finder agent does this is undesirable and not really in the spirit of A2UI. I think the workaround you're doing to avoid duplicate surface errors is good for now. In the future, we should redesign the agent so that it manages surfaces in a better way e.g. deleting the surface before recreating it, or explicitly reusing the surface, with no duplicate createSurface messages. Potentially there are two issues:

  1. Restaurant finder is designed such that the samples for each step include a createSurface(surfaceId: default... message, which has duplicate surfaces
  2. ADK delivers the entire conversation history with each request, which would redeliver the same createSurface( message even if only one was ever added to the history.

I was aware of the first issue but not the second. If the second issue is also present, then we should consider how to solve that at the library level, so that developers can use ADK / A2A with A2UI easily. But this is a future problem - your workaround LGTM for now.

@andrewkolos
Copy link
Copy Markdown
Collaborator

Closing as superseded by #1262.

@github-project-automation github-project-automation Bot moved this from Todo to Done in A2UI Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants