Files
Claude-Code-Workflow/ccw/docs/a2ui-protocol-guide.md
catlog22 345437415f Add end-to-end tests for workspace switching and backend tests for ask_question tool
- Implemented E2E tests for workspace switching functionality, covering scenarios such as switching workspaces, data isolation, language preference maintenance, and UI updates.
- Added tests to ensure workspace data is cleared on logout and handles unsaved changes during workspace switches.
- Created comprehensive backend tests for the ask_question tool, validating question creation, execution, answer handling, cancellation, and timeout scenarios.
- Included edge case tests to ensure robustness against duplicate questions and invalid answers.
2026-01-31 16:02:20 +08:00

16 KiB

A2UI Protocol Usage and Troubleshooting Guide

This guide provides comprehensive information about the A2UI protocol for AI agent developers using CCW.

Table of Contents

  1. Protocol Overview
  2. Surface Update Structure
  3. Component Reference
  4. Action Handling
  5. State Management
  6. Code Examples
  7. Troubleshooting
  8. Best Practices

Protocol Overview

A2UI (AI-to-UI) is a protocol that enables AI agents to generate dynamic user interfaces through structured JSON messages. The protocol defines:

  • Surface Updates: Complete UI descriptions that can be rendered
  • Components: Reusable UI building blocks
  • Actions: User interaction handlers
  • State: Data binding and state management

Message Flow

AI Agent ──(generates)──▶ A2UI Surface Update ──(WebSocket)──▶ Frontend
                                                          │
                                                          ▼
                                                    A2UI Parser
                                                          │
                                                          ▼
                                                    Component Registry
                                                          │
                                                          ▼
                                                    Rendered UI
                                                          │
                                                          ▼
                                                    User Interaction
                                                          │
                                                          ▼
                                                    Action Event ──(WebSocket)──▶ Backend
                                                                                   │
                                                                                   ▼
                                                                              AI Agent

Surface Update Structure

Basic Structure

{
  "surfaceId": "unique-surface-identifier",
  "components": [
    {
      "id": "component-1",
      "component": {
        "ComponentType": {
          // Component-specific properties
        }
      }
    }
  ],
  "initialState": {
    "key": "value",
    "nested": {
      "data": true
    }
  }
}

Properties

Property Type Required Description
surfaceId string Yes Unique identifier for this surface
components SurfaceComponent[] Yes Array of component definitions
initialState Record<string, unknown> No Initial state for bindings

Component Reference

Content Types

Literal String

Direct text value:

{
  "literalString": "Hello, World!"
}

Binding

Reference to state value:

{
  "path": "user.name"
}

Standard Components

Text

Display text with semantic hints.

{
  "Text": {
    "text": { "literalString": "Hello" },
    "usageHint": "h1"
  }
}
Property Type Required Values
text Content Yes Literal or binding
usageHint string No h1, h2, h3, h4, h5, h6, p, span, code, small

Button

Clickable button with nested content.

{
  "Button": {
    "onClick": { "actionId": "submit", "parameters": { "formId": "login" } },
    "content": {
      "Text": { "text": { "literalString": "Submit" } }
    },
    "variant": "primary",
    "disabled": { "literalBoolean": false }
  }
}
Property Type Required Values
onClick Action Yes Action definition
content Component Yes Nested component (usually Text)
variant string No primary, secondary, destructive, ghost, outline
disabled BooleanContent No Literal or binding

Dropdown

Select dropdown with options.

{
  "Dropdown": {
    "options": [
      { "label": { "literalString": "Option 1" }, "value": "opt1" },
      { "label": { "literalString": "Option 2" }, "value": "opt2" }
    ],
    "selectedValue": { "literalString": "opt1" },
    "onChange": { "actionId": "select-change" },
    "placeholder": "Select an option"
  }
}

TextField

Single-line text input.

{
  "TextField": {
    "value": { "literalString": "Initial value" },
    "onChange": { "actionId": "input-change", "parameters": { "field": "username" } },
    "placeholder": "Enter username",
    "type": "text"
  }
}
Property Type Required Values
type string No text, email, password, number, url

TextArea

Multi-line text input.

{
  "TextArea": {
    "onChange": { "actionId": "textarea-change" },
    "placeholder": "Enter description",
    "rows": 5
  }
}

Checkbox

Boolean checkbox with label.

{
  "Checkbox": {
    "checked": { "literalBoolean": true },
    "onChange": { "actionId": "checkbox-change" },
    "label": { "literalString": "Accept terms" }
  }
}

Progress

Progress bar indicator.

{
  "Progress": {
    "value": { "literalNumber": 75 },
    "max": 100
  }
}

