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