distri_types/
a2a_converters.rs

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