Card

Container with title and nested content.

{
  "Card": {
    "title": { "literalString": "Card Title" },
    "description": { "literalString": "Card description" },
    "content": [
      {
        "id": "text-1",
        "component": {
          "Text": { "text": { "literalString": "Card content" } }
        }
      }
    ]
  }
}

Custom Components

CLIOutput

Terminal-style output with syntax highlighting.

{
  "CLIOutput": {
    "output": { "literalString": "$ npm install\nInstalling...\nDone!" },
    "language": "bash",
    "streaming": false,
    "maxLines": 100
  }
}
Property Type Required Description
output Content Yes Text to display
language string No bash, javascript, python, etc.
streaming boolean No Show streaming indicator
maxLines number No Limit output lines

DateTimeInput

Date and time picker.

{
  "DateTimeInput": {
    "value": { "literalString": "2024-01-15T10:30:00Z" },
    "onChange": { "actionId": "datetime-change" },
    "placeholder": "Select date and time",
    "includeTime": true,
    "minDate": { "literalString": "2024-01-01T00:00:00Z" },
    "maxDate": { "literalString": "2024-12-31T23:59:59Z" }
  }
}
Property Type Required Description
includeTime boolean No Include time (default: true)
minDate Content No Minimum selectable date
maxDate Content No Maximum selectable date

Action Handling

Action Structure

{
  "actionId": "unique-action-id",
  "parameters": {
    "key1": "value1",
    "key2": 42
  }
}

Action Flow

  1. User interacts with component (click, type, select)
  2. Component triggers onAction callback
  3. Action sent via WebSocket to backend
  4. Backend processes action and responds

Action Response

Backend can respond with:

  • State Update: Update component state
  • New Surface: Replace or add components
  • Close Surface: Dismiss notification/dialog

State Management

State Binding

Components can bind to state values:

{
  "TextField": {
    "value": { "path": "form.username" },
    "onChange": { "actionId": "update-field" }
  }
}

State Update

Backend sends state updates:

{
  "type": "a2ui-state-update",
  "surfaceId": "form-surface",
  "updates": {
    "form": {
      "username": "newvalue",
      "email": "updated@example.com"
    }
  }
}

Code Examples

Example 1: Simple Form

{
  "surfaceId": "login-form",
  "components": [
    {
      "id": "title",
      "component": {
        "Text": {
          "text": { "literalString": "Login" },
          "usageHint": "h2"
        }
      }
    },
    {
      "id": "username",
      "component": {
        "TextField": {
          "onChange": { "actionId": "field-change", "parameters": { "field": "username" } },
          "placeholder": "Username",
          "type": "text"
        }
      }
    },
    {
      "id": "password",
      "component": {
        "TextField": {
          "onChange": { "actionId": "field-change", "parameters": { "field": "password" } },
          "placeholder": "Password",
          "type": "password"
        }
      }
    },
    {
      "id": "submit",
      "component": {
        "Button": {
          "onClick": { "actionId": "login" },
          "content": {
            "Text": { "text": { "literalString": "Login" } }
          },
          "variant": "primary"
        }
      }
    }
  ],
  "initialState": {
    "username": "",
    "password": ""
  }
}

Example 2: Data Display with Actions

{
  "surfaceId": "user-list",
  "components": [
    {
      "id": "title",
      "component": {
        "Text": {
          "text": { "literalString": "Users" },
          "usageHint": "h3"
        }
      }
    },
    {
      "id": "user-card",
      "component": {
        "Card": {
          "title": { "path": "users.0.name" },
          "description": { "path": "users.0.email" },
          "content": [
            {
              "id": "edit-btn",
              "component": {
                "Button": {
                  "onClick": { "actionId": "edit-user", "parameters": { "userId": "1" } },
                  "content": { "Text": { "text": { "literalString": "Edit" } } },
                  "variant": "secondary"
                }
              }
            }
          ]
        }
      }
    }
  ],
  "initialState": {
    "users": [
      { "id": 1, "name": "Alice", "email": "alice@example.com" },
      { "id": 2, "name": "Bob", "email": "bob@example.com" }
    ]
  }
}

Example 3: CLI Output with Progress

{
  "surfaceId": "build-progress",
  "components": [
    {
      "id": "title",
      "component": {
        "Text": {
          "text": { "literalString": "Building Project" },
          "usageHint": "h3"
        }
      }
    },
    {
      "id": "progress",
      "component": {
        "Progress": {
          "value": { "path": "build.progress" },
          "max": 100
        }
      }
    },
    {
      "id": "output",
      "component": {
        "CLIOutput": {
          "output": { "path": "build.output" },
          "language": "bash",
          "streaming": { "path": "build.running" }
        }
      }
    }
  ],
  "initialState": {
    "build": {
      "progress": 45,
      "output": "$ npm run build\nBuilding module 1/3...",
      "running": true
    }
  }
}

