1#![allow(dead_code)]
9
10use crate::a2a::proto;
11use crate::a2a::types as local;
12
13pub fn proto_message_to_local(msg: &proto::Message) -> local::Message {
19 local::Message {
20 message_id: msg.message_id.clone(),
21 role: match msg.role() {
22 proto::Role::User => local::MessageRole::User,
23 _ => local::MessageRole::Agent,
24 },
25 parts: msg.content.iter().filter_map(proto_part_to_local).collect(),
26 context_id: if msg.context_id.is_empty() {
27 None
28 } else {
29 Some(msg.context_id.clone())
30 },
31 task_id: if msg.task_id.is_empty() {
32 None
33 } else {
34 Some(msg.task_id.clone())
35 },
36 metadata: Default::default(),
37 extensions: msg.extensions.clone(),
38 }
39}
40
41fn proto_part_to_local(part: &proto::Part) -> Option<local::Part> {
42 match &part.part {
43 Some(proto::part::Part::Text(text)) => Some(local::Part::Text { text: text.clone() }),
44 Some(proto::part::Part::File(file)) => {
45 let (bytes, uri) = match &file.file {
46 Some(proto::file_part::File::FileWithUri(u)) => (None, Some(u.clone())),
47 Some(proto::file_part::File::FileWithBytes(b)) => {
48 use base64::Engine;
49 (
50 Some(base64::engine::general_purpose::STANDARD.encode(b)),
51 None,
52 )
53 }
54 None => (None, None),
55 };
56 Some(local::Part::File {
57 file: local::FileContent {
58 bytes,
59 uri,
60 mime_type: if file.mime_type.is_empty() {
61 None
62 } else {
63 Some(file.mime_type.clone())
64 },
65 name: if file.name.is_empty() {
66 None
67 } else {
68 Some(file.name.clone())
69 },
70 },
71 })
72 }
73 Some(proto::part::Part::Data(data_part)) => {
74 let val = data_part
75 .data
76 .as_ref()
77 .map(prost_struct_to_json)
78 .unwrap_or(serde_json::Value::Null);
79 Some(local::Part::Data { data: val })
80 }
81 None => None,
82 }
83}
84
85fn proto_task_state_to_local(state: proto::TaskState) -> local::TaskState {
86 match state {
87 proto::TaskState::Submitted => local::TaskState::Submitted,
88 proto::TaskState::Working => local::TaskState::Working,
89 proto::TaskState::Completed => local::TaskState::Completed,
90 proto::TaskState::Failed => local::TaskState::Failed,
91 proto::TaskState::Cancelled => local::TaskState::Cancelled,
92 proto::TaskState::InputRequired => local::TaskState::InputRequired,
93 proto::TaskState::Rejected => local::TaskState::Rejected,
94 proto::TaskState::AuthRequired => local::TaskState::AuthRequired,
95 proto::TaskState::Unspecified => local::TaskState::Submitted,
96 }
97}
98
99pub fn proto_task_to_local(task: &proto::Task) -> local::Task {
101 let status = task
102 .status
103 .as_ref()
104 .map(|s| local::TaskStatus {
105 state: proto_task_state_to_local(s.state()),
106 message: s.update.as_ref().map(proto_message_to_local),
107 timestamp: s.timestamp.as_ref().map(|t| {
108 chrono::DateTime::from_timestamp(t.seconds, t.nanos as u32)
109 .map(|dt| dt.to_rfc3339())
110 .unwrap_or_default()
111 }),
112 })
113 .unwrap_or(local::TaskStatus {
114 state: local::TaskState::Submitted,
115 message: None,
116 timestamp: None,
117 });
118
119 local::Task {
120 id: task.id.clone(),
121 context_id: if task.context_id.is_empty() {
122 None
123 } else {
124 Some(task.context_id.clone())
125 },
126 status,
127 artifacts: task.artifacts.iter().map(proto_artifact_to_local).collect(),
128 history: task.history.iter().map(proto_message_to_local).collect(),
129 metadata: Default::default(),
130 }
131}
132
133fn proto_artifact_to_local(art: &proto::Artifact) -> local::Artifact {
134 local::Artifact {
135 artifact_id: art.artifact_id.clone(),
136 parts: art.parts.iter().filter_map(proto_part_to_local).collect(),
137 name: if art.name.is_empty() {
138 None
139 } else {
140 Some(art.name.clone())
141 },
142 description: if art.description.is_empty() {
143 None
144 } else {
145 Some(art.description.clone())
146 },
147 metadata: Default::default(),
148 extensions: art.extensions.clone(),
149 }
150}
151
152pub fn local_task_to_proto(task: &local::Task) -> proto::Task {
158 proto::Task {
159 id: task.id.clone(),
160 context_id: task.context_id.clone().unwrap_or_default(),
161 status: Some(local_task_status_to_proto(&task.status)),
162 artifacts: task.artifacts.iter().map(local_artifact_to_proto).collect(),
163 history: task.history.iter().map(local_message_to_proto).collect(),
164 metadata: None,
165 }
166}
167
168pub fn local_task_status_to_proto(status: &local::TaskStatus) -> proto::TaskStatus {
169 proto::TaskStatus {
170 state: local_task_state_to_proto(status.state).into(),
171 update: status.message.as_ref().map(local_message_to_proto),
172 timestamp: status.timestamp.as_ref().and_then(|ts| {
173 chrono::DateTime::parse_from_rfc3339(ts)
174 .ok()
175 .map(|dt| prost_types::Timestamp {
176 seconds: dt.timestamp(),
177 nanos: dt.timestamp_subsec_nanos() as i32,
178 })
179 }),
180 }
181}
182
183fn local_task_state_to_proto(state: local::TaskState) -> proto::TaskState {
184 match state {
185 local::TaskState::Submitted => proto::TaskState::Submitted,
186 local::TaskState::Working => proto::TaskState::Working,
187 local::TaskState::Completed => proto::TaskState::Completed,
188 local::TaskState::Failed => proto::TaskState::Failed,
189 local::TaskState::Cancelled => proto::TaskState::Cancelled,
190 local::TaskState::InputRequired => proto::TaskState::InputRequired,
191 local::TaskState::Rejected => proto::TaskState::Rejected,
192 local::TaskState::AuthRequired => proto::TaskState::AuthRequired,
193 }
194}
195
196pub fn local_message_to_proto(msg: &local::Message) -> proto::Message {
197 proto::Message {
198 message_id: msg.message_id.clone(),
199 context_id: msg.context_id.clone().unwrap_or_default(),
200 task_id: msg.task_id.clone().unwrap_or_default(),
201 role: match msg.role {
202 local::MessageRole::User => proto::Role::User.into(),
203 local::MessageRole::Agent => proto::Role::Agent.into(),
204 },
205 content: msg.parts.iter().map(local_part_to_proto).collect(),
206 metadata: None,
207 extensions: msg.extensions.clone(),
208 }
209}
210
211fn local_part_to_proto(part: &local::Part) -> proto::Part {
212 match part {
213 local::Part::Text { text } => proto::Part {
214 part: Some(proto::part::Part::Text(text.clone())),
215 metadata: None,
216 },
217 local::Part::File { file } => proto::Part {
218 part: Some(proto::part::Part::File(proto::FilePart {
219 file: file
220 .uri
221 .as_ref()
222 .map(|u| proto::file_part::File::FileWithUri(u.clone()))
223 .or_else(|| {
224 file.bytes.as_ref().and_then(|b| {
225 use base64::Engine;
226 base64::engine::general_purpose::STANDARD
227 .decode(b)
228 .ok()
229 .map(proto::file_part::File::FileWithBytes)
230 })
231 }),
232 mime_type: file.mime_type.clone().unwrap_or_default(),
233 name: file.name.clone().unwrap_or_default(),
234 })),
235 metadata: None,
236 },
237 local::Part::Data { data } => proto::Part {
238 part: Some(proto::part::Part::Data(proto::DataPart {
239 data: Some(json_to_prost_struct(data)),
240 })),
241 metadata: None,
242 },
243 }
244}
245
246fn local_artifact_to_proto(art: &local::Artifact) -> proto::Artifact {
247 proto::Artifact {
248 artifact_id: art.artifact_id.clone(),
249 name: art.name.clone().unwrap_or_default(),
250 description: art.description.clone().unwrap_or_default(),
251 parts: art.parts.iter().map(local_part_to_proto).collect(),
252 metadata: None,
253 extensions: art.extensions.clone(),
254 }
255}
256
257pub fn local_card_to_proto(card: &local::AgentCard) -> proto::AgentCard {
259 proto::AgentCard {
260 protocol_version: card.protocol_version.clone(),
261 name: card.name.clone(),
262 description: card.description.clone(),
263 url: card.url.clone(),
264 preferred_transport: card.preferred_transport.clone().unwrap_or_default(),
265 additional_interfaces: card
266 .additional_interfaces
267 .iter()
268 .map(|i| proto::AgentInterface {
269 url: i.url.clone(),
270 transport: i.transport.clone(),
271 })
272 .collect(),
273 provider: card.provider.as_ref().map(|p| proto::AgentProvider {
274 url: p.url.clone(),
275 organization: p.organization.clone(),
276 }),
277 version: card.version.clone(),
278 documentation_url: card.documentation_url.clone().unwrap_or_default(),
279 capabilities: Some(proto::AgentCapabilities {
280 streaming: card.capabilities.streaming,
281 push_notifications: card.capabilities.push_notifications,
282 extensions: card
283 .capabilities
284 .extensions
285 .iter()
286 .map(|e| proto::AgentExtension {
287 uri: e.uri.clone(),
288 description: e.description.clone().unwrap_or_default(),
289 required: e.required,
290 params: None,
291 })
292 .collect(),
293 }),
294 security_schemes: Default::default(), security: vec![],
296 default_input_modes: card.default_input_modes.clone(),
297 default_output_modes: card.default_output_modes.clone(),
298 skills: card
299 .skills
300 .iter()
301 .map(|s| proto::AgentSkill {
302 id: s.id.clone(),
303 name: s.name.clone(),
304 description: s.description.clone(),
305 tags: s.tags.clone(),
306 examples: s.examples.clone(),
307 input_modes: s.input_modes.clone(),
308 output_modes: s.output_modes.clone(),
309 security: vec![],
310 })
311 .collect(),
312 supports_authenticated_extended_card: card.supports_authenticated_extended_card,
313 signatures: card
314 .signatures
315 .iter()
316 .map(|s| proto::AgentCardSignature {
317 protected: s.algorithm.clone().unwrap_or_default(),
318 signature: s.signature.clone(),
319 header: None,
320 })
321 .collect(),
322 icon_url: card.icon_url.clone().unwrap_or_default(),
323 }
324}
325
326pub fn prost_struct_to_json(s: &prost_types::Struct) -> serde_json::Value {
332 let map: serde_json::Map<String, serde_json::Value> = s
333 .fields
334 .iter()
335 .map(|(k, v)| (k.clone(), prost_value_to_json(v)))
336 .collect();
337 serde_json::Value::Object(map)
338}
339
340fn prost_value_to_json(v: &prost_types::Value) -> serde_json::Value {
341 match &v.kind {
342 Some(prost_types::value::Kind::NullValue(_)) => serde_json::Value::Null,
343 Some(prost_types::value::Kind::NumberValue(n)) => {
344 serde_json::Value::Number(serde_json::Number::from_f64(*n).unwrap_or_else(|| 0.into()))
345 }
346 Some(prost_types::value::Kind::StringValue(s)) => serde_json::Value::String(s.clone()),
347 Some(prost_types::value::Kind::BoolValue(b)) => serde_json::Value::Bool(*b),
348 Some(prost_types::value::Kind::StructValue(s)) => prost_struct_to_json(s),
349 Some(prost_types::value::Kind::ListValue(l)) => {
350 serde_json::Value::Array(l.values.iter().map(prost_value_to_json).collect())
351 }
352 None => serde_json::Value::Null,
353 }
354}
355
356pub fn json_to_prost_struct(v: &serde_json::Value) -> prost_types::Struct {
358 match v {
359 serde_json::Value::Object(map) => prost_types::Struct {
360 fields: map
361 .iter()
362 .map(|(k, v)| (k.clone(), json_to_prost_value(v)))
363 .collect(),
364 },
365 _ => prost_types::Struct::default(),
366 }
367}
368
369fn json_to_prost_value(v: &serde_json::Value) -> prost_types::Value {
370 let kind = match v {
371 serde_json::Value::Null => prost_types::value::Kind::NullValue(0),
372 serde_json::Value::Bool(b) => prost_types::value::Kind::BoolValue(*b),
373 serde_json::Value::Number(n) => {
374 prost_types::value::Kind::NumberValue(n.as_f64().unwrap_or(0.0))
375 }
376 serde_json::Value::String(s) => prost_types::value::Kind::StringValue(s.clone()),
377 serde_json::Value::Array(arr) => {
378 prost_types::value::Kind::ListValue(prost_types::ListValue {
379 values: arr.iter().map(json_to_prost_value).collect(),
380 })
381 }
382 serde_json::Value::Object(map) => {
383 prost_types::value::Kind::StructValue(prost_types::Struct {
384 fields: map
385 .iter()
386 .map(|(k, v)| (k.clone(), json_to_prost_value(v)))
387 .collect(),
388 })
389 }
390 };
391 prost_types::Value { kind: Some(kind) }
392}