1use super::types::{A2aFileContent, A2aPart};
4use gemini_genai_rs::prelude::{
5 Blob, CodeExecutionResult, ExecutableCode, FunctionCall, FunctionResponse, Part,
6};
7use std::collections::HashMap;
8
9pub const ADK_TYPE_KEY: &str = "adk_type";
11pub const ADK_IS_LONG_RUNNING_KEY: &str = "adk_is_long_running";
13pub const ADK_THOUGHT_KEY: &str = "adk_thought";
15
16pub const DATA_TYPE_FUNCTION_CALL: &str = "function_call";
18pub const DATA_TYPE_FUNCTION_RESPONSE: &str = "function_response";
20pub const DATA_TYPE_CODE_EXEC_RESULT: &str = "code_execution_result";
22pub const DATA_TYPE_EXECUTABLE_CODE: &str = "executable_code";
24
25pub fn to_a2a_parts(parts: &[Part], long_running_tool_ids: &[String]) -> Vec<A2aPart> {
27 parts
28 .iter()
29 .filter_map(|p| to_a2a_part(p, long_running_tool_ids))
30 .collect()
31}
32
33pub fn to_a2a_part(part: &Part, long_running_tool_ids: &[String]) -> Option<A2aPart> {
35 match part {
36 Part::Text { text } => Some(A2aPart::Text {
37 text: text.clone(),
38 metadata: None,
39 }),
40 Part::InlineData { inline_data } => Some(A2aPart::File {
41 file: A2aFileContent {
42 name: None,
43 mime_type: Some(inline_data.mime_type.clone()),
44 bytes: Some(inline_data.data.clone()),
45 uri: None,
46 },
47 metadata: None,
48 }),
49 Part::FunctionCall { function_call } => {
50 let mut metadata = HashMap::new();
51 metadata.insert(
52 ADK_TYPE_KEY.to_string(),
53 serde_json::json!(DATA_TYPE_FUNCTION_CALL),
54 );
55 if let Some(id) = &function_call.id {
56 if long_running_tool_ids.contains(id) {
57 metadata.insert(ADK_IS_LONG_RUNNING_KEY.to_string(), serde_json::json!(true));
58 }
59 }
60 Some(A2aPart::Data {
61 data: serde_json::json!({
62 "name": function_call.name,
63 "args": function_call.args,
64 "id": function_call.id,
65 }),
66 metadata: Some(metadata),
67 })
68 }
69 Part::FunctionResponse { function_response } => {
70 let mut metadata = HashMap::new();
71 metadata.insert(
72 ADK_TYPE_KEY.to_string(),
73 serde_json::json!(DATA_TYPE_FUNCTION_RESPONSE),
74 );
75 Some(A2aPart::Data {
76 data: serde_json::json!({
77 "name": function_response.name,
78 "response": function_response.response,
79 "id": function_response.id,
80 }),
81 metadata: Some(metadata),
82 })
83 }
84 Part::ExecutableCode { executable_code } => {
85 let mut metadata = HashMap::new();
86 metadata.insert(
87 ADK_TYPE_KEY.to_string(),
88 serde_json::json!(DATA_TYPE_EXECUTABLE_CODE),
89 );
90 Some(A2aPart::Data {
91 data: serde_json::json!({
92 "language": executable_code.language,
93 "code": executable_code.code,
94 }),
95 metadata: Some(metadata),
96 })
97 }
98 Part::CodeExecutionResult {
99 code_execution_result,
100 } => {
101 let mut metadata = HashMap::new();
102 metadata.insert(
103 ADK_TYPE_KEY.to_string(),
104 serde_json::json!(DATA_TYPE_CODE_EXEC_RESULT),
105 );
106 Some(A2aPart::Data {
107 data: serde_json::json!({
108 "outcome": code_execution_result.outcome,
109 "output": code_execution_result.output,
110 }),
111 metadata: Some(metadata),
112 })
113 }
114 Part::Thought { text, .. } => Some(A2aPart::Text {
115 text: text.clone(),
116 metadata: None,
117 }),
118 }
119}
120
121pub fn to_genai_parts(a2a_parts: &[A2aPart]) -> Vec<Part> {
123 a2a_parts.iter().filter_map(to_genai_part).collect()
124}
125
126pub fn to_genai_part(a2a_part: &A2aPart) -> Option<Part> {
128 match a2a_part {
129 A2aPart::Text { text, .. } => Some(Part::Text { text: text.clone() }),
130 A2aPart::File { file, .. } => {
131 file.bytes.as_ref().map(|bytes| Part::InlineData {
133 inline_data: Blob {
134 mime_type: file.mime_type.clone().unwrap_or_default(),
135 data: bytes.clone(),
136 },
137 })
138 }
139 A2aPart::Data { data, metadata } => {
140 let adk_type = metadata
141 .as_ref()
142 .and_then(|m| m.get(ADK_TYPE_KEY))
143 .and_then(|v| v.as_str());
144
145 match adk_type {
146 Some(DATA_TYPE_FUNCTION_CALL) => Some(Part::FunctionCall {
147 function_call: FunctionCall {
148 name: data.get("name")?.as_str()?.to_string(),
149 args: data.get("args").cloned().unwrap_or(serde_json::json!({})),
150 id: data.get("id").and_then(|v| v.as_str()).map(String::from),
151 },
152 }),
153 Some(DATA_TYPE_FUNCTION_RESPONSE) => Some(Part::FunctionResponse {
154 function_response: FunctionResponse {
155 name: data.get("name")?.as_str()?.to_string(),
156 response: data
157 .get("response")
158 .cloned()
159 .unwrap_or(serde_json::json!({})),
160 id: data.get("id").and_then(|v| v.as_str()).map(String::from),
161 scheduling: None,
162 },
163 }),
164 Some(DATA_TYPE_EXECUTABLE_CODE) => Some(Part::ExecutableCode {
165 executable_code: ExecutableCode {
166 language: data.get("language")?.as_str()?.to_string(),
167 code: data.get("code")?.as_str()?.to_string(),
168 },
169 }),
170 Some(DATA_TYPE_CODE_EXEC_RESULT) => Some(Part::CodeExecutionResult {
171 code_execution_result: CodeExecutionResult {
172 outcome: data.get("outcome")?.as_str()?.to_string(),
173 output: data
174 .get("output")
175 .and_then(|v| v.as_str())
176 .map(String::from),
177 },
178 }),
179 _ => None,
180 }
181 }
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn text_part_to_a2a() {
191 let part = Part::Text {
192 text: "hello world".to_string(),
193 };
194 let a2a = to_a2a_part(&part, &[]).unwrap();
195 match a2a {
196 A2aPart::Text { text, metadata } => {
197 assert_eq!(text, "hello world");
198 assert!(metadata.is_none());
199 }
200 _ => panic!("Expected Text part"),
201 }
202 }
203
204 #[test]
205 fn inline_data_to_a2a_file() {
206 let part = Part::InlineData {
207 inline_data: Blob {
208 mime_type: "image/png".to_string(),
209 data: "base64data".to_string(),
210 },
211 };
212 let a2a = to_a2a_part(&part, &[]).unwrap();
213 match a2a {
214 A2aPart::File { file, metadata } => {
215 assert_eq!(file.mime_type.as_deref(), Some("image/png"));
216 assert_eq!(file.bytes.as_deref(), Some("base64data"));
217 assert!(file.uri.is_none());
218 assert!(file.name.is_none());
219 assert!(metadata.is_none());
220 }
221 _ => panic!("Expected File part"),
222 }
223 }
224
225 #[test]
226 fn function_call_to_a2a_data_with_metadata() {
227 let part = Part::FunctionCall {
228 function_call: FunctionCall {
229 name: "get_weather".to_string(),
230 args: serde_json::json!({"city": "London"}),
231 id: Some("call-1".to_string()),
232 },
233 };
234 let a2a = to_a2a_part(&part, &[]).unwrap();
235 match &a2a {
236 A2aPart::Data { data, metadata } => {
237 assert_eq!(data["name"], "get_weather");
238 assert_eq!(data["args"]["city"], "London");
239 assert_eq!(data["id"], "call-1");
240 let meta = metadata.as_ref().unwrap();
241 assert_eq!(meta[ADK_TYPE_KEY], DATA_TYPE_FUNCTION_CALL);
242 assert!(!meta.contains_key(ADK_IS_LONG_RUNNING_KEY));
243 }
244 _ => panic!("Expected Data part"),
245 }
246 }
247
248 #[test]
249 fn function_call_long_running() {
250 let part = Part::FunctionCall {
251 function_call: FunctionCall {
252 name: "slow_op".to_string(),
253 args: serde_json::json!({}),
254 id: Some("lr-1".to_string()),
255 },
256 };
257 let long_running = vec!["lr-1".to_string()];
258 let a2a = to_a2a_part(&part, &long_running).unwrap();
259 match &a2a {
260 A2aPart::Data { metadata, .. } => {
261 let meta = metadata.as_ref().unwrap();
262 assert_eq!(meta[ADK_IS_LONG_RUNNING_KEY], serde_json::json!(true));
263 }
264 _ => panic!("Expected Data part"),
265 }
266 }
267
268 #[test]
269 fn function_response_to_a2a_data() {
270 let part = Part::FunctionResponse {
271 function_response: FunctionResponse {
272 name: "get_weather".to_string(),
273 response: serde_json::json!({"temp": 20}),
274 id: Some("call-1".to_string()),
275 scheduling: None,
276 },
277 };
278 let a2a = to_a2a_part(&part, &[]).unwrap();
279 match &a2a {
280 A2aPart::Data { data, metadata } => {
281 assert_eq!(data["name"], "get_weather");
282 assert_eq!(data["response"]["temp"], 20);
283 assert_eq!(data["id"], "call-1");
284 let meta = metadata.as_ref().unwrap();
285 assert_eq!(meta[ADK_TYPE_KEY], DATA_TYPE_FUNCTION_RESPONSE);
286 }
287 _ => panic!("Expected Data part"),
288 }
289 }
290
291 #[test]
292 fn executable_code_to_a2a_data() {
293 let part = Part::ExecutableCode {
294 executable_code: ExecutableCode {
295 language: "python".to_string(),
296 code: "print('hi')".to_string(),
297 },
298 };
299 let a2a = to_a2a_part(&part, &[]).unwrap();
300 match &a2a {
301 A2aPart::Data { data, metadata } => {
302 assert_eq!(data["language"], "python");
303 assert_eq!(data["code"], "print('hi')");
304 let meta = metadata.as_ref().unwrap();
305 assert_eq!(meta[ADK_TYPE_KEY], DATA_TYPE_EXECUTABLE_CODE);
306 }
307 _ => panic!("Expected Data part"),
308 }
309 }
310
311 #[test]
312 fn code_execution_result_to_a2a_data() {
313 let part = Part::CodeExecutionResult {
314 code_execution_result: CodeExecutionResult {
315 outcome: "success".to_string(),
316 output: Some("hi".to_string()),
317 },
318 };
319 let a2a = to_a2a_part(&part, &[]).unwrap();
320 match &a2a {
321 A2aPart::Data { data, metadata } => {
322 assert_eq!(data["outcome"], "success");
323 assert_eq!(data["output"], "hi");
324 let meta = metadata.as_ref().unwrap();
325 assert_eq!(meta[ADK_TYPE_KEY], DATA_TYPE_CODE_EXEC_RESULT);
326 }
327 _ => panic!("Expected Data part"),
328 }
329 }
330
331 #[test]
332 fn a2a_text_to_genai_part() {
333 let a2a = A2aPart::Text {
334 text: "hello".to_string(),
335 metadata: None,
336 };
337 let part = to_genai_part(&a2a).unwrap();
338 match part {
339 Part::Text { text } => assert_eq!(text, "hello"),
340 _ => panic!("Expected Text part"),
341 }
342 }
343
344 #[test]
345 fn a2a_file_to_genai_inline_data() {
346 let a2a = A2aPart::File {
347 file: A2aFileContent {
348 name: None,
349 mime_type: Some("audio/pcm".to_string()),
350 bytes: Some("pcmdata".to_string()),
351 uri: None,
352 },
353 metadata: None,
354 };
355 let part = to_genai_part(&a2a).unwrap();
356 match part {
357 Part::InlineData { inline_data } => {
358 assert_eq!(inline_data.mime_type, "audio/pcm");
359 assert_eq!(inline_data.data, "pcmdata");
360 }
361 _ => panic!("Expected InlineData part"),
362 }
363 }
364
365 #[test]
366 fn a2a_file_uri_only_returns_none() {
367 let a2a = A2aPart::File {
368 file: A2aFileContent {
369 name: None,
370 mime_type: Some("image/png".to_string()),
371 bytes: None,
372 uri: Some("gs://bucket/img.png".to_string()),
373 },
374 metadata: None,
375 };
376 assert!(to_genai_part(&a2a).is_none());
377 }
378
379 #[test]
380 fn a2a_data_function_call_to_genai() {
381 let mut metadata = HashMap::new();
382 metadata.insert(
383 ADK_TYPE_KEY.to_string(),
384 serde_json::json!(DATA_TYPE_FUNCTION_CALL),
385 );
386 let a2a = A2aPart::Data {
387 data: serde_json::json!({
388 "name": "search",
389 "args": {"query": "rust"},
390 "id": "fc-1",
391 }),
392 metadata: Some(metadata),
393 };
394 let part = to_genai_part(&a2a).unwrap();
395 match part {
396 Part::FunctionCall { function_call } => {
397 assert_eq!(function_call.name, "search");
398 assert_eq!(function_call.args["query"], "rust");
399 assert_eq!(function_call.id.as_deref(), Some("fc-1"));
400 }
401 _ => panic!("Expected FunctionCall part"),
402 }
403 }
404
405 #[test]
406 fn a2a_data_function_response_to_genai() {
407 let mut metadata = HashMap::new();
408 metadata.insert(
409 ADK_TYPE_KEY.to_string(),
410 serde_json::json!(DATA_TYPE_FUNCTION_RESPONSE),
411 );
412 let a2a = A2aPart::Data {
413 data: serde_json::json!({
414 "name": "search",
415 "response": {"results": [1, 2, 3]},
416 "id": "fc-1",
417 }),
418 metadata: Some(metadata),
419 };
420 let part = to_genai_part(&a2a).unwrap();
421 match part {
422 Part::FunctionResponse { function_response } => {
423 assert_eq!(function_response.name, "search");
424 assert_eq!(
425 function_response.response["results"],
426 serde_json::json!([1, 2, 3])
427 );
428 assert_eq!(function_response.id.as_deref(), Some("fc-1"));
429 }
430 _ => panic!("Expected FunctionResponse part"),
431 }
432 }
433
434 #[test]
435 fn a2a_data_executable_code_to_genai() {
436 let mut metadata = HashMap::new();
437 metadata.insert(
438 ADK_TYPE_KEY.to_string(),
439 serde_json::json!(DATA_TYPE_EXECUTABLE_CODE),
440 );
441 let a2a = A2aPart::Data {
442 data: serde_json::json!({
443 "language": "python",
444 "code": "x = 1",
445 }),
446 metadata: Some(metadata),
447 };
448 let part = to_genai_part(&a2a).unwrap();
449 match part {
450 Part::ExecutableCode { executable_code } => {
451 assert_eq!(executable_code.language, "python");
452 assert_eq!(executable_code.code, "x = 1");
453 }
454 _ => panic!("Expected ExecutableCode part"),
455 }
456 }
457
458 #[test]
459 fn a2a_data_code_exec_result_to_genai() {
460 let mut metadata = HashMap::new();
461 metadata.insert(
462 ADK_TYPE_KEY.to_string(),
463 serde_json::json!(DATA_TYPE_CODE_EXEC_RESULT),
464 );
465 let a2a = A2aPart::Data {
466 data: serde_json::json!({
467 "outcome": "success",
468 "output": "42",
469 }),
470 metadata: Some(metadata),
471 };
472 let part = to_genai_part(&a2a).unwrap();
473 match part {
474 Part::CodeExecutionResult {
475 code_execution_result,
476 } => {
477 assert_eq!(code_execution_result.outcome, "success");
478 assert_eq!(code_execution_result.output.as_deref(), Some("42"));
479 }
480 _ => panic!("Expected CodeExecutionResult part"),
481 }
482 }
483
484 #[test]
485 fn a2a_data_unknown_type_returns_none() {
486 let a2a = A2aPart::Data {
487 data: serde_json::json!({"foo": "bar"}),
488 metadata: None,
489 };
490 assert!(to_genai_part(&a2a).is_none());
491 }
492
493 #[test]
496 fn round_trip_text() {
497 let original = Part::Text {
498 text: "round trip".to_string(),
499 };
500 let a2a = to_a2a_part(&original, &[]).unwrap();
501 let back = to_genai_part(&a2a).unwrap();
502 assert_eq!(back, original);
503 }
504
505 #[test]
506 fn round_trip_function_call() {
507 let original = Part::FunctionCall {
508 function_call: FunctionCall {
509 name: "my_tool".to_string(),
510 args: serde_json::json!({"x": 10}),
511 id: Some("id-1".to_string()),
512 },
513 };
514 let a2a = to_a2a_part(&original, &[]).unwrap();
515 let back = to_genai_part(&a2a).unwrap();
516 assert_eq!(back, original);
517 }
518
519 #[test]
520 fn round_trip_function_response() {
521 let original = Part::FunctionResponse {
522 function_response: FunctionResponse {
523 name: "my_tool".to_string(),
524 response: serde_json::json!({"result": "ok"}),
525 id: Some("id-1".to_string()),
526 scheduling: None,
527 },
528 };
529 let a2a = to_a2a_part(&original, &[]).unwrap();
530 let back = to_genai_part(&a2a).unwrap();
531 assert_eq!(back, original);
532 }
533
534 #[test]
535 fn to_a2a_parts_filters_and_collects() {
536 let parts = vec![
537 Part::Text {
538 text: "a".to_string(),
539 },
540 Part::Text {
541 text: "b".to_string(),
542 },
543 ];
544 let a2a = to_a2a_parts(&parts, &[]);
545 assert_eq!(a2a.len(), 2);
546 }
547
548 #[test]
549 fn to_genai_parts_filters_and_collects() {
550 let a2a_parts = vec![
551 A2aPart::Text {
552 text: "x".to_string(),
553 metadata: None,
554 },
555 A2aPart::Data {
557 data: serde_json::json!({}),
558 metadata: None,
559 },
560 ];
561 let genai = to_genai_parts(&a2a_parts);
562 assert_eq!(genai.len(), 1);
563 }
564}