feat: add contextId support to A2A request and response payloads across samples#1070
feat: add contextId support to A2A request and response payloads across samples#1070nan-yu wants to merge 2 commits intogoogle:mainfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces contextId support across multiple Angular and Lit samples to enable session persistence between the client and server. The changes include updating client-side logic to store and send contextId, modifying server-side middleware to handle the new request/response formats, and adding unit tests for the orchestrator and custom components. Feedback focuses on improving backward compatibility for legacy server response formats (top-level arrays) in the Angular samples and refining error handling in the Lit component gallery to ensure errors are correctly detected when the response object contains both parts and error fields.
There was a problem hiding this comment.
Code Review
This pull request introduces contextId support across multiple client and server implementations to enable session persistence. The changes involve updating request payloads to include contextId, modifying server middleware to extract and return contextId, and updating clients to store and send this ID in subsequent requests. I have identified a few areas for improvement: the client-side response parsing logic in 'contact/src/app/client.ts' needs to be more robust to handle legacy array formats, and the 'rizzcharts' service requires a more comprehensive check for contextId in the response. Additionally, I recommend improving type safety when accessing contextId in the server middleware to avoid fragile 'any' casting.
samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts
Outdated
Show resolved
Hide resolved
…ss samples Currently, only the rizzcharts sample support multi-turn conversation with contextId: - server.ts: https://github.com/google/A2UI/blob/d50db0a75d9f8c4f144dc73bb058940fbfd7090d/samples/client/angular/projects/rizzcharts/src/server.ts#L61 - a2a_service.ts: https://github.com/google/A2UI/blob/d50db0a75d9f8c4f144dc73bb058940fbfd7090d/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts#L39 Validation steps: - Start the rizzcharts agent: `cd samples/agent/adk/rizzcharts && uv run . --port=10002` - Start the rizzcharts Angular client: `cd samples/client/angular/projects && npm start -- rizzcharts` - Send the first query and get the contextId from the response: `contextId=$(curl -X POST -H "Content-Type: application/json" -d '{"parts": [{"kind": "text", "text": "Show me sales data for Q4"}]}' http://localhost:4200/a2a | jq -r .result.contextId)` - Send the second query with the same contextId: `curl -X POST -H "Content-Type: application/json" -d "{\"parts\": [{\"kind\": \"text\", \"text\": \"Plot this as a pie chart\"}], \"context_id\": \"$contextId\"}" http://localhost:4200/a2a | jq` - Confirm A2UI messages are generated However, other samples don't have the contextId set correctly. A simple verification for the contact_lookup sample. - Start the contact_lookup agent: `cd samples/agent/adk/contact_lookup && uv run . --port=10003` - Start the contact_lookup angular client: `cd samples/client/angular/projects && npm start -- contact` - Send the first query: `curl -X POST -H "Content-Type: application/json" -d '{"query": "Who is Alex Jordan?"}' http://localhost:4200/a2a | jq` - Confirm no `contextId` is returned This commit adds contextId support to A2A request and response payloads across samples. Verification: - Unit tests passed: `/projects/a2ui/samples/client/angular$ npx ng test orchestrator --watch=false` - Contact_lookup sample has contextId returned and propagated: - Start the contact_lookup agent: `cd samples/agent/adk/contact_lookup && uv run . --port=10003` - Start the contact_lookup angular client: `cd samples/client/angular/projects && npm start -- contact` - Send the first query and get the contextId from the response: `contextId=$(curl -X POST -H "Content-Type: application/json" -d '{"query": "Who is Alex Jordan?"}' http://localhost:4200/a2a | jq -r .contextId)` - Send the second query with the same contextId: `curl -X POST -H "Content-Type: application/json" -d "{\"query\": \"show me his contact card\", \"contextId\": \"$contextId\"}" http://localhost:4200/a2a | jq` - Confirm a successful response is returned with Alex's contact info. - Restaurant_finder sample has contextId returned and propagated: - Start the restaurant_finder agent: `cd samples/agent/adk/restaurant_finder && uv run . --port=10004` - Start the restaurant_finder angular client: `cd samples/client/angular/projects && npm start -- restaurant` - Send the first query and get the contextId from the response: `contextId=$(curl -X POST -H "Content-Type: application/json" -d '{"query": "Find Chinese restaurants in New York"}' http://localhost:4200/a2a | jq -r .contextId)` - Send the second query with the same contextId: `curl -X POST -H "Content-Type: application/json" -d "{\"query\": \"Show those restaurants again\", \"contextId\": \"$contextId\"}" http://localhost:4200/a2a | jq` - Confirm a successful response is returned with the Chinese restaurant list. - component_gallery sample has contextId returned and propagated: - Start the component_gallery agent: `cd samples/agent/adk/component_gallery && uv run . --port=10005` - Start the component_gallery lit client: `cd samples/client/lit/component_gallery && npm run dev` - Send the first query and get the contextId from the response: `contextId=$(curl -X POST -H "Content-Type: application/json" -d '{"event": {"type": "some_ui_event_or_query"}}' http://localhost:5173/a2a | jq -r .contextId)` - Send the second query with the same contextId: `curl -X POST -H "Content-Type: application/json" -d "{\"event\": {\"type\": \"follow_up_event\"}, \"contextId\": \"$contextId\"}" http://localhost:5173/a2a | jq` - Confirm a successful response is returned with the follow up event. - custom_comonent_example sample - Start the custom-components-example agent: `cd samples/agent/adk/custom-components-example && uv run . --port=10004` - Start the custom-components-example lit client: `cd samples/client/lit/custom-components-example && npm run dev` - Define the inline catalogs in client capabilities: `a2ui_capabilities='{"a2uiClientCapabilities":{"inlineCatalogs":[{"components":{"OrgChart":{"type":"object","properties":{"chain":{"type":"object","properties":{"path":{"type":"string"},"literalArray":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"}},"required":["title","name"]}}}},"action":{"type":"object","properties":{"name":{"type":"string"},"context":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"object","properties":{"path":{"type":"string"},"literalString":{"type":"string"},"literalNumber":{"type":"number"},"literalBoolean":{"type":"boolean"}}}},"required":["key","value"]}}},"required":["name"]}},"required":["chain"]},"McpApp":{"type":"object","properties":{"resourceUri":{"type":"string"},"htmlContent":{"type":"string"},"height":{"type":"number"},"allowedTools":{"type":"array","items":{"type":"string"}}}},"WebFrame":{"type":"object","properties":{"url":{"type":"string"},"html":{"type":"string"},"height":{"type":"number"},"interactionMode":{"type":"string","enum":["readOnly","interactive"]},"allowedEvents":{"type":"array","items":{"type":"string"}}}}}}]}}'` - Send the first query and get the contextId from the response: `contextId=$(curl -X POST -H "Content-Type: application/json" -d "{\"event\":{\"request\":\"Alex Jordan\",\"metadata\": $a2ui_capabilities}}" http://localhost:5173/a2a | jq -r .contextId)` - Send the second query with the same contextId: `curl -X POST -H "Content-Type: application/json" -d "{\"event\":{\"userAction\":{\"surfaceId\":\"contact-card\",\"name\":\"ACTION: view_location\",\"sourceComponentId\":\"location-button\",\"context\":{\"contactId\":\"1\"}},\"metadata\": $a2ui_capabilities},\"contextId\":\"$contextId\"}" http://localhost:5173/a2a | jq` - Confirm a successful response is returned with the floor plan. - orchestrator sample - Start the restaurant_finder agent: `cd samples/agent/adk/restaurant_finder && uv run . --port=10003` - Start the contact_lookup agent: `cd samples/agent/adk/contact_lookup && uv run . --port=10004` - Start the rizzcharts agent: `cd samples/agent/adk/rizzcharts && uv run . --port=10005` - Start the orchestrator agent: `cd samples/agent/adk/orchestrator && uv run . --port=10002 --subagent_urls=http://localhost:10003 --subagent_urls=http://localhost:10004 --subagent_urls=http://localhost:10005` - Start the orchestrator angular client: `cd samples/client/angular/projects && npm start -- orchestrator` - Send the first query and get the contextId from the response: `contextId=$(curl -X POST -H "Content-Type: application/json" -d "{\"parts\": [{\"kind\": \"text\", \"text\": \"Who is Alex Jordan?\"}]}" http://localhost:4200/a2a | jq -r .result.contextId)` - Send the second query with the same contextId: `curl -X POST -H "Content-Type: application/json" -d "{\"parts\": [{\"kind\": \"text\", \"text\": \"show me his contact card\"}], \"contextId\": \"$contextId\"}" http://localhost:4200/a2a | jq` - Confirm a successful response is returned with Alex's contact info.
Currently, only the rizzcharts sample support multi-turn conversation with contextId:
A2UI/samples/client/angular/projects/rizzcharts/src/server.ts
Line 61 in d50db0a
A2UI/samples/client/angular/projects/rizzcharts/src/services/a2a_service.ts
Line 39 in d50db0a
Validation steps:
cd samples/agent/adk/rizzcharts && uv run . --port=10002cd samples/client/angular/projects && npm start -- rizzchartscontextId=$(curl -X POST -H "Content-Type: application/json" -d '{"parts": [{"kind": "text", "text": "Show me sales data for Q4"}]}' http://localhost:4200/a2a | jq -r .result.contextId)curl -X POST -H "Content-Type: application/json" -d "{\"parts\": [{\"kind\": \"text\", \"text\": \"Plot this as a pie chart\"}], \"context_id\": \"$contextId\"}" http://localhost:4200/a2a | jqHowever, other samples don't have the contextId set correctly. A simple verification for the contact_lookup sample.
cd samples/agent/adk/contact_lookup && uv run . --port=10003cd samples/client/angular/projects && npm start -- contactcurl -X POST -H "Content-Type: application/json" -d '{"query": "Who is Alex Jordan?"}' http://localhost:4200/a2a | jqcontextIdis returnedThis commit adds contextId support to A2A request and response payloads across samples.
Verification:
Unit tests passed:
/projects/a2ui/samples/client/angular$ npx ng test orchestrator --watch=falseContact_lookup sample has contextId returned and propagated:
cd samples/agent/adk/contact_lookup && uv run . --port=10003cd samples/client/angular/projects && npm start -- contactcontextId=$(curl -X POST -H "Content-Type: application/json" -d '{"query": "Who is Alex Jordan?"}' http://localhost:4200/a2a | jq -r .contextId)curl -X POST -H "Content-Type: application/json" -d "{\"query\": \"show me his contact card\", \"contextId\": \"$contextId\"}" http://localhost:4200/a2a | jqRestaurant_finder sample has contextId returned and propagated:
cd samples/agent/adk/restaurant_finder && uv run . --port=10004cd samples/client/angular/projects && npm start -- restaurantcontextId=$(curl -X POST -H "Content-Type: application/json" -d '{"query": "Find Chinese restaurants in New York"}' http://localhost:4200/a2a | jq -r .contextId)curl -X POST -H "Content-Type: application/json" -d "{\"query\": \"Show those restaurants again\", \"contextId\": \"$contextId\"}" http://localhost:4200/a2a | jqcomponent_gallery sample has contextId returned and propagated:
cd samples/agent/adk/component_gallery && uv run . --port=10005cd samples/client/lit/component_gallery && npm run devcontextId=$(curl -X POST -H "Content-Type: application/json" -d '{"event": {"type": "some_ui_event_or_query"}}' http://localhost:5173/a2a | jq -r .contextId)curl -X POST -H "Content-Type: application/json" -d "{\"event\": {\"type\": \"follow_up_event\"}, \"contextId\": \"$contextId\"}" http://localhost:5173/a2a | jqcustom_comonent_example sample
cd samples/agent/adk/custom-components-example && uv run . --port=10004cd samples/client/lit/custom-components-example && npm run deva2ui_capabilities='{"a2uiClientCapabilities":{"inlineCatalogs":[{"components":{"OrgChart":{"type":"object","properties":{"chain":{"type":"object","properties":{"path":{"type":"string"},"literalArray":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"}},"required":["title","name"]}}}},"action":{"type":"object","properties":{"name":{"type":"string"},"context":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"value":{"type":"object","properties":{"path":{"type":"string"},"literalString":{"type":"string"},"literalNumber":{"type":"number"},"literalBoolean":{"type":"boolean"}}}},"required":["key","value"]}}},"required":["name"]}},"required":["chain"]},"McpApp":{"type":"object","properties":{"resourceUri":{"type":"string"},"htmlContent":{"type":"string"},"height":{"type":"number"},"allowedTools":{"type":"array","items":{"type":"string"}}}},"WebFrame":{"type":"object","properties":{"url":{"type":"string"},"html":{"type":"string"},"height":{"type":"number"},"interactionMode":{"type":"string","enum":["readOnly","interactive"]},"allowedEvents":{"type":"array","items":{"type":"string"}}}}}}]}}'contextId=$(curl -X POST -H "Content-Type: application/json" -d "{\"event\":{\"request\":\"Alex Jordan\",\"metadata\": $a2ui_capabilities}}" http://localhost:5173/a2a | jq -r .contextId)curl -X POST -H "Content-Type: application/json" -d "{\"event\":{\"userAction\":{\"surfaceId\":\"contact-card\",\"name\":\"ACTION: view_location\",\"sourceComponentId\":\"location-button\",\"context\":{\"contactId\":\"1\"}},\"metadata\": $a2ui_capabilities},\"contextId\":\"$contextId\"}" http://localhost:5173/a2a | jqorchestrator sample
cd samples/agent/adk/restaurant_finder && uv run . --port=10003cd samples/agent/adk/contact_lookup && uv run . --port=10004cd samples/agent/adk/rizzcharts && uv run . --port=10005cd samples/agent/adk/orchestrator && uv run . --port=10002 --subagent_urls=http://localhost:10003 --subagent_urls=http://localhost:10004 --subagent_urls=http://localhost:10005cd samples/client/angular/projects && npm start -- orchestratorcontextId=$(curl -X POST -H "Content-Type: application/json" -d "{\"parts\": [{\"kind\": \"text\", \"text\": \"Who is Alex Jordan?\"}]}" http://localhost:4200/a2a | jq -r .result.contextId)curl -X POST -H "Content-Type: application/json" -d "{\"parts\": [{\"kind\": \"text\", \"text\": \"show me his contact card\"}], \"contextId\": \"$contextId\"}" http://localhost:4200/a2a | jqFixes #1060
Description
Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.
List which issues are fixed by this PR. For larger changes, raising an issue first helps reduce redundant work.
Pre-launch Checklist
If you need help, consider asking for advice on the discussion board.