# UI Schema Protocol > **NOTE**: This document is the single source of truth. All implementations must follow this specification. ## Overview A generic UI schema for rendering tool/agent execution results. Designed for AI Agent / Tool ecosystem with extensibility. **Design Philosophy**: Keep only "primitive components + layout containers". Frontend only needs to recursively render the root layout tree. ## Version - **Current**: `2.0` - **Status**: Active ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ UiSchemaRenderer (root) │ │ - version / locale / status / theme │ │ - meta (protocol-level metadata) │ │ - root (UiLayoutNode - stack or grid) │ ├─────────────────────────────────────────────────────────────┤ │ Rendering Flow: │ │ 1. Backend returns UiSchemaRenderer with root layout │ │ 2. Frontend recursively renders root layout tree │ │ 3. Layout nodes (stack/grid) contain children │ │ 4. Primitive nodes (text/icon/button/etc) are leaves │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Data Types ### UiStatus ```typescript type UiStatus = 'info' | 'success' | 'warning' | 'error' | 'pending'; ``` ### IconSource ```typescript type IconSource = 'icon' | 'emoji' | 'url'; ``` ### TextFormat ```typescript type TextFormat = 'plain' | 'markdown'; ``` ### TextRole ```typescript type TextRole = 'title' | 'subtitle' | 'body' | 'caption' | 'code'; ``` ### ButtonStyle ```typescript type ButtonStyle = 'primary' | 'secondary' | 'ghost' | 'danger'; ``` ### LayoutDirection ```typescript type LayoutDirection = 'vertical' | 'horizontal'; ``` ### LayoutAppearance ```typescript type LayoutAppearance = 'plain' | 'card' | 'section'; ``` ### LayoutAlign ```typescript type LayoutAlign = 'start' | 'center' | 'end' | 'stretch'; ``` ### LayoutJustify ```typescript type LayoutJustify = 'start' | 'center' | 'end' | 'space-between'; ``` ### RendererTheme ```typescript type RendererTheme = 'default' | 'light' | 'dark'; ``` --- ## Root Structure ```typescript interface UiSchemaRenderer { version: string; // "2.0" locale: string; // "zh-CN" status: UiStatus; theme: RendererTheme; // Protocol-level metadata (not rendered) meta?: { requestId?: string; toolId?: string; traceId?: string; userId?: string; }; // Root layout node root: UiLayoutNode; } ``` --- ## Node Types ### Primitive Components #### 1. Text Node ```typescript interface UiTextNode extends UiBaseNode { type: 'text'; content: string; format: TextFormat; // 'plain' | 'markdown' role: TextRole; // 'title' | 'subtitle' | 'body' | 'caption' | 'code' status?: UiStatus; maxLines?: number; visible?: boolean; } ``` #### 2. Icon Node ```typescript interface UiIconNode extends UiBaseNode { type: 'icon'; source: IconSource; // 'icon' | 'emoji' | 'url' value: string; color?: string; size?: number; visible?: boolean; } ``` #### 3. Badge Node ```typescript interface UiBadgeNode extends UiBaseNode { type: 'badge'; label: string; status: UiStatus; visible?: boolean; } ``` #### 4. Button Node ```typescript interface UiButtonNode extends UiBaseNode { type: 'button'; label: string; style: ButtonStyle; disabled?: boolean; icon?: UiIconSpec; action: UiActionPayload; visible?: boolean; } ``` #### 5. Key-Value Node ```typescript interface UiKvNode extends UiBaseNode { type: 'kv'; items: UiKvItem[]; columns?: number; visible?: boolean; } interface UiKvItem { key: string; label?: string; value: any; copyable?: boolean; } ``` #### 6. Divider Node ```typescript interface UiDividerNode extends UiBaseNode { type: 'divider'; inset?: number; visible?: boolean; } ``` ### Layout Containers #### 7. Stack Node ```typescript interface UiStackNode extends UiBaseNode { type: 'stack'; direction: LayoutDirection; // 'vertical' | 'horizontal' gap?: number; appearance: LayoutAppearance; // 'plain' | 'card' | 'section' status?: UiStatus; align?: LayoutAlign; justify?: LayoutJustify; wrap?: boolean; children: UiNode[]; visible?: boolean; } ``` #### 8. Grid Node ```typescript interface UiGridNode extends UiBaseNode { type: 'grid'; columns: number; gap?: number; appearance: LayoutAppearance; status?: UiStatus; children: UiNode[]; visible?: boolean; } ``` ### Base Node ```typescript interface UiBaseNode { id?: string; visible?: boolean; } ``` ### Node Union ```typescript type UiNode = | UiTextNode | UiIconNode | UiBadgeNode | UiButtonNode | UiKvNode | UiDividerNode | UiStackNode | UiGridNode; type UiLayoutNode = UiStackNode | UiGridNode; ``` --- ## Action Payloads ```typescript type UiActionPayload = | NavigateAction | UrlAction | EventAction | ToolAction | CopyAction | PayloadAction; // Navigation action interface NavigateAction { type: 'navigation'; path: string; params?: Record; } // Navigation Contract (current implementation constraint) // 1) path MUST be an internal app route and MUST be fully materialized // (e.g. '/todo/123', not '/todo/:id'). // 2) path MUST NOT include query string or fragment. // 3) params, when provided, is treated as query params only. // 4) params values MUST be scalar (string | number | boolean). // 5) Backend MUST generate path from route catalog // `backend/src/core/config/static/route/frontend_routes.yaml`. // URL action interface UrlAction { type: 'url'; url: string; target?: '_self' | '_blank'; } // Event action interface EventAction { type: 'event'; event: string; payload?: Record; } // Tool action interface ToolAction { type: 'tool'; toolId: string; params?: Record; } // Copy action interface CopyAction { type: 'copy'; content: string; successMessage?: string; } // Payload action interface PayloadAction { type: 'payload'; payload: Record; submitTo?: string; } ``` --- ## Icon Specification ```typescript interface UiIconSpec { source: IconSource; value: string; color?: string; size?: number; } ``` --- ## Usage Patterns--- ## JSON Examples ### Example 1: Simple Text ```json { "version": "2.0", "locale": "zh-CN", "status": "success", "theme": "default", "root": { "type": "stack", "direction": "vertical", "gap": 12, "appearance": "plain", "children": [ { "type": "text", "content": "操作成功", "role": "title" }, { "type": "text", "content": "您的事项已创建完成", "role": "body" } ] } } ``` ### Example 2: Card with Actions ```json { "version": "2.0", "locale": "zh-CN", "status": "success", "theme": "default", "root": { "type": "stack", "direction": "vertical", "gap": 16, "appearance": "card", "children": [ { "type": "text", "content": "日程已创建", "role": "title" }, { "type": "kv", "items": [ { "key": "title", "label": "主题", "value": "Q1 规划会议" }, { "key": "time", "label": "时间", "value": "2026-03-15 14:00" } ], "columns": 2 }, { "type": "stack", "direction": "horizontal", "gap": 8, "children": [ { "type": "button", "label": "查看详情", "style": "primary", "action": { "type": "navigation", "path": "/calendar/evt_abc123" } }, { "type": "button", "label": "删除", "style": "danger", "action": { "type": "tool", "toolId": "calendar.delete", "params": { "eventId": "evt_abc123" } } } ] } ] } } ``` ### Example 3: Error Status Panel ```json { "version": "2.0", "locale": "zh-CN", "status": "error", "theme": "default", "root": { "type": "stack", "direction": "vertical", "gap": 12, "appearance": "card", "status": "error", "children": [ { "type": "stack", "direction": "horizontal", "gap": 8, "align": "center", "justify": "space-between", "children": [ { "type": "text", "content": "删除失败", "role": "title" }, { "type": "badge", "label": "ERROR", "status": "error" } ] }, { "type": "text", "content": "您没有权限执行此操作", "role": "body", "status": "error" }, { "type": "stack", "direction": "horizontal", "gap": 8, "children": [ { "type": "button", "label": "重试", "style": "primary", "action": { "type": "tool", "toolId": "user.delete", "params": { "userId": "u1" } } }, { "type": "button", "label": "联系管理员", "style": "secondary", "action": { "type": "url", "url": "mailto:admin@example.com" } } ] } ] } } ``` ### Example 4: Grid Layout ```json { "version": "2.0", "locale": "zh-CN", "status": "info", "theme": "default", "root": { "type": "grid", "columns": 3, "gap": 16, "appearance": "plain", "children": [ { "type": "card", "children": [ { "type": "text", "content": "今日订单", "role": "title" }, { "type": "text", "content": "128", "role": "subtitle" } ] }, { "type": "card", "children": [ { "type": "text", "content": "待处理", "role": "title" }, { "type": "text", "content": "24", "role": "subtitle", "status": "warning" } ] }, { "type": "card", "children": [ { "type": "text", "content": "总收入", "role": "title" }, { "type": "text", "content": "¥8,640", "role": "subtitle", "status": "success" } ] } ] } } ``` ### Example 5: Section Layout ```json { "version": "2.0", "locale": "zh-CN", "status": "success", "theme": "default", "root": { "type": "stack", "direction": "vertical", "gap": 24, "appearance": "plain", "children": [ { "type": "stack", "direction": "vertical", "gap": 12, "appearance": "section", "children": [ { "type": "text", "content": "基本信息", "role": "title" }, { "type": "kv", "items": [ { "key": "name", "label": "姓名", "value": "张三" }, { "key": "phone", "label": "手机号", "value": "+8613812345678", "copyable": true } ]} ] }, { "type": "stack", "direction": "vertical", "gap": 12, "appearance": "section", "children": [ { "type": "text", "content": "账户设置", "role": "title" }, { "type": "button", "label": "修改密码", "style": "secondary", "action": { "type": "navigation", "path": "/settings/password" } }, { "type": "button", "label": "退出登录", "style": "ghost", "action": { "type": "event", "event": "logout" } } ] } ] } } ``` --- --- ## UiHints (Descriptive UI) ### Overview UiHints is a **descriptive** UI representation designed for AI agents to express UI intent with minimal token cost. It describes **what to show**, not **how to render**. The `ui_compiler` transforms UiHints into UiSchemaRenderer for frontend rendering. ### Design Principles 1. **Descriptive not Rendered**: Express content intent, not visual instructions 2. **Minimal Token Cost**: Simple structure, semantic field names 3. **Composable**: Supports nested sections and mixed content 4. **Compilable**: Mechanical transformation to UiSchemaRenderer 5. **Lossless**: Main content fields in hints are preserved in renderer ### Intent Types Intent is a **weak hint** - it only affects default layout style, not field presence. | Intent | Default Layout | |--------|----------------| | `message` | plain | | `data` | card | | `list` | plain | | `status` | card | | `form` | section | | `mixed` | card | ### UiHints Payload ```typescript interface UiHintsPayload { version: string; // "2.1" intent: UiHintIntent; // Primary display intent (weak hint) status: UiStatus; // Overall status title?: string; // Top-level title description?: string; // Top-level description body?: string; // Top-level main body text bodyFormat?: "plain" | "markdown"; // Body text format items?: UiHintKvItem[]; // Top-level key-value items listItems?: UiHintListItem[]; // Top-level list items sections?: UiHintSection[]; // Grouped sections actions?: UiHintAction[]; // Top-level actions icon?: UiHintIcon; // Top-level icon meta?: Record; // Extra meta } interface UiHintSection { title?: string; description?: string; icon?: UiHintIcon; content?: string; contentFormat?: "plain" | "markdown"; items?: UiHintKvItem[]; listItems?: UiHintListItem[]; actions?: UiHintAction[]; } interface UiHintListItem { id?: string; title: string; subtitle?: string; description?: string; icon?: UiHintIcon; status?: UiHintStatus; actions?: UiHintAction[]; } interface UiHintKvItem { key: string; label?: string; value?: any; copyable?: boolean; } interface UiHintAction { label: string; style?: "primary" | "secondary" | "ghost" | "danger"; disabled?: boolean; action: UiHintActionTarget; } type UiHintActionTarget = | { type: "navigation"; path: string; params?: Record } | { type: "url"; url: string; target?: "_self" | "_blank" } | { type: "event"; event: string; payload?: Record } | { type: "tool"; toolId: string; params?: Record } | { type: "copy"; content: string; successMessage?: string } | { type: "payload"; payload: Record; submitTo?: string }; interface UiHintIcon { source: "icon" | "emoji" | "url"; value: string; color?: string; size?: number; } ``` ### Compilation Flow ``` Agent Output (UiHints 2.1) │ ▼ ui_compiler │ ▼ UiSchemaRenderer (for frontend rendering) ``` ### Example **Input (UiHints)**: ```json { "intent": "status", "status": "success", "title": "日程已创建", "body": "本次创建已成功完成。", "items": [ {"key": "title", "label": "主题", "value": "Q1 规划会议"}, {"key": "time", "label": "时间", "value": "2026-03-15 14:00"} ], "actions": [ {"label": "查看详情", "style": "primary", "action": {"type": "navigation", "path": "/calendar/evt_123"}}, {"label": "删除", "style": "danger", "action": {"type": "tool", "toolId": "calendar.delete", "params": {"eventId": "evt_123"}}} ] } ``` **Output (UiSchemaRenderer)**: ```json { "version": "2.0", "locale": "zh-CN", "status": "success", "theme": "default", "root": { "type": "stack", "appearance": "card", "status": "success", "children": [ { "type": "stack", "direction": "horizontal", "gap": 8, "children": [ {"type": "text", "content": "日程已创建", "role": "title"}, {"type": "badge", "label": "SUCCESS", "status": "success"} ], "justify": "space-between", "align": "center" }, {"type": "text", "content": "本次创建已成功完成。", "role": "body"}, {"type": "kv", "items": [...]}, { "type": "stack", "direction": "horizontal", "gap": 8, "children": [ {"type": "button", "label": "查看详情", "style": "primary", ...}, {"type": "button", "label": "删除", "style": "danger", ...} ] } ] } } ``` ### Python Implementation - `schemas.agent.ui_hints.UiHintsPayload` - Descriptive UI model (v2.1) - `core.agentscope.runtime.ui_compiler.compile(hints)` - Compile to UiSchemaRenderer