1use serde::{Deserialize, Serialize};
6
7use crate::catalog::Component;
8
9pub const PROTOCOL_VERSION: &str = "v0.10";
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
18pub struct ServerMessage {
19 pub version: String,
21 #[serde(flatten)]
23 pub content: ServerMessageContent,
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
28#[serde(rename_all = "camelCase")]
29pub enum ServerMessageContent {
30 CreateSurface(CreateSurface),
32 UpdateComponents(UpdateComponents),
34 UpdateDataModel(UpdateDataModel),
36 DeleteSurface(DeleteSurface),
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
42#[serde(rename_all = "camelCase")]
43pub struct CreateSurface {
44 pub surface_id: String,
46 pub catalog_id: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub theme: Option<Theme>,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub send_data_model: Option<bool>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
58#[serde(rename_all = "camelCase")]
59pub struct Theme {
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub primary_color: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub icon_url: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub agent_display_name: Option<String>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
73#[serde(rename_all = "camelCase")]
74pub struct UpdateComponents {
75 pub surface_id: String,
77 pub components: Vec<Component>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
83#[serde(rename_all = "camelCase")]
84pub struct UpdateDataModel {
85 pub surface_id: String,
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub path: Option<String>,
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub value: Option<serde_json::Value>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
97#[serde(rename_all = "camelCase")]
98pub struct DeleteSurface {
99 pub surface_id: String,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
109pub struct ClientMessage {
110 pub version: String,
112 #[serde(flatten)]
114 pub content: ClientMessageContent,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
119#[serde(rename_all = "camelCase")]
120pub enum ClientMessageContent {
121 Action(ActionMessage),
123 Error(ErrorMessage),
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129#[serde(rename_all = "camelCase")]
130pub struct ActionMessage {
131 pub name: String,
133 pub surface_id: String,
135 pub source_component_id: String,
137 pub timestamp: String,
139 pub context: serde_json::Map<String, serde_json::Value>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
145#[serde(rename_all = "camelCase")]
146pub struct ErrorMessage {
147 pub code: ErrorCode,
149 pub surface_id: String,
151 pub message: String,
153 #[serde(skip_serializing_if = "Option::is_none")]
155 pub path: Option<String>,
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
160pub enum ErrorCode {
161 #[serde(rename = "VALIDATION_FAILED")]
163 ValidationFailed,
164 #[serde(other)]
166 Other,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
171#[serde(rename_all = "camelCase")]
172pub struct ClientCapabilities {
173 pub supported_catalog_ids: Vec<String>,
175 #[serde(skip_serializing_if = "Option::is_none")]
177 pub inline_catalogs: Option<Vec<Catalog>>,
178}
179
180#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
182#[serde(rename_all = "camelCase")]
183pub struct Catalog {
184 pub catalog_id: String,
186 #[serde(skip_serializing_if = "Option::is_none")]
188 pub components: Option<serde_json::Map<String, serde_json::Value>>,
189 #[serde(skip_serializing_if = "Option::is_none")]
191 pub functions: Option<Vec<FunctionDefinition>>,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub theme: Option<serde_json::Map<String, serde_json::Value>>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
199#[serde(rename_all = "camelCase")]
200pub struct FunctionDefinition {
201 pub name: String,
203 #[serde(skip_serializing_if = "Option::is_none")]
205 pub description: Option<String>,
206 pub parameters: serde_json::Value,
208 pub return_type: String,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
214#[serde(rename_all = "camelCase")]
215pub struct ClientDataModel {
216 pub version: String,
218 pub surfaces: std::collections::HashMap<String, serde_json::Value>,
220}
221
222impl ServerMessage {
227 pub fn new(content: ServerMessageContent) -> Self {
229 Self {
230 version: PROTOCOL_VERSION.to_string(),
231 content,
232 }
233 }
234
235 pub fn create_surface(surface_id: &str, catalog_id: &str) -> Self {
237 Self::new(ServerMessageContent::CreateSurface(CreateSurface {
238 surface_id: surface_id.to_string(),
239 catalog_id: catalog_id.to_string(),
240 theme: None,
241 send_data_model: None,
242 }))
243 }
244
245 pub fn update_components(surface_id: &str, components: Vec<Component>) -> Self {
247 Self::new(ServerMessageContent::UpdateComponents(UpdateComponents {
248 surface_id: surface_id.to_string(),
249 components,
250 }))
251 }
252
253 pub fn update_data_model(surface_id: &str, value: serde_json::Value) -> Self {
255 Self::new(ServerMessageContent::UpdateDataModel(UpdateDataModel {
256 surface_id: surface_id.to_string(),
257 path: None,
258 value: Some(value),
259 }))
260 }
261
262 pub fn delete_surface(surface_id: &str) -> Self {
264 Self::new(ServerMessageContent::DeleteSurface(DeleteSurface {
265 surface_id: surface_id.to_string(),
266 }))
267 }
268}
269
270impl ClientMessage {
271 pub fn new(content: ClientMessageContent) -> Self {
273 Self {
274 version: PROTOCOL_VERSION.to_string(),
275 content,
276 }
277 }
278
279 pub fn action(
281 surface_id: &str,
282 name: &str,
283 source_component_id: &str,
284 context: serde_json::Map<String, serde_json::Value>,
285 ) -> Self {
286 Self::new(ClientMessageContent::Action(ActionMessage {
287 name: name.to_string(),
288 surface_id: surface_id.to_string(),
289 source_component_id: source_component_id.to_string(),
290 timestamp: chrono::Utc::now().to_rfc3339(),
291 context,
292 }))
293 }
294
295 pub fn validation_error(surface_id: &str, path: &str, message: &str) -> Self {
297 Self::new(ClientMessageContent::Error(ErrorMessage {
298 code: ErrorCode::ValidationFailed,
299 surface_id: surface_id.to_string(),
300 message: message.to_string(),
301 path: Some(path.to_string()),
302 }))
303 }
304
305 pub fn error(surface_id: &str, message: &str) -> Self {
307 Self::new(ClientMessageContent::Error(ErrorMessage {
308 code: ErrorCode::Other,
309 surface_id: surface_id.to_string(),
310 message: message.to_string(),
311 path: None,
312 }))
313 }
314}
315
316impl ClientCapabilities {
317 pub fn new(supported_catalog_ids: Vec<String>) -> Self {
319 Self {
320 supported_catalog_ids,
321 inline_catalogs: None,
322 }
323 }
324
325 pub fn with_inline_catalog(mut self, catalog: Catalog) -> Self {
327 self.inline_catalogs
328 .get_or_insert_with(Vec::new)
329 .push(catalog);
330 self
331 }
332}
333
334impl ClientDataModel {
335 pub fn new() -> Self {
337 Self {
338 version: PROTOCOL_VERSION.to_string(),
339 surfaces: std::collections::HashMap::new(),
340 }
341 }
342
343 pub fn with_surface(mut self, surface_id: &str, data: serde_json::Value) -> Self {
345 self.surfaces.insert(surface_id.to_string(), data);
346 self
347 }
348}
349
350impl Default for ClientDataModel {
351 fn default() -> Self {
352 Self::new()
353 }
354}