Skip to content

feat: add contextId support to A2A request and response payloads across samples#1070

Open
nan-yu wants to merge 2 commits intogoogle:mainfrom
nan-yu:context-id
Open

feat: add contextId support to A2A request and response payloads across samples#1070
nan-yu wants to merge 2 commits intogoogle:mainfrom
nan-yu:context-id

Conversation

@nan-yu
Copy link
Copy Markdown
Collaborator

@nan-yu nan-yu commented Apr 3, 2026

Currently, only the rizzcharts sample support multi-turn conversation with contextId:

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.

Fixes #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.

@github-project-automation github-project-automation bot moved this to Todo in A2UI Apr 3, 2026
@nan-yu nan-yu changed the title feat: add contextId support to A2A request and response payloads acro… feat: add contextId support to A2A request and response payloads across samples Apr 3, 2026
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 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.

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 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.

nan-yu added 2 commits April 6, 2026 18:13
…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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Feature: enable multi-turn conversation support via contextId propagation

1 participant