claude_agent_sdk/internal/
message_parser.rs1use crate::errors::{ClaudeError, MessageParseError, Result};
31use crate::types::messages::Message;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
37pub enum ParsingMode {
38 #[default]
43 Traditional,
44
45 ZeroCopy,
55}
56
57pub struct MessageParser;
59
60impl MessageParser {
61 pub fn parse(data: serde_json::Value) -> Result<Message> {
63 serde_json::from_value(data).map_err(|e| {
64 MessageParseError::new(format!("Failed to parse message: {}", e), None).into()
65 })
66 }
67}
68
69pub struct ZeroCopyMessageParser;
75
76impl ZeroCopyMessageParser {
77 pub fn parse(json: &str) -> Result<Message> {
98 serde_json::from_str(json).map_err(|e| {
99 ClaudeError::MessageParse(MessageParseError::new(
100 format!("Failed to parse message: {}", e),
101 Some(serde_json::Value::String(json.to_string())),
102 ))
103 })
104 }
105
106 #[allow(dead_code)]
119 pub fn parse_bytes(bytes: &[u8]) -> Result<Message> {
120 let json = std::str::from_utf8(bytes).map_err(|e| {
121 ClaudeError::MessageParse(MessageParseError::new(
122 format!("Invalid UTF-8 in message: {}", e),
123 None,
124 ))
125 })?;
126 Self::parse(json)
127 }
128}
129
130pub fn parse_with_mode(json: &str, mode: ParsingMode) -> Result<Message> {
153 match mode {
154 ParsingMode::Traditional => {
155 serde_json::from_str(json).map_err(|e| {
158 ClaudeError::MessageParse(MessageParseError::new(
159 format!("Failed to parse JSON: {}", e),
160 None,
161 ))
162 })
163 }
164 ParsingMode::ZeroCopy => ZeroCopyMessageParser::parse(json),
165 }
166}
167
168#[allow(dead_code)]
173pub fn parse_from_value(value: serde_json::Value) -> Result<Message> {
174 MessageParser::parse(value)
175}
176
177#[allow(dead_code)]
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub enum MessageKind {
184 Assistant,
186 System,
188 Result,
190 StreamEvent,
192 User,
194 Control,
196 Unknown,
198}
199
200#[allow(dead_code)]
201impl MessageKind {
202 pub fn detect(json: &str) -> Self {
204 let trimmed = json.trim();
205
206 if trimmed.contains(r#""type":"assistant""#) || trimmed.contains(r#""type": "assistant""#) {
207 return MessageKind::Assistant;
208 }
209 if trimmed.contains(r#""type":"system""#) || trimmed.contains(r#""type": "system""#) {
210 return MessageKind::System;
211 }
212 if trimmed.contains(r#""type":"result""#) || trimmed.contains(r#""type": "result""#) {
213 return MessageKind::Result;
214 }
215 if trimmed.contains(r#""type":"stream_event""#) || trimmed.contains(r#""type": "stream_event""#)
216 {
217 return MessageKind::StreamEvent;
218 }
219 if trimmed.contains(r#""type":"user""#) || trimmed.contains(r#""type": "user""#) {
220 return MessageKind::User;
221 }
222 if trimmed.contains(r#""type":"control""#) || trimmed.contains(r#""type": "control""#) {
223 return MessageKind::Control;
224 }
225
226 MessageKind::Unknown
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_message_parser() {
236 let json = serde_json::json!({
237 "type": "assistant",
238 "message": {
239 "role": "assistant",
240 "content": [{"type": "text", "text": "Hello, world!"}]
241 }
242 });
243
244 let result = MessageParser::parse(json);
245 assert!(result.is_ok());
246 }
247
248 #[test]
249 fn test_zero_copy_parser_assistant() {
250 let json = r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
251 let result = ZeroCopyMessageParser::parse(json);
252 assert!(result.is_ok());
253
254 let message = result.unwrap();
255 match message {
256 Message::Assistant(msg) => {
257 assert!(!msg.message.content.is_empty());
259 }
260 _ => panic!("Expected Assistant message"),
261 }
262 }
263
264 #[test]
265 fn test_zero_copy_parser_system() {
266 let json = r#"{"type":"system","subtype":"init","cwd":"/home/user","session_id":"test-123"}"#;
267 let result = ZeroCopyMessageParser::parse(json);
268 assert!(result.is_ok());
269
270 let message = result.unwrap();
271 match message {
272 Message::System(msg) => {
273 assert_eq!(msg.subtype, "init");
274 }
275 _ => panic!("Expected System message"),
276 }
277 }
278
279 #[test]
280 fn test_zero_copy_parser_result() {
281 let json = r#"{"type":"result","subtype":"complete","result":"Task completed","session_id":"test-123","cost_usd":0.001,"duration_ms":500,"duration_api_ms":300,"num_turns":1,"total_cost_usd":0.001,"is_error":false}"#;
282 let result = ZeroCopyMessageParser::parse(json);
283 if result.is_err() {
284 eprintln!("Error: {:?}", result.as_ref().err());
285 }
286 assert!(result.is_ok());
287
288 let message = result.unwrap();
289 match message {
290 Message::Result(msg) => {
291 assert_eq!(msg.result, Some("Task completed".to_string()));
292 assert!(!msg.is_error);
293 }
294 _ => panic!("Expected Result message"),
295 }
296 }
297
298 #[test]
299 fn test_zero_copy_parser_invalid_json() {
300 let json = r#"{"type":"assistant","invalid"#;
301 let result = ZeroCopyMessageParser::parse(json);
302 assert!(result.is_err());
303 }
304
305 #[test]
306 fn test_zero_copy_parser_bytes() {
307 let json = br#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
308 let result = ZeroCopyMessageParser::parse_bytes(json);
309 assert!(result.is_ok());
310 }
311
312 #[test]
313 fn test_zero_copy_parser_bytes_invalid_utf8() {
314 let invalid_bytes: &[u8] = &[0xff, 0xfe, 0xfd];
315 let result = ZeroCopyMessageParser::parse_bytes(invalid_bytes);
316 assert!(result.is_err());
317 }
318
319 #[test]
320 fn test_message_kind_detect_assistant() {
321 let json = r#"{"type":"assistant","message":{}}"#;
322 assert_eq!(MessageKind::detect(json), MessageKind::Assistant);
323 }
324
325 #[test]
326 fn test_message_kind_detect_system() {
327 let json = r#"{"type":"system","subtype":"init"}"#;
328 assert_eq!(MessageKind::detect(json), MessageKind::System);
329 }
330
331 #[test]
332 fn test_message_kind_detect_result() {
333 let json = r#"{"type":"result","session_id":"123"}"#;
334 assert_eq!(MessageKind::detect(json), MessageKind::Result);
335 }
336
337 #[test]
338 fn test_message_kind_detect_stream_event() {
339 let json = r#"{"type":"stream_event","event":"text"}}"#;
340 assert_eq!(MessageKind::detect(json), MessageKind::StreamEvent);
341 }
342
343 #[test]
344 fn test_message_kind_detect_user() {
345 let json = r#"{"type":"user","text":"Hello"}"#;
346 assert_eq!(MessageKind::detect(json), MessageKind::User);
347 }
348
349 #[test]
350 fn test_message_kind_detect_unknown() {
351 let json = r#"{"foo":"bar"}"#;
352 assert_eq!(MessageKind::detect(json), MessageKind::Unknown);
353 }
354
355 #[test]
356 fn test_message_kind_detect_with_spaces() {
357 let json = r#"{"type": "assistant", "message": {}}"#;
358 assert_eq!(MessageKind::detect(json), MessageKind::Assistant);
359 }
360
361 #[test]
362 fn test_parse_with_mode_zero_copy() {
363 let json = r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
364 let result = parse_with_mode(json, ParsingMode::ZeroCopy);
365 assert!(result.is_ok());
366
367 let message = result.unwrap();
368 match message {
369 Message::Assistant(msg) => {
370 assert!(!msg.message.content.is_empty());
371 }
372 _ => panic!("Expected Assistant message"),
373 }
374 }
375
376 #[test]
377 fn test_parse_with_mode_traditional() {
378 let json = r#"{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello"}]}}"#;
379 let result = parse_with_mode(json, ParsingMode::Traditional);
380 assert!(result.is_ok());
381
382 let message = result.unwrap();
383 match message {
384 Message::Assistant(msg) => {
385 assert!(!msg.message.content.is_empty());
386 }
387 _ => panic!("Expected Assistant message"),
388 }
389 }
390
391 #[test]
392 fn test_parsing_mode_default() {
393 assert_eq!(ParsingMode::default(), ParsingMode::Traditional);
395 }
396}