Skip to main content

claude_code_sdk_rust/internal/
parser.rs

1use crate::error::{ClaudeSDKError, MessageParseError, Result};
2use crate::types::{
3    HookEventMessage, Message, MirrorErrorMessage, TaskNotificationMessage, TaskProgressMessage,
4    TaskStartedMessage, TaskUpdatedMessage,
5};
6
7const KNOWN_MESSAGE_TYPES: &[&str] = &[
8    "user",
9    "assistant",
10    "system",
11    "result",
12    "stream_event",
13    "rate_limit_event",
14];
15
16pub fn parse_message_line(line: &str) -> Result<Option<Message>> {
17    let value = serde_json::from_str::<serde_json::Value>(line)?;
18    parse_message_value(value)
19}
20
21pub fn parse_message_value(value: serde_json::Value) -> Result<Option<Message>> {
22    let message_type = value.get("type").and_then(|v| v.as_str()).ok_or_else(|| {
23        let data = value.as_object().cloned();
24        let mut error = MessageParseError::new("Message missing 'type' field");
25        if let Some(data) = data {
26            error = error.with_data(data);
27        }
28        ClaudeSDKError::MessageParse(error)
29    })?;
30
31    if !KNOWN_MESSAGE_TYPES.contains(&message_type) {
32        return Ok(None);
33    }
34
35    if message_type == "system" {
36        return parse_system_message_value(value);
37    }
38
39    match serde_json::from_value::<Message>(value.clone()) {
40        Ok(message) => Ok(Some(message)),
41        Err(err) => Err(parse_error_with_payload(err, &value)),
42    }
43}
44
45// Surface the offending payload alongside the serde error; a bare
46// "invalid type: sequence, expected a map" is undebuggable without it.
47fn parse_error_with_payload(err: serde_json::Error, value: &serde_json::Value) -> ClaudeSDKError {
48    let payload = value.to_string();
49    let payload = if payload.len() > 600 {
50        let cut = payload
51            .char_indices()
52            .take_while(|(idx, _)| *idx <= 600)
53            .last()
54            .map(|(idx, ch)| idx + ch.len_utf8())
55            .unwrap_or(payload.len());
56        format!("{}...", &payload[..cut])
57    } else {
58        payload
59    };
60    let mut error =
61        MessageParseError::new(format!("Failed to parse CLI message: {err}; payload: {payload}"));
62    if let Some(data) = value.as_object() {
63        error = error.with_data(data.clone());
64    }
65    ClaudeSDKError::MessageParse(error)
66}
67
68fn parse_system_message_value(value: serde_json::Value) -> Result<Option<Message>> {
69    let subtype = value.get("subtype").and_then(|v| v.as_str());
70    match subtype {
71        Some("task_started") => parse_task_started(value)
72            .map(Message::TaskStartedMsg)
73            .map(Some),
74        Some("task_progress") => parse_task_progress(value)
75            .map(Message::TaskProgressMsg)
76            .map(Some),
77        Some("task_notification") => parse_task_notification(value)
78            .map(Message::TaskNotificationMsg)
79            .map(Some),
80        Some("task_updated") => parse_task_updated(value)
81            .map(Message::TaskUpdatedMsg)
82            .map(Some),
83        Some("hook_started" | "hook_response") => {
84            parse_hook_event(value).map(Message::HookEventMsg).map(Some)
85        }
86        Some("mirror_error") => parse_mirror_error(value)
87            .map(Message::MirrorErrorMsg)
88            .map(Some),
89        _ => serde_json::from_value::<Message>(value)
90            .map(Some)
91            .map_err(ClaudeSDKError::Serialization),
92    }
93}
94
95fn parse_mirror_error(value: serde_json::Value) -> Result<MirrorErrorMessage> {
96    let mut data = value.as_object().cloned().ok_or_else(|| {
97        ClaudeSDKError::MessageParse(MessageParseError::new("System message must be an object"))
98    })?;
99    data.remove("type");
100    let key = data.get("key").and_then(|value| value.as_object()).cloned();
101    let error = data
102        .get("error")
103        .and_then(|value| value.as_str())
104        .unwrap_or_default()
105        .to_string();
106    Ok(MirrorErrorMessage { key, error, data })
107}
108
109fn parse_task_started(value: serde_json::Value) -> Result<TaskStartedMessage> {
110    serde_json::from_value::<TaskStartedMessage>(strip_system_fields(value)?)
111        .map_err(ClaudeSDKError::Serialization)
112}
113
114fn parse_task_progress(value: serde_json::Value) -> Result<TaskProgressMessage> {
115    serde_json::from_value::<TaskProgressMessage>(strip_system_fields(value)?)
116        .map_err(ClaudeSDKError::Serialization)
117}
118
119fn parse_task_notification(value: serde_json::Value) -> Result<TaskNotificationMessage> {
120    serde_json::from_value::<TaskNotificationMessage>(strip_system_fields(value)?)
121        .map_err(ClaudeSDKError::Serialization)
122}
123
124// Parsed defensively: a terminal task completion sometimes arrives only as a
125// `task_updated` patch (no separate `task_notification`), and the patch may omit
126// uuid/session_id. `status` is derived from `patch.status` (the CLI sets it on
127// terminal transitions); an unrecognized status falls back to `None` while the
128// full patch is preserved on `.patch` for callers that need more.
129fn parse_task_updated(value: serde_json::Value) -> Result<TaskUpdatedMessage> {
130    let data = value.as_object().ok_or_else(|| {
131        ClaudeSDKError::MessageParse(MessageParseError::new("System message must be an object"))
132    })?;
133    let task_id = data
134        .get("task_id")
135        .and_then(|v| v.as_str())
136        .unwrap_or_default()
137        .to_string();
138    let patch = data
139        .get("patch")
140        .and_then(|v| v.as_object())
141        .cloned()
142        .unwrap_or_default();
143    let status = patch.get("status").and_then(|v| {
144        serde_json::from_value::<crate::types::TaskUpdatedStatus>(v.clone()).ok()
145    });
146    let session_id = data
147        .get("session_id")
148        .and_then(|v| v.as_str())
149        .map(|s| s.to_string());
150    let uuid = data
151        .get("uuid")
152        .and_then(|v| v.as_str())
153        .map(|s| s.to_string());
154    Ok(TaskUpdatedMessage {
155        task_id,
156        patch,
157        status,
158        session_id,
159        uuid,
160    })
161}
162
163fn parse_hook_event(value: serde_json::Value) -> Result<HookEventMessage> {
164    let mut data = value.as_object().cloned().ok_or_else(|| {
165        ClaudeSDKError::MessageParse(MessageParseError::new("System message must be an object"))
166    })?;
167    let subtype = data
168        .get("subtype")
169        .and_then(|value| value.as_str())
170        .unwrap_or_default()
171        .to_string();
172    let hook_event_name = data
173        .get("hook_event")
174        .or_else(|| data.get("hook_name"))
175        .and_then(|value| value.as_str())
176        .map(ToString::to_string);
177    let session_id = data
178        .get("session_id")
179        .and_then(|value| value.as_str())
180        .map(ToString::to_string);
181    let uuid = data
182        .get("uuid")
183        .and_then(|value| value.as_str())
184        .map(ToString::to_string);
185    data.remove("type");
186    Ok(HookEventMessage {
187        subtype,
188        hook_event_name,
189        session_id,
190        uuid,
191        data,
192    })
193}
194
195fn strip_system_fields(value: serde_json::Value) -> Result<serde_json::Value> {
196    let mut data = value.as_object().cloned().ok_or_else(|| {
197        ClaudeSDKError::MessageParse(MessageParseError::new("System message must be an object"))
198    })?;
199    data.remove("type");
200    data.remove("subtype");
201    Ok(serde_json::Value::Object(data))
202}