diff --git a/.github/workflows/python_samples_build.yml b/.github/workflows/python_samples_build.yml index 6435adc28..a43dbfb82 100644 --- a/.github/workflows/python_samples_build.yml +++ b/.github/workflows/python_samples_build.yml @@ -51,6 +51,10 @@ jobs: working-directory: samples/agent/adk run: uv run pyink --check . + - name: Validate Sample Examples + run: | + PYTHONPATH=agent_sdks/python/src uv run --project agent_sdks/python pytest -vv samples/agent/adk/tests/test_examples_validation.py + - name: Build contact_lookup working-directory: samples/agent/adk/contact_lookup run: uv build . diff --git a/samples/agent/adk/contact_lookup/agent.py b/samples/agent/adk/contact_lookup/agent.py index 9a8fe4eeb..9f94a44c8 100644 --- a/samples/agent/adk/contact_lookup/agent.py +++ b/samples/agent/adk/contact_lookup/agent.py @@ -32,7 +32,7 @@ from google.genai import types from prompt_builder import get_text_prompt, ROLE_DESCRIPTION, WORKFLOW_DESCRIPTION, UI_DESCRIPTION from tools import get_contact_info -from a2ui.core.schema.constants import VERSION_0_8 +from a2ui.core.schema.constants import VERSION_0_9 from a2ui.core.schema.manager import A2uiSchemaManager from a2ui.basic_catalog.provider import BasicCatalog from a2ui.a2a import get_a2ui_agent_extension @@ -48,11 +48,15 @@ class ContactAgent: def __init__(self, base_url: str, use_ui: bool = False): self.base_url = base_url self.use_ui = use_ui + self.version = VERSION_0_9 self._schema_manager = ( A2uiSchemaManager( - version=VERSION_0_8, + version=self.version, catalogs=[ - BasicCatalog.get_config(version=VERSION_0_8, examples_path="examples") + BasicCatalog.get_config( + version=self.version, + examples_path=f"examples/{self.version}", + ) ], ) if use_ui diff --git a/samples/agent/adk/contact_lookup/examples/action_confirmation.json b/samples/agent/adk/contact_lookup/examples/0.8/action_confirmation.json similarity index 100% rename from samples/agent/adk/contact_lookup/examples/action_confirmation.json rename to samples/agent/adk/contact_lookup/examples/0.8/action_confirmation.json diff --git a/samples/agent/adk/contact_lookup/examples/contact_card.json b/samples/agent/adk/contact_lookup/examples/0.8/contact_card.json similarity index 100% rename from samples/agent/adk/contact_lookup/examples/contact_card.json rename to samples/agent/adk/contact_lookup/examples/0.8/contact_card.json diff --git a/samples/agent/adk/contact_lookup/examples/contact_list.json b/samples/agent/adk/contact_lookup/examples/0.8/contact_list.json similarity index 100% rename from samples/agent/adk/contact_lookup/examples/contact_list.json rename to samples/agent/adk/contact_lookup/examples/0.8/contact_list.json diff --git a/samples/agent/adk/contact_lookup/examples/follow_success.json b/samples/agent/adk/contact_lookup/examples/0.8/follow_success.json similarity index 100% rename from samples/agent/adk/contact_lookup/examples/follow_success.json rename to samples/agent/adk/contact_lookup/examples/0.8/follow_success.json diff --git a/samples/agent/adk/contact_lookup/examples/0.9/action_confirmation.json b/samples/agent/adk/contact_lookup/examples/0.9/action_confirmation.json new file mode 100644 index 000000000..98b6c85be --- /dev/null +++ b/samples/agent/adk/contact_lookup/examples/0.9/action_confirmation.json @@ -0,0 +1,84 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "action-modal", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#007BFF", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "action-modal", + "components": [ + { + "id": "root", + "component": "Modal", + "trigger": "hidden-entry-point", + "content": "modal-content-column" + }, + { + "id": "hidden-entry-point", + "component": "Text", + "text": "" + }, + { + "id": "modal-content-column", + "component": "Column", + "children": [ + "modal-title", + "modal-message", + "dismiss-button" + ], + "align": "center" + }, + { + "id": "modal-title", + "component": "Text", + "variant": "h2", + "text": { + "path": "/actionTitle" + } + }, + { + "id": "modal-message", + "component": "Text", + "text": { + "path": "/actionMessage" + } + }, + { + "id": "dismiss-button-text", + "component": "Text", + "text": "Dismiss" + }, + { + "id": "dismiss-button", + "component": "Button", + "child": "dismiss-button-text", + "variant": "primary", + "action": { + "event": { + "name": "dismiss_modal" + } + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "action-modal", + "path": "/", + "value": { + "actionTitle": "Action Confirmation", + "actionMessage": "Your action has been processed." + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_lookup/examples/0.9/contact_card.json b/samples/agent/adk/contact_lookup/examples/0.9/contact_card.json new file mode 100644 index 000000000..aebfa3366 --- /dev/null +++ b/samples/agent/adk/contact_lookup/examples/0.9/contact_card.json @@ -0,0 +1,318 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "contact-card", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "contact-card", + "components": [ + { + "id": "profile_image", + "component": "Image", + "url": { + "path": "/imageUrl" + }, + "variant": "avatar", + "fit": "cover" + }, + { + "id": "user_heading", + "component": "Text", + "weight": 1, + "text": { + "path": "/name" + }, + "variant": "h2" + }, + { + "id": "description_text_1", + "component": "Text", + "text": { + "path": "/title" + } + }, + { + "id": "description_text_2", + "component": "Text", + "text": { + "path": "/team" + } + }, + { + "id": "description_column", + "component": "Column", + "children": [ + "user_heading", + "description_text_1", + "description_text_2" + ], + "align": "center" + }, + { + "id": "calendar_icon", + "component": "Icon", + "name": "calendar_today" + }, + { + "id": "calendar_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/calendar" + } + }, + { + "id": "calendar_secondary_text", + "component": "Text", + "text": "Calendar" + }, + { + "id": "calendar_text_column", + "component": "Column", + "children": [ + "calendar_primary_text", + "calendar_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_1", + "component": "Row", + "children": [ + "calendar_icon", + "calendar_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "location_icon", + "component": "Icon", + "name": "location_on" + }, + { + "id": "location_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/location" + } + }, + { + "id": "location_secondary_text", + "component": "Text", + "text": "Location" + }, + { + "id": "location_text_column", + "component": "Column", + "children": [ + "location_primary_text", + "location_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_2", + "component": "Row", + "children": [ + "location_icon", + "location_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "mail_icon", + "component": "Icon", + "name": "mail" + }, + { + "id": "mail_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/email" + } + }, + { + "id": "mail_secondary_text", + "component": "Text", + "text": "Email" + }, + { + "id": "mail_text_column", + "component": "Column", + "children": [ + "mail_primary_text", + "mail_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_3", + "component": "Row", + "children": [ + "mail_icon", + "mail_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "div", + "component": "Divider" + }, + { + "id": "call_icon", + "component": "Icon", + "name": "call" + }, + { + "id": "call_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/mobile" + } + }, + { + "id": "call_secondary_text", + "component": "Text", + "text": "Mobile" + }, + { + "id": "call_text_column", + "component": "Column", + "children": [ + "call_primary_text", + "call_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_4", + "component": "Row", + "children": [ + "call_icon", + "call_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_rows_column", + "component": "Column", + "weight": 1, + "children": [ + "info_row_1", + "info_row_2", + "info_row_3", + "info_row_4" + ], + "align": "stretch" + }, + { + "id": "button_1_text", + "component": "Text", + "text": "Follow" + }, + { + "id": "button_1", + "component": "Button", + "child": "button_1_text", + "variant": "primary", + "action": { + "event": { + "name": "follow_contact" + } + } + }, + { + "id": "button_2_text", + "component": "Text", + "text": "Message" + }, + { + "id": "button_2", + "component": "Button", + "child": "button_2_text", + "variant": "default", + "action": { + "event": { + "name": "send_message" + } + } + }, + { + "id": "action_buttons_row", + "component": "Row", + "children": [ + "button_1", + "button_2" + ], + "justify": "center", + "align": "center" + }, + { + "id": "link_text", + "component": "Text", + "text": "[View Full Profile](/profile)" + }, + { + "id": "link_text_wrapper", + "component": "Row", + "children": [ + "link_text" + ], + "justify": "center", + "align": "center" + }, + { + "id": "main_column", + "component": "Column", + "children": [ + "profile_image", + "description_column", + "div", + "info_rows_column", + "action_buttons_row", + "link_text_wrapper" + ], + "align": "stretch" + }, + { + "id": "root", + "component": "Card", + "child": "main_column" + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "contact-card", + "path": "/", + "value": { + "name": "", + "title": "", + "team": "", + "location": "", + "email": "", + "mobile": "", + "calendar": "", + "imageUrl": "" + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_lookup/examples/0.9/contact_list.json b/samples/agent/adk/contact_lookup/examples/0.9/contact_list.json new file mode 100644 index 000000000..fd2727da4 --- /dev/null +++ b/samples/agent/adk/contact_lookup/examples/0.9/contact_list.json @@ -0,0 +1,141 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "contact-list", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#007BFF", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "contact-list", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "title-heading", + "item-list" + ] + }, + { + "id": "title-heading", + "component": "Text", + "variant": "h1", + "text": "Found Contacts" + }, + { + "id": "item-list", + "component": "List", + "direction": "vertical", + "children": { + "componentId": "item-card-template", + "path": "/contacts" + } + }, + { + "id": "item-card-template", + "component": "Card", + "child": "card-layout" + }, + { + "id": "card-layout", + "component": "Row", + "children": [ + "template-image", + "card-details", + "view-button" + ], + "align": "center" + }, + { + "id": "template-image", + "component": "Image", + "url": { + "path": "/imageUrl" + }, + "fit": "cover" + }, + { + "id": "card-details", + "component": "Column", + "children": [ + "template-name", + "template-title" + ] + }, + { + "id": "template-name", + "component": "Text", + "variant": "h3", + "text": { + "path": "/name" + } + }, + { + "id": "template-title", + "component": "Text", + "text": { + "path": "/title" + } + }, + { + "id": "view-button-text", + "component": "Text", + "text": "View" + }, + { + "id": "view-button", + "component": "Button", + "child": "view-button-text", + "variant": "primary", + "action": { + "event": { + "name": "view_profile", + "context": { + "contactName": { + "path": "/name" + }, + "department": { + "path": "/department" + } + } + } + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "contact-list", + "path": "/", + "value": { + "contacts": { + "contact1": { + "name": "Alice Wonderland", + "phone": "+1-555-123-4567", + "email": "alice@example.com", + "imageUrl": "https://example.com/alice.jpg", + "title": "Mad Hatter", + "department": "Wonderland" + }, + "contact2": { + "name": "Bob The Builder", + "phone": "+1-555-765-4321", + "email": "bob@example.com", + "imageUrl": "https://example.com/bob.jpg", + "title": "Construction", + "department": "Building" + } + } + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_lookup/examples/0.9/follow_success.json b/samples/agent/adk/contact_lookup/examples/0.9/follow_success.json new file mode 100644 index 000000000..631404ebe --- /dev/null +++ b/samples/agent/adk/contact_lookup/examples/0.9/follow_success.json @@ -0,0 +1,42 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "contact-card", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "contact-card", + "components": [ + { + "id": "success_icon", + "component": "Icon", + "name": "check" + }, + { + "id": "success_text", + "component": "Text", + "text": "Successfully Followed", + "variant": "h2" + }, + { + "id": "success_column", + "component": "Column", + "children": [ + "success_icon", + "success_text" + ], + "align": "center" + }, + { + "id": "root", + "component": "Card", + "child": "success_column" + } + ] + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_lookup/prompt_builder.py b/samples/agent/adk/contact_lookup/prompt_builder.py index 82abb870a..1f8eb2827 100644 --- a/samples/agent/adk/contact_lookup/prompt_builder.py +++ b/samples/agent/adk/contact_lookup/prompt_builder.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from a2ui.core.schema.constants import VERSION_0_8 +from a2ui.core.schema.constants import VERSION_0_9 from a2ui.core.schema.manager import A2uiSchemaManager from a2ui.basic_catalog.provider import BasicCatalog @@ -27,14 +27,14 @@ 2. The first part is your conversational text response (e.g., "Here is the contact you requested..."). 3. The second part is a single, raw JSON object which is a list of A2UI messages. 4. The JSON part MUST validate against the A2UI JSON SCHEMA provided below. -5. Buttons that represent the main action on a card or view (e.g., 'Follow', 'Email', 'Search') SHOULD include the `"primary": true` attribute. +5. Buttons that represent the main action on a card or view (e.g., 'Follow', 'Email', 'Search') SHOULD include the `"variant": "primary"` attribute. """ UI_DESCRIPTION = """ - **For finding contacts (e.g., "Who is Alex Jordan?"):** a. You MUST call the `get_contact_info` tool. - b. If the tool returns a **single contact**, you MUST use the `CONTACT_CARD_EXAMPLE` template. Populate the `dataModelUpdate.contents` with the contact's details (name, title, email, etc.). - c. If the tool returns **multiple contacts**, you MUST use the `CONTACT_LIST_EXAMPLE` template. Populate the `dataModelUpdate.contents` with the list of contacts for the "contacts" key. + b. If the tool returns a **single contact**, you MUST use the `CONTACT_CARD_EXAMPLE` template. Populate the `updateDataModel.value` with the contact's details (name, title, email, etc.). + c. If the tool returns **multiple contacts**, you MUST use the `CONTACT_LIST_EXAMPLE` template. Populate the `updateDataModel.value` with the list of contacts for the "contacts" key. d. If the tool returns an **empty list**, respond with text only and an empty JSON list: "I couldn't find anyone by that name.---a2ui_JSON---[]" - **For handling a profile view (e.g., "WHO_IS: Alex Jordan..."):** @@ -69,16 +69,22 @@ def get_text_prompt() -> str: if __name__ == "__main__": # Example of how to use the A2UI Schema Manager to generate a system prompt + my_version = VERSION_0_9 contact_prompt = A2uiSchemaManager( - VERSION_0_8, - catalogs=[BasicCatalog.get_config(version=VERSION_0_8, examples_path="examples")], + my_version, + catalogs=[ + BasicCatalog.get_config( + version=my_version, + examples_path=f"examples/{my_version}", + ) + ], ).generate_system_prompt( role_description=ROLE_DESCRIPTION, workflow_description=WORKFLOW_DESCRIPTION, ui_description=UI_DESCRIPTION, include_schema=True, include_examples=True, - validate_examples=False, + validate_examples=False, # Use invalid examples to test retry logic ) print(contact_prompt) with open("generated_prompt.txt", "w") as f: diff --git a/samples/agent/adk/contact_multiple_surfaces/a2ui_examples.py b/samples/agent/adk/contact_multiple_surfaces/a2ui_examples.py index fc08f25af..4d5943506 100644 --- a/samples/agent/adk/contact_multiple_surfaces/a2ui_examples.py +++ b/samples/agent/adk/contact_multiple_surfaces/a2ui_examples.py @@ -21,22 +21,12 @@ logger = logging.getLogger(__name__) -# Map logical example names (used in prompt) to filenames -EXAMPLE_FILES = { - "CONTACT_LIST_EXAMPLE": "contact_list.json", - "CONTACT_CARD_EXAMPLE": "contact_card.json", - "ACTION_CONFIRMATION_EXAMPLE": "action_confirmation.json", - "ORG_CHART_EXAMPLE": "org_chart.json", - "MULTI_SURFACE_EXAMPLE": "multi_surface.json", - "CHART_NODE_CLICK_EXAMPLE": "chart_node_click.json", -} - FLOOR_PLAN_FILE = "floor_plan.json" -def load_floor_plan_example() -> str: +def load_floor_plan_example(version: str) -> str: """Loads the floor plan example specifically.""" - examples_dir = Path(os.path.dirname(__file__)) / "examples" + examples_dir = Path(os.path.dirname(__file__)) / "examples" / version file_path = examples_dir / FLOOR_PLAN_FILE try: return file_path.read_text(encoding="utf-8") diff --git a/samples/agent/adk/contact_multiple_surfaces/agent.py b/samples/agent/adk/contact_multiple_surfaces/agent.py index 93a8314dc..9a8e7b60c 100644 --- a/samples/agent/adk/contact_multiple_surfaces/agent.py +++ b/samples/agent/adk/contact_multiple_surfaces/agent.py @@ -36,7 +36,7 @@ UI_DESCRIPTION, ) from tools import get_contact_info -from a2ui.core.schema.constants import VERSION_0_8 +from a2ui.core.schema.constants import VERSION_0_9 from a2ui.core.schema.manager import A2uiSchemaManager from a2ui.basic_catalog.provider import BasicCatalog from a2ui.core.schema.common_modifiers import remove_strict_validation @@ -53,11 +53,15 @@ class ContactAgent: def __init__(self, base_url: str, use_ui: bool = False): self.base_url = base_url self.use_ui = use_ui + self.version = VERSION_0_9 self.schema_manager = ( A2uiSchemaManager( - VERSION_0_8, + self.version, catalogs=[ - BasicCatalog.get_config(version=VERSION_0_8, examples_path="examples") + BasicCatalog.get_config( + version=self.version, + examples_path=f"examples/{self.version}", + ) ], schema_modifiers=[remove_strict_validation], accepts_inline_catalogs=True, @@ -193,7 +197,7 @@ async def stream(self, query, session_id) -> AsyncIterable[dict[str, Any]]: # Re-implement logic to read from file from pathlib import Path - examples_dir = Path(__file__).parent / "examples" + examples_dir = Path(__file__).parent / "examples" / self.version action_file = examples_dir / "action_confirmation.json" if action_file.exists(): @@ -242,7 +246,7 @@ async def stream(self, query, session_id) -> AsyncIterable[dict[str, Any]]: logger.info("--- ContactAgent.stream: Detected view_location ACTION ---") # Use the predefined example floor plan - json_content = load_floor_plan_example().strip() + json_content = load_floor_plan_example(self.version).strip() start_idx = json_content.find("[") end_idx = json_content.rfind("]") if start_idx != -1 and end_idx != -1: diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/action_confirmation.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.8/action_confirmation.json similarity index 100% rename from samples/agent/adk/contact_multiple_surfaces/examples/action_confirmation.json rename to samples/agent/adk/contact_multiple_surfaces/examples/0.8/action_confirmation.json diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/chart_node_click.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.8/chart_node_click.json similarity index 100% rename from samples/agent/adk/contact_multiple_surfaces/examples/chart_node_click.json rename to samples/agent/adk/contact_multiple_surfaces/examples/0.8/chart_node_click.json diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/contact_card.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.8/contact_card.json similarity index 100% rename from samples/agent/adk/contact_multiple_surfaces/examples/contact_card.json rename to samples/agent/adk/contact_multiple_surfaces/examples/0.8/contact_card.json diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/contact_list.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.8/contact_list.json similarity index 100% rename from samples/agent/adk/contact_multiple_surfaces/examples/contact_list.json rename to samples/agent/adk/contact_multiple_surfaces/examples/0.8/contact_list.json diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/floor_plan.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.8/floor_plan.json similarity index 100% rename from samples/agent/adk/contact_multiple_surfaces/examples/floor_plan.json rename to samples/agent/adk/contact_multiple_surfaces/examples/0.8/floor_plan.json diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/multi_surface.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.8/multi_surface.json similarity index 100% rename from samples/agent/adk/contact_multiple_surfaces/examples/multi_surface.json rename to samples/agent/adk/contact_multiple_surfaces/examples/0.8/multi_surface.json diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/org_chart.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.8/org_chart.json similarity index 100% rename from samples/agent/adk/contact_multiple_surfaces/examples/org_chart.json rename to samples/agent/adk/contact_multiple_surfaces/examples/0.8/org_chart.json diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/0.9/action_confirmation.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/action_confirmation.json new file mode 100644 index 000000000..98b6c85be --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/action_confirmation.json @@ -0,0 +1,84 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "action-modal", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#007BFF", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "action-modal", + "components": [ + { + "id": "root", + "component": "Modal", + "trigger": "hidden-entry-point", + "content": "modal-content-column" + }, + { + "id": "hidden-entry-point", + "component": "Text", + "text": "" + }, + { + "id": "modal-content-column", + "component": "Column", + "children": [ + "modal-title", + "modal-message", + "dismiss-button" + ], + "align": "center" + }, + { + "id": "modal-title", + "component": "Text", + "variant": "h2", + "text": { + "path": "/actionTitle" + } + }, + { + "id": "modal-message", + "component": "Text", + "text": { + "path": "/actionMessage" + } + }, + { + "id": "dismiss-button-text", + "component": "Text", + "text": "Dismiss" + }, + { + "id": "dismiss-button", + "component": "Button", + "child": "dismiss-button-text", + "variant": "primary", + "action": { + "event": { + "name": "dismiss_modal" + } + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "action-modal", + "path": "/", + "value": { + "actionTitle": "Action Confirmation", + "actionMessage": "Your action has been processed." + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/0.9/chart_node_click.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/chart_node_click.json new file mode 100644 index 000000000..e3784e20f --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/chart_node_click.json @@ -0,0 +1,329 @@ +[ + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "contact-card", + "components": [ + { + "id": "profile_image", + "component": "Image", + "url": { + "path": "/imageUrl" + }, + "variant": "avatar", + "fit": "cover" + }, + { + "id": "user_heading", + "component": "Text", + "weight": 1, + "text": { + "path": "/name" + }, + "variant": "h2" + }, + { + "id": "description_text_1", + "component": "Text", + "text": { + "path": "/title" + } + }, + { + "id": "description_text_2", + "component": "Text", + "text": { + "path": "/team" + } + }, + { + "id": "description_column", + "component": "Column", + "children": [ + "user_heading", + "description_text_1", + "description_text_2" + ], + "align": "center" + }, + { + "id": "calendar_icon", + "component": "Icon", + "name": "calendarToday" + }, + { + "id": "calendar_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/calendar" + } + }, + { + "id": "calendar_secondary_text", + "component": "Text", + "text": "Calendar" + }, + { + "id": "calendar_text_column", + "component": "Column", + "children": [ + "calendar_primary_text", + "calendar_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_1", + "component": "Row", + "children": [ + "calendar_icon", + "calendar_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "location_icon", + "component": "Icon", + "name": "locationOn" + }, + { + "id": "location_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/location" + } + }, + { + "id": "location_secondary_text", + "component": "Text", + "text": "Location" + }, + { + "id": "location_text_column", + "component": "Column", + "children": [ + "location_primary_text", + "location_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_2", + "component": "Row", + "children": [ + "location_icon", + "location_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "mail_icon", + "component": "Icon", + "name": "mail" + }, + { + "id": "mail_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/email" + } + }, + { + "id": "mail_secondary_text", + "component": "Text", + "text": "Email" + }, + { + "id": "mail_text_column", + "component": "Column", + "children": [ + "mail_primary_text", + "mail_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_3", + "component": "Row", + "children": [ + "mail_icon", + "mail_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "div", + "component": "Divider" + }, + { + "id": "call_icon", + "component": "Icon", + "name": "call" + }, + { + "id": "call_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/mobile" + } + }, + { + "id": "call_secondary_text", + "component": "Text", + "text": "Mobile" + }, + { + "id": "call_text_column", + "component": "Column", + "children": [ + "call_primary_text", + "call_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_4", + "component": "Row", + "children": [ + "call_icon", + "call_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_rows_column", + "component": "Column", + "weight": 1, + "children": [ + "info_row_1", + "info_row_2", + "info_row_3", + "info_row_4" + ], + "align": "stretch" + }, + { + "id": "message-button-text", + "component": "Text", + "text": "Message" + }, + { + "id": "message-button", + "component": "Button", + "child": "message-button-text", + "action": { + "event": { + "name": "send_message", + "context": { + "contactName": { + "path": "/name" + } + } + } + } + }, + { + "id": "location-button-text", + "component": "Text", + "text": "Location" + }, + { + "id": "location-button", + "component": "Button", + "child": "location-button-text", + "action": { + "event": { + "name": "view_location", + "context": { + "contactId": { + "path": "/id" + } + } + } + } + }, + { + "id": "action_buttons_row", + "component": "Row", + "children": [ + "message-button", + "location-button" + ], + "justify": "center", + "align": "center" + }, + { + "id": "main_column", + "component": "Column", + "children": [ + "profile_image", + "description_column", + "div", + "info_rows_column", + "action_buttons_row" + ], + "align": "stretch" + }, + { + "id": "root", + "component": "Card", + "child": "main_column" + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "contact-card", + "path": "/", + "value": { + "name": "John Smith", + "title": "VP Marketing", + "team": "Marketing", + "location": "New York", + "email": "john.smith@example.com", + "mobile": "+1 (212) 555-0101", + "calendar": "Free", + "imageUrl": "http://localhost:10004/static/profile1.png" + } + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "org-chart-view", + "path": "/", + "value": { + "hierarchy": { + "0": { + "title": "CEO", + "name": "Jane Doe" + }, + "1": { + "title": "VP Marketing", + "name": "John Smith" + } + } + } + } + }, + { + "version": "v0.9", + "deleteSurface": { + "surfaceId": "location-modal" + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/0.9/contact_card.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/contact_card.json new file mode 100644 index 000000000..07f4c2626 --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/contact_card.json @@ -0,0 +1,311 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "contact-card", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "contact-card", + "components": [ + { + "id": "profile_image", + "component": "Image", + "url": { + "path": "/imageUrl" + }, + "variant": "avatar", + "fit": "cover" + }, + { + "id": "user_heading", + "component": "Text", + "weight": 1, + "text": { + "path": "/name" + }, + "variant": "h2" + }, + { + "id": "description_text_1", + "component": "Text", + "text": { + "path": "/title" + } + }, + { + "id": "description_text_2", + "component": "Text", + "text": { + "path": "/team" + } + }, + { + "id": "description_column", + "component": "Column", + "children": [ + "user_heading", + "description_text_1", + "description_text_2" + ], + "align": "center" + }, + { + "id": "calendar_icon", + "component": "Icon", + "name": "calendarToday" + }, + { + "id": "calendar_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/calendar" + } + }, + { + "id": "calendar_secondary_text", + "component": "Text", + "text": "Calendar" + }, + { + "id": "calendar_text_column", + "component": "Column", + "children": [ + "calendar_primary_text", + "calendar_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_1", + "component": "Row", + "children": [ + "calendar_icon", + "calendar_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "location_icon", + "component": "Icon", + "name": "locationOn" + }, + { + "id": "location_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/location" + } + }, + { + "id": "location_secondary_text", + "component": "Text", + "text": "Location" + }, + { + "id": "location_text_column", + "component": "Column", + "children": [ + "location_primary_text", + "location_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_2", + "component": "Row", + "children": [ + "location_icon", + "location_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "mail_icon", + "component": "Icon", + "name": "mail" + }, + { + "id": "mail_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/email" + } + }, + { + "id": "mail_secondary_text", + "component": "Text", + "text": "Email" + }, + { + "id": "mail_text_column", + "component": "Column", + "children": [ + "mail_primary_text", + "mail_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_3", + "component": "Row", + "children": [ + "mail_icon", + "mail_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "div", + "component": "Divider" + }, + { + "id": "call_icon", + "component": "Icon", + "name": "call" + }, + { + "id": "call_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/mobile" + } + }, + { + "id": "call_secondary_text", + "component": "Text", + "text": "Mobile" + }, + { + "id": "call_text_column", + "component": "Column", + "children": [ + "call_primary_text", + "call_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_4", + "component": "Row", + "children": [ + "call_icon", + "call_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_rows_column", + "component": "Column", + "weight": 1, + "children": [ + "info_row_1", + "info_row_2", + "info_row_3", + "info_row_4" + ], + "align": "stretch" + }, + { + "id": "message-button-text", + "component": "Text", + "text": "Message" + }, + { + "id": "message-button", + "component": "Button", + "child": "message-button-text", + "action": { + "event": { + "name": "send_message", + "context": { + "contactName": { + "path": "/name" + } + } + } + } + }, + { + "id": "location-button-text", + "component": "Text", + "text": "Location" + }, + { + "id": "location-button", + "component": "Button", + "child": "location-button-text", + "action": { + "event": { + "name": "view_location", + "context": { + "contactId": { + "path": "/id" + } + } + } + } + }, + { + "id": "action_buttons_row", + "component": "Row", + "children": [ + "message-button", + "location-button" + ], + "justify": "center", + "align": "center" + }, + { + "id": "main_column", + "component": "Column", + "children": [ + "profile_image", + "description_column", + "div", + "info_rows_column", + "action_buttons_row" + ], + "align": "stretch" + }, + { + "id": "root", + "component": "Card", + "child": "main_column" + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "contact-card", + "path": "/", + "value": { + "name": "", + "title": "", + "team": "", + "location": "", + "email": "", + "mobile": "", + "calendar": "", + "imageUrl": "" + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/0.9/contact_list.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/contact_list.json new file mode 100644 index 000000000..fd2727da4 --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/contact_list.json @@ -0,0 +1,141 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "contact-list", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#007BFF", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "contact-list", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "title-heading", + "item-list" + ] + }, + { + "id": "title-heading", + "component": "Text", + "variant": "h1", + "text": "Found Contacts" + }, + { + "id": "item-list", + "component": "List", + "direction": "vertical", + "children": { + "componentId": "item-card-template", + "path": "/contacts" + } + }, + { + "id": "item-card-template", + "component": "Card", + "child": "card-layout" + }, + { + "id": "card-layout", + "component": "Row", + "children": [ + "template-image", + "card-details", + "view-button" + ], + "align": "center" + }, + { + "id": "template-image", + "component": "Image", + "url": { + "path": "/imageUrl" + }, + "fit": "cover" + }, + { + "id": "card-details", + "component": "Column", + "children": [ + "template-name", + "template-title" + ] + }, + { + "id": "template-name", + "component": "Text", + "variant": "h3", + "text": { + "path": "/name" + } + }, + { + "id": "template-title", + "component": "Text", + "text": { + "path": "/title" + } + }, + { + "id": "view-button-text", + "component": "Text", + "text": "View" + }, + { + "id": "view-button", + "component": "Button", + "child": "view-button-text", + "variant": "primary", + "action": { + "event": { + "name": "view_profile", + "context": { + "contactName": { + "path": "/name" + }, + "department": { + "path": "/department" + } + } + } + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "contact-list", + "path": "/", + "value": { + "contacts": { + "contact1": { + "name": "Alice Wonderland", + "phone": "+1-555-123-4567", + "email": "alice@example.com", + "imageUrl": "https://example.com/alice.jpg", + "title": "Mad Hatter", + "department": "Wonderland" + }, + "contact2": { + "name": "Bob The Builder", + "phone": "+1-555-765-4321", + "email": "bob@example.com", + "imageUrl": "https://example.com/bob.jpg", + "title": "Construction", + "department": "Building" + } + } + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/0.9/floor_plan.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/floor_plan.json new file mode 100644 index 000000000..cc21bf4a6 --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/floor_plan.json @@ -0,0 +1,63 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "location-surface", + "catalogId": "inline_catalog" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "location-surface", + "components": [ + { + "id": "root", + "component": "Card", + "child": "floor-plan-col" + }, + { + "id": "floor-plan-col", + "component": "Column", + "children": [ + "floor-plan-title", + "floor-plan-comp", + "dismiss-fp" + ] + }, + { + "id": "floor-plan-title", + "component": "Text", + "variant": "h2", + "text": "Office Floor Plan" + }, + { + "id": "floor-plan-comp", + "component": "WebFrame", + "url": "http://localhost:10004/static/floorplan.html?data=%7B%22mappings%22%3A%20%5B%7B%22deskId%22%3A%20%22desk-1%22%2C%20%22contactId%22%3A%20%224%22%2C%20%22contactName%22%3A%20%22Jane%20Doe%22%7D%2C%20%7B%22deskId%22%3A%20%22desk-2%22%2C%20%22contactId%22%3A%20%221%22%2C%20%22contactName%22%3A%20%22Alex%20Jordan%22%7D%2C%20%7B%22deskId%22%3A%20%22desk-3%22%2C%20%22contactId%22%3A%20%223%22%2C%20%22contactName%22%3A%20%22Jordan%20Taylor%22%7D%2C%20%7B%22deskId%22%3A%20%22desk-4%22%2C%20%22contactId%22%3A%20%225%22%2C%20%22contactName%22%3A%20%22John%20Smith%22%7D%2C%20%7B%22deskId%22%3A%20%22desk-5%22%2C%20%22contactId%22%3A%20%226%22%2C%20%22contactName%22%3A%20%22Alice%20Johnson%22%7D%2C%20%7B%22deskId%22%3A%20%22desk-7%22%2C%20%22contactId%22%3A%20%222%22%2C%20%22contactName%22%3A%20%22Casey%20Smith%22%7D%5D%7D", + "height": 400, + "interactionMode": "interactive", + "allowedEvents": [ + "chart_node_click" + ] + }, + { + "id": "dismiss-fp-text", + "component": "Text", + "text": "Close Map" + }, + { + "id": "dismiss-fp", + "component": "Button", + "child": "dismiss-fp-text", + "action": { + "event": { + "name": "close_modal", + "context": {} + } + } + } + ] + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/0.9/multi_surface.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/multi_surface.json new file mode 100644 index 000000000..ccaa04f80 --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/multi_surface.json @@ -0,0 +1,373 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "contact-card", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json" + } + }, + { + "version": "v0.9", + "createSurface": { + "surfaceId": "org-chart-view", + "catalogId": "inline_catalog" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "contact-card", + "components": [ + { + "id": "profile_image", + "component": "Image", + "url": { + "path": "/imageUrl" + }, + "variant": "avatar", + "fit": "cover" + }, + { + "id": "user_heading", + "component": "Text", + "weight": 1, + "text": { + "path": "/name" + }, + "variant": "h2" + }, + { + "id": "description_text_1", + "component": "Text", + "text": { + "path": "/title" + } + }, + { + "id": "description_text_2", + "component": "Text", + "text": { + "path": "/team" + } + }, + { + "id": "description_column", + "component": "Column", + "children": [ + "user_heading", + "description_text_1", + "description_text_2" + ], + "align": "center" + }, + { + "id": "calendar_icon", + "component": "Icon", + "name": "calendarToday" + }, + { + "id": "calendar_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/calendar" + } + }, + { + "id": "calendar_secondary_text", + "component": "Text", + "text": "Calendar" + }, + { + "id": "calendar_text_column", + "component": "Column", + "children": [ + "calendar_primary_text", + "calendar_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_1", + "component": "Row", + "children": [ + "calendar_icon", + "calendar_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "location_icon", + "component": "Icon", + "name": "locationOn" + }, + { + "id": "location_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/location" + } + }, + { + "id": "location_secondary_text", + "component": "Text", + "text": "Location" + }, + { + "id": "location_text_column", + "component": "Column", + "children": [ + "location_primary_text", + "location_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_2", + "component": "Row", + "children": [ + "location_icon", + "location_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "mail_icon", + "component": "Icon", + "name": "mail" + }, + { + "id": "mail_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/email" + } + }, + { + "id": "mail_secondary_text", + "component": "Text", + "text": "Email" + }, + { + "id": "mail_text_column", + "component": "Column", + "children": [ + "mail_primary_text", + "mail_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_3", + "component": "Row", + "children": [ + "mail_icon", + "mail_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "div", + "component": "Divider" + }, + { + "id": "call_icon", + "component": "Icon", + "name": "call" + }, + { + "id": "call_primary_text", + "component": "Text", + "variant": "h5", + "text": { + "path": "/mobile" + } + }, + { + "id": "call_secondary_text", + "component": "Text", + "text": "Mobile" + }, + { + "id": "call_text_column", + "component": "Column", + "children": [ + "call_primary_text", + "call_secondary_text" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_row_4", + "component": "Row", + "children": [ + "call_icon", + "call_text_column" + ], + "justify": "start", + "align": "start" + }, + { + "id": "info_rows_column", + "component": "Column", + "weight": 1, + "children": [ + "info_row_1", + "info_row_2", + "info_row_3", + "info_row_4" + ], + "align": "stretch" + }, + { + "id": "message-button-text", + "component": "Text", + "text": "Message" + }, + { + "id": "message-button", + "component": "Button", + "child": "message-button-text", + "action": { + "event": { + "name": "send_message", + "context": { + "contactName": { + "path": "/name" + } + } + } + } + }, + { + "id": "location-button-text", + "component": "Text", + "text": "Location" + }, + { + "id": "location-button", + "component": "Button", + "child": "location-button-text", + "action": { + "event": { + "name": "view_location", + "context": { + "contactId": { + "path": "/id" + } + } + } + } + }, + { + "id": "action_buttons_row", + "component": "Row", + "children": [ + "message-button", + "location-button" + ], + "justify": "center", + "align": "center" + }, + { + "id": "main_column", + "component": "Column", + "children": [ + "profile_image", + "description_column", + "div", + "info_rows_column", + "action_buttons_row" + ], + "align": "stretch" + }, + { + "id": "root", + "component": "Card", + "child": "main_column" + } + ] + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "org-chart-view", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "org-chart" + ] + }, + { + "id": "org-chart", + "component": "OrgChart", + "chain": { + "path": "/hierarchy" + }, + "action": { + "event": { + "name": "chart_node_click", + "context": {} + } + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "contact-card", + "path": "/", + "value": { + "name": "Casey Smith", + "title": "Digital Marketing Specialist", + "team": "Marketing", + "location": "New York", + "email": "casey@example.com", + "mobile": "+1 (212) 555-0123", + "calendar": "In a meeting", + "imageUrl": "http://localhost:10004/static/profile2.png" + } + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "org-chart-view", + "path": "/", + "value": { + "hierarchy": { + "0": { + "title": "CEO", + "name": "Jane Doe" + }, + "1": { + "title": "VP Marketing", + "name": "John Smith" + }, + "2": { + "title": "Director", + "name": "Alice Johnson" + }, + "3": { + "title": "Specialist", + "name": "Casey Smith" + } + } + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_multiple_surfaces/examples/0.9/org_chart.json b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/org_chart.json new file mode 100644 index 000000000..3d46dddfe --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/examples/0.9/org_chart.json @@ -0,0 +1,71 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "org-chart-view", + "catalogId": "inline_catalog" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "org-chart-view", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "title", + "org-chart" + ] + }, + { + "id": "title", + "component": "Text", + "variant": "h2", + "text": "Organizational Chart" + }, + { + "id": "org-chart", + "component": "OrgChart", + "chain": { + "path": "/hierarchy" + }, + "action": { + "event": { + "name": "chart_node_click", + "context": {} + } + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "org-chart-view", + "path": "/", + "value": { + "hierarchy": { + "0": { + "title": "CEO", + "name": "Jane Doe" + }, + "1": { + "title": "VP Marketing", + "name": "John Smith" + }, + "2": { + "title": "Director", + "name": "Alice Johnson" + }, + "3": { + "title": "Digital Marketing Specialist", + "name": "Casey Smith" + } + } + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/contact_multiple_surfaces/inline_catalog.json b/samples/agent/adk/contact_multiple_surfaces/inline_catalog.json new file mode 100644 index 000000000..465193a9d --- /dev/null +++ b/samples/agent/adk/contact_multiple_surfaces/inline_catalog.json @@ -0,0 +1,1620 @@ +{ + "catalogId": "inline_catalog", + "title": "Contact Multiple Surfaces Inline Catalog", + "components": { + "Text": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Text" + }, + "text": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text content to display. While simple Markdown formatting is supported (i.e. without HTML, images, or links), utilizing dedicated UI components is generally preferred for a richer and more structured presentation." + }, + "variant": { + "type": "string", + "description": "A hint for the base text style.", + "enum": [ + "h1", + "h2", + "h3", + "h4", + "h5", + "caption", + "body" + ], + "default": "body" + } + }, + "required": [ + "component", + "text" + ] + } + ], + "unevaluatedProperties": false + }, + "Image": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Image" + }, + "url": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The URL of the image to display." + }, + "fit": { + "type": "string", + "description": "Specifies how the image should be resized to fit its container. This corresponds to the CSS 'object-fit' property.", + "enum": [ + "contain", + "cover", + "fill", + "none", + "scaleDown" + ], + "default": "fill" + }, + "variant": { + "type": "string", + "description": "A hint for the image size and style.", + "enum": [ + "icon", + "avatar", + "smallFeature", + "mediumFeature", + "largeFeature", + "header" + ], + "default": "mediumFeature" + } + }, + "required": [ + "component", + "url" + ] + } + ], + "unevaluatedProperties": false + }, + "Icon": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Icon" + }, + "name": { + "description": "The name of the icon to display.", + "oneOf": [ + { + "type": "string", + "enum": [ + "accountCircle", + "add", + "arrowBack", + "arrowForward", + "attachFile", + "calendarToday", + "call", + "camera", + "check", + "close", + "delete", + "download", + "edit", + "event", + "error", + "fastForward", + "favorite", + "favoriteOff", + "folder", + "help", + "home", + "info", + "locationOn", + "lock", + "lockOpen", + "mail", + "menu", + "moreVert", + "moreHoriz", + "notificationsOff", + "notifications", + "pause", + "payment", + "person", + "phone", + "photo", + "play", + "print", + "refresh", + "rewind", + "search", + "send", + "settings", + "share", + "shoppingCart", + "skipNext", + "skipPrevious", + "star", + "starHalf", + "starOff", + "stop", + "upload", + "visibility", + "visibilityOff", + "volumeDown", + "volumeMute", + "volumeOff", + "volumeUp", + "warning" + ] + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] + } + }, + "required": [ + "component", + "name" + ] + } + ], + "unevaluatedProperties": false + }, + "Video": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Video" + }, + "url": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The URL of the video to display." + } + }, + "required": [ + "component", + "url" + ] + } + ], + "unevaluatedProperties": false + }, + "AudioPlayer": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "AudioPlayer" + }, + "url": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The URL of the audio to be played." + }, + "description": { + "description": "A description of the audio, such as a title or summary.", + "$ref": "common_types.json#/$defs/DynamicString" + } + }, + "required": [ + "component", + "url" + ] + } + ], + "unevaluatedProperties": false + }, + "Row": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "description": "A layout component that arranges its children horizontally. To create a grid layout, nest Columns within this Row.", + "properties": { + "component": { + "const": "Row" + }, + "children": { + "description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list. Children cannot be defined inline, they must be referred to by ID.", + "$ref": "common_types.json#/$defs/ChildList" + }, + "justify": { + "type": "string", + "description": "Defines the arrangement of children along the main axis (horizontally). Use 'spaceBetween' to push items to the edges, or 'start'/'end'/'center' to pack them together.", + "enum": [ + "center", + "end", + "spaceAround", + "spaceBetween", + "spaceEvenly", + "start", + "stretch" + ], + "default": "start" + }, + "align": { + "type": "string", + "description": "Defines the alignment of children along the cross axis (vertically). This is similar to the CSS 'align-items' property, but uses camelCase values (e.g., 'start').", + "enum": [ + "start", + "center", + "end", + "stretch" + ], + "default": "stretch" + } + }, + "required": [ + "component", + "children" + ] + } + ], + "unevaluatedProperties": false + }, + "Column": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "description": "A layout component that arranges its children vertically. To create a grid layout, nest Rows within this Column.", + "properties": { + "component": { + "const": "Column" + }, + "children": { + "description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list. Children cannot be defined inline, they must be referred to by ID.", + "$ref": "common_types.json#/$defs/ChildList" + }, + "justify": { + "type": "string", + "description": "Defines the arrangement of children along the main axis (vertically). Use 'spaceBetween' to push items to the edges (e.g. header at top, footer at bottom), or 'start'/'end'/'center' to pack them together.", + "enum": [ + "start", + "center", + "end", + "spaceBetween", + "spaceAround", + "spaceEvenly", + "stretch" + ], + "default": "start" + }, + "align": { + "type": "string", + "description": "Defines the alignment of children along the cross axis (horizontally). This is similar to the CSS 'align-items' property.", + "enum": [ + "center", + "end", + "start", + "stretch" + ], + "default": "stretch" + } + }, + "required": [ + "component", + "children" + ] + } + ], + "unevaluatedProperties": false + }, + "List": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "List" + }, + "children": { + "description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list.", + "$ref": "common_types.json#/$defs/ChildList" + }, + "direction": { + "type": "string", + "description": "The direction in which the list items are laid out.", + "enum": [ + "vertical", + "horizontal" + ], + "default": "vertical" + }, + "align": { + "type": "string", + "description": "Defines the alignment of children along the cross axis.", + "enum": [ + "start", + "center", + "end", + "stretch" + ], + "default": "stretch" + } + }, + "required": [ + "component", + "children" + ] + } + ], + "unevaluatedProperties": false + }, + "Card": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Card" + }, + "child": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the single child component to be rendered inside the card. To display multiple elements, you MUST wrap them in a layout component (like Column or Row) and pass that container's ID here. Do NOT pass multiple IDs or a non-existent ID. Do NOT define the child component inline." + } + }, + "required": [ + "component", + "child" + ] + } + ], + "unevaluatedProperties": false + }, + "Tabs": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Tabs" + }, + "tabs": { + "type": "array", + "description": "An array of objects, where each object defines a tab with a title and a child component.", + "items": { + "type": "object", + "properties": { + "title": { + "description": "The tab title.", + "$ref": "common_types.json#/$defs/DynamicString" + }, + "child": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the child component. Do NOT define the component inline." + } + }, + "required": [ + "title", + "child" + ], + "additionalProperties": false + } + } + }, + "required": [ + "component", + "tabs" + ] + } + ], + "unevaluatedProperties": false + }, + "Modal": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Modal" + }, + "trigger": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the component that opens the modal when interacted with (e.g., a button). Do NOT define the component inline." + }, + "content": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the component to be displayed inside the modal. Do NOT define the component inline." + } + }, + "required": [ + "component", + "trigger", + "content" + ] + } + ], + "unevaluatedProperties": false + }, + "Divider": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Divider" + }, + "axis": { + "type": "string", + "description": "The orientation of the divider.", + "enum": [ + "horizontal", + "vertical" + ], + "default": "horizontal" + } + }, + "required": [ + "component" + ] + } + ], + "unevaluatedProperties": false + }, + "Button": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Button" + }, + "child": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the child component. Use a 'Text' component for a labeled button. Only use an 'Icon' if the requirements explicitly ask for an icon-only button. Do NOT define the child component inline." + }, + "variant": { + "type": "string", + "description": "A hint for the button style. If omitted, a default button style is used. 'primary' indicates this is the main call-to-action button. 'borderless' means the button has no visual border or background, making its child content appear like a clickable link.", + "enum": [ + "default", + "primary", + "borderless" + ], + "default": "default" + }, + "action": { + "$ref": "common_types.json#/$defs/Action" + } + }, + "required": [ + "component", + "child", + "action" + ] + } + ], + "unevaluatedProperties": false + }, + "TextField": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { + "type": "object", + "properties": { + "component": { + "const": "TextField" + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text label for the input field." + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The value of the text field." + }, + "variant": { + "type": "string", + "description": "The type of input field to display.", + "enum": [ + "longText", + "number", + "shortText", + "obscured" + ], + "default": "shortText" + }, + "validationRegexp": { + "type": "string", + "description": "A regular expression used for client-side validation of the input." + } + }, + "required": [ + "component", + "label" + ] + } + ], + "unevaluatedProperties": false + }, + "CheckBox": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { + "type": "object", + "properties": { + "component": { + "const": "CheckBox" + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text to display next to the checkbox." + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicBoolean", + "description": "The current state of the checkbox (true for checked, false for unchecked)." + } + }, + "required": [ + "component", + "label", + "value" + ] + } + ], + "unevaluatedProperties": false + }, + "ChoicePicker": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { + "type": "object", + "description": "A component that allows selecting one or more options from a list.", + "properties": { + "component": { + "const": "ChoicePicker" + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The label for the group of options." + }, + "variant": { + "type": "string", + "description": "A hint for how the choice picker should be displayed and behave.", + "enum": [ + "multipleSelection", + "mutuallyExclusive" + ], + "default": "mutuallyExclusive" + }, + "options": { + "type": "array", + "description": "The list of available options to choose from.", + "items": { + "type": "object", + "properties": { + "label": { + "description": "The text to display for this option.", + "$ref": "common_types.json#/$defs/DynamicString" + }, + "value": { + "type": "string", + "description": "The stable value associated with this option." + } + }, + "required": [ + "label", + "value" + ], + "additionalProperties": false + } + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicStringList", + "description": "The list of currently selected values. This should be bound to a string array in the data model." + }, + "displayStyle": { + "type": "string", + "description": "The display style of the component.", + "enum": [ + "checkbox", + "chips" + ], + "default": "checkbox" + }, + "filterable": { + "type": "boolean", + "description": "If true, displays a search input to filter the options.", + "default": false + } + }, + "required": [ + "component", + "options", + "value" + ] + } + ], + "unevaluatedProperties": false + }, + "Slider": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Slider" + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The label for the slider." + }, + "min": { + "type": "number", + "description": "The minimum value of the slider.", + "default": 0 + }, + "max": { + "type": "number", + "description": "The maximum value of the slider." + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicNumber", + "description": "The current value of the slider." + } + }, + "required": [ + "component", + "value", + "max" + ] + } + ], + "unevaluatedProperties": false + }, + "DateTimeInput": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { + "type": "object", + "properties": { + "component": { + "const": "DateTimeInput" + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The selected date and/or time value in ISO 8601 format. If not yet set, initialize with an empty string." + }, + "enableDate": { + "type": "boolean", + "description": "If true, allows the user to select a date.", + "default": false + }, + "enableTime": { + "type": "boolean", + "description": "If true, allows the user to select a time.", + "default": false + }, + "min": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/DynamicString" + }, + { + "if": { + "type": "string" + }, + "then": { + "oneOf": [ + { + "format": "date" + }, + { + "format": "time" + }, + { + "format": "date-time" + } + ] + } + } + ], + "description": "The minimum allowed date/time in ISO 8601 format." + }, + "max": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/DynamicString" + }, + { + "if": { + "type": "string" + }, + "then": { + "oneOf": [ + { + "format": "date" + }, + { + "format": "time" + }, + { + "format": "date-time" + } + ] + } + } + ], + "description": "The maximum allowed date/time in ISO 8601 format." + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text label for the input field." + } + }, + "required": [ + "component", + "value" + ] + } + ], + "unevaluatedProperties": false + }, + "OrgChart": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "OrgChart" + }, + "chain": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "title", + "name" + ] + } + }, + { + "$ref": "common_types.json#/$defs/DataBinding" + } + ] + }, + "action": { + "$ref": "common_types.json#/$defs/Action" + } + }, + "required": [ + "component", + "chain" + ] + } + ], + "unevaluatedProperties": false + }, + "WebFrame": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "WebFrame" + }, + "url": { + "type": "string" + }, + "html": { + "type": "string" + }, + "height": { + "type": "number" + }, + "interactionMode": { + "type": "string", + "enum": [ + "readOnly", + "interactive" + ] + }, + "allowedEvents": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "component", + "url" + ] + } + ], + "unevaluatedProperties": false + } + }, + "$defs": { + "CatalogComponentCommon": { + "type": "object", + "properties": { + "weight": { + "type": "number", + "description": "The relative weight of this component within a Row or Column. This is similar to the CSS 'flex-grow' property. Note: this may ONLY be set when the component is a direct descendant of a Row or Column." + } + } + }, + "theme": { + "type": "object", + "properties": { + "primaryColor": { + "type": "string", + "description": "The primary brand color used for highlights (e.g., primary buttons, active borders). Renderers may generate variants of this color for different contexts. Format: Hexadecimal code (e.g., '#00BFFF').", + "pattern": "^#[0-9a-fA-F]{6}$" + }, + "iconUrl": { + "type": "string", + "format": "uri", + "description": "A URL for an image that identifies the agent or tool associated with the surface." + }, + "agentDisplayName": { + "type": "string", + "description": "Text to be displayed next to the surface to identify the agent or tool that created it." + } + }, + "additionalProperties": true + }, + "anyComponent": { + "oneOf": [ + { + "$ref": "#/components/Text" + }, + { + "$ref": "#/components/Image" + }, + { + "$ref": "#/components/Icon" + }, + { + "$ref": "#/components/Video" + }, + { + "$ref": "#/components/AudioPlayer" + }, + { + "$ref": "#/components/Row" + }, + { + "$ref": "#/components/Column" + }, + { + "$ref": "#/components/List" + }, + { + "$ref": "#/components/Card" + }, + { + "$ref": "#/components/Tabs" + }, + { + "$ref": "#/components/Modal" + }, + { + "$ref": "#/components/Divider" + }, + { + "$ref": "#/components/Button" + }, + { + "$ref": "#/components/TextField" + }, + { + "$ref": "#/components/CheckBox" + }, + { + "$ref": "#/components/ChoicePicker" + }, + { + "$ref": "#/components/Slider" + }, + { + "$ref": "#/components/DateTimeInput" + }, + { + "$ref": "#/components/OrgChart" + }, + { + "$ref": "#/components/WebFrame" + } + ], + "discriminator": { + "propertyName": "component" + } + }, + "anyFunction": { + "oneOf": [ + { + "$ref": "#/functions/required" + }, + { + "$ref": "#/functions/regex" + }, + { + "$ref": "#/functions/length" + }, + { + "$ref": "#/functions/numeric" + }, + { + "$ref": "#/functions/email" + }, + { + "$ref": "#/functions/formatString" + }, + { + "$ref": "#/functions/formatNumber" + }, + { + "$ref": "#/functions/formatCurrency" + }, + { + "$ref": "#/functions/formatDate" + }, + { + "$ref": "#/functions/pluralize" + }, + { + "$ref": "#/functions/openUrl" + }, + { + "$ref": "#/functions/and" + }, + { + "$ref": "#/functions/or" + }, + { + "$ref": "#/functions/not" + } + ] + } + }, + "functions": { + "required": { + "type": "object", + "description": "Validation function: check if a value is NOT null or empty.", + "properties": { + "call": { + "const": "required" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicValue" + } + }, + "required": [ + "value" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "regex": { + "type": "object", + "description": "Validation function: check if a string matches a regular expression.", + "properties": { + "call": { + "const": "regex" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicString" + }, + "pattern": { + "type": "string" + } + }, + "required": [ + "value", + "pattern" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "length": { + "type": "object", + "description": "Validation function: check string or array length.", + "properties": { + "call": { + "const": "length" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicValue" + }, + "min": { + "type": "number" + }, + "max": { + "type": "number" + } + }, + "required": [ + "value" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "numeric": { + "type": "object", + "description": "Validation function: check if value is numeric within range.", + "properties": { + "call": { + "const": "numeric" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicNumber" + }, + "min": { + "type": "number" + }, + "max": { + "type": "number" + } + }, + "required": [ + "value" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "email": { + "type": "object", + "description": "Validation function: check if value is a valid email address.", + "properties": { + "call": { + "const": "email" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicString" + } + }, + "required": [ + "value" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "formatString": { + "type": "object", + "description": "Formats a string using placeholders.", + "properties": { + "call": { + "const": "formatString" + }, + "args": { + "type": "object", + "properties": { + "template": { + "type": "string" + }, + "values": { + "type": "object", + "additionalProperties": { + "$ref": "common_types.json#/$defs/DynamicValue" + } + } + }, + "required": [ + "template", + "values" + ] + }, + "returnType": { + "const": "string" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "formatNumber": { + "type": "object", + "description": "Formats a number as a string.", + "properties": { + "call": { + "const": "formatNumber" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicNumber" + }, + "decimals": { + "$ref": "common_types.json#/$defs/DynamicNumber" + }, + "grouping": { + "$ref": "common_types.json#/$defs/DynamicBoolean" + } + }, + "required": [ + "value" + ] + }, + "returnType": { + "const": "string" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "formatCurrency": { + "type": "object", + "description": "Formats a number as a currency string.", + "properties": { + "call": { + "const": "formatCurrency" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicNumber" + }, + "currency": { + "$ref": "common_types.json#/$defs/DynamicString" + }, + "decimals": { + "$ref": "common_types.json#/$defs/DynamicNumber" + }, + "grouping": { + "$ref": "common_types.json#/$defs/DynamicBoolean" + } + }, + "required": [ + "currency", + "value" + ] + }, + "returnType": { + "const": "string" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "formatDate": { + "type": "object", + "description": "Formats a timestamp into a string using a pattern.", + "properties": { + "call": { + "const": "formatDate" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicValue" + }, + "format": { + "$ref": "common_types.json#/$defs/DynamicString" + } + }, + "required": [ + "format", + "value" + ] + }, + "returnType": { + "const": "string" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "pluralize": { + "type": "object", + "description": "Returns a localized string based on count.", + "properties": { + "call": { + "const": "pluralize" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicNumber" + }, + "zero": { + "$ref": "common_types.json#/$defs/DynamicString" + }, + "one": { + "$ref": "common_types.json#/$defs/DynamicString" + }, + "two": { + "$ref": "common_types.json#/$defs/DynamicString" + }, + "few": { + "$ref": "common_types.json#/$defs/DynamicString" + }, + "many": { + "$ref": "common_types.json#/$defs/DynamicString" + }, + "other": { + "$ref": "common_types.json#/$defs/DynamicString" + } + }, + "required": [ + "value", + "other" + ] + }, + "returnType": { + "const": "string" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "openUrl": { + "type": "object", + "description": "Opens the specified URL.", + "properties": { + "call": { + "const": "openUrl" + }, + "args": { + "type": "object", + "properties": { + "url": { + "type": "string" + } + }, + "required": [ + "url" + ] + }, + "returnType": { + "const": "void" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "and": { + "type": "object", + "description": "Logical AND.", + "properties": { + "call": { + "const": "and" + }, + "args": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "common_types.json#/$defs/DynamicBoolean" + } + } + }, + "required": [ + "values" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "or": { + "type": "object", + "description": "Logical OR.", + "properties": { + "call": { + "const": "or" + }, + "args": { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "common_types.json#/$defs/DynamicBoolean" + } + } + }, + "required": [ + "values" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + }, + "not": { + "type": "object", + "description": "Logical NOT.", + "properties": { + "call": { + "const": "not" + }, + "args": { + "type": "object", + "properties": { + "value": { + "$ref": "common_types.json#/$defs/DynamicBoolean" + } + }, + "required": [ + "value" + ] + }, + "returnType": { + "const": "boolean" + } + }, + "required": [ + "call", + "args" + ], + "unevaluatedProperties": false + } + } +} diff --git a/samples/agent/adk/contact_multiple_surfaces/prompt_builder.py b/samples/agent/adk/contact_multiple_surfaces/prompt_builder.py index 3914079f7..e92ca905f 100644 --- a/samples/agent/adk/contact_multiple_surfaces/prompt_builder.py +++ b/samples/agent/adk/contact_multiple_surfaces/prompt_builder.py @@ -14,10 +14,12 @@ import json -from a2ui.core.schema.constants import VERSION_0_8 -from a2ui.core.schema.manager import A2uiSchemaManager +from a2ui.core.schema.constants import VERSION_0_9 +from a2ui.core.schema.manager import A2uiSchemaManager, CatalogConfig from a2ui.basic_catalog.provider import BasicCatalog from a2ui.core.schema.common_modifiers import remove_strict_validation +from a2ui.core.schema.catalog_provider import A2uiCatalogProvider, FileSystemCatalogProvider +from typing import Dict, Any ROLE_DESCRIPTION = ( "You are a helpful contact lookup assistant. Your final output MUST be a a2ui UI" @@ -30,14 +32,14 @@ 2. The first part is your conversational text response (e.g., "Here is the contact you requested..."). 3. The second part is a single, raw JSON object which is a list of A2UI messages. 4. The JSON part MUST validate against the A2UI JSON SCHEMA provided below. -5. Buttons that represent the main action on a card or view (e.g., 'Follow', 'Email', 'Search') SHOULD include the `"primary": true` attribute. +5. Buttons that represent the main action on a card or view (e.g., 'Follow', 'Email', 'Search') SHOULD include the `"variant": "primary"` attribute. """ UI_DESCRIPTION = """ - **For finding contacts (e.g., "Who is Alex Jordan?"):** a. You MUST call the `get_contact_info` tool. b. If the tool returns a **single contact**, you MUST use the `MULTI_SURFACE_EXAMPLE` template. Provide BOTH the Contact Card and the Org Chart in a single response. - c. If the tool returns **multiple contacts**, you MUST use the `CONTACT_LIST_EXAMPLE` template. Populate the `dataModelUpdate.contents` with the list of contacts for the "contacts" key. + c. If the tool returns **multiple contacts**, you MUST use the `CONTACT_LIST_EXAMPLE` template. Populate the `updateDataModel.value` with the list of contacts for the "contacts" key. d. If the tool returns an **empty list**, respond with text only and an empty JSON list: "I couldn't find anyone by that name.---a2ui_JSON---[]" - **For handling a profile view (e.g., "WHO_IS: Alex Jordan..."):** @@ -46,7 +48,7 @@ - **For handling actions (e.g., "USER_WANTS_TO_EMAIL: ..."):** a. You MUST use the `ACTION_CONFIRMATION_EXAMPLE` template. - b. Populate the `dataModelUpdate.contents` with a confirmation title and message (e.g., title: "Email Drafted", message: "Drafting an email to Alex Jordan..."). + b. Populate the `updateDataModel.value` with a confirmation title and message (e.g., title: "Email Drafted", message: "Drafting an email to Alex Jordan..."). """ @@ -72,9 +74,16 @@ def get_text_prompt() -> str: if __name__ == "__main__": # Example of how to use the A2UI Schema Manager to generate a system prompt my_base_url = "http://localhost:8000" + my_version = VERSION_0_9 schema_manager = A2uiSchemaManager( - VERSION_0_8, - catalogs=[BasicCatalog.get_config(version=VERSION_0_8, examples_path="examples")], + my_version, + catalogs=[ + CatalogConfig.from_path( + name="contact_multiple_surfaces_inline_catalog", + catalog_path="inline_catalog.json", + examples_path=f"examples/{my_version}", + ), + ], accepts_inline_catalogs=True, schema_modifiers=[remove_strict_validation], ) @@ -91,11 +100,10 @@ def get_text_prompt() -> str: f.write(contact_prompt) print("\nGenerated prompt saved to generated_prompt.txt") - client_ui_capabilities_str = ( - '{"inlineCatalogs":[{"catalogId": "inline_catalog",' - ' "components":{"OrgChart":{"type":"object","properties":{"chain":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"name":{"type":"string"}},"required":["title","name"]}},"action":{"$ref":"#/definitions/Action"}},"required":["chain"]},"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"}}}}}}]}' - ) - client_ui_capabilities = json.loads(client_ui_capabilities_str) + with open("inline_catalog.json", "r", encoding="utf-8") as f: + inline_catalog = json.load(f) + + client_ui_capabilities = {"inlineCatalogs": [inline_catalog]} inline_catalog = schema_manager.get_selected_catalog( client_ui_capabilities=client_ui_capabilities, ) diff --git a/samples/agent/adk/migrate_v08_to_v09.py b/samples/agent/adk/migrate_v08_to_v09.py new file mode 100644 index 000000000..d7de9522b --- /dev/null +++ b/samples/agent/adk/migrate_v08_to_v09.py @@ -0,0 +1,254 @@ +import json +import argparse +from pathlib import Path + +VERSION_0_9_CATALOG_ID = "https://a2ui.org/specification/v0_9/basic_catalog.json" + + +def _convert_map(v_map): + res = {} + for m_item in v_map: + m_key = m_item["key"] + m_val_key = [mk for mk in m_item.keys() if mk.startswith("value")][0] + m_val = m_item[m_val_key] + if m_val_key == "valueMap": + res[m_key] = _convert_map(m_val) + else: + res[m_key] = m_val + return res + + +def migrate_v08_to_v09(v08_data, catalog_id=VERSION_0_9_CATALOG_ID): + v09_data = [] + + # We need to track which component is the root for each surface + surface_roots = {} + + for msg in v08_data: + new_msg = {"version": "v0.9"} + + if "beginRendering" in msg: + br = msg["beginRendering"] + surface_id = br["surfaceId"] + root_id = br["root"] + surface_roots[surface_id] = root_id + + new_br = { + "surfaceId": surface_id, + "catalogId": catalog_id, + } + if "styles" in br: + new_br["theme"] = br["styles"] + + new_msg["createSurface"] = new_br + + elif "surfaceUpdate" in msg: + su = msg["surfaceUpdate"] + surface_id = su["surfaceId"] + root_id = surface_roots.get(surface_id) + + new_components = [] + for comp_wrapper in su["components"]: + comp_id = comp_wrapper["id"] + # Rename the designated root component to "root" + if comp_id == root_id: + comp_id = "root" + + # Unwrap component type and properties + if "component" not in comp_wrapper: + # Possibly already migrated or invalid format? Skip or attempt to handle? + # Some custom components might be different. + continue + + comp_keys = list(comp_wrapper["component"].keys()) + if not comp_keys: + continue + comp_type = comp_keys[0] + comp_props = comp_wrapper["component"][comp_type] + + new_comp = {"id": comp_id, "component": comp_type} + + # Add weight if present + if "weight" in comp_wrapper: + new_comp["weight"] = comp_wrapper["weight"] + + # Transform properties + for k, v in comp_props.items(): + if k == "children": + if "explicitList" in v: + # Update child IDs if they were the root + new_comp["children"] = [ + c if c != root_id else "root" for c in v["explicitList"] + ] + elif "template" in v: + template = v["template"] + new_comp["children"] = { + "componentId": ( + template["componentId"] + if template["componentId"] != root_id + else "root" + ), + "path": template.get("dataBinding", ""), + } + elif k == "child": + new_comp["child"] = v if v != root_id else "root" + elif k == "tabItems": + new_comp["tabs"] = [ + { + "title": item["title"], + "child": item["child"] if item["child"] != root_id else "root", + } + for item in v + ] + elif k == "entryPointChild": + new_comp["trigger"] = v if v != root_id else "root" + elif k == "contentChild": + new_comp["content"] = v if v != root_id else "root" + elif k == "usageHint": + new_comp["variant"] = v + elif k == "action": + new_action = {"event": {"name": v["name"]}} + if "context" in v: + new_context = {} + for item in v["context"]: + # Unwrap context value if needed + val = item["value"] + if isinstance(val, dict) and "literalString" in val: + val = val["literalString"] + elif isinstance(val, dict) and "literalNumber" in val: + val = val["literalNumber"] + elif isinstance(val, dict) and "literalBoolean" in val: + val = val["literalBoolean"] + + new_context[item["key"]] = val + new_action["event"]["context"] = new_context + new_comp["action"] = new_action + elif k == "fit": + new_comp["fit"] = "scaleDown" if v == "scale-down" else v + elif k == "alignment": + new_comp["align"] = v + elif k == "distribution": + new_comp["justify"] = v + elif k == "text" and comp_type == "TextField": + new_comp["value"] = v + elif k == "type" and comp_type == "TextField": + new_comp["variant"] = v + elif k == "primary": + # Map Button primary to variant + if comp_type == "Button": + new_comp["variant"] = "primary" if v else "default" + else: + new_comp[k] = v + else: + # General literal unwrapping + if isinstance(v, dict) and "literalString" in v: + v = v["literalString"] + elif isinstance(v, dict) and "literalNumber" in v: + v = v["literalNumber"] + elif isinstance(v, dict) and "literalBoolean" in v: + v = v["literalBoolean"] + + # Icon specific handling + if comp_type == "Icon": + if k == "name": + # Map common icon names to v0.9 enum + icon_map = { + "check_circle": "check", + "calendar_today": "calendarToday", + "location_on": "locationOn", + } + v = icon_map.get(v, v) + elif k in ["size", "color"]: + # Skip properties not in basic catalog v0.9 + continue + + # Image specific handling + if comp_type == "Image": + if k in ["width", "height"]: + continue + + new_comp[k] = v + + new_components.append(new_comp) + + new_msg["updateComponents"] = { + "surfaceId": surface_id, + "components": new_components, + } + + elif "dataModelUpdate" in msg: + dmu = msg["dataModelUpdate"] + surface_id = dmu["surfaceId"] + path = dmu.get("path", "/") + + # Simple transformation for now: convert contents array to object + value_obj = {} + for item in dmu.get("contents", []): + key = item["key"] + # Find the value key (valueString, valueNumber, etc.) + val_key = next((k for k in item.keys() if k.startswith("value")), None) + if val_key is None: + continue + val = item[val_key] + + # Recursive map conversion if needed + if val_key == "valueMap": + val = _convert_map(val) + + value_obj[key] = val + + new_msg["updateDataModel"] = { + "surfaceId": surface_id, + "path": path, + "value": value_obj, + } + + elif "deleteSurface" in msg: + new_msg["deleteSurface"] = {"surfaceId": msg["deleteSurface"]["surfaceId"]} + + v09_data.append(new_msg) + + return v09_data + + +def process_file(src_file, dst_file, catalog_id): + print(f"Migrating {src_file.name}...") + with open(src_file, "r", encoding="utf-8") as f: + v08_data = json.load(f) + + v09_data = migrate_v08_to_v09(v08_data, catalog_id=catalog_id) + + dst_file.parent.mkdir(parents=True, exist_ok=True) + with open(dst_file, "w", encoding="utf-8") as f: + json.dump(v09_data, f, indent=2) + + +def main(): + parser = argparse.ArgumentParser(description="Migrate A2UI v0.8 examples to v0.9") + parser.add_argument("--src", required=True, help="Source file or directory") + parser.add_argument("--dst", required=True, help="Destination file or directory") + parser.add_argument( + "--catalog_id", + default=VERSION_0_9_CATALOG_ID, + help="Catalog ID to use in v0.9", + ) + + args = parser.parse_args() + + src = Path(args.src) + dst = Path(args.dst) + + if src.is_file(): + process_file(src, dst, args.catalog_id) + elif src.is_dir(): + for json_file in src.glob("*.json"): + process_file(json_file, dst / json_file.name, args.catalog_id) + else: + print(f"Error: {src} is not a file or directory") + exit(1) + + print("Migration complete.") + + +if __name__ == "__main__": + main() diff --git a/samples/agent/adk/restaurant_finder/agent.py b/samples/agent/adk/restaurant_finder/agent.py index c2bc37e57..59308bbe3 100644 --- a/samples/agent/adk/restaurant_finder/agent.py +++ b/samples/agent/adk/restaurant_finder/agent.py @@ -34,7 +34,7 @@ UI_DESCRIPTION, ) from tools import get_restaurants -from a2ui.core.schema.constants import VERSION_0_8 +from a2ui.core.schema.constants import VERSION_0_9 from a2ui.core.schema.manager import A2uiSchemaManager from a2ui.basic_catalog.provider import BasicCatalog from a2ui.core.schema.common_modifiers import remove_strict_validation @@ -51,11 +51,15 @@ class RestaurantAgent: def __init__(self, base_url: str, use_ui: bool = False): self.base_url = base_url self.use_ui = use_ui + self.version = VERSION_0_9 self._schema_manager = ( A2uiSchemaManager( - VERSION_0_8, + self.version, catalogs=[ - BasicCatalog.get_config(version=VERSION_0_8, examples_path="examples") + BasicCatalog.get_config( + version=self.version, + examples_path=f"examples/{self.version}", + ) ], schema_modifiers=[remove_strict_validation], ) diff --git a/samples/agent/adk/restaurant_finder/examples/booking_form.json b/samples/agent/adk/restaurant_finder/examples/0.8/booking_form.json similarity index 100% rename from samples/agent/adk/restaurant_finder/examples/booking_form.json rename to samples/agent/adk/restaurant_finder/examples/0.8/booking_form.json diff --git a/samples/agent/adk/restaurant_finder/examples/confirmation.json b/samples/agent/adk/restaurant_finder/examples/0.8/confirmation.json similarity index 100% rename from samples/agent/adk/restaurant_finder/examples/confirmation.json rename to samples/agent/adk/restaurant_finder/examples/0.8/confirmation.json diff --git a/samples/agent/adk/restaurant_finder/examples/single_column_list.json b/samples/agent/adk/restaurant_finder/examples/0.8/single_column_list.json similarity index 100% rename from samples/agent/adk/restaurant_finder/examples/single_column_list.json rename to samples/agent/adk/restaurant_finder/examples/0.8/single_column_list.json diff --git a/samples/agent/adk/restaurant_finder/examples/two_column_list.json b/samples/agent/adk/restaurant_finder/examples/0.8/two_column_list.json similarity index 100% rename from samples/agent/adk/restaurant_finder/examples/two_column_list.json rename to samples/agent/adk/restaurant_finder/examples/0.8/two_column_list.json diff --git a/samples/agent/adk/restaurant_finder/examples/0.9/booking_form.json b/samples/agent/adk/restaurant_finder/examples/0.9/booking_form.json new file mode 100644 index 000000000..62ef5ce54 --- /dev/null +++ b/samples/agent/adk/restaurant_finder/examples/0.9/booking_form.json @@ -0,0 +1,131 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "booking-form", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#FF0000", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "booking-form", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "booking-title", + "restaurant-image", + "restaurant-address", + "party-size-field", + "datetime-field", + "dietary-field", + "submit-button" + ] + }, + { + "id": "booking-title", + "component": "Text", + "variant": "h2", + "text": { + "path": "/title" + } + }, + { + "id": "restaurant-image", + "component": "Image", + "url": { + "path": "/imageUrl" + } + }, + { + "id": "restaurant-address", + "component": "Text", + "text": { + "path": "/address" + } + }, + { + "id": "party-size-field", + "component": "TextField", + "label": "Party Size", + "value": { + "path": "/partySize" + }, + "variant": "number" + }, + { + "id": "datetime-field", + "component": "DateTimeInput", + "label": "Date & Time", + "value": { + "path": "/reservationTime" + }, + "enableDate": true, + "enableTime": true + }, + { + "id": "dietary-field", + "component": "TextField", + "label": "Dietary Requirements", + "value": { + "path": "/dietary" + } + }, + { + "id": "submit-button", + "component": "Button", + "child": "submit-reservation-text", + "action": { + "event": { + "name": "submit_booking", + "context": { + "restaurantName": { + "path": "/restaurantName" + }, + "partySize": { + "path": "/partySize" + }, + "reservationTime": { + "path": "/reservationTime" + }, + "dietary": { + "path": "/dietary" + }, + "imageUrl": { + "path": "/imageUrl" + } + } + } + } + }, + { + "id": "submit-reservation-text", + "component": "Text", + "text": "Submit Reservation" + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "booking-form", + "path": "/", + "value": { + "title": "Book a Table at [RestaurantName]", + "address": "[Restaurant Address]", + "restaurantName": "[RestaurantName]", + "partySize": "2", + "reservationTime": "", + "dietary": "", + "imageUrl": "" + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/restaurant_finder/examples/0.9/confirmation.json b/samples/agent/adk/restaurant_finder/examples/0.9/confirmation.json new file mode 100644 index 000000000..9f1fde693 --- /dev/null +++ b/samples/agent/adk/restaurant_finder/examples/0.9/confirmation.json @@ -0,0 +1,100 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "confirmation", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#FF0000", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "confirmation", + "components": [ + { + "id": "root", + "component": "Card", + "child": "confirmation-column" + }, + { + "id": "confirmation-column", + "component": "Column", + "children": [ + "confirm-title", + "confirm-image", + "confirm-details", + "confirm-dietary", + "divider1", + "divider2", + "divider3", + "confirm-text" + ] + }, + { + "id": "confirm-title", + "component": "Text", + "variant": "h2", + "text": { + "path": "/title" + } + }, + { + "id": "confirm-image", + "component": "Image", + "url": { + "path": "/imageUrl" + } + }, + { + "id": "confirm-details", + "component": "Text", + "text": { + "path": "/bookingDetails" + } + }, + { + "id": "confirm-dietary", + "component": "Text", + "text": { + "path": "/dietaryRequirements" + } + }, + { + "id": "confirm-text", + "component": "Text", + "variant": "h5", + "text": "We look forward to seeing you!" + }, + { + "id": "divider1", + "component": "Divider" + }, + { + "id": "divider2", + "component": "Divider" + }, + { + "id": "divider3", + "component": "Divider" + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "confirmation", + "path": "/", + "value": { + "title": "Booking at [RestaurantName]", + "bookingDetails": "[PartySize] people at [Time]", + "dietaryRequirements": "Dietary Requirements: [Requirements]", + "imageUrl": "[ImageUrl]" + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/restaurant_finder/examples/0.9/single_column_list.json b/samples/agent/adk/restaurant_finder/examples/0.9/single_column_list.json new file mode 100644 index 000000000..904d652b7 --- /dev/null +++ b/samples/agent/adk/restaurant_finder/examples/0.9/single_column_list.json @@ -0,0 +1,162 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "default", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#FF0000", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "default", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "title-heading", + "item-list" + ] + }, + { + "id": "title-heading", + "component": "Text", + "variant": "h1", + "text": { + "path": "/title" + } + }, + { + "id": "item-list", + "component": "List", + "direction": "vertical", + "children": { + "componentId": "item-card-template", + "path": "/items" + } + }, + { + "id": "item-card-template", + "component": "Card", + "child": "card-layout" + }, + { + "id": "card-layout", + "component": "Row", + "children": [ + "template-image", + "card-details" + ] + }, + { + "id": "template-image", + "component": "Image", + "weight": 1, + "url": { + "path": "/imageUrl" + } + }, + { + "id": "card-details", + "component": "Column", + "weight": 2, + "children": [ + "template-name", + "template-rating", + "template-detail", + "template-link", + "template-book-button" + ] + }, + { + "id": "template-name", + "component": "Text", + "variant": "h3", + "text": { + "path": "/name" + } + }, + { + "id": "template-rating", + "component": "Text", + "text": { + "path": "/rating" + } + }, + { + "id": "template-detail", + "component": "Text", + "text": { + "path": "/detail" + } + }, + { + "id": "template-link", + "component": "Text", + "text": { + "path": "/infoLink" + } + }, + { + "id": "template-book-button", + "component": "Button", + "child": "book-now-text", + "variant": "primary", + "action": { + "event": { + "name": "book_restaurant", + "context": { + "restaurantName": { + "path": "/name" + }, + "imageUrl": { + "path": "/imageUrl" + }, + "address": { + "path": "/address" + } + } + } + } + }, + { + "id": "book-now-text", + "component": "Text", + "text": "Book Now" + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "default", + "path": "/", + "value": { + "items": { + "item1": { + "name": "The Fancy Place", + "rating": 4.8, + "detail": "Fine dining experience", + "infoLink": "https://example.com/fancy", + "imageUrl": "https://example.com/fancy.jpg", + "address": "123 Main St" + }, + "item2": { + "name": "Quick Bites", + "rating": 4.2, + "detail": "Casual and fast", + "infoLink": "https://example.com/quick", + "imageUrl": "https://example.com/quick.jpg", + "address": "456 Oak Ave" + } + } + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/restaurant_finder/examples/0.9/two_column_list.json b/samples/agent/adk/restaurant_finder/examples/0.9/two_column_list.json new file mode 100644 index 000000000..df7e7e6fc --- /dev/null +++ b/samples/agent/adk/restaurant_finder/examples/0.9/two_column_list.json @@ -0,0 +1,247 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "default", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#FF0000", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "default", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "title-heading", + "restaurant-row-1" + ] + }, + { + "id": "title-heading", + "component": "Text", + "variant": "h1", + "text": { + "path": "/title" + } + }, + { + "id": "restaurant-row-1", + "component": "Row", + "children": [ + "item-card-1", + "item-card-2" + ] + }, + { + "id": "item-card-1", + "component": "Card", + "weight": 1, + "child": "card-layout-1" + }, + { + "id": "card-layout-1", + "component": "Column", + "children": [ + "template-image-1", + "card-details-1" + ] + }, + { + "id": "template-image-1", + "component": "Image", + "url": { + "path": "/items/0/imageUrl" + } + }, + { + "id": "card-details-1", + "component": "Column", + "children": [ + "template-name-1", + "template-rating-1", + "template-detail-1", + "template-link-1", + "template-book-button-1" + ] + }, + { + "id": "template-name-1", + "component": "Text", + "variant": "h3", + "text": { + "path": "/items/0/name" + } + }, + { + "id": "template-rating-1", + "component": "Text", + "text": { + "path": "/items/0/rating" + } + }, + { + "id": "template-detail-1", + "component": "Text", + "text": { + "path": "/items/0/detail" + } + }, + { + "id": "template-link-1", + "component": "Text", + "text": { + "path": "/items/0/infoLink" + } + }, + { + "id": "template-book-button-1", + "component": "Button", + "child": "book-now-text-1", + "action": { + "event": { + "name": "book_restaurant", + "context": { + "restaurantName": { + "path": "/items/0/name" + }, + "imageUrl": { + "path": "/items/0/imageUrl" + }, + "address": { + "path": "/items/0/address" + } + } + } + } + }, + { + "id": "book-now-text-1", + "component": "Text", + "text": "Book Now" + }, + { + "id": "item-card-2", + "component": "Card", + "weight": 1, + "child": "card-layout-2" + }, + { + "id": "card-layout-2", + "component": "Column", + "children": [ + "template-image-2", + "card-details-2" + ] + }, + { + "id": "template-image-2", + "component": "Image", + "url": { + "path": "/items/1/imageUrl" + } + }, + { + "id": "card-details-2", + "component": "Column", + "children": [ + "template-name-2", + "template-rating-2", + "template-detail-2", + "template-link-2", + "template-book-button-2" + ] + }, + { + "id": "template-name-2", + "component": "Text", + "variant": "h3", + "text": { + "path": "/items/1/name" + } + }, + { + "id": "template-rating-2", + "component": "Text", + "text": { + "path": "/items/1/rating" + } + }, + { + "id": "template-detail-2", + "component": "Text", + "text": { + "path": "/items/1/detail" + } + }, + { + "id": "template-link-2", + "component": "Text", + "text": { + "path": "/items/1/infoLink" + } + }, + { + "id": "template-book-button-2", + "component": "Button", + "child": "book-now-text-2", + "action": { + "event": { + "name": "book_restaurant", + "context": { + "restaurantName": { + "path": "/items/1/name" + }, + "imageUrl": { + "path": "/items/1/imageUrl" + }, + "address": { + "path": "/items/1/address" + } + } + } + } + }, + { + "id": "book-now-text-2", + "component": "Text", + "text": "Book Now" + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "default", + "path": "/", + "value": { + "title": "Top Restaurants", + "items": { + "item1": { + "name": "The Fancy Place", + "rating": 4.8, + "detail": "Fine dining experience", + "infoLink": "https://example.com/fancy", + "imageUrl": "https://example.com/fancy.jpg", + "address": "123 Main St" + }, + "item2": { + "name": "Quick Bites", + "rating": 4.2, + "detail": "Casual and fast", + "infoLink": "https://example.com/quick", + "imageUrl": "https://example.com/quick.jpg", + "address": "456 Oak Ave" + } + } + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/restaurant_finder/prompt_builder.py b/samples/agent/adk/restaurant_finder/prompt_builder.py index 3db192751..3d8276f90 100644 --- a/samples/agent/adk/restaurant_finder/prompt_builder.py +++ b/samples/agent/adk/restaurant_finder/prompt_builder.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from a2ui.core.schema.constants import VERSION_0_8 +from a2ui.core.schema.constants import VERSION_0_9 from a2ui.core.schema.manager import A2uiSchemaManager from a2ui.basic_catalog.provider import BasicCatalog from a2ui.core.schema.common_modifiers import remove_strict_validation @@ -31,7 +31,7 @@ """ UI_DESCRIPTION = """ -- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `dataModelUpdate.contents` array (e.g., as a `valueMap` for the "items" key). +- If the query is for a list of restaurants, use the restaurant data you have already received from the `get_restaurants` tool to populate the `updateDataModel.value` object (e.g., for the "items" key). - If the number of restaurants is 5 or fewer, you MUST use the `SINGLE_COLUMN_LIST_EXAMPLE` template. - If the number of restaurants is more than 5, you MUST use the `TWO_COLUMN_LIST_EXAMPLE` template. - If the query is to book a restaurant (e.g., "USER_WANTS_TO_BOOK..."), you MUST use the `BOOKING_FORM_EXAMPLE` template. @@ -66,9 +66,15 @@ def get_text_prompt() -> str: # You can now easily construct a prompt with the relevant examples. # For a different agent (e.g., a flight booker), you would pass in # different examples but use the same `get_ui_prompt` function. + version = VERSION_0_9 restaurant_prompt = A2uiSchemaManager( - VERSION_0_8, - catalogs=[BasicCatalog.get_config(version=VERSION_0_8, examples_path="examples")], + version, + catalogs=[ + BasicCatalog.get_config( + version=version, + examples_path=f"examples/{version}", + ) + ], schema_modifiers=[remove_strict_validation], ).generate_system_prompt( role_description=ROLE_DESCRIPTION, diff --git a/samples/agent/adk/rizzcharts/__main__.py b/samples/agent/adk/rizzcharts/__main__.py index 9c988f102..d15de3bfb 100644 --- a/samples/agent/adk/rizzcharts/__main__.py +++ b/samples/agent/adk/rizzcharts/__main__.py @@ -21,7 +21,7 @@ from a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore -from a2ui.core.schema.constants import VERSION_0_8 +from a2ui.core.schema.constants import VERSION_0_9 from a2ui.core.schema.manager import A2uiSchemaManager, CatalogConfig from a2ui.basic_catalog.provider import BasicCatalog from agent_executor import RizzchartsAgentExecutor, get_a2ui_enabled, get_a2ui_catalog, get_a2ui_examples @@ -60,18 +60,19 @@ def main(host, port): lite_llm_model = os.getenv("LITELLM_MODEL", "gemini/gemini-2.5-flash") base_url = f"http://{host}:{port}" + version = VERSION_0_9 schema_manager = A2uiSchemaManager( - VERSION_0_8, + version, catalogs=[ CatalogConfig.from_path( name="rizzcharts", catalog_path="rizzcharts_catalog_definition.json", - examples_path="examples/rizzcharts_catalog", + examples_path=f"examples/rizzcharts_catalog/{version}", ), BasicCatalog.get_config( - version=VERSION_0_8, - examples_path="examples/standard_catalog", + version=version, + examples_path=f"examples/standard_catalog/{version}", ), ], accepts_inline_catalogs=True, diff --git a/samples/agent/adk/rizzcharts/agent.py b/samples/agent/adk/rizzcharts/agent.py index d6ce9cd1c..e8796be54 100644 --- a/samples/agent/adk/rizzcharts/agent.py +++ b/samples/agent/adk/rizzcharts/agent.py @@ -58,8 +58,8 @@ 4. **Construct the JSON Payload:** * Use the **entire** JSON array from the chosen example as the base value for the `a2ui_json` argument. - * **Generate a new `surfaceId`:** You MUST generate a new, unique `surfaceId` for this request (e.g., `sales_breakdown_q3_surface`, `regional_outliers_northeast_surface`). This new ID must be used for the `surfaceId` in all three messages within the JSON array (`beginRendering`, `surfaceUpdate`, `dataModelUpdate`). - * **Update the title Text:** You MUST update the `literalString` value for the `Text` component (the component with `id: "page_header"`) to accurately reflect the specific user query. For example, if the user asks for "Q3" sales, update the generic template text to "Q3 2025 Sales by Product Category". + * **Generate a new `surfaceId`:** You MUST generate a new, unique `surfaceId` for this request (e.g., `sales_breakdown_q3_surface`, `regional_outliers_northeast_surface`). This new ID must be used for the `surfaceId` in all three messages within the JSON array (`createSurface`, `updateComponents`, `updateDataModel`). + * **Update the title Text:** You MUST update the `text` property of the `Text` component (the component with `id: "page_header"`) to accurately reflect the specific user query. For example, if the user asks for "Q3" sales, update the generic template text to "Q3 2025 Sales by Product Category". * Ensure the generated JSON perfectly matches the A2UI specification. It will be validated against the json_schema and rejected if it does not conform. * If you get an error in the tool response apologize to the user and let them know they should try again. diff --git a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/chart.json b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.8/chart.json similarity index 100% rename from samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/chart.json rename to samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.8/chart.json diff --git a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/map.json b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.8/map.json similarity index 100% rename from samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/map.json rename to samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.8/map.json diff --git a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.9/chart.json b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.9/chart.json new file mode 100644 index 000000000..655c08f6c --- /dev/null +++ b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.9/chart.json @@ -0,0 +1,87 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "sales-dashboard", + "catalogId": "https://github.com/google/A2UI/blob/main/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "sales-dashboard", + "components": [ + { + "id": "root", + "component": "Canvas", + "children": [ + "chart-container" + ] + }, + { + "id": "chart-container", + "component": "Column", + "children": [ + "sales-chart" + ], + "align": "center" + }, + { + "id": "sales-chart", + "component": "Chart", + "type": "doughnut", + "title": { + "path": "/chart.title" + }, + "chartData": { + "path": "/chart.items" + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "sales-dashboard", + "path": "/", + "value": { + "chart.title": "Sales by Category", + "chart.items[0].label": "Apparel", + "chart.items[0].value": 41, + "chart.items[0].drillDown[0].label": "Tops", + "chart.items[0].drillDown[0].value": 31, + "chart.items[0].drillDown[1].label": "Bottoms", + "chart.items[0].drillDown[1].value": 38, + "chart.items[0].drillDown[2].label": "Outerwear", + "chart.items[0].drillDown[2].value": 20, + "chart.items[0].drillDown[3].label": "Footwear", + "chart.items[0].drillDown[3].value": 11, + "chart.items[1].label": "Home Goods", + "chart.items[1].value": 15, + "chart.items[1].drillDown[0].label": "Pillow", + "chart.items[1].drillDown[0].value": 8, + "chart.items[1].drillDown[1].label": "Coffee Maker", + "chart.items[1].drillDown[1].value": 16, + "chart.items[1].drillDown[2].label": "Area Rug", + "chart.items[1].drillDown[2].value": 3, + "chart.items[1].drillDown[3].label": "Bath Towels", + "chart.items[1].drillDown[3].value": 14, + "chart.items[2].label": "Electronics", + "chart.items[2].value": 28, + "chart.items[2].drillDown[0].label": "Phones", + "chart.items[2].drillDown[0].value": 25, + "chart.items[2].drillDown[1].label": "Laptops", + "chart.items[2].drillDown[1].value": 27, + "chart.items[2].drillDown[2].label": "TVs", + "chart.items[2].drillDown[2].value": 21, + "chart.items[2].drillDown[3].label": "Other", + "chart.items[2].drillDown[3].value": 27, + "chart.items[3].label": "Health & Beauty", + "chart.items[3].value": 10, + "chart.items[4].label": "Other", + "chart.items[4].value": 6 + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.9/map.json b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.9/map.json new file mode 100644 index 000000000..a403a461f --- /dev/null +++ b/samples/agent/adk/rizzcharts/examples/rizzcharts_catalog/0.9/map.json @@ -0,0 +1,86 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "la-map-view", + "catalogId": "https://github.com/google/A2UI/blob/main/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json" + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "la-map-view", + "components": [ + { + "id": "root", + "component": "Canvas", + "children": [ + "map-layout-container" + ] + }, + { + "id": "map-layout-container", + "component": "Column", + "children": [ + "map-header", + "location-map" + ], + "align": "stretch" + }, + { + "id": "map-header", + "component": "Text", + "text": "Points of Interest in Los Angeles", + "variant": "h2" + }, + { + "id": "location-map", + "component": "GoogleMap", + "center": { + "path": "/mapConfig.center" + }, + "zoom": { + "path": "/mapConfig.zoom" + }, + "pins": { + "path": "/mapConfig.locations" + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "la-map-view", + "path": "/", + "value": { + "mapConfig.center.lat": 34.0522, + "mapConfig.center.lng": -118.2437, + "mapConfig.zoom": 11, + "mapConfig.locations[0].lat": 34.0135, + "mapConfig.locations[0].lng": -118.4947, + "mapConfig.locations[0].name": "Google Store Santa Monica", + "mapConfig.locations[0].description": "Your local destination for Google hardware.", + "mapConfig.locations[0].background": "#4285F4", + "mapConfig.locations[0].borderColor": "#FFFFFF", + "mapConfig.locations[0].glyphColor": "#FFFFFF", + "mapConfig.locations[1].lat": 34.1341, + "mapConfig.locations[1].lng": -118.3215, + "mapConfig.locations[1].name": "Griffith Observatory", + "mapConfig.locations[2].lat": 34.134, + "mapConfig.locations[2].lng": -118.3397, + "mapConfig.locations[2].name": "Hollywood Sign Viewpoint", + "mapConfig.locations[3].lat": 34.0453, + "mapConfig.locations[3].lng": -118.2673, + "mapConfig.locations[3].name": "Crypto.com Arena", + "mapConfig.locations[4].lat": 34.0639, + "mapConfig.locations[4].lng": -118.3592, + "mapConfig.locations[4].name": "LACMA", + "mapConfig.locations[5].lat": 33.985, + "mapConfig.locations[5].lng": -118.4729, + "mapConfig.locations[5].name": "Venice Beach Boardwalk" + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/examples/standard_catalog/chart.json b/samples/agent/adk/rizzcharts/examples/standard_catalog/0.8/chart.json similarity index 100% rename from samples/agent/adk/rizzcharts/examples/standard_catalog/chart.json rename to samples/agent/adk/rizzcharts/examples/standard_catalog/0.8/chart.json diff --git a/samples/agent/adk/rizzcharts/examples/standard_catalog/map.json b/samples/agent/adk/rizzcharts/examples/standard_catalog/0.8/map.json similarity index 100% rename from samples/agent/adk/rizzcharts/examples/standard_catalog/map.json rename to samples/agent/adk/rizzcharts/examples/standard_catalog/0.8/map.json diff --git a/samples/agent/adk/rizzcharts/examples/standard_catalog/0.9/chart.json b/samples/agent/adk/rizzcharts/examples/standard_catalog/0.9/chart.json new file mode 100644 index 000000000..e6517ae65 --- /dev/null +++ b/samples/agent/adk/rizzcharts/examples/standard_catalog/0.9/chart.json @@ -0,0 +1,94 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "sales-dashboard", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#00BFFF", + "font": "Arial" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "sales-dashboard", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "chart-title", + "category-list" + ] + }, + { + "id": "chart-title", + "component": "Text", + "text": { + "path": "/chart.title" + }, + "variant": "h2" + }, + { + "id": "category-list", + "component": "List", + "direction": "vertical", + "children": { + "componentId": "category-item-template", + "path": "/chart.items" + } + }, + { + "id": "category-item-template", + "component": "Card", + "child": "item-row" + }, + { + "id": "item-row", + "component": "Row", + "justify": "spaceBetween", + "children": [ + "item-label", + "item-value" + ] + }, + { + "id": "item-label", + "component": "Text", + "text": { + "path": "/label" + } + }, + { + "id": "item-value", + "component": "Text", + "text": { + "path": "/value" + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "sales-dashboard", + "path": "/", + "value": { + "chart.title": "Sales by Category", + "chart.items[0].label": "Apparel", + "chart.items[0].value": 41, + "chart.items[1].label": "Home Goods", + "chart.items[1].value": 15, + "chart.items[2].label": "Electronics", + "chart.items[2].value": 28, + "chart.items[3].label": "Health & Beauty", + "chart.items[3].value": 10, + "chart.items[4].label": "Other", + "chart.items[4].value": 6 + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/examples/standard_catalog/0.9/map.json b/samples/agent/adk/rizzcharts/examples/standard_catalog/0.9/map.json new file mode 100644 index 000000000..d1c1521c3 --- /dev/null +++ b/samples/agent/adk/rizzcharts/examples/standard_catalog/0.9/map.json @@ -0,0 +1,101 @@ +[ + { + "version": "v0.9", + "createSurface": { + "surfaceId": "la-map-view", + "catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json", + "theme": { + "primaryColor": "#4285F4", + "font": "Roboto" + } + } + }, + { + "version": "v0.9", + "updateComponents": { + "surfaceId": "la-map-view", + "components": [ + { + "id": "root", + "component": "Column", + "children": [ + "map-header", + "map-image", + "location-list" + ], + "align": "stretch" + }, + { + "id": "map-header", + "component": "Text", + "text": "Points of Interest in Los Angeles", + "variant": "h2" + }, + { + "id": "map-image", + "component": "Image", + "url": "https://maps.googleapis.com/maps/api/staticmap?center=Los+Angeles,CA&zoom=11&size=600x300&key=YOUR_API_KEY", + "fit": "cover" + }, + { + "id": "location-list", + "component": "List", + "direction": "vertical", + "children": { + "componentId": "location-card-template", + "path": "/mapConfig.locations" + } + }, + { + "id": "location-card-template", + "component": "Card", + "child": "location-details" + }, + { + "id": "location-details", + "component": "Column", + "children": [ + "location-name", + "location-description" + ] + }, + { + "id": "location-name", + "component": "Text", + "text": { + "path": "/name" + }, + "variant": "h4" + }, + { + "id": "location-description", + "component": "Text", + "text": { + "path": "/description" + } + } + ] + } + }, + { + "version": "v0.9", + "updateDataModel": { + "surfaceId": "la-map-view", + "path": "/", + "value": { + "mapConfig.locations[0].name": "Google Store Santa Monica", + "mapConfig.locations[0].description": "Your local destination for Google hardware.", + "mapConfig.locations[1].name": "Griffith Observatory", + "mapConfig.locations[1].description": "A public observatory with views of the Hollywood Sign.", + "mapConfig.locations[2].name": "Hollywood Sign Viewpoint", + "mapConfig.locations[2].description": "Iconic landmark in the Hollywood Hills.", + "mapConfig.locations[3].name": "Crypto.com Arena", + "mapConfig.locations[3].description": "Multi-purpose sports and entertainment arena.", + "mapConfig.locations[4].name": "LACMA", + "mapConfig.locations[4].description": "Los Angeles County Museum of Art.", + "mapConfig.locations[5].name": "Venice Beach Boardwalk", + "mapConfig.locations[5].description": "Famous oceanfront promenade." + } + } + } +] \ No newline at end of file diff --git a/samples/agent/adk/rizzcharts/prompt_builder.py b/samples/agent/adk/rizzcharts/prompt_builder.py new file mode 100644 index 000000000..75225e2b9 --- /dev/null +++ b/samples/agent/adk/rizzcharts/prompt_builder.py @@ -0,0 +1,84 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Prompt builder for the rizzcharts agent.""" + +# pylint: disable=g-importing-member, line-too-long +from a2ui.core.schema.constants import VERSION_0_9 +from a2ui.core.schema.manager import A2uiSchemaManager, CatalogConfig +from a2ui.basic_catalog.provider import BasicCatalog +from a2ui.core.schema.common_modifiers import remove_strict_validation +from agent import ROLE_DESCRIPTION, WORKFLOW_DESCRIPTION, UI_DESCRIPTION + + +if __name__ == "__main__": + version = VERSION_0_9 + schema_manager = A2uiSchemaManager( + version, + catalogs=[ + CatalogConfig.from_path( + name="rizzcharts", + catalog_path="rizzcharts_catalog_definition.json", + examples_path=f"examples/rizzcharts_catalog/{version}", + ), + BasicCatalog.get_config( + version=version, + examples_path=f"examples/standard_catalog/{version}", + ), + ], + accepts_inline_catalogs=True, + schema_modifiers=[remove_strict_validation], + ) + + # Generate prompt for rizzcharts catalog + print("Building prompt and validating rizzcharts examples...") + system_prompt = schema_manager.generate_system_prompt( + role_description=ROLE_DESCRIPTION, + workflow_description=WORKFLOW_DESCRIPTION, + ui_description=UI_DESCRIPTION, + include_schema=True, + include_examples=True, + validate_examples=True, + ) + + output = system_prompt + + # Also validate standard catalog examples + print("Validating standard catalog examples...") + # We can trigger this by selecting the basic catalog + std_prompt = schema_manager.generate_system_prompt( + role_description=ROLE_DESCRIPTION, + workflow_description=WORKFLOW_DESCRIPTION, + ui_description=UI_DESCRIPTION, + client_ui_capabilities={ + "supported_catalog_ids": [ + "https://a2ui.org/specification/v0_9/basic_catalog.json" + ] + }, + include_schema=False, + include_examples=True, + validate_examples=True, + ) + + if std_prompt: + output += "\n\n### Standard Catalog Examples:\n" + # Find the start of examples in std_prompt + if "### Examples:" in std_prompt: + output += std_prompt.split("### Examples:")[1] + + print(output) + + with open("generated_prompt.txt", "w") as f: + f.write(output) + print("\nGenerated prompt saved to generated_prompt.txt") diff --git a/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json b/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json index e7ccff637..7f5bc5f0c 100644 --- a/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json +++ b/samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json @@ -3,948 +3,1215 @@ "components": { "Text": { "type": "object", - "additionalProperties": false, - "properties": { - "text": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "The text content to display. This can be a literal string or a reference to a value in the data model ('path', e.g., '/doc/title'). While simple Markdown formatting is supported (i.e. without HTML, images, or links), utilizing dedicated UI components is generally preferred for a richer and more structured presentation.", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "Text" + }, + "text": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text content to display. While simple Markdown formatting is supported (i.e. without HTML, images, or links), utilizing dedicated UI components is generally preferred for a richer and more structured presentation." }, - "path": { - "type": "string" + "variant": { + "type": "string", + "description": "A hint for the base text style.", + "enum": [ + "h1", + "h2", + "h3", + "h4", + "h5", + "caption", + "body" + ], + "default": "body" } - } - }, - "usageHint": { - "type": "string", - "description": "A hint for the base text style. One of:\n- `h1`: Largest heading.\n- `h2`: Second largest heading.\n- `h3`: Third largest heading.\n- `h4`: Fourth largest heading.\n- `h5`: Fifth largest heading.\n- `caption`: Small text for captions.\n- `body`: Standard body text.", - "enum": [ - "h1", - "h2", - "h3", - "h4", - "h5", - "caption", - "body" + }, + "required": [ + "component", + "text" ] } - }, - "required": [ - "text" - ] + ], + "unevaluatedProperties": false }, "Image": { "type": "object", - "additionalProperties": false, - "properties": { - "url": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "The URL of the image to display. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/thumbnail/url').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "Image" + }, + "url": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The URL of the image to display." + }, + "fit": { + "type": "string", + "description": "Specifies how the image should be resized to fit its container. This corresponds to the CSS 'object-fit' property.", + "enum": [ + "contain", + "cover", + "fill", + "none", + "scaleDown" + ], + "default": "fill" }, - "path": { - "type": "string" + "variant": { + "type": "string", + "description": "A hint for the image size and style.", + "enum": [ + "icon", + "avatar", + "smallFeature", + "mediumFeature", + "largeFeature", + "header" + ], + "default": "mediumFeature" } - } - }, - "fit": { - "type": "string", - "description": "Specifies how the image should be resized to fit its container. This corresponds to the CSS 'object-fit' property.", - "enum": [ - "contain", - "cover", - "fill", - "none", - "scale-down" - ] - }, - "usageHint": { - "type": "string", - "description": "A hint for the image size and style. One of:\n- `icon`: Small square icon.\n- `avatar`: Circular avatar image.\n- `smallFeature`: Small feature image.\n- `mediumFeature`: Medium feature image.\n- `largeFeature`: Large feature image.\n- `header`: Full-width, full bleed, header image.", - "enum": [ - "icon", - "avatar", - "smallFeature", - "mediumFeature", - "largeFeature", - "header" + }, + "required": [ + "component", + "url" ] } - }, - "required": [ - "url" - ] + ], + "unevaluatedProperties": false }, "Icon": { "type": "object", - "additionalProperties": false, - "properties": { - "name": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "The name of the icon to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/form/submit').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string", - "enum": [ - "accountCircle", - "add", - "arrowBack", - "arrowForward", - "attachFile", - "calendarToday", - "call", - "camera", - "check", - "close", - "delete", - "download", - "edit", - "event", - "error", - "favorite", - "favoriteOff", - "folder", - "help", - "home", - "info", - "locationOn", - "lock", - "lockOpen", - "mail", - "menu", - "moreVert", - "moreHoriz", - "notificationsOff", - "notifications", - "payment", - "person", - "phone", - "photo", - "print", - "refresh", - "search", - "send", - "settings", - "share", - "shoppingCart", - "star", - "starHalf", - "starOff", - "upload", - "visibility", - "visibilityOff", - "warning" - ] + "component": { + "const": "Icon" }, - "path": { - "type": "string" + "name": { + "description": "The name of the icon to display.", + "oneOf": [ + { + "type": "string", + "enum": [ + "accountCircle", + "add", + "arrowBack", + "arrowForward", + "attachFile", + "calendarToday", + "call", + "camera", + "check", + "close", + "delete", + "download", + "edit", + "event", + "error", + "fastForward", + "favorite", + "favoriteOff", + "folder", + "help", + "home", + "info", + "locationOn", + "lock", + "lockOpen", + "mail", + "menu", + "moreVert", + "moreHoriz", + "notificationsOff", + "notifications", + "pause", + "payment", + "person", + "phone", + "photo", + "play", + "print", + "refresh", + "rewind", + "search", + "send", + "settings", + "share", + "shoppingCart", + "skipNext", + "skipPrevious", + "star", + "starHalf", + "starOff", + "stop", + "upload", + "visibility", + "visibilityOff", + "volumeDown", + "volumeMute", + "volumeOff", + "volumeUp", + "warning" + ] + }, + { + "type": "object", + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "additionalProperties": false + } + ] } - } + }, + "required": [ + "component", + "name" + ] } - }, - "required": [ - "name" - ] + ], + "unevaluatedProperties": false }, "Video": { "type": "object", - "additionalProperties": false, - "properties": { - "url": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "The URL of the video to display. This can be a literal string or a reference to a value in the data model ('path', e.g. '/video/url').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "Video" }, - "path": { - "type": "string" + "url": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The URL of the video to display." } - } + }, + "required": [ + "component", + "url" + ] } - }, - "required": [ - "url" - ] + ], + "unevaluatedProperties": false }, "AudioPlayer": { "type": "object", - "additionalProperties": false, - "properties": { - "url": { - "type": "object", - "description": "The URL of the audio to be played. This can be a literal string ('literal') or a reference to a value in the data model ('path', e.g. '/song/url').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" }, - "description": { + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "A description of the audio, such as a title or summary. This can be a literal string or a reference to a value in the data model ('path', e.g. '/song/title').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "AudioPlayer" }, - "path": { - "type": "string" + "url": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The URL of the audio to be played." + }, + "description": { + "description": "A description of the audio, such as a title or summary.", + "$ref": "common_types.json#/$defs/DynamicString" } - } + }, + "required": [ + "component", + "url" + ] } - }, - "required": [ - "url" - ] + ], + "unevaluatedProperties": false }, "Row": { "type": "object", - "additionalProperties": false, - "properties": { - "children": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.", - "additionalProperties": false, + "description": "A layout component that arranges its children horizontally. To create a grid layout, nest Columns within this Row.", "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } + "component": { + "const": "Row" }, - "template": { - "type": "object", - "description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.", - "additionalProperties": false, - "properties": { - "componentId": { - "type": "string" - }, - "dataBinding": { - "type": "string" - } - }, - "required": [ - "componentId", - "dataBinding" - ] + "children": { + "description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list. Children cannot be defined inline, they must be referred to by ID.", + "$ref": "common_types.json#/$defs/ChildList" + }, + "justify": { + "type": "string", + "description": "Defines the arrangement of children along the main axis (horizontally). Use 'spaceBetween' to push items to the edges, or 'start'/'end'/'center' to pack them together.", + "enum": [ + "center", + "end", + "spaceAround", + "spaceBetween", + "spaceEvenly", + "start", + "stretch" + ], + "default": "start" + }, + "align": { + "type": "string", + "description": "Defines the alignment of children along the cross axis (vertically). This is similar to the CSS 'align-items' property, but uses camelCase values (e.g., 'start').", + "enum": [ + "start", + "center", + "end", + "stretch" + ], + "default": "stretch" } - } - }, - "distribution": { - "type": "string", - "description": "Defines the arrangement of children along the main axis (horizontally). This corresponds to the CSS 'justify-content' property.", - "enum": [ - "center", - "end", - "spaceAround", - "spaceBetween", - "spaceEvenly", - "start" - ] - }, - "alignment": { - "type": "string", - "description": "Defines the alignment of children along the cross axis (vertically). This corresponds to the CSS 'align-items' property.", - "enum": [ - "start", - "center", - "end", - "stretch" + }, + "required": [ + "component", + "children" ] } - }, - "required": [ - "children" - ] + ], + "unevaluatedProperties": false }, "Column": { "type": "object", - "additionalProperties": false, - "properties": { - "children": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.", - "additionalProperties": false, + "description": "A layout component that arranges its children vertically. To create a grid layout, nest Rows within this Column.", "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } + "component": { + "const": "Column" }, - "template": { - "type": "object", - "description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.", - "additionalProperties": false, - "properties": { - "componentId": { - "type": "string" - }, - "dataBinding": { - "type": "string" - } - }, - "required": [ - "componentId", - "dataBinding" - ] + "children": { + "description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list. Children cannot be defined inline, they must be referred to by ID.", + "$ref": "common_types.json#/$defs/ChildList" + }, + "justify": { + "type": "string", + "description": "Defines the arrangement of children along the main axis (vertically). Use 'spaceBetween' to push items to the edges (e.g. header at top, footer at bottom), or 'start'/'end'/'center' to pack them together.", + "enum": [ + "start", + "center", + "end", + "spaceBetween", + "spaceAround", + "spaceEvenly", + "stretch" + ], + "default": "start" + }, + "align": { + "type": "string", + "description": "Defines the alignment of children along the cross axis (horizontally). This is similar to the CSS 'align-items' property.", + "enum": [ + "center", + "end", + "start", + "stretch" + ], + "default": "stretch" } - } - }, - "distribution": { - "type": "string", - "description": "Defines the arrangement of children along the main axis (vertically). This corresponds to the CSS 'justify-content' property.", - "enum": [ - "start", - "center", - "end", - "spaceBetween", - "spaceAround", - "spaceEvenly" - ] - }, - "alignment": { - "type": "string", - "description": "Defines the alignment of children along the cross axis (horizontally). This corresponds to the CSS 'align-items' property.", - "enum": [ - "center", - "end", - "start", - "stretch" + }, + "required": [ + "component", + "children" ] } - }, - "required": [ - "children" - ] + ], + "unevaluatedProperties": false }, "List": { "type": "object", - "additionalProperties": false, - "properties": { - "children": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children, or 'template' to generate children from a data list.", - "additionalProperties": false, "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } + "component": { + "const": "List" }, - "template": { - "type": "object", - "description": "A template for generating a dynamic list of children from a data model list. `componentId` is the component to use as a template, and `dataBinding` is the path to the map of components in the data model. Values in the map will define the list of children.", - "additionalProperties": false, - "properties": { - "componentId": { - "type": "string" - }, - "dataBinding": { - "type": "string" - } - }, - "required": [ - "componentId", - "dataBinding" - ] + "children": { + "description": "Defines the children. Use an array of strings for a fixed set of children, or a template object to generate children from a data list.", + "$ref": "common_types.json#/$defs/ChildList" + }, + "direction": { + "type": "string", + "description": "The direction in which the list items are laid out.", + "enum": [ + "vertical", + "horizontal" + ], + "default": "vertical" + }, + "align": { + "type": "string", + "description": "Defines the alignment of children along the cross axis.", + "enum": [ + "start", + "center", + "end", + "stretch" + ], + "default": "stretch" } - } - }, - "direction": { - "type": "string", - "description": "The direction in which the list items are laid out.", - "enum": [ - "vertical", - "horizontal" - ] - }, - "alignment": { - "type": "string", - "description": "Defines the alignment of children along the cross axis.", - "enum": [ - "start", - "center", - "end", - "stretch" + }, + "required": [ + "component", + "children" ] } - }, - "required": [ - "children" - ] + ], + "unevaluatedProperties": false }, "Card": { "type": "object", - "additionalProperties": false, - "properties": { - "child": { - "type": "string", - "description": "The ID of the component to be rendered inside the card." - } - }, - "required": [ - "child" - ] - }, - "Tabs": { - "type": "object", - "additionalProperties": false, - "properties": { - "tabItems": { - "type": "array", - "description": "An array of objects, where each object defines a tab with a title and a child component.", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "title": { - "type": "object", - "description": "The tab title. Defines the value as either a literal value or a path to data model value (e.g. '/options/title').", - "additionalProperties": false, - "properties": { - "literalString": { - "type": "string" - }, - "path": { - "type": "string" - } - } - }, - "child": { - "type": "string" - } + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "type": "object", + "properties": { + "component": { + "const": "Card" }, - "required": [ - "title", - "child" - ] - } - } - }, - "required": [ - "tabItems" - ] - }, - "Divider": { - "type": "object", - "additionalProperties": false, - "properties": { - "axis": { - "type": "string", - "description": "The orientation of the divider.", - "enum": [ - "horizontal", - "vertical" + "child": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the single child component to be rendered inside the card. To display multiple elements, you MUST wrap them in a layout component (like Column or Row) and pass that container's ID here. Do NOT pass multiple IDs or a non-existent ID. Do NOT define the child component inline." + } + }, + "required": [ + "component", + "child" ] } - } - }, - "Modal": { - "type": "object", - "additionalProperties": false, - "properties": { - "entryPointChild": { - "type": "string", - "description": "The ID of the component that opens the modal when interacted with (e.g., a button)." - }, - "contentChild": { - "type": "string", - "description": "The ID of the component to be displayed inside the modal." - } - }, - "required": [ - "entryPointChild", - "contentChild" - ] + ], + "unevaluatedProperties": false }, - "Button": { + "Tabs": { "type": "object", - "additionalProperties": false, - "properties": { - "child": { - "type": "string", - "description": "The ID of the component to display in the button, typically a Text component." + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" }, - "primary": { - "type": "boolean", - "description": "Indicates if this button should be styled as the primary action." + { + "$ref": "#/$defs/CatalogComponentCommon" }, - "action": { + { "type": "object", - "description": "The client-side action to be dispatched when the button is clicked. It includes the action's name and an optional context payload.", - "additionalProperties": false, "properties": { - "name": { - "type": "string" + "component": { + "const": "Tabs" }, - "context": { + "tabs": { "type": "array", + "description": "An array of objects, where each object defines a tab with a title and a child component.", "items": { "type": "object", - "additionalProperties": false, "properties": { - "key": { - "type": "string" + "title": { + "description": "The tab title.", + "$ref": "common_types.json#/$defs/DynamicString" }, - "value": { - "type": "object", - "description": "Defines the value to be included in the context as either a literal value or a path to a data model value (e.g. '/user/name').", - "additionalProperties": false, - "properties": { - "path": { - "type": "string" - }, - "literalString": { - "type": "string" - }, - "literalNumber": { - "type": "number" - }, - "literalBoolean": { - "type": "boolean" - } - } + "child": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the child component. Do NOT define the component inline." } }, "required": [ - "key", - "value" - ] + "title", + "child" + ], + "additionalProperties": false } } }, "required": [ - "name" + "component", + "tabs" ] } - }, - "required": [ - "child", - "action" - ] + ], + "unevaluatedProperties": false }, - "CheckBox": { + "Modal": { "type": "object", - "additionalProperties": false, - "properties": { - "label": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "The text to display next to the checkbox. Defines the value as either a literal value or a path to data model ('path', e.g. '/option/label').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "Modal" }, - "path": { - "type": "string" + "trigger": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the component that opens the modal when interacted with (e.g., a button). Do NOT define the component inline." + }, + "content": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the component to be displayed inside the modal. Do NOT define the component inline." } - } + }, + "required": [ + "component", + "trigger", + "content" + ] + } + ], + "unevaluatedProperties": false + }, + "Divider": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" }, - "value": { + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "The current state of the checkbox (true for checked, false for unchecked). This can be a literal boolean ('literalBoolean') or a reference to a value in the data model ('path', e.g. '/filter/open').", - "additionalProperties": false, "properties": { - "literalBoolean": { - "type": "boolean" + "component": { + "const": "Divider" }, - "path": { - "type": "string" + "axis": { + "type": "string", + "description": "The orientation of the divider.", + "enum": [ + "horizontal", + "vertical" + ], + "default": "horizontal" } - } + }, + "required": [ + "component" + ] } - }, - "required": [ - "label", - "value" - ] + ], + "unevaluatedProperties": false }, - "TextField": { + "Button": { "type": "object", - "additionalProperties": false, - "properties": { - "label": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { "type": "object", - "description": "The text label for the input field. This can be a literal string or a reference to a value in the data model ('path, e.g. '/user/name').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "Button" + }, + "child": { + "$ref": "common_types.json#/$defs/ComponentId", + "description": "The ID of the child component. Use a 'Text' component for a labeled button. Only use an 'Icon' if the requirements explicitly ask for an icon-only button. Do NOT define the child component inline." }, - "path": { - "type": "string" + "variant": { + "type": "string", + "description": "A hint for the button style. If omitted, a default button style is used. 'primary' indicates this is the main call-to-action button. 'borderless' means the button has no visual border or background, making its child content appear like a clickable link.", + "enum": [ + "default", + "primary", + "borderless" + ], + "default": "default" + }, + "action": { + "$ref": "common_types.json#/$defs/Action" } - } + }, + "required": [ + "component", + "child", + "action" + ] + } + ], + "unevaluatedProperties": false + }, + "TextField": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" }, - "text": { + { "type": "object", - "description": "The value of the text field. This can be a literal string or a reference to a value in the data model ('path', e.g. '/user/name').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "TextField" + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text label for the input field." + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The value of the text field." + }, + "variant": { + "type": "string", + "description": "The type of input field to display.", + "enum": [ + "longText", + "number", + "shortText", + "obscured" + ], + "default": "shortText" }, - "path": { - "type": "string" + "validationRegexp": { + "type": "string", + "description": "A regular expression used for client-side validation of the input." } - } - }, - "textFieldType": { - "type": "string", - "description": "The type of input field to display.", - "enum": [ - "date", - "longText", - "number", - "shortText", - "obscured" + }, + "required": [ + "component", + "label" ] - }, - "validationRegexp": { - "type": "string", - "description": "A regular expression used for client-side validation of the input." } - }, - "required": [ - "label" - ] + ], + "unevaluatedProperties": false }, - "DateTimeInput": { + "CheckBox": { "type": "object", - "additionalProperties": false, - "properties": { - "value": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { "type": "object", - "description": "The selected date and/or time value in ISO 8601 format. This can be a literal string ('literalString') or a reference to a value in the data model ('path', e.g. '/user/dob').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "component": { + "const": "CheckBox" + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text to display next to the checkbox." }, - "path": { - "type": "string" + "value": { + "$ref": "common_types.json#/$defs/DynamicBoolean", + "description": "The current state of the checkbox (true for checked, false for unchecked)." } - } - }, - "enableDate": { - "type": "boolean", - "description": "If true, allows the user to select a date." - }, - "enableTime": { - "type": "boolean", - "description": "If true, allows the user to select a time." + }, + "required": [ + "component", + "label", + "value" + ] } - }, - "required": [ - "value" - ] + ], + "unevaluatedProperties": false }, - "MultipleChoice": { + "ChoicePicker": { "type": "object", - "additionalProperties": false, - "required": [ - "selections", - "options" - ], - "properties": { - "selections": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { "type": "object", - "description": "The currently selected values for the component. This can be a literal array of strings or a path to an array in the data model('path', e.g. '/hotel/options').", - "additionalProperties": false, + "description": "A component that allows selecting one or more options from a list.", "properties": { - "literalArray": { + "component": { + "const": "ChoicePicker" + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The label for the group of options." + }, + "variant": { + "type": "string", + "description": "A hint for how the choice picker should be displayed and behave.", + "enum": [ + "multipleSelection", + "mutuallyExclusive" + ], + "default": "mutuallyExclusive" + }, + "options": { "type": "array", + "description": "The list of available options to choose from.", "items": { - "type": "string" - } - }, - "path": { - "type": "string" - } - } - }, - "options": { - "type": "array", - "description": "An array of available options for the user to choose from.", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { "type": "object", - "description": "The text to display for this option. This can be a literal string or a reference to a value in the data model (e.g. '/option/label').", - "additionalProperties": false, "properties": { - "literalString": { - "type": "string" + "label": { + "description": "The text to display for this option.", + "$ref": "common_types.json#/$defs/DynamicString" }, - "path": { - "type": "string" + "value": { + "type": "string", + "description": "The stable value associated with this option." } - } - }, - "value": { - "type": "string", - "description": "The value to be associated with this option when selected." + }, + "required": [ + "label", + "value" + ], + "additionalProperties": false } }, - "required": [ - "label", - "value" - ] - } - }, - "maxAllowedSelections": { - "type": "integer", - "description": "The maximum number of options that the user is allowed to select." - }, - "variant": { - "type": "string", - "description": "The display style of the component.", - "enum": [ - "checkbox", - "chips" + "value": { + "$ref": "common_types.json#/$defs/DynamicStringList", + "description": "The list of currently selected values. This should be bound to a string array in the data model." + }, + "displayStyle": { + "type": "string", + "description": "The display style of the component.", + "enum": [ + "checkbox", + "chips" + ], + "default": "checkbox" + }, + "filterable": { + "type": "boolean", + "description": "If true, displays a search input to filter the options.", + "default": false + } + }, + "required": [ + "component", + "options", + "value" ] - }, - "filterable": { - "type": "boolean", - "description": "If true, displays a search input to filter the options." } - } + ], + "unevaluatedProperties": false }, "Slider": { "type": "object", - "additionalProperties": false, - "properties": { - "value": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { "type": "object", - "description": "The current value of the slider. This can be a literal number ('literalNumber') or a reference to a value in the data model ('path', e.g. '/restaurant/cost').", - "additionalProperties": false, "properties": { - "literalNumber": { - "type": "number" + "component": { + "const": "Slider" }, - "path": { - "type": "string" + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The label for the slider." + }, + "min": { + "type": "number", + "description": "The minimum value of the slider.", + "default": 0 + }, + "max": { + "type": "number", + "description": "The maximum value of the slider." + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicNumber", + "description": "The current value of the slider." } - } - }, - "minValue": { - "type": "number", - "description": "The minimum value of the slider." - }, - "maxValue": { - "type": "number", - "description": "The maximum value of the slider." + }, + "required": [ + "component", + "value", + "max" + ] } - }, - "required": [ - "value" - ] + ], + "unevaluatedProperties": false }, - "Canvas": { + "DateTimeInput": { "type": "object", - "description": "Renders the UI element in a stateful panel next to the chat window.", - "properties": { - "children": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { + "$ref": "common_types.json#/$defs/Checkable" + }, + { "type": "object", - "description": "Defines the children. Use 'explicitList' for a fixed set of children.", "properties": { - "explicitList": { - "type": "array", - "items": { - "type": "string" - } + "component": { + "const": "DateTimeInput" + }, + "value": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The selected date and/or time value in ISO 8601 format. If not yet set, initialize with an empty string." + }, + "enableDate": { + "type": "boolean", + "description": "If true, allows the user to select a date.", + "default": false + }, + "enableTime": { + "type": "boolean", + "description": "If true, allows the user to select a time.", + "default": false + }, + "min": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/DynamicString" + }, + { + "if": { + "type": "string" + }, + "then": { + "oneOf": [ + { + "format": "date" + }, + { + "format": "time" + }, + { + "format": "date-time" + } + ] + } + } + ], + "description": "The minimum allowed date/time in ISO 8601 format." + }, + "max": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/DynamicString" + }, + { + "if": { + "type": "string" + }, + "then": { + "oneOf": [ + { + "format": "date" + }, + { + "format": "time" + }, + { + "format": "date-time" + } + ] + } + } + ], + "description": "The maximum allowed date/time in ISO 8601 format." + }, + "label": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The text label for the input field." } - } + }, + "required": [ + "component", + "value" + ] } - }, - "required": [ - "children" - ] + ], + "unevaluatedProperties": false }, - "Chart": { + "Canvas": { "type": "object", - "description": "An interactive chart that uses a hierarchical list of objects for its data.", - "properties": { - "type": { - "type": "string", - "description": "The type of chart to render.", - "enum": [ - "doughnut", - "pie" - ] + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" }, - "title": { + { "type": "object", - "description": "The title of the chart. Can be a literal string or a data model path.", + "description": "Renders the UI element in a stateful panel next to the chat window.", "properties": { - "literalString": { - "type": "string" + "component": { + "const": "Canvas" }, - "path": { - "type": "string" + "children": { + "$ref": "common_types.json#/$defs/ChildList", + "description": "Defines the children of the canvas." } - } + }, + "required": [ + "component", + "children" + ] + } + ], + "unevaluatedProperties": false + }, + "Chart": { + "type": "object", + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" }, - "chartData": { + { "type": "object", - "description": "The data for the chart, provided as a list of items. Can be a literal array or a data model path.", + "description": "An interactive chart that uses a hierarchical list of objects for its data.", "properties": { - "literalArray": { - "type": "array", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "value": { - "type": "number" - }, - "drillDown": { - "type": "array", - "description": "An optional list of items for the next level of data.", - "items": { - "type": "object", - "properties": { - "label": { - "type": "string" - }, - "value": { - "type": "number" - } - }, - "required": [ - "label", - "value" - ] - } + "component": { + "const": "Chart" + }, + "type": { + "type": "string", + "description": "The type of chart to render.", + "enum": [ + "doughnut", + "pie" + ] + }, + "title": { + "$ref": "common_types.json#/$defs/DynamicString", + "description": "The title of the chart." + }, + "chartData": { + "description": "The data for the chart, provided as a list of items. Can be a literal array or a data model path.", + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/ChartItem" } }, - "required": [ - "label", - "value" - ] - } - }, - "path": { - "type": "string" + { + "$ref": "common_types.json#/$defs/DataBinding" + } + ] } - } + }, + "required": [ + "component", + "type", + "chartData" + ] } - }, - "required": [ - "type", - "chartData" - ] + ], + "unevaluatedProperties": false }, "GoogleMap": { "type": "object", - "description": "A component to display a Google Map with pins.", - "properties": { - "center": { + "allOf": [ + { + "$ref": "common_types.json#/$defs/ComponentCommon" + }, + { + "$ref": "#/$defs/CatalogComponentCommon" + }, + { "type": "object", - "description": "The center point of the map, containing latitude and longitude. Can be a literal object or a data model path.", + "description": "A component to display a Google Map with pins.", "properties": { - "literalObject": { - "type": "object", - "properties": { - "lat": { - "type": "number" + "component": { + "const": "GoogleMap" + }, + "center": { + "$ref": "#/$defs/DynamicLatLng", + "description": "The center point of the map, containing latitude and longitude." + }, + "zoom": { + "$ref": "common_types.json#/$defs/DynamicNumber", + "description": "The zoom level of the map." + }, + "pins": { + "description": "A list of pin objects to display on the map. Can be a literal array or a data model path.", + "oneOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/MapPin" + } }, - "lng": { - "type": "number" + { + "$ref": "common_types.json#/$defs/DataBinding" } - }, - "required": [ - "lat", - "lng" ] - }, - "path": { - "type": "string" } - } + }, + "required": [ + "component", + "center", + "zoom" + ] + } + ], + "unevaluatedProperties": false + } + }, + "$defs": { + "CatalogComponentCommon": { + "type": "object", + "properties": { + "weight": { + "$ref": "common_types.json#/$defs/DynamicNumber" + } + } + }, + "ChartItem": { + "type": "object", + "properties": { + "label": { + "type": "string" }, - "zoom": { - "type": "object", - "description": "The zoom level of the map. Can be a literal number or a data model path.", - "properties": { - "literalNumber": { - "type": "number" + "value": { + "type": "number" + }, + "drillDown": { + "type": "array", + "description": "An optional list of items for the next level of data.", + "items": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "number" + } }, - "path": { - "type": "string" - } + "required": [ + "label", + "value" + ] } - }, - "pins": { + } + }, + "required": [ + "label", + "value" + ] + }, + "DynamicLatLng": { + "oneOf": [ + { "type": "object", - "description": "A list of pin objects to display on the map. Can be a literal array or a data model path.", "properties": { - "literalArray": { - "type": "array", - "items": { - "type": "object", - "properties": { - "lat": { - "type": "number" - }, - "lng": { - "type": "number" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "background": { - "type": "string", - "description": "Hex color code for the pin background (e.g., '#FBBC04')." - }, - "borderColor": { - "type": "string", - "description": "Hex color code for the pin border (e.g., '#000000')." - }, - "glyphColor": { - "type": "string", - "description": "Hex color code for the pin's glyph/icon (e.g., '#000000')." - } - }, - "required": [ - "lat", - "lng", - "name" - ] - } + "lat": { + "type": "number" }, - "path": { - "type": "string" + "lng": { + "type": "number" } - } + }, + "required": [ + "lat", + "lng" + ], + "additionalProperties": false + }, + { + "$ref": "common_types.json#/$defs/DataBinding" + } + ] + }, + "MapPin": { + "type": "object", + "properties": { + "lat": { + "type": "number" + }, + "lng": { + "type": "number" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "background": { + "type": "string", + "description": "Hex color code for the pin background (e.g., '#FBBC04')." + }, + "borderColor": { + "type": "string", + "description": "Hex color code for the pin border (e.g., '#000000')." + }, + "glyphColor": { + "type": "string", + "description": "Hex color code for the pin's glyph/icon (e.g., '#000000')." } }, "required": [ - "center", - "zoom" + "lat", + "lng", + "name" + ], + "additionalProperties": false + }, + "anyComponent": { + "oneOf": [ + { + "$ref": "#/components/AudioPlayer" + }, + { + "$ref": "#/components/Button" + }, + { + "$ref": "#/components/Canvas" + }, + { + "$ref": "#/components/Card" + }, + { + "$ref": "#/components/Chart" + }, + { + "$ref": "#/components/CheckBox" + }, + { + "$ref": "#/components/ChoicePicker" + }, + { + "$ref": "#/components/Column" + }, + { + "$ref": "#/components/DateTimeInput" + }, + { + "$ref": "#/components/Divider" + }, + { + "$ref": "#/components/GoogleMap" + }, + { + "$ref": "#/components/Icon" + }, + { + "$ref": "#/components/Image" + }, + { + "$ref": "#/components/List" + }, + { + "$ref": "#/components/Modal" + }, + { + "$ref": "#/components/Row" + }, + { + "$ref": "#/components/Slider" + }, + { + "$ref": "#/components/Tabs" + }, + { + "$ref": "#/components/Text" + }, + { + "$ref": "#/components/TextField" + }, + { + "$ref": "#/components/Video" + } + ] + }, + "anyFunction": { + "enum": [ + "none" ] } } diff --git a/samples/agent/adk/tests/test_examples_validation.py b/samples/agent/adk/tests/test_examples_validation.py new file mode 100644 index 000000000..c7b9f95b8 --- /dev/null +++ b/samples/agent/adk/tests/test_examples_validation.py @@ -0,0 +1,132 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import json +from pathlib import Path +from typing import Dict, Any +import pytest + +from a2ui.core.schema.constants import VERSION_0_9 +from a2ui.core.schema.manager import A2uiSchemaManager, CatalogConfig +from a2ui.basic_catalog.provider import BasicCatalog +from a2ui.core.schema.common_modifiers import remove_strict_validation +from a2ui.core.schema.catalog_provider import A2uiCatalogProvider + + +ROOT_DIR = Path(__file__).parent.parent.parent.parent.parent # a2ui root +SAMPLES_DIR = ROOT_DIR / "samples" / "agent" / "adk" + +SAMPLE_CONFIGS = [ + { + "name": "contact_lookup", + "path": SAMPLES_DIR / "contact_lookup", + "catalogs": [ + BasicCatalog.get_config( + version=VERSION_0_9, + examples_path="examples/0.9", + ) + ], + "schema_modifiers": [], + "validate": False, # Use invalid examples to test retry logic + }, + { + "name": "contact_multiple_surfaces", + "path": SAMPLES_DIR / "contact_multiple_surfaces", + "catalogs": [ + CatalogConfig.from_path( + name="contact_multiple_surfaces_inline_catalog", + catalog_path="inline_catalog.json", + examples_path=f"examples/{VERSION_0_9}", + ), + ], + "schema_modifiers": [remove_strict_validation], + "validate": True, + }, + { + "name": "restaurant_finder", + "path": SAMPLES_DIR / "restaurant_finder", + "catalogs": [ + BasicCatalog.get_config( + version=VERSION_0_9, + examples_path="examples/0.9", + ) + ], + "schema_modifiers": [remove_strict_validation], + "validate": True, + }, + { + "name": "rizzcharts", + "path": SAMPLES_DIR / "rizzcharts", + "catalogs": [ + CatalogConfig.from_path( + name="rizzcharts", + catalog_path="rizzcharts_catalog_definition.json", + examples_path="examples/rizzcharts_catalog/0.9", + ), + BasicCatalog.get_config( + version=VERSION_0_9, + examples_path="examples/standard_catalog/0.9", + ), + ], + "schema_modifiers": [remove_strict_validation], + "validate": True, + }, +] + + +@pytest.mark.parametrize("config", SAMPLE_CONFIGS) +def test_sample_examples_validation(config): + """Validates that all examples for a given sample pass A2UI validation.""" + do_validate = config.get("validate", True) + sample_path = config["path"] + os.chdir(sample_path) # Change to sample dir to resolve relative catalog paths if any + + manager = A2uiSchemaManager( + VERSION_0_9, + catalogs=config["catalogs"], + accepts_inline_catalogs=True, + schema_modifiers=config["schema_modifiers"], + ) + + # Iterate through each catalog and validate its examples + for catalog in manager._supported_catalogs: + examples_path = manager._catalog_example_paths.get(catalog.catalog_id) + if not examples_path: + continue + + # manager.load_examples(catalog, validate=True) returns a combined string. + # It internally calls _validate_example which logs warnings on failure. + # To strictly fail the test, we want to capture those failures or re-implement. + + path = Path(examples_path) + if not path.is_absolute(): + path = sample_path / path + + assert ( + path.is_dir() + ), f"Examples directory not found: {path} for sample {config['name']}" + + for filename in os.listdir(path): + if filename.endswith(".json"): + full_path = path / filename + with open(full_path, "r", encoding="utf-8") as f: + content = json.load(f) + try: + if do_validate: + catalog.validator.validate(content) + except Exception as e: + pytest.fail( + f"Validation failed for {full_path} in sample {config['name']}: {e}" + )