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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 243 additions & 0 deletions docs/guides/authoring-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# Authoring Custom Components

Learn how to define, implement, and register custom components in A2UI using the `rizzcharts` sample as an example. This guide focuses on authoring a component around your Angular code.

## Overview

Authoring a new component involves four main steps:

1. **Define the Catalog Schema**: Specify the component's properties and types in a JSON Schema.
2. **Define the Component (Client)**: Implement the UI using your framework (e.g., Angular).
3. **Register with the Renderer (Client)**: Add the component to your client-side catalog.
4. **Invoke from the Agent**: Instruct the agent to use the component via `send_a2ui_json_to_client`.

---

## 1. Defining the Catalog Schema

The catalog schema defines the API of your catalog. It lists available components and their properties, which the agent uses to construct UI payloads.

**This schema acts as a contract between the client and the server (agent).** Both must agree on this schema for rendering to work. The client advertises what catalogs it supports, and the server selects a compatible one. For details on how this handshake works, see [A2UI Catalog Negotiation](../concepts/catalogs.md#a2ui-catalog-negotiation).

In the [`rizzcharts`](../../samples/agent/adk/rizzcharts/README.md) example, the catalog schema is defined in [`rizzcharts_catalog_definition.json`](../../samples/agent/adk/rizzcharts/rizzcharts_catalog_definition.json).

Here is the schema for the `Chart` component:

```json
"Chart": {
"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"
]
},
"title": {
"type": "object",
"description": "The title of the chart. Can be a literal string or a data model path.",
"properties": {
"literalString": {
"type": "string"
},
"path": {
"type": "string"
}
}
},
"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.",
"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"
]
}
}
},
"required": [
"label",
"value"
]
}
},
"path": {
"type": "string"
}
}
}
},
"required": [
"type",
"chartData"
]
}
Comment on lines +27 to +102
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The JSON snippet for the Chart component is not a valid JSON object on its own, as it's a fragment (a key-value pair) rather than a complete object. This can be confusing for readers trying to understand or validate the schema. To make it a valid and self-contained example, it should be wrapped in curly braces {}.

{
  "Chart": {
    "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"
        ]
      },
      "title": {
        "type": "object",
        "description": "The title of the chart. Can be a literal string or a data model path.",
        "properties": {
          "literalString": {
            "type": "string"
          },
          "path": {
            "type": "string"
          }
        }
      },
      "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.",
        "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"
                    ]
                  }
                }
              },
              "required": [
                "label",
                "value"
              ]
            }
          },
          "path": {
            "type": "string"
          }
        }
      }
    },
    "required": [
      "type",
      "chartData"
    ]
  }
}

```

---

## 2. Implementing the Component (Client)

Implement your component using your client-side framework. For Angular, your component should extend `DynamicComponent` provided by `@a2ui/angular`.

In the [`rizzcharts`](../../samples/client/angular/projects/rizzcharts/README.md) example, the `Chart` component is defined in [`chart.ts`](../../samples/client/angular/projects/rizzcharts/src/a2ui-catalog/chart.ts).

```typescript
import { DynamicComponent } from '@a2ui/angular';
import * as Primitives from '@a2ui/web_core/types/primitives';
import * as Types from '@a2ui/web_core/types/types';
import { Component, computed, input, Signal, signal } from '@angular/core';

@Component({
selector: 'a2ui-chart',
template: `
<div>
<h2>{{ resolvedTitle() }}</h2>
<canvas baseChart [data]="currentData()" [type]="chartType()"></canvas>
</div>
`,
})
export class Chart extends DynamicComponent<Types.CustomNode> {
readonly type = input.required<string>();
protected readonly chartType = computed(() => this.type() as ChartType);

readonly title = input<Primitives.StringValue | null>();
protected readonly resolvedTitle = computed(() => super.resolvePrimitive(this.title() ?? null));

readonly chartData = input.required<Primitives.StringValue | null>();
// ... data resolution logic using super.resolvePrimitive for data paths
}
```

Key points:
- **Extend `DynamicComponent`**: This gives you access to `resolvePrimitive` for data binding resolution.
- **Use Angular Inputs**: Map properties from the schema to Angular inputs.

---

## 3. Registering with the Renderer (Client)

Once the component is implemented, register it in your client catalog. This maps the component name (used by agents) to the implementation class.

In the [`rizzcharts`](../../samples/agent/adk/rizzcharts/README.md) example, this is done in [`catalog.ts`](../../samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The link to the rizzcharts example points to the agent-side README, but the catalog.ts file being discussed is part of the client-side implementation. For consistency and to avoid confusion, it's better to link to the client-side rizzcharts README, similar to the link on line 111.

Suggested change
In the [`rizzcharts`](../../samples/agent/adk/rizzcharts/README.md) example, this is done in [`catalog.ts`](../../samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts).
In the [`rizzcharts`](../../samples/client/angular/projects/rizzcharts/README.md) example, this is done in [`catalog.ts`](../../samples/client/angular/projects/rizzcharts/src/a2ui-catalog/catalog.ts).


```typescript
import { Catalog, DEFAULT_CATALOG } from '@a2ui/angular';
import { inputBinding } from '@angular/core';

export const RIZZ_CHARTS_CATALOG = {
...DEFAULT_CATALOG,
Chart: {
type: () => import('./chart').then((r) => r.Chart),
bindings: ({ properties }) => [
inputBinding('type', () => ('type' in properties && properties['type']) || undefined),
inputBinding('title', () => ('title' in properties && properties['title']) || undefined),
inputBinding(
'chartData',
() => ('chartData' in properties && properties['chartData']) || undefined,
),
],
},
} as Catalog;
```

Key points:
- **Lazy Loading**: Use `import()` to lazy-load the component code.
- **Input Bindings**: Use `inputBinding` to map properties from the schema to Angular inputs.

---

## 4. Invoking from the Agent

To use the custom component, you initialize the agent with tools from the A2UI SDK that understand your catalog. The SDK handles resolving the catalog and providing examples to the model.

Here is how the flow wires up:

### 4.1 Session Preparation (Executor)

The execution layer (e.g., `RizzchartsAgentExecutor`) intercepts the incoming message to detect if A2UI is enabled and what catalogs the client supports. It resolves the catalog and saves it to the session state.

```python
# In agent_executor.py

use_ui = try_activate_a2ui_extension(context)
if use_ui:
# Resolve catalog based on client capabilities
a2ui_catalog = self.schema_manager.get_selected_catalog(
client_ui_capabilities=capabilities
)
examples = self.schema_manager.load_examples(a2ui_catalog, validate=True)

# Save to session (Event contains state_delta)
await runner.session_service.append_event(
session,
Event(
actions=EventActions(
state_delta={
_A2UI_ENABLED_KEY: True,
_A2UI_CATALOG_KEY: a2ui_catalog,
_A2UI_EXAMPLES_KEY: examples,
}
),
),
)
```

### 4.2 Agent Tool Setup

The Agent uses [SendA2uiToClientToolset](../../agent_sdks/python/src/a2ui/adk/a2a_extension/send_a2ui_to_client_toolset.py) to give the agent a tool that it can use to send A2UI to the client.

```python
from a2ui.adk.a2a_extension.send_a2ui_to_client_toolset import SendA2uiToClientToolset

a2ui_catalog = self.schema_manager.get_selected_catalog(
client_ui_capabilities=capabilities
)
agent.tools = [
SendA2uiToClientToolset(
a2ui_catalog=a2ui_catalog,
a2ui_enabled=True,
)
]
```

### 4.3 Tool Execution

Invocations of the tool in [SendA2uiToClientToolset](../../agent_sdks/python/src/a2ui/adk/a2a_extension/send_a2ui_to_client_toolset.py) by the LLM are intercepted in the A2A Agent Executor using the [A2uiEventConverter](../../agent_sdks/python/src/a2ui/adk/a2a_extension/send_a2ui_to_client_toolset.py). This automatically translates tool calls into A2A Dataparts with the A2UI payload.

```python
from a2ui.adk.a2a_extension.send_a2ui_to_client_toolset import (
A2uiEventConverter,
)

config = A2aAgentExecutorConfig(event_converter=A2uiEventConverter())
```

7 changes: 4 additions & 3 deletions docs/guides/custom-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ TODO: Add detailed guide for defining custom catalogs for each platform.

**Web (Lit / Angular):**

- How to define a catalog with both standard and custom components
- How to register the catalog with the A2UI client
- How to implement custom component classes
- [Authoring Custom Components](authoring-components.md): A detailed guide using Angular and rizzcharts as an example.
- How to define a catalog with both standard and custom components in Lit
- How to register the catalog with the A2UI client in Lit


**Flutter:**

Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ nav:
- Agent Development: guides/agent-development.md
- Renderer Development: guides/renderer-development.md
- Custom Components: guides/custom-components.md
- Authoring Custom Components: guides/authoring-components.md
- Theming & Styling: guides/theming.md

- A2UI over MCP: guides/a2ui_over_mcp.md
- Reference:
- Component Gallery: reference/components.md
Expand Down
Loading