Troubleshooting

Common Errors

Parse Errors

Error: A2UI validation failed

Causes:

  • Invalid JSON structure
  • Missing required fields
  • Invalid component type
  • Wrong data type for property

Solution:

// Use safeParse to get detailed errors
const result = a2uiParser.safeParse(jsonString);
if (!result.success) {
  console.error('Validation errors:', result.error.errors);
}

Unknown Component Type

Error: Unknown component type: XYZ

Causes:

  • Component not registered in registry
  • Typo in component type name
  • Custom component not exported

Solution:

// Check registered components
console.log(a2uiRegistry.getRegisteredTypes());
// Should include: ['Text', 'Button', 'XYZ', ...]

// Register custom component
a2uiRegistry.register('XYZ', XYZRenderer);

State Binding Failures

Error: State not updating or showing undefined

Causes:

  • Wrong binding path
  • State not initialized
  • Case-sensitive path mismatch

Solution:

// Ensure state exists in initialState
initialState: {
  user: {
    name: "Alice"
  }
}

// Use correct binding path
{ "path": "user.name" }  // ✓
{ "path": "User.name" }  // ✗ (case mismatch)

Debugging Tips

Enable Verbose Logging

// In development
if (process.env.NODE_ENV === 'development') {
  (window as any).A2UI_DEBUG = true;
}

Inspect Component Props

// Add logging in renderer
export const A2UICustom: ComponentRenderer = (props) => {
  console.log('[A2UICustom] Props:', props);
  // ... rest of implementation
};

Validate Before Sending

// Backend: Validate before sending
const result = a2uiParser.safeParseObject(surfaceUpdate);
if (!result.success) {
  console.error('Invalid surface:', result.error);
  return;
}
// Safe to send
ws.send(JSON.stringify(surfaceUpdate));

Performance Issues

Too Many Re-renders

Symptoms: UI lagging, high CPU usage

Solutions:

  • Memoize expensive components
  • Debounce rapid actions
  • Limit component count per surface
import { memo } from 'react';

export const ExpensiveComponent = memo<A2UIComponentType>(({ 
  component, state, onAction, resolveBinding 
}) => {
  // Component implementation
});

Large Output in CLIOutput

Symptoms: Page freeze with large CLI output

Solutions:

{
  "CLIOutput": {
    "output": { "path": "output" },
    "maxLines": 1000
  }
}

Best Practices

1. Component IDs

Use unique, descriptive IDs:

{
  "id": "user-form-username",
  "component": { ... }
}

2. Surface IDs

Include timestamp for uniqueness:

{
  "surfaceId": "form-1704067200000",
  "components": [...]
}

3. State Structure

Organize state logically:

{
  "initialState": {
    "form": {
      "username": "",
      "email": ""
    },
    "ui": {
      "loading": false,
      "error": null
    }
  }
}

4. Error Handling

Always include error states:

{
  "components": [
    {
      "id": "error-display",
      "component": {
        "Text": {
          "text": { "path": "ui.error" },
          "usageHint": "p"
        }
      }
    }
  ]
}

5. Progressive Enhancement

Start simple, add complexity:

// Simple: Just text
{ "Text": { "text": { "literalString": "Status: OK" } } }

// Enhanced: Add status indicator
{
  "Card": {
    "title": { "literalString": "Status" },
    "content": [
      {
        "Text": { "text": { "literalString": "OK" } }
      },
      {
        "Progress": { "value": { "literalNumber": 100 } }
      }
    ]
  }
}

6. Accessibility

  • Use semantic usageHint values (h1-h6, p)
  • Provide labels for inputs
  • Include descriptions for complex interactions

Quick Reference

Action IDs

Use descriptive, action-oriented IDs:

  • submit-form, cancel-action, delete-item
  • refresh-data, load-more, sort-by-date

Binding Paths

Use dot notation for nested paths:

  • user.profile.name
  • items.0.title
  • form.settings.theme

Component Variants

Variant Use Case
primary Main action, important
secondary Alternative action
destructive Dangerous actions (delete)
ghost Subtle, unobtrusive
outline Bordered, less emphasis

Language Values for CLIOutput

Language Use For
bash Shell commands, terminal output
javascript JS/TS code, console logs
python Python code, error traces
text Plain text, no highlighting

Support

For issues, questions, or contributions: