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 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 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 crate::MessageRole::Developer => Role::User,
218 _ => Role::Agent,
219 }
220 }
221}