distri_types/
a2a_converters.rs

1use distri_a2a::{
2    DataPart, EventKind, FileObject, FilePart, Message, Part, Role, Task, TaskState, TaskStatus,
3    TextPart,
4};
5
6use browsr_types::FileType;
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10use crate::AgentError;
11
12#[derive(Debug, Serialize, Deserialize, Clone)]
13#[serde(rename_all = "snake_case")]
14pub enum MessageMetadata {
15    Text,
16    Plan,
17    ToolCall,
18    ToolResult,
19}
20
21impl From<crate::Message> for MessageMetadata {
22    fn from(message: crate::Message) -> Self {
23        for part in message.parts.iter() {
24            match part {
25                crate::Part::ToolCall(_) => return MessageMetadata::ToolCall,
26                crate::Part::ToolResult(_) => return MessageMetadata::ToolResult,
27                _ => continue,
28            }
29        }
30        MessageMetadata::Text
31    }
32}
33
34impl TryFrom<Message> for crate::Message {
35    type Error = AgentError;
36
37    fn try_from(message: Message) -> Result<Self, Self::Error> {
38        let mut parts = Vec::new();
39        for part in message.parts {
40            match part {
41                Part::Text(t) => parts.push(crate::Part::Text(t.text.clone())),
42                Part::Data(d) => {
43                    if let Some(part_type) = d.data.get("part_type").and_then(|v| v.as_str()) {
44                        if let Some(data_content) = d.data.get("data") {
45                            // Create the properly structured object for
46
47                            let structured = json!({
48                                "part_type": part_type,
49                                "data": data_content
50                            });
51
52                            let part: crate::Part = serde_json::from_value(structured)?;
53                            parts.push(part);
54                        } else {
55                            return Err(AgentError::Validation(
56                                "Missing data 
57                field for typed part"
58                                    .to_string(),
59                            ));
60                        }
61                    } else {
62                        return Err(AgentError::Validation(
63                            "Invalid part 
64                type"
65                                .to_string(),
66                        ));
67                    }
68                }
69                Part::File(f) => {
70                    let mime_type = f.mime_type();
71                    if let Some(mime_type) = mime_type {
72                        if mime_type.starts_with("image/") {
73                            let ft = file_object_to_filetype(f.file.clone());
74                            parts.push(crate::Part::Image(ft));
75                        } else {
76                            return Err(AgentError::UnsupportedFileType(mime_type.to_string()));
77                        }
78                    } else {
79                        return Err(AgentError::UnsupportedFileType("unknown".to_string()));
80                    }
81                }
82            }
83        }
84
85        let is_tool = parts.iter().any(|part| {
86            if let crate::Part::ToolResult(_) = part {
87                return true;
88            }
89            false
90        });
91
92        Ok(crate::Message {
93            id: message.message_id.clone(),
94            role: if is_tool {
95                crate::MessageRole::Tool
96            } else {
97                match message.role {
98                    Role::User => crate::MessageRole::User,
99                    Role::Agent => crate::MessageRole::Assistant,
100                }
101            },
102            name: None,
103            parts,
104            ..Default::default()
105        })
106    }
107}
108
109impl From<crate::TaskStatus> for TaskState {
110    fn from(status: crate::TaskStatus) -> Self {
111        match status {
112            crate::TaskStatus::Pending => TaskState::Submitted,
113            crate::TaskStatus::Running => TaskState::Working,
114            crate::TaskStatus::InputRequired => TaskState::InputRequired,
115            crate::TaskStatus::Completed => TaskState::Completed,
116            crate::TaskStatus::Failed => TaskState::Failed,
117            crate::TaskStatus::Canceled => TaskState::Canceled,
118        }
119    }
120}
121
122impl From<crate::Part> for Part {
123    fn from(part: crate::Part) -> Self {
124        match part {
125            crate::Part::Text(text) => Part::Text(TextPart { text: text }),
126            crate::Part::Image(image) => Part::File(FilePart {
127                file: filetype_to_fileobject(image),
128                metadata: None,
129            }),
130
131            // handle all  the additional parts with a part_type
132            x => Part::Data(DataPart {
133                data: serde_json::to_value(x).unwrap(),
134            }),
135        }
136    }
137}
138
139fn file_object_to_filetype(file: FileObject) -> FileType {
140    match file {
141        FileObject::WithBytes {
142            bytes,
143            mime_type,
144            name,
145        } => FileType::Bytes {
146            bytes,
147            mime_type: mime_type.unwrap_or_default(),
148            name,
149        },
150        FileObject::WithUri {
151            uri,
152            mime_type,
153            name,
154        } => FileType::Url {
155            url: uri,
156            mime_type: mime_type.unwrap_or_default(),
157            name,
158        },
159    }
160}
161
162fn filetype_to_fileobject(file: FileType) -> FileObject {
163    match file {
164        FileType::Bytes {
165            bytes,
166            mime_type,
167            name,
168        } => FileObject::WithBytes {
169            bytes,
170            mime_type: Some(mime_type),
171            name: name.clone(),
172        },
173        FileType::Url {
174            url,
175            mime_type,
176            name,
177        } => FileObject::WithUri {
178            uri: url.clone(),
179            mime_type: Some(mime_type),
180            name: name.clone(),
181        },
182    }
183}
184
185impl From<crate::Task> for Task {
186    fn from(task: crate::Task) -> Self {
187        let history = vec![];
188        Task {
189            id: task.id.clone(),
190            status: TaskStatus {
191                state: match task.status {
192                    crate::TaskStatus::Pending => TaskState::Submitted,
193                    crate::TaskStatus::Running => TaskState::Working,
194                    crate::TaskStatus::InputRequired => TaskState::InputRequired,
195                    crate::TaskStatus::Completed => TaskState::Completed,
196                    crate::TaskStatus::Failed => TaskState::Failed,
197                    crate::TaskStatus::Canceled => TaskState::Canceled,
198                },
199                message: None,
200                timestamp: None,
201            },
202            kind: EventKind::Task,
203            context_id: task.thread_id.clone(),
204            artifacts: vec![],
205            history,
206            metadata: None,
207        }
208    }
209}
210
211impl From<crate::MessageRole> for Role {
212    fn from(role: crate::MessageRole) -> Self {
213        match role {
214            crate::MessageRole::User => Role::User,
215            crate::MessageRole::Assistant => Role::Agent,
216            _ => Role::Agent,
217        }
218    }
219}