1use serde_json::Value;
10
11use crate::errors::MessageParseError;
12use crate::types::{
13 AssistantMessage, ContentBlock, Message, ResultMessage, StreamEvent, SystemMessage, TextBlock,
14 ThinkingBlock, ToolResultBlock, ToolUseBlock, UserContent, UserMessage,
15};
16
17fn value_type_name(value: &Value) -> &'static str {
19 match value {
20 Value::Null => "null",
21 Value::Bool(_) => "bool",
22 Value::Number(_) => "number",
23 Value::String(_) => "str",
24 Value::Array(_) => "list",
25 Value::Object(_) => "dict",
26 }
27}
28
29fn get_required<'a>(
31 data: &'a serde_json::Map<String, Value>,
32 key: &str,
33 context: &str,
34 full_data: &Value,
35) -> std::result::Result<&'a Value, MessageParseError> {
36 data.get(key).ok_or_else(|| {
37 MessageParseError::new(
38 format!("Missing required field in {context} message: '{key}'"),
39 Some(full_data.clone()),
40 )
41 })
42}
43
44fn parse_content_blocks(blocks: &[Value], include_thinking: bool) -> Vec<ContentBlock> {
49 let mut content_blocks = Vec::new();
50
51 for block in blocks {
52 let Some(block_obj) = block.as_object() else {
53 continue;
54 };
55
56 let Some(block_type) = block_obj.get("type").and_then(Value::as_str) else {
57 continue;
58 };
59
60 match block_type {
61 "text" => {
62 if let Some(text) = block_obj.get("text").and_then(Value::as_str) {
63 content_blocks.push(ContentBlock::Text(TextBlock {
64 text: text.to_string(),
65 }));
66 }
67 }
68 "thinking" if include_thinking => {
69 if let (Some(thinking), Some(signature)) = (
70 block_obj.get("thinking").and_then(Value::as_str),
71 block_obj.get("signature").and_then(Value::as_str),
72 ) {
73 content_blocks.push(ContentBlock::Thinking(ThinkingBlock {
74 thinking: thinking.to_string(),
75 signature: signature.to_string(),
76 }));
77 }
78 }
79 "tool_use" => {
80 if let (Some(id), Some(name), Some(input)) = (
81 block_obj.get("id").and_then(Value::as_str),
82 block_obj.get("name").and_then(Value::as_str),
83 block_obj.get("input"),
84 ) {
85 content_blocks.push(ContentBlock::ToolUse(ToolUseBlock {
86 id: id.to_string(),
87 name: name.to_string(),
88 input: input.clone(),
89 }));
90 }
91 }
92 "tool_result" => {
93 if let Some(tool_use_id) = block_obj.get("tool_use_id").and_then(Value::as_str) {
94 content_blocks.push(ContentBlock::ToolResult(ToolResultBlock {
95 tool_use_id: tool_use_id.to_string(),
96 content: block_obj.get("content").cloned(),
97 is_error: block_obj.get("is_error").and_then(Value::as_bool),
98 }));
99 }
100 }
101 _ => {}
102 }
103 }
104
105 content_blocks
106}
107
108pub fn parse_message(data: &Value) -> std::result::Result<Option<Message>, MessageParseError> {
145 let Some(obj) = data.as_object() else {
146 return Err(MessageParseError::new(
147 format!(
148 "Invalid message data type (expected dict, got {})",
149 value_type_name(data)
150 ),
151 Some(data.clone()),
152 ));
153 };
154
155 let Some(message_type) = obj.get("type").and_then(Value::as_str) else {
156 return Err(MessageParseError::new(
157 "Message missing 'type' field",
158 Some(data.clone()),
159 ));
160 };
161
162 match message_type {
163 "user" => {
164 let message = get_required(obj, "message", "user", data)?;
165 let message_obj = message.as_object().ok_or_else(|| {
166 MessageParseError::new(
167 "Missing required field in user message: 'message'",
168 Some(data.clone()),
169 )
170 })?;
171 let content = get_required(message_obj, "content", "user", data)?;
172
173 let user_content = if let Some(content_blocks) = content.as_array() {
174 UserContent::Blocks(parse_content_blocks(content_blocks, false))
175 } else if let Some(content_text) = content.as_str() {
176 UserContent::Text(content_text.to_string())
177 } else {
178 UserContent::Text(content.to_string())
179 };
180
181 Ok(Some(Message::User(UserMessage {
182 content: user_content,
183 uuid: obj
184 .get("uuid")
185 .and_then(Value::as_str)
186 .map(ToString::to_string),
187 parent_tool_use_id: obj
188 .get("parent_tool_use_id")
189 .and_then(Value::as_str)
190 .map(ToString::to_string),
191 tool_use_result: obj.get("tool_use_result").cloned(),
192 })))
193 }
194 "assistant" => {
195 let message = get_required(obj, "message", "assistant", data)?;
196 let message_obj = message.as_object().ok_or_else(|| {
197 MessageParseError::new(
198 "Missing required field in assistant message: 'message'",
199 Some(data.clone()),
200 )
201 })?;
202
203 let content = get_required(message_obj, "content", "assistant", data)?;
204 let model = get_required(message_obj, "model", "assistant", data)?
205 .as_str()
206 .ok_or_else(|| {
207 MessageParseError::new(
208 "Missing required field in assistant message: 'model'",
209 Some(data.clone()),
210 )
211 })?;
212
213 let blocks = content.as_array().ok_or_else(|| {
214 MessageParseError::new(
215 "Missing required field in assistant message: 'content'",
216 Some(data.clone()),
217 )
218 })?;
219
220 Ok(Some(Message::Assistant(AssistantMessage {
221 content: parse_content_blocks(blocks, true),
222 model: model.to_string(),
223 parent_tool_use_id: obj
224 .get("parent_tool_use_id")
225 .and_then(Value::as_str)
226 .map(ToString::to_string),
227 error: obj
228 .get("error")
229 .and_then(Value::as_str)
230 .map(ToString::to_string),
231 })))
232 }
233 "system" => {
234 let subtype = get_required(obj, "subtype", "system", data)?
235 .as_str()
236 .ok_or_else(|| {
237 MessageParseError::new(
238 "Missing required field in system message: 'subtype'",
239 Some(data.clone()),
240 )
241 })?;
242
243 Ok(Some(Message::System(SystemMessage {
244 subtype: subtype.to_string(),
245 data: data.clone(),
246 })))
247 }
248 "result" => {
249 let subtype = get_required(obj, "subtype", "result", data)?
250 .as_str()
251 .ok_or_else(|| {
252 MessageParseError::new(
253 "Missing required field in result message: 'subtype'",
254 Some(data.clone()),
255 )
256 })?;
257 let duration_ms = get_required(obj, "duration_ms", "result", data)?
258 .as_i64()
259 .ok_or_else(|| {
260 MessageParseError::new(
261 "Missing required field in result message: 'duration_ms'",
262 Some(data.clone()),
263 )
264 })?;
265 let duration_api_ms = get_required(obj, "duration_api_ms", "result", data)?
266 .as_i64()
267 .ok_or_else(|| {
268 MessageParseError::new(
269 "Missing required field in result message: 'duration_api_ms'",
270 Some(data.clone()),
271 )
272 })?;
273 let is_error = get_required(obj, "is_error", "result", data)?
274 .as_bool()
275 .ok_or_else(|| {
276 MessageParseError::new(
277 "Missing required field in result message: 'is_error'",
278 Some(data.clone()),
279 )
280 })?;
281 let num_turns = get_required(obj, "num_turns", "result", data)?
282 .as_i64()
283 .ok_or_else(|| {
284 MessageParseError::new(
285 "Missing required field in result message: 'num_turns'",
286 Some(data.clone()),
287 )
288 })?;
289 let session_id = get_required(obj, "session_id", "result", data)?
290 .as_str()
291 .ok_or_else(|| {
292 MessageParseError::new(
293 "Missing required field in result message: 'session_id'",
294 Some(data.clone()),
295 )
296 })?;
297
298 Ok(Some(Message::Result(ResultMessage {
299 subtype: subtype.to_string(),
300 duration_ms,
301 duration_api_ms,
302 is_error,
303 num_turns,
304 session_id: session_id.to_string(),
305 stop_reason: obj
306 .get("stop_reason")
307 .and_then(Value::as_str)
308 .map(ToString::to_string),
309 total_cost_usd: obj.get("total_cost_usd").and_then(Value::as_f64),
310 usage: obj.get("usage").cloned(),
311 result: obj
312 .get("result")
313 .and_then(Value::as_str)
314 .map(ToString::to_string),
315 structured_output: obj.get("structured_output").cloned(),
316 })))
317 }
318 "stream_event" => {
319 let uuid = get_required(obj, "uuid", "stream_event", data)?
320 .as_str()
321 .ok_or_else(|| {
322 MessageParseError::new(
323 "Missing required field in stream_event message: 'uuid'",
324 Some(data.clone()),
325 )
326 })?;
327 let session_id = get_required(obj, "session_id", "stream_event", data)?
328 .as_str()
329 .ok_or_else(|| {
330 MessageParseError::new(
331 "Missing required field in stream_event message: 'session_id'",
332 Some(data.clone()),
333 )
334 })?;
335 let event = get_required(obj, "event", "stream_event", data)?;
336
337 Ok(Some(Message::StreamEvent(StreamEvent {
338 uuid: uuid.to_string(),
339 session_id: session_id.to_string(),
340 event: event.clone(),
341 parent_tool_use_id: obj
342 .get("parent_tool_use_id")
343 .and_then(Value::as_str)
344 .map(ToString::to_string),
345 })))
346 }
347 _ => Ok(None),
348 }
349}