ai_agent/bridge/
inbound_messages.rs1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
13#[serde(tag = "type", rename_all = "snake_case")]
14pub enum SDKMessage {
15 User {
16 message: Option<UserMessageContent>,
17 uuid: Option<String>,
18 },
19 Assistant {
20 message: Option<AssistantMessageContent>,
21 uuid: Option<String>,
22 },
23 ToolUse {
24 message: Option<ToolUseMessageContent>,
25 uuid: Option<String>,
26 },
27 ToolResult {
28 message: Option<ToolResultMessageContent>,
29 uuid: Option<String>,
30 },
31 System {
32 message: Option<SystemMessageContent>,
33 uuid: Option<String>,
34 },
35}
36
37impl SDKMessage {
38 pub fn user_message_with_session(_session_id: String) -> Self {
40 SDKMessage::User {
41 message: None,
42 uuid: None,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(untagged)]
50pub enum UserMessageContent {
51 String(String),
52 Blocks(Vec<ContentBlock>),
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct AssistantMessageContent {
58 pub content: Option<serde_json::Value>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ToolUseMessageContent {
64 pub content: Option<serde_json::Value>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct ToolResultMessageContent {
70 pub content: Option<serde_json::Value>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct SystemMessageContent {
76 pub content: Option<serde_json::Value>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81#[serde(tag = "type", content = "source", rename_all = "snake_case")]
82pub enum ContentBlock {
83 Text {
84 text: String,
85 },
86 Image {
87 #[serde(rename = "media_type")]
88 media_type: Option<String>,
89 data: String,
90 },
91 #[serde(other)]
93 Other,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct ImageBlock {
99 #[serde(rename = "media_type")]
100 pub media_type: Option<String>,
101 pub r#type: String,
102 pub data: String,
103}
104
105#[derive(Debug, Clone)]
111pub struct InboundMessageFields {
112 pub content: String,
113 pub uuid: Option<String>,
114}
115
116pub fn extract_inbound_message_fields(msg: &SDKMessage) -> Option<InboundMessageFields> {
123 let SDKMessage::User { message, uuid } = msg else {
124 return None;
125 };
126
127 let content = match message {
128 Some(UserMessageContent::String(s)) => {
129 if s.is_empty() {
130 return None;
131 }
132 s.clone()
133 }
134 Some(UserMessageContent::Blocks(blocks)) => {
135 if blocks.is_empty() {
136 return None;
137 }
138 let normalized = normalize_image_blocks(blocks);
140 extract_text_from_blocks(&normalized)
141 }
142 None => return None,
143 };
144
145 Some(InboundMessageFields {
146 content,
147 uuid: uuid.clone(),
148 })
149}
150
151pub fn normalize_image_blocks(blocks: &[ContentBlock]) -> Vec<ContentBlock> {
159 if !blocks.iter().any(|b| is_malformed_base64_image(b)) {
160 return blocks.to_vec();
161 }
162
163 blocks
164 .iter()
165 .map(|block| {
166 if !is_malformed_base64_image(block) {
167 return block.clone();
168 }
169 let media_type = detect_image_format(block);
172 ContentBlock::Image {
173 media_type: Some(media_type),
174 data: get_image_data(block),
175 }
176 })
177 .collect()
178}
179
180fn is_malformed_base64_image(block: &ContentBlock) -> bool {
181 match block {
182 ContentBlock::Image { media_type, .. } => media_type.is_none(),
183 _ => false,
184 }
185}
186
187fn detect_image_format(_block: &ContentBlock) -> String {
188 "image/png".to_string()
192}
193
194fn get_image_data(block: &ContentBlock) -> String {
195 match block {
196 ContentBlock::Image { data, .. } => data.clone(),
197 _ => String::new(),
198 }
199}
200
201fn extract_text_from_blocks(blocks: &[ContentBlock]) -> String {
202 blocks
203 .iter()
204 .filter_map(|block| {
205 if let ContentBlock::Text { text } = block {
206 Some(text.clone())
207 } else {
208 None
209 }
210 })
211 .collect::<Vec<_>>()
212 .join("\n")
213}
214
215pub fn extract_inbound_message_fields_from_json(
221 msg: &serde_json::Value,
222) -> Option<InboundMessageFields> {
223 let msg_type = msg.get("type")?.as_str()?;
225 if msg_type != "user" {
226 return None;
227 }
228
229 let message = msg.get("message")?;
230 let content = if let Some(s) = message.as_str() {
231 if s.is_empty() {
232 return None;
233 }
234 s.to_string()
235 } else if let Some(arr) = message.as_array() {
236 if arr.is_empty() {
237 return None;
238 }
239 let normalized: Vec<ContentBlock> = arr
241 .iter()
242 .filter_map(|b| serde_json::from_value(b.clone()).ok())
243 .collect();
244 extract_text_from_blocks(&normalized)
245 } else {
246 return None;
247 };
248
249 let uuid = msg.get("uuid").and_then(|v| v.as_str()).map(String::from);
250
251 Some(InboundMessageFields { content, uuid })
252}