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 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 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}