adk_ui/interop/
surface.rs1use crate::a2ui::{
2 A2uiMessage, CreateSurface, CreateSurfaceMessage, UpdateComponents, UpdateComponentsMessage,
3 UpdateDataModel, UpdateDataModelMessage, encode_jsonl,
4};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
11#[serde(rename_all = "snake_case")]
12pub enum UiProtocol {
13 #[default]
14 A2ui,
15 AgUi,
16 McpApps,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct UiSurface {
23 pub surface_id: String,
24 pub catalog_id: String,
25 pub components: Vec<Value>,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub data_model: Option<Value>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub theme: Option<Value>,
30 pub send_data_model: bool,
31}
32
33impl UiSurface {
34 pub fn new(
35 surface_id: impl Into<String>,
36 catalog_id: impl Into<String>,
37 components: Vec<Value>,
38 ) -> Self {
39 Self {
40 surface_id: surface_id.into(),
41 catalog_id: catalog_id.into(),
42 components,
43 data_model: None,
44 theme: None,
45 send_data_model: true,
46 }
47 }
48
49 pub fn with_data_model(mut self, data_model: Option<Value>) -> Self {
50 self.data_model = data_model;
51 self
52 }
53
54 pub fn with_theme(mut self, theme: Option<Value>) -> Self {
55 self.theme = theme;
56 self
57 }
58
59 pub fn with_send_data_model(mut self, send_data_model: bool) -> Self {
60 self.send_data_model = send_data_model;
61 self
62 }
63
64 pub fn to_a2ui_messages(&self) -> Vec<A2uiMessage> {
65 let mut messages = vec![A2uiMessage::CreateSurface(CreateSurfaceMessage {
66 create_surface: CreateSurface {
67 surface_id: self.surface_id.clone(),
68 catalog_id: self.catalog_id.clone(),
69 theme: self.theme.clone(),
70 send_data_model: Some(self.send_data_model),
71 },
72 })];
73
74 if let Some(data_model) = self.data_model.clone() {
75 messages.push(A2uiMessage::UpdateDataModel(UpdateDataModelMessage {
76 update_data_model: UpdateDataModel {
77 surface_id: self.surface_id.clone(),
78 path: Some("/".to_string()),
79 value: Some(data_model),
80 },
81 }));
82 }
83
84 messages.push(A2uiMessage::UpdateComponents(UpdateComponentsMessage {
85 update_components: UpdateComponents {
86 surface_id: self.surface_id.clone(),
87 components: self.components.clone(),
88 },
89 }));
90
91 messages
92 }
93
94 pub fn to_a2ui_jsonl(&self) -> Result<String, serde_json::Error> {
95 encode_jsonl(self.to_a2ui_messages())
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use serde_json::json;
103
104 #[test]
105 fn surface_to_a2ui_messages_emits_expected_order() {
106 let surface = UiSurface::new(
107 "main",
108 "catalog",
109 vec![json!({"id":"root","component":{"Column":{"children":[]}}})],
110 )
111 .with_data_model(Some(json!({"ok": true})));
112
113 let messages = surface.to_a2ui_messages();
114 assert_eq!(messages.len(), 3);
115
116 let first = serde_json::to_value(&messages[0]).unwrap();
117 let second = serde_json::to_value(&messages[1]).unwrap();
118 let third = serde_json::to_value(&messages[2]).unwrap();
119
120 assert!(first.get("createSurface").is_some());
121 assert!(second.get("updateDataModel").is_some());
122 assert!(third.get("updateComponents").is_some());
123 }
124
125 #[test]
126 fn surface_to_a2ui_jsonl_serializes() {
127 let surface = UiSurface::new(
128 "main",
129 "catalog",
130 vec![json!({"id":"root","component":{"Column":{"children":[]}}})],
131 );
132 let jsonl = surface.to_a2ui_jsonl().unwrap();
133 assert!(jsonl.contains("createSurface"));
134 assert!(jsonl.contains("updateComponents"));
135 }
136}