gemini_adk_rs/a2a/
event_converter.rs

1//! Bidirectional Event <-> A2A Message conversion.
2
3use super::types::{A2aMessage, A2aPart};
4use crate::events::Event;
5
6/// Convert an ADK Event to an A2A Message.
7pub fn to_a2a_message(event: &Event) -> Option<A2aMessage> {
8    let role = if event.author == "user" {
9        "user"
10    } else {
11        "agent"
12    };
13
14    let mut parts = Vec::new();
15
16    // Add text content as a text part
17    if let Some(content) = &event.content {
18        if !content.is_empty() {
19            parts.push(A2aPart::Text {
20                text: content.clone(),
21                metadata: None,
22            });
23        }
24    }
25
26    if parts.is_empty() {
27        return None;
28    }
29
30    Some(A2aMessage {
31        message_id: event.id.clone(),
32        role: role.to_string(),
33        parts,
34        metadata: None,
35    })
36}
37
38/// Convert an A2A event/message JSON to an ADK Event.
39pub fn to_adk_event(
40    event_json: &serde_json::Value,
41    invocation_id: &str,
42    agent_name: &str,
43) -> Option<Event> {
44    // Try to parse as A2aMessage — look for "message" key or treat the whole object as a message
45    let message = event_json.get("message").or(Some(event_json));
46
47    if let Some(message) = message {
48        let role = message
49            .get("role")
50            .and_then(|v| v.as_str())
51            .unwrap_or("agent");
52        let author = if role == "user" {
53            "user".to_string()
54        } else {
55            agent_name.to_string()
56        };
57
58        // Extract text from parts
59        let content = message
60            .get("parts")
61            .and_then(|p| p.as_array())
62            .map(|parts| {
63                parts
64                    .iter()
65                    .filter_map(|p| {
66                        if p.get("kind").and_then(|k| k.as_str()) == Some("text") {
67                            p.get("text").and_then(|t| t.as_str()).map(String::from)
68                        } else {
69                            None
70                        }
71                    })
72                    .collect::<Vec<_>>()
73                    .join("")
74            })
75            .filter(|s| !s.is_empty());
76
77        let mut event = Event::new(author, content);
78        event.invocation_id = invocation_id.to_string();
79        Some(event)
80    } else {
81        None
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn user_event_to_a2a_message() {
91        let event = Event::new("user", Some("Hello, agent!".to_string()));
92        let msg = to_a2a_message(&event).unwrap();
93        assert_eq!(msg.role, "user");
94        assert_eq!(msg.message_id, event.id);
95        assert_eq!(msg.parts.len(), 1);
96        match &msg.parts[0] {
97            A2aPart::Text { text, .. } => assert_eq!(text, "Hello, agent!"),
98            _ => panic!("Expected Text part"),
99        }
100    }
101
102    #[test]
103    fn agent_event_to_a2a_message() {
104        let event = Event::new("my-agent", Some("Here is your answer.".to_string()));
105        let msg = to_a2a_message(&event).unwrap();
106        assert_eq!(msg.role, "agent");
107        assert_eq!(msg.parts.len(), 1);
108        match &msg.parts[0] {
109            A2aPart::Text { text, .. } => assert_eq!(text, "Here is your answer."),
110            _ => panic!("Expected Text part"),
111        }
112    }
113
114    #[test]
115    fn empty_content_returns_none() {
116        let event = Event::new("user", None);
117        assert!(to_a2a_message(&event).is_none());
118    }
119
120    #[test]
121    fn empty_string_content_returns_none() {
122        let event = Event::new("user", Some(String::new()));
123        assert!(to_a2a_message(&event).is_none());
124    }
125
126    #[test]
127    fn to_adk_event_basic() {
128        let json = serde_json::json!({
129            "role": "agent",
130            "parts": [
131                { "kind": "text", "text": "Done!" }
132            ]
133        });
134        let event = to_adk_event(&json, "inv-1", "helper").unwrap();
135        assert_eq!(event.author, "helper");
136        assert_eq!(event.invocation_id, "inv-1");
137        assert_eq!(event.content.as_deref(), Some("Done!"));
138    }
139
140    #[test]
141    fn to_adk_event_user_role() {
142        let json = serde_json::json!({
143            "role": "user",
144            "parts": [
145                { "kind": "text", "text": "Request" }
146            ]
147        });
148        let event = to_adk_event(&json, "inv-2", "agent-x").unwrap();
149        assert_eq!(event.author, "user");
150        assert_eq!(event.content.as_deref(), Some("Request"));
151    }
152
153    #[test]
154    fn to_adk_event_nested_message() {
155        let json = serde_json::json!({
156            "message": {
157                "role": "agent",
158                "parts": [
159                    { "kind": "text", "text": "Nested" }
160                ]
161            }
162        });
163        let event = to_adk_event(&json, "inv-3", "bot").unwrap();
164        assert_eq!(event.author, "bot");
165        assert_eq!(event.content.as_deref(), Some("Nested"));
166    }
167
168    #[test]
169    fn to_adk_event_no_text_parts() {
170        let json = serde_json::json!({
171            "role": "agent",
172            "parts": [
173                { "kind": "data", "data": {"key": "val"} }
174            ]
175        });
176        let event = to_adk_event(&json, "inv-4", "bot").unwrap();
177        // No text parts means content is None
178        assert!(event.content.is_none());
179    }
180
181    #[test]
182    fn to_adk_event_multiple_text_parts_concatenated() {
183        let json = serde_json::json!({
184            "role": "agent",
185            "parts": [
186                { "kind": "text", "text": "Hello " },
187                { "kind": "text", "text": "World" }
188            ]
189        });
190        let event = to_adk_event(&json, "inv-5", "bot").unwrap();
191        assert_eq!(event.content.as_deref(), Some("Hello World"));
192    }
193}