Claude Code Plugins

Community-maintained marketplace

Feedback

castella-a2ui

@i2y/castella
31
0

Render A2UI JSON as native Castella widgets. Parse A2UI messages, handle actions, progressive rendering, data binding, and connect to A2UI-enabled agents.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name castella-a2ui
description Render A2UI JSON as native Castella widgets. Parse A2UI messages, handle actions, progressive rendering, data binding, and connect to A2UI-enabled agents.

Castella A2UI Integration

A2UI (Agent-to-User Interface) enables AI agents to generate rich, interactive UIs by outputting JSON component descriptions. Castella renders these natively across desktop, web, and terminal platforms.

When to use: "render A2UI JSON", "A2UI component", "A2UIRenderer", "A2UI data binding", "A2UI streaming", "connect to A2UI agent", "updateDataModel", "A2UIClient"

Quick Start

Render A2UI JSON to a Castella widget:

from castella import App
from castella.a2ui import A2UIRenderer, A2UIComponent
from castella.frame import Frame

renderer = A2UIRenderer()
widget = renderer.render_json({
    "components": [
        {"id": "root", "component": "Column", "children": {"explicitList": ["text1"]}},
        {"id": "text1", "component": "Text", "text": {"literalString": "Hello A2UI!"}}
    ],
    "rootId": "root"
})

App(Frame("A2UI Demo", 800, 600), widget).run()

Core Concepts

A2UIRenderer

The main class for converting A2UI messages to Castella widgets:

from castella.a2ui import A2UIRenderer, UserAction

def on_action(action: UserAction):
    print(f"Action: {action.name}")
    print(f"Source: {action.source_component_id}")
    print(f"Context: {action.context}")

renderer = A2UIRenderer(on_action=on_action)

Value Types

A2UI uses typed values for properties:

# Literal values (static)
{"text": {"literalString": "Hello"}}
{"value": {"literalNumber": 42}}
{"checked": {"literalBoolean": True}}

# Data binding (dynamic, JSON Pointer RFC 6901)
{"text": {"path": "/user/name"}}
{"value": {"path": "/counter"}}

Helper functions:

from castella.a2ui import literal, binding

literal("Hello")      # {"literalString": "Hello"}
literal(42)           # {"literalNumber": 42}
literal(True)         # {"literalBoolean": True}
binding("/counter")   # {"path": "/counter"}

Supported Components

A2UI Component Castella Widget Notes
Text Text usageHint: h1-h5, body, caption
Button Button action with context
TextField Input/MultilineInput usageHint: password, multiline
CheckBox CheckBox Two-way binding
Slider Slider min/max range
DateTimeInput DateTimeInput Date/time picker
ChoicePicker RadioButtons/Column Single/multiple selection
Image NetImage URL-based images
Icon Text Material Icons → emoji
Divider Spacer Horizontal/vertical
Row Row Horizontal layout
Column Column Vertical layout
Card Column Container with styling
List Column TemplateChildren support
Tabs Tabs Tabbed navigation
Modal Modal Overlay dialog
Markdown Markdown Rich text (Castella extension)

See references/components.md for detailed component reference.

Data Binding

Bind widget values to a data model using JSON Pointer paths:

a2ui_json = {
    "components": [
        {"id": "root", "component": "Column", "children": {"explicitList": ["greeting"]}},
        {"id": "greeting", "component": "Text", "text": {"path": "/message"}}
    ],
    "rootId": "root"
}

# Provide initial data
initial_data = {"/message": "Hello, World!"}
widget = renderer.render_json(a2ui_json, initial_data=initial_data)

Actions

Handle user interactions via actions:

{
    "id": "submit_btn",
    "component": "Button",
    "text": {"literalString": "Submit"},
    "action": {"name": "submit", "context": ["/formData"]}
}

Action handler receives UserAction:

def on_action(action: UserAction):
    print(action.name)                  # "submit"
    print(action.source_component_id)   # "submit_btn"
    print(action.context)               # ["/formData"]

updateDataModel

Update bound values dynamically:

renderer.handle_message({
    "updateDataModel": {
        "surfaceId": "default",
        "data": {"/message": "Updated message!"}
    }
})

A2UIComponent (Reactive)

Wrap surface in A2UIComponent for automatic UI updates:

from castella.a2ui import A2UIComponent

renderer.render_json(a2ui_json, initial_data=initial_data)
surface = renderer.get_surface("default")
component = A2UIComponent(surface)  # Auto-updates on data changes

