Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions a2a_samples/a2ui_extension/spec/spec.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# A2UI (Agent-to-Agent UI) Extension Spec

## Overview

This extension implements the A2UI (Agent-to-Agent UI) spec, a format for agents to send streaming, interactive user interfaces to clients.

## Extension URI
The URI of this extension is https://github.com/google/a2ui/a2a_samples/a2ui_extension/spec/spec.md.

The URI of this extension is https://raw.githubusercontent.com/google/A2UI/refs/heads/main/a2a_samples/a2ui_extension/spec/spec.md

This is the only URI accepted for this extension.

## Core Concepts

The A2UI extension is built on three main concepts:

Surfaces: A "Surface" is a distinct, controllable region of the client's UI. The spec uses a surfaceId to direct updates to specific surfaces (e.g., a main content area, a side panel, or a new chat bubble). This allows a single agent stream to manage multiple UI areas independently.
Expand Down Expand Up @@ -48,4 +52,4 @@ For JSON-RPC and HTTP transports, this is indicated via the X-A2A-Extensions HTT

For gRPC, this is indicated via the X-A2A-Extensions metadata value.

Activating this extension implies that the server can send A2UI-specific messages (like surfaceUpdate) and the client is expected to send A2UI-specific events (like userAction).
Activating this extension implies that the server can send A2UI-specific messages (like surfaceUpdate) and the client is expected to send A2UI-specific events (like userAction).
2 changes: 1 addition & 1 deletion a2a_samples/a2ui_extension/src/a2ui_ext/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def agent_extension(self) -> AgentExtension:
description="Provides a declarative a2ui UI JSON structure in messages.",
params={
"supportedSchemas": [
"https://raw.githubusercontent.com/google/a2ui/refs/heads/main/schemas/v0.1/standard_catalog.json"
"https://raw.githubusercontent.com/google/A2UI/refs/heads/main/specification/json/server_to_client.json"
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we also update this in the a2a extension? spec.md

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

OKay, done, but note that the links don't actually work (they get 404) until the repo is public. There's a token I could add to the URL now to make them work, but I figured it was better to have the final version.

],
"acceptsDynamicSchemas": True,
},
Expand Down
54 changes: 34 additions & 20 deletions a2a_samples/a2ui_restaurant_finder/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
A2UI_SCHEMA = """
{
"title": "A2UI Message Schema",
"description": "Describes a JSON payload for an A2UI message, which is used to dynamically construct and update user interfaces. A message MUST contain exactly ONE of the action properties: 'beginRendering', 'surfaceUpdate', 'dataModelUpdate', or 'surfaceDeletion'.",
"description": "Describes a JSON payload for an A2UI message, which is used to dynamically construct and update user interfaces. A message MUST contain exactly ONE of the action properties: 'beginRendering', 'surfaceUpdate', 'dataModelUpdate', or 'deleteSurface'.",
"type": "object",
"properties": {
"beginRendering": {
Expand All @@ -25,10 +25,6 @@
"type": "string",
"description": "The primary font for the UI."
},
"logoUrl": {
"type": "string",
"description": "A URL for the logo image."
},
"primaryColor": {
"type": "string",
"description": "The primary UI color as a hexadecimal code (e.g., '#00BFFF').",
Expand Down Expand Up @@ -641,23 +637,37 @@
"description": "An array of data entries. Each entry must contain a 'key' and exactly one corresponding typed 'value*' property.",
"items": {
"type": "object",
"description": "A single data entry. Exactly one 'value_' property should be provided alongside the key.",
"description": "A single data entry. Exactly one 'value*' property should be provided alongside the key.",
"properties": {
"key": {
"type": "string",
"description": "The key for this data entry."
Copy link
Collaborator

Choose a reason for hiding this comment

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

I know this is an issue with the schema source of truth, but we should be clear on the key format, and how it handles lists of objects, e.g. how do you define an object like:

{
  "books": [
    {
      "title": "Dune",
      "author": "Frank Herbert"
    },
    {
      "title": "1984",
      "author": "George Orwell"
    },
    {
      "title": "The Hobbit",
      "author": "J.R.R. Tolkien"
    }
  ]
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right, there's no good way to define that right now. The closest you could get would be a list of titles:

{
  "/book_titles": {
    "valueList" : [{"valueString": "Dune"}, {"valueString":"1984"}, {"valueString": "The Hobbit"}]
  }
}

You could define individual paths, e.g. "/books/dune/title", "/books/dune/author", and use those as valus for a card, and then collect the cards in a list by id.

I agree we want to support your scenario, though.

},
"valueString": {
"type": "string",
"description": "A string value."
"type": "string"
},
"valueNumber": {
"type": "number",
"description": "A number value."
"type": "number"
},
"valueBoolean": {
"type": "boolean",
"description": "A boolean value."
"type": "boolean"
},
"valueList": {
"type": "array",
"items": {
"type": "object",
"properties": {
"valueString": {
"type": "string"
},
"valueNumber": {
"type": "number"
},
"valueBoolean": {
"type": "boolean"
}
}
}
}
},
"required": ["key"]
Expand All @@ -684,7 +694,7 @@
RESTAURANT_UI_EXAMPLES = """
---BEGIN SINGLE_COLUMN_LIST_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto", "logoUrl": "{base_url}/static/logo.png" }} }} }},
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "default",
"components": [
Expand All @@ -699,7 +709,8 @@
{{ "id": "template-rating", "componentProperties": {{ "Text": {{ "text": {{ "path": "rating" }} }} }} }},
{{ "id": "template-detail", "componentProperties": {{ "Text": {{ "text": {{ "path": "detail" }} }} }} }},
{{ "id": "template-link", "componentProperties": {{ "Text": {{ "text": {{ "path": "infoLink" }} }} }} }},
{{ "id": "template-book-button", "componentProperties": {{ "Button": {{ "label": {{ "literalString": "Book Now" }}, "action": {{ "action": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "address" }} }} ] }} }} }} }}
{{ "id": "template-book-button", "componentProperties": {{ "Button": {{ "child": "book-now-text", "action": {{ "name": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "address" }} }} ] }} }} }} }},
{{ "id": "book-now-text", "componentProperties": {{ "Text": {{ "text": {{ "literalString": "Book Now" }} }} }} }}
]
}} }},
{{ "dataModelUpdate": {{
Expand All @@ -714,7 +725,7 @@
---BEGIN TWO_COLUMN_LIST_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto", "logoUrl": "{base_url}/static/logo.png" }} }} }},
{{ "beginRendering": {{ "surfaceId": "default", "root": "root-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "default",
"components": [
Expand All @@ -729,7 +740,8 @@
{{ "id": "template-rating-1", "componentProperties": {{ "Text": {{ "text": {{ "path": "/items/0/rating" }} }} }} }},
{{ "id": "template-detail-1", "componentProperties": {{ "Text": {{ "text": {{ "path": "/items/0/detail" }} }} }} }},
{{ "id": "template-link-1", "componentProperties": {{ "Text": {{ "text": {{ "path": "/items/0/infoLink" }} }} }} }},
{{ "id": "template-book-button-1", "componentProperties": {{ "Button": {{ "label": {{ "literalString": "Book Now" }}, "action": {{ "action": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "/items/0/name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "/items/0/imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "/items/0/address" }} }} ] }} }} }} }},
{{ "id": "template-book-button-1", "componentProperties": {{ "Button": {{ "child": "book-now-text-1", "action": {{ "name": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "/items/0/name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "/items/0/imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "/items/0/address" }} }} ] }} }} }} }},
{{ "id": "book-now-text-1", "componentProperties": {{ "Text": {{ "text": {{ "literalString": "Book Now" }} }} }} }},
{{ "id": "item-card-2", "weight": 1, "componentProperties": {{ "Card": {{ "child": "card-layout-2" }} }} }},
{{ "id": "card-layout-2", "componentProperties": {{ "Column": {{ "children": {{ "explicitList": ["template-image-2", "card-details-2"] }} }} }} }},
{{ "id": "template-image-2", "componentProperties": {{ "Image": {{ "url": {{ "path": "/items/1/imageUrl" }}, "width": "100%" }} }} }},
Expand All @@ -738,7 +750,8 @@
{{ "id": "template-rating-2", "componentProperties": {{ "Text": {{ "text": {{ "path": "/items/1/rating" }} }} }} }},
{{ "id": "template-detail-2", "componentProperties": {{ "Text": {{ "text": {{ "path": "/items/1/detail" }} }} }} }},
{{ "id": "template-link-2", "componentProperties": {{ "Text": {{ "text": {{ "path": "/items/1/infoLink" }} }} }} }},
{{ "id": "template-book-button-2", "componentProperties": {{ "Button": {{ "label": {{ "literalString": "Book Now" }}, "action": {{ "action": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "/items/1/name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "/items/1/imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "/items/1/address" }} }} ] }} }} }} }}
{{ "id": "template-book-button-2", "componentProperties": {{ "Button": {{ "child": "book-now-text-2", "action": {{ "name": "book_restaurant", "context": [ {{ "key": "restaurantName", "value": {{ "path": "/items/1/name" }} }}, {{ "key": "imageUrl", "value": {{ "path": "/items/1/imageUrl" }} }}, {{ "key": "address", "value": {{ "path": "/items/1/address" }} }} ] }} }} }} }},
{{ "id": "book-now-text-2", "componentProperties": {{ "Text": {{ "text": {{ "literalString": "Book Now" }} }} }} }}
]
}} }},
{{ "dataModelUpdate": {{
Expand All @@ -753,7 +766,7 @@
---BEGIN BOOKING_FORM_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "booking-form", "root": "booking-form-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto", "logoUrl": "{base_url}/static/logo.png" }} }} }},
{{ "beginRendering": {{ "surfaceId": "booking-form", "root": "booking-form-column", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "booking-form",
"components": [
Expand All @@ -764,7 +777,8 @@
{{ "id": "party-size-field", "componentProperties": {{ "TextField": {{ "label": {{ "literalString": "Party Size" }}, "text": {{ "path": "partySize" }}, "type": "number" }} }} }},
{{ "id": "datetime-field", "componentProperties": {{ "DateTimeInput": {{ "label": {{ "literalString": "Date & Time" }}, "value": {{ "path": "reservationTime" }}, "enableDate": true, "enableTime": true }} }} }},
{{ "id": "dietary-field", "componentProperties": {{ "TextField": {{ "label": {{ "literalString": "Dietary Requirements" }}, "text": {{ "path": "dietary" }} }} }} }},
{{ "id": "submit-button", "componentProperties": {{ "Button": {{ "label": {{ "literalString": "Submit Reservation" }}, "action": {{ "action": "submit_booking", "context": [ {{ "key": "restaurantName", "value": {{ "path": "restaurantName" }} }}, {{ "key": "partySize", "value": {{ "path": "partySize" }} }}, {{ "key": "reservationTime", "value": {{ "path": "reservationTime" }} }}, {{ "key": "dietary", "value": {{ "path": "dietary" }} }}, {{ "key": "imageUrl", "value": {{ "path": "imageUrl" }} }} ] }} }} }} }}
{{ "id": "submit-button", "componentProperties": {{ "Button": {{ "child": "submit-reservation-text", "action": {{ "name": "submit_booking", "context": [ {{ "key": "restaurantName", "value": {{ "path": "restaurantName" }} }}, {{ "key": "partySize", "value": {{ "path": "partySize" }} }}, {{ "key": "reservationTime", "value": {{ "path": "reservationTime" }} }}, {{ "key": "dietary", "value": {{ "path": "dietary" }} }}, {{ "key": "imageUrl", "value": {{ "path": "imageUrl" }} }} ] }} }} }} }},
{{ "id": "submit-reservation-text", "componentProperties": {{ "Text": {{ "text": {{ "literalString": "Submit Reservation" }} }} }} }}
]
}} }},
{{ "dataModelUpdate": {{
Expand All @@ -785,7 +799,7 @@
---BEGIN CONFIRMATION_EXAMPLE---
[
{{ "beginRendering": {{ "surfaceId": "confirmation", "root": "confirmation-card", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto", "logoUrl": "{base_url}/static/logo.png" }} }} }},
{{ "beginRendering": {{ "surfaceId": "confirmation", "root": "confirmation-card", "styles": {{ "primaryColor": "#FF0000", "font": "Roboto" }} }} }},
{{ "surfaceUpdate": {{
"surfaceId": "confirmation",
"components": [
Expand Down
Loading