// UI Schema Protocol Implementation. /// /// This file is the single source of truth for UI Schema. /// All implementations must follow docs/protocols/ui-schema.md. /// /// Version: 1.0 library; // ========== Enums ========== enum SchemaType { toolResult('tool_result'), agentResponse('agent_response'), notification('notification'); final String value; const SchemaType(this.value); } enum UiStatus { info('info'), success('success'), warning('warning'), error('error'), pending('pending'); final String value; const UiStatus(this.value); } enum IconSource { icon('icon'), emoji('emoji'), url('url'); final String value; const IconSource(this.value); } enum OperationType { create('create'), update('update'), delete('delete'), execute('execute'); final String value; const OperationType(this.value); } enum OperationResult { success('success'), failure('failure'), partial('partial'); final String value; const OperationResult(this.value); } enum ContainerDirection { vertical('vertical'), horizontal('horizontal'); final String value; const ContainerDirection(this.value); } enum TextFormat { plain('plain'), markdown('markdown'); final String value; const TextFormat(this.value); } enum KvLayout { vertical('vertical'), horizontal('horizontal'), grid('grid'); final String value; const KvLayout(this.value); } enum BadgeVariant { def('default'), success('success'), warning('warning'), error('error'), info('info'); final String value; const BadgeVariant(this.value); } enum ActionStyle { primary('primary'), secondary('secondary'), ghost('ghost'), danger('danger'); final String value; const ActionStyle(this.value); } enum RendererTheme { def('default'), dark('dark'), light('light'); final String value; const RendererTheme(this.value); } // ========== Common Types ========== class UiIcon { final IconSource source; final String value; final String? color; final int? size; const UiIcon({ required this.source, required this.value, this.color, this.size, }); factory UiIcon.fromJson(Map json) { return UiIcon( source: IconSource.values.firstWhere( (e) => e.value == json['source'], orElse: () => IconSource.icon, ), value: json['value'] as String? ?? '', color: json['color'] as String?, size: json['size'] as int?, ); } Map toJson() { return { 'source': source.value, 'value': value, if (color != null) 'color': color, if (size != null) 'size': size, }; } } class UiBadge { final String label; final BadgeVariant variant; const UiBadge({required this.label, this.variant = BadgeVariant.def}); factory UiBadge.fromJson(Map json) { return UiBadge( label: json['label'] as String? ?? '', variant: BadgeVariant.values.firstWhere( (e) => e.value == json['variant'], orElse: () => BadgeVariant.def, ), ); } Map toJson() { return {'label': label, 'variant': variant.value}; } } class Pagination { final int page; final int pageSize; final int total; final bool hasMore; const Pagination({ required this.page, required this.pageSize, required this.total, required this.hasMore, }); factory Pagination.fromJson(Map json) { return Pagination( page: json['page'] as int? ?? 1, pageSize: json['pageSize'] as int? ?? 20, total: json['total'] as int? ?? 0, hasMore: json['hasMore'] as bool? ?? false, ); } Map toJson() { return { 'page': page, 'pageSize': pageSize, 'total': total, 'hasMore': hasMore, }; } } class ActionConfirm { final String? title; final String? message; final String? confirmLabel; final String? cancelLabel; const ActionConfirm({ this.title, this.message, this.confirmLabel, this.cancelLabel, }); factory ActionConfirm.fromJson(Map json) { return ActionConfirm( title: json['title'] as String?, message: json['message'] as String?, confirmLabel: json['confirmLabel'] as String?, cancelLabel: json['cancelLabel'] as String?, ); } Map toJson() { return { if (title != null) 'title': title, if (message != null) 'message': message, if (confirmLabel != null) 'confirmLabel': confirmLabel, if (cancelLabel != null) 'cancelLabel': cancelLabel, }; } } class KeyValuePair { final String key; final String? label; final dynamic value; final bool? copyable; const KeyValuePair({ required this.key, this.label, required this.value, this.copyable, }); factory KeyValuePair.fromJson(Map json) { return KeyValuePair( key: json['key'] as String? ?? '', label: json['label'] as String?, value: json['value'], copyable: json['copyable'] as bool?, ); } Map toJson() { return { 'key': key, if (label != null) 'label': label, 'value': value, if (copyable != null) 'copyable': copyable, }; } } class TableColumn { final String key; final String label; final String? width; final String? align; const TableColumn({ required this.key, required this.label, this.width, this.align, }); factory TableColumn.fromJson(Map json) { return TableColumn( key: json['key'] as String? ?? '', label: json['label'] as String? ?? '', width: json['width'] as String?, align: json['align'] as String?, ); } Map toJson() { return { 'key': key, 'label': label, if (width != null) 'width': width, if (align != null) 'align': align, }; } } class TableRow { final String id; final Map cells; final Map? metadata; final List? actions; const TableRow({ required this.id, required this.cells, this.metadata, this.actions, }); factory TableRow.fromJson(Map json) { return TableRow( id: json['id'] as String? ?? '', cells: json['cells'] as Map? ?? {}, metadata: json['metadata'] as Map?, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'cells': cells, if (metadata != null) 'metadata': metadata, if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class ListItem { final String id; final String title; final String? subtitle; final String? description; final UiIcon? icon; final UiBadge? badge; final Map? metadata; final List? actions; const ListItem({ required this.id, required this.title, this.subtitle, this.description, this.icon, this.badge, this.metadata, this.actions, }); factory ListItem.fromJson(Map json) { return ListItem( id: json['id'] as String? ?? '', title: json['title'] as String? ?? '', subtitle: json['subtitle'] as String?, description: json['description'] as String?, icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, badge: json['badge'] != null ? UiBadge.fromJson(json['badge'] as Map) : null, metadata: json['metadata'] as Map?, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'title': title, if (subtitle != null) 'subtitle': subtitle, if (description != null) 'description': description, if (icon != null) 'icon': icon!.toJson(), if (badge != null) 'badge': badge!.toJson(), if (metadata != null) 'metadata': metadata, if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } // ========== Action Types ========== abstract class ActionSpec { String get type; Map toJson(); } class NavigateAction implements ActionSpec { final String path; final Map? params; const NavigateAction({required this.path, this.params}); @override String get type => 'navigation'; @override Map toJson() { return {'type': type, 'path': path, if (params != null) 'params': params}; } } class LinkAction implements ActionSpec { final String url; final String? target; const LinkAction({required this.url, this.target}); @override String get type => 'url'; @override Map toJson() { return {'type': type, 'url': url, if (target != null) 'target': target}; } } class EventAction implements ActionSpec { final String event; final Map? payload; const EventAction({required this.event, this.payload}); @override String get type => 'event'; @override Map toJson() { return { 'type': type, 'event': event, if (payload != null) 'payload': payload, }; } } class ToolAction implements ActionSpec { final String toolId; final Map? params; const ToolAction({required this.toolId, this.params}); @override String get type => 'tool'; @override Map toJson() { return { 'type': type, 'toolId': toolId, if (params != null) 'params': params, }; } } class CopyAction implements ActionSpec { final String content; final String? successMessage; const CopyAction({required this.content, this.successMessage}); @override String get type => 'copy'; @override Map toJson() { return { 'type': type, 'content': content, if (successMessage != null) 'successMessage': successMessage, }; } } class PayloadAction implements ActionSpec { final Map payload; final String? submitTo; const PayloadAction({required this.payload, this.submitTo}); @override String get type => 'payload'; @override Map toJson() { return { 'type': type, 'payload': payload, if (submitTo != null) 'submitTo': submitTo, }; } } ActionSpec actionSpecFromJson(Map json) { final type = json['type'] as String? ?? ''; switch (type) { case 'navigation': return NavigateAction( path: json['path'] as String? ?? '', params: json['params'] as Map?, ); case 'url': return LinkAction( url: json['url'] as String? ?? '', target: json['target'] as String?, ); case 'event': return EventAction( event: json['event'] as String? ?? '', payload: json['payload'] as Map?, ); case 'tool': return ToolAction( toolId: json['toolId'] as String? ?? '', params: json['params'] as Map?, ); case 'copy': return CopyAction( content: json['content'] as String? ?? '', successMessage: json['successMessage'] as String?, ); case 'payload': return PayloadAction( payload: json['payload'] as Map? ?? {}, submitTo: json['submitTo'] as String?, ); default: return EventAction(event: 'unknown'); } } // ========== Action ========== class UiAction { final String id; final String label; final UiIcon? icon; final ActionStyle? style; final bool disabled; final ActionSpec action; final ActionConfirm? confirm; const UiAction({ required this.id, required this.label, this.icon, this.style, this.disabled = false, required this.action, this.confirm, }); factory UiAction.fromJson(Map json) { return UiAction( id: json['id'] as String? ?? '', label: json['label'] as String? ?? '', icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, style: json['style'] != null ? ActionStyle.values.firstWhere( (e) => e.value == json['style'], orElse: () => ActionStyle.primary, ) : null, disabled: json['disabled'] as bool? ?? false, action: actionSpecFromJson( json['action'] as Map? ?? {'type': 'event'}, ), confirm: json['confirm'] != null ? ActionConfirm.fromJson(json['confirm'] as Map) : null, ); } Map toJson() { return { 'id': id, 'label': label, if (icon != null) 'icon': icon!.toJson(), if (style != null) 'style': style!.value, 'disabled': disabled, 'action': action.toJson(), if (confirm != null) 'confirm': confirm!.toJson(), }; } } // ========== Node Types ========== abstract class UiNode { String get type; String? get id; factory UiNode.fromJson(Map json) { final type = json['type'] as String? ?? ''; switch (type) { case 'text': return UiTextNode.fromJson(json); case 'card': return UiCardNode.fromJson(json); case 'list': return UiListNode.fromJson(json); case 'table': return UiTableNode.fromJson(json); case 'kv': return UiKvNode.fromJson(json); case 'operation': return UiOperationNode.fromJson(json); case 'error': return UiErrorNode.fromJson(json); case 'container': return UiContainerNode.fromJson(json); default: return UiTextNode(content: 'Unknown node type: $type'); } } } class UiTextNode implements UiNode { @override final String? id; @override String get type => 'text'; final String content; final TextFormat format; final UiIcon? icon; final List? actions; const UiTextNode({ this.id, required this.content, this.format = TextFormat.plain, this.icon, this.actions, }); factory UiTextNode.fromJson(Map json) { return UiTextNode( id: json['id'] as String?, content: json['content'] as String? ?? '', format: TextFormat.values.firstWhere( (e) => e.value == json['format'], orElse: () => TextFormat.plain, ), icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, 'content': content, 'format': format.value, if (icon != null) 'icon': icon!.toJson(), if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class UiCardNode implements UiNode { @override final String? id; @override String get type => 'card'; final String? title; final String? description; final UiIcon? icon; final UiStatus? status; final String? timestamp; final List? children; final UiTextNode? footer; final Map? extensions; final List? actions; const UiCardNode({ this.id, this.title, this.description, this.icon, this.status, this.timestamp, this.children, this.footer, this.extensions, this.actions, }); factory UiCardNode.fromJson(Map json) { return UiCardNode( id: json['id'] as String?, title: json['title'] as String?, description: json['description'] as String?, icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, status: json['status'] != null ? UiStatus.values.firstWhere( (e) => e.value == json['status'], orElse: () => UiStatus.info, ) : null, timestamp: json['timestamp'] as String?, children: (json['children'] as List?) ?.map((e) => UiNode.fromJson(e as Map)) .toList(), footer: json['footer'] != null ? UiTextNode.fromJson(json['footer'] as Map) : null, extensions: json['extensions'] as Map?, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, if (title != null) 'title': title, if (description != null) 'description': description, if (icon != null) 'icon': icon!.toJson(), if (status != null) 'status': status!.value, if (timestamp != null) 'timestamp': timestamp, if (children != null) 'children': children!.map((e) => (e as dynamic).toJson()).toList(), if (footer != null) 'footer': footer!.toJson(), if (extensions != null) 'extensions': extensions, if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class UiListNode implements UiNode { @override final String? id; @override String get type => 'list'; final String? title; final String? description; final UiIcon? icon; final UiStatus? status; final List items; final Pagination? pagination; final String? emptyText; final Map? extensions; final List? actions; const UiListNode({ this.id, this.title, this.description, this.icon, this.status, required this.items, this.pagination, this.emptyText, this.extensions, this.actions, }); factory UiListNode.fromJson(Map json) { return UiListNode( id: json['id'] as String?, title: json['title'] as String?, description: json['description'] as String?, icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, status: json['status'] != null ? UiStatus.values.firstWhere( (e) => e.value == json['status'], orElse: () => UiStatus.info, ) : null, items: (json['items'] as List?) ?.map((e) => ListItem.fromJson(e as Map)) .toList() ?? [], pagination: json['pagination'] != null ? Pagination.fromJson(json['pagination'] as Map) : null, emptyText: json['emptyText'] as String?, extensions: json['extensions'] as Map?, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, if (title != null) 'title': title, if (description != null) 'description': description, if (icon != null) 'icon': icon!.toJson(), if (status != null) 'status': status!.value, 'items': items.map((e) => e.toJson()).toList(), if (pagination != null) 'pagination': pagination!.toJson(), if (emptyText != null) 'emptyText': emptyText, if (extensions != null) 'extensions': extensions, if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class UiTableNode implements UiNode { @override final String? id; @override String get type => 'table'; final String? title; final String? description; final UiIcon? icon; final UiStatus? status; final List columns; final List rows; final Pagination? pagination; final Map? extensions; final List? actions; const UiTableNode({ this.id, this.title, this.description, this.icon, this.status, required this.columns, required this.rows, this.pagination, this.extensions, this.actions, }); factory UiTableNode.fromJson(Map json) { return UiTableNode( id: json['id'] as String?, title: json['title'] as String?, description: json['description'] as String?, icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, status: json['status'] != null ? UiStatus.values.firstWhere( (e) => e.value == json['status'], orElse: () => UiStatus.info, ) : null, columns: (json['columns'] as List?) ?.map((e) => TableColumn.fromJson(e as Map)) .toList() ?? [], rows: (json['rows'] as List?) ?.map((e) => TableRow.fromJson(e as Map)) .toList() ?? [], pagination: json['pagination'] != null ? Pagination.fromJson(json['pagination'] as Map) : null, extensions: json['extensions'] as Map?, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, if (title != null) 'title': title, if (description != null) 'description': description, if (icon != null) 'icon': icon!.toJson(), if (status != null) 'status': status!.value, 'columns': columns.map((e) => e.toJson()).toList(), 'rows': rows.map((e) => e.toJson()).toList(), if (pagination != null) 'pagination': pagination!.toJson(), if (extensions != null) 'extensions': extensions, if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class UiKvNode implements UiNode { @override final String? id; @override String get type => 'kv'; final String? title; final String? description; final UiIcon? icon; final UiStatus? status; final List pairs; final KvLayout layout; final Map? extensions; final List? actions; const UiKvNode({ this.id, this.title, this.description, this.icon, this.status, required this.pairs, this.layout = KvLayout.vertical, this.extensions, this.actions, }); factory UiKvNode.fromJson(Map json) { return UiKvNode( id: json['id'] as String?, title: json['title'] as String?, description: json['description'] as String?, icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, status: json['status'] != null ? UiStatus.values.firstWhere( (e) => e.value == json['status'], orElse: () => UiStatus.info, ) : null, pairs: (json['pairs'] as List?) ?.map((e) => KeyValuePair.fromJson(e as Map)) .toList() ?? [], layout: KvLayout.values.firstWhere( (e) => e.value == json['layout'], orElse: () => KvLayout.vertical, ), extensions: json['extensions'] as Map?, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, if (title != null) 'title': title, if (description != null) 'description': description, if (icon != null) 'icon': icon!.toJson(), if (status != null) 'status': status!.value, 'pairs': pairs.map((e) => e.toJson()).toList(), 'layout': layout.value, if (extensions != null) 'extensions': extensions, if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class UiOperationNode implements UiNode { @override final String? id; @override String get type => 'operation'; final String? title; final String? description; final UiIcon? icon; final UiStatus? status; final OperationType operation; final OperationResult result; final String? message; final int? affectedCount; final UiNode? details; final UiAction? rollback; final Map? extensions; final List? actions; const UiOperationNode({ this.id, this.title, this.description, this.icon, this.status, required this.operation, required this.result, this.message, this.affectedCount, this.details, this.rollback, this.extensions, this.actions, }); factory UiOperationNode.fromJson(Map json) { return UiOperationNode( id: json['id'] as String?, title: json['title'] as String?, description: json['description'] as String?, icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, status: json['status'] != null ? UiStatus.values.firstWhere( (e) => e.value == json['status'], orElse: () => UiStatus.info, ) : null, operation: OperationType.values.firstWhere( (e) => e.value == json['operation'], orElse: () => OperationType.execute, ), result: OperationResult.values.firstWhere( (e) => e.value == json['result'], orElse: () => OperationResult.failure, ), message: json['message'] as String?, affectedCount: json['affectedCount'] as int?, details: json['details'] != null ? UiNode.fromJson(json['details'] as Map) : null, rollback: json['rollback'] != null ? UiAction.fromJson(json['rollback'] as Map) : null, extensions: json['extensions'] as Map?, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, if (title != null) 'title': title, if (description != null) 'description': description, if (icon != null) 'icon': icon!.toJson(), if (status != null) 'status': status!.value, 'operation': operation.value, 'result': result.value, if (message != null) 'message': message, if (affectedCount != null) 'affectedCount': affectedCount, if (details != null) 'details': (details as dynamic).toJson(), if (rollback != null) 'rollback': rollback!.toJson(), if (extensions != null) 'extensions': extensions, if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class UiErrorNode implements UiNode { @override final String? id; @override String get type => 'error'; final String? title; final UiIcon? icon; final String errorCode; final String message; final String? details; final String? stack; final bool retryable; final List? suggestions; final UiAction? retry; final UiAction? support; final List? actions; const UiErrorNode({ this.id, this.title, this.icon, required this.errorCode, required this.message, this.details, this.stack, this.retryable = false, this.suggestions, this.retry, this.support, this.actions, }); factory UiErrorNode.fromJson(Map json) { return UiErrorNode( id: json['id'] as String?, title: json['title'] as String?, icon: json['icon'] != null ? UiIcon.fromJson(json['icon'] as Map) : null, errorCode: json['errorCode'] as String? ?? 'UNKNOWN', message: json['message'] as String? ?? 'An error occurred', details: json['details'] as String?, stack: json['stack'] as String?, retryable: json['retryable'] as bool? ?? false, suggestions: (json['suggestions'] as List?) ?.map((e) => e as String) .toList(), retry: json['retry'] != null ? UiAction.fromJson(json['retry'] as Map) : null, support: json['support'] != null ? UiAction.fromJson(json['support'] as Map) : null, actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, if (title != null) 'title': title, if (icon != null) 'icon': icon!.toJson(), 'errorCode': errorCode, 'message': message, if (details != null) 'details': details, if (stack != null) 'stack': stack, 'retryable': retryable, if (suggestions != null) 'suggestions': suggestions, if (retry != null) 'retry': retry!.toJson(), if (support != null) 'support': support!.toJson(), if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } class UiContainerNode implements UiNode { @override final String? id; @override String get type => 'container'; final ContainerDirection direction; final int? gap; final List children; final List? actions; const UiContainerNode({ this.id, this.direction = ContainerDirection.vertical, this.gap, required this.children, this.actions, }); factory UiContainerNode.fromJson(Map json) { return UiContainerNode( id: json['id'] as String?, direction: ContainerDirection.values.firstWhere( (e) => e.value == json['direction'], orElse: () => ContainerDirection.vertical, ), gap: json['gap'] as int?, children: (json['children'] as List?) ?.map((e) => UiNode.fromJson(e as Map)) .toList() ?? [], actions: (json['actions'] as List?) ?.map((e) => UiAction.fromJson(e as Map)) .toList(), ); } Map toJson() { return { 'id': id, 'type': type, 'direction': direction.value, if (gap != null) 'gap': gap, 'children': children.map((e) => (e as dynamic).toJson()).toList(), if (actions != null) 'actions': actions!.map((e) => e.toJson()).toList(), }; } } // ========== Document Types ========== class RendererConfig { final String? renderer; final RendererTheme? theme; const RendererConfig({this.renderer, this.theme}); factory RendererConfig.fromJson(Map json) { return RendererConfig( renderer: json['renderer'] as String?, theme: json['theme'] != null ? RendererTheme.values.firstWhere( (e) => e.value == json['theme'], orElse: () => RendererTheme.def, ) : null, ); } Map toJson() { return { if (renderer != null) 'renderer': renderer, if (theme != null) 'theme': theme!.value, }; } } class DocumentMeta { final String? requestId; final String? toolId; final String? traceId; final String? userId; final Map? extra; const DocumentMeta({ this.requestId, this.toolId, this.traceId, this.userId, this.extra, }); factory DocumentMeta.fromJson(Map json) { return DocumentMeta( requestId: json['requestId'] as String?, toolId: json['toolId'] as String?, traceId: json['traceId'] as String?, userId: json['userId'] as String?, extra: json, ); } Map toJson() { return { if (requestId != null) 'requestId': requestId, if (toolId != null) 'toolId': toolId, if (traceId != null) 'traceId': traceId, if (userId != null) 'userId': userId, }; } } class UiSchemaDocument { final String version; final SchemaType schemaType; final String? docId; final String? timestamp; final String? locale; final UiStatus status; final RendererConfig? renderer; final DocumentMeta? meta; final List nodes; const UiSchemaDocument({ required this.version, required this.schemaType, this.docId, this.timestamp, this.locale, required this.status, this.renderer, this.meta, required this.nodes, }); factory UiSchemaDocument.fromJson(Map json) { return UiSchemaDocument( version: json['version'] as String? ?? '1.0', schemaType: SchemaType.values.firstWhere( (e) => e.value == json['schemaType'], orElse: () => SchemaType.toolResult, ), docId: json['docId'] as String?, timestamp: json['timestamp'] as String?, locale: json['locale'] as String?, status: UiStatus.values.firstWhere( (e) => e.value == json['status'], orElse: () => UiStatus.info, ), renderer: json['renderer'] != null ? RendererConfig.fromJson(json['renderer'] as Map) : null, meta: json['meta'] != null ? DocumentMeta.fromJson(json['meta'] as Map) : null, nodes: (json['nodes'] as List?) ?.map((e) => UiNode.fromJson(e as Map)) .toList() ?? [], ); } Map toJson() { return { 'version': version, 'schemaType': schemaType.value, if (docId != null) 'docId': docId, if (timestamp != null) 'timestamp': timestamp, if (locale != null) 'locale': locale, 'status': status.value, if (renderer != null) 'renderer': renderer!.toJson(), if (meta != null) 'meta': meta!.toJson(), 'nodes': nodes.map((e) => (e as dynamic).toJson()).toList(), }; } } // ========== Builder Functions ========== UiSchemaDocument buildSuccessDocument( List nodes, { String version = '1.0', SchemaType schemaType = SchemaType.toolResult, String? docId, String? timestamp, String locale = 'zh-CN', RendererConfig? renderer, DocumentMeta? meta, }) { return UiSchemaDocument( version: version, schemaType: schemaType, docId: docId, timestamp: timestamp, locale: locale, status: UiStatus.success, renderer: renderer, meta: meta, nodes: nodes, ); } UiSchemaDocument buildErrorDocument( List nodes, { String version = '1.0', SchemaType schemaType = SchemaType.toolResult, String? docId, String? timestamp, String locale = 'zh-CN', RendererConfig? renderer, DocumentMeta? meta, }) { return UiSchemaDocument( version: version, schemaType: schemaType, docId: docId, timestamp: timestamp, locale: locale, status: UiStatus.error, renderer: renderer, meta: meta, nodes: nodes, ); }