App(Frame("A2UI", 800, 600), component).run()

TemplateChildren (Dynamic Lists)

Render lists from data arrays:

a2ui_json = {
    "components": [
        {"id": "root", "component": "Column", "children": {"explicitList": ["user_list"]}},
        {"id": "user_list", "component": "List", "children": {
            "path": "/users",           # JSON Pointer to array
            "componentId": "user_item"  # Template component
        }},
        {"id": "user_item", "component": "Text", "text": {"path": "name"}}  # Relative path
    ],
    "rootId": "root"
}

initial_data = {"/users": [{"name": "Alice"}, {"name": "Bob"}, {"name": "Charlie"}]}
widget = renderer.render_json(a2ui_json, initial_data=initial_data)

ChoicePicker

Single or multiple selection:

# Single selection (renders as RadioButtons)
{
    "id": "color_picker",
    "component": "ChoicePicker",
    "choices": [
        {"literalString": "Red"},
        {"literalString": "Green"},
        {"literalString": "Blue"}
    ],
    "selected": {"literalString": "Red"},
    "allowMultiple": False
}

# Multiple selection (renders as CheckBox list)
{
    "id": "toppings",
    "component": "ChoicePicker",
    "choices": [
        {"literalString": "Cheese"},
        {"literalString": "Pepperoni"},
        {"literalString": "Mushrooms"}
    ],
    "selected": {"path": "/selectedToppings"},
    "allowMultiple": True
}

Progressive Rendering (Streaming)

Handle JSONL streams for incremental UI updates:

# From JSONL string
jsonl_content = """
{"beginRendering": {"surfaceId": "main", "root": "root"}}
{"updateComponents": {"surfaceId": "main", "components": [...]}}
"""
surface = renderer.handle_jsonl(jsonl_content)

# From file
with open("ui.jsonl") as f:
    surface = renderer.handle_stream(f, on_update=lambda s: app.redraw())

# From async SSE stream
from castella.a2ui.transports import sse_stream
surface = await renderer.handle_stream_async(await sse_stream(url))

Message sequence:

  1. beginRendering - Start progressive render
  2. updateComponents - Add/update components
  3. updateDataModel - Update data values

See references/streaming.md for transport details.

A2UIClient

Connect to A2A agents with A2UI extension:

from castella.a2ui import A2UIClient, A2UIComponent, UserAction

def on_action(action: UserAction):
    print(f"Action: {action.name}")

client = A2UIClient("http://localhost:10002", on_action=on_action)
surface = client.send("Find restaurants in Tokyo")

if surface:
    App(Frame("Restaurant Finder", 800, 600), A2UIComponent(surface)).run()

Async usage:

async def main():
    client = A2UIClient("http://localhost:10002")
    surface = await client.send_async("Hello!")

    if surface:
        # Send action back to agent
        await client.send_action_async(action)

A2UI 0.9 Compatibility

Castella auto-normalizes A2UI 0.9 spec format:

# A2UI 0.9 format (plain values) - accepted
{"text": "Hello", "children": ["a", "b"]}

# Castella internal format (wrapped) - also accepted
{"text": {"literalString": "Hello"}, "children": {"explicitList": ["a", "b"]}}

Key normalizations:

  • text: "Hello"text: {literalString: "Hello"}
  • children: ["a", "b"]children: {explicitList: ["a", "b"]}
  • usageHint: "shortText"usageHint: "text"
  • usageHint: "obscured"usageHint: "password"

TextField usageHint

# Password field (masked ●●●●)
{"id": "password", "component": "TextField", "usageHint": "password"}

# Multiline field
{"id": "comments", "component": "TextField", "usageHint": "multiline"}

Best Practices

  1. Use A2UIComponent wrapper for reactive data binding
  2. Provide initial_data for TemplateChildren/List components
  3. Handle actions to update data model dynamically
  4. Use semantic IDs - A2UI component IDs become MCP semantic IDs
  5. Test with mock data before connecting to live agents

Installation

uv sync --extra agent   # A2UI + A2A support

Reference

  • references/components.md - Complete A2UI component reference
  • references/messages.md - A2UI message types
  • references/streaming.md - Streaming and transports
  • scripts/ - Executable examples (basic_a2ui.py, a2ui_form.py, a2ui_list.py)
  • A2UI Specification: https://a2ui.org/specification/v0.9-a2ui/