1use serde::{Deserialize, Serialize};
33use serde_json::Value;
34use std::fmt;
35use tracing::debug;
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(tag = "type", rename_all = "snake_case")]
40pub enum ClaudeInput {
41 User(UserMessage),
43
44 #[serde(untagged)]
46 Raw(Value),
47}
48
49#[derive(Debug, Clone)]
51pub struct ParseError {
52 pub raw_json: Value,
54 pub error_message: String,
56}
57
58impl fmt::Display for ParseError {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(f, "Failed to parse ClaudeOutput: {}", self.error_message)
61 }
62}
63
64impl std::error::Error for ParseError {}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(tag = "type", rename_all = "snake_case")]
69pub enum ClaudeOutput {
70 System(SystemMessage),
72
73 User(UserMessage),
75
76 Assistant(AssistantMessage),
78
79 Result(ResultMessage),
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct UserMessage {
86 pub message: MessageContent,
87 #[serde(skip_serializing_if = "Option::is_none")]
88 pub session_id: Option<String>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct MessageContent {
94 pub role: String,
95 pub content: Vec<ContentBlock>,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct SystemMessage {
101 pub subtype: String,
102 #[serde(flatten)]
103 pub data: Value, }
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct AssistantMessage {
109 pub message: AssistantMessageContent,
110 pub session_id: String,
111 #[serde(skip_serializing_if = "Option::is_none")]
112 pub uuid: Option<String>,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub parent_tool_use_id: Option<String>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct AssistantMessageContent {
120 pub id: String,
121 pub role: String,
122 pub model: String,
123 pub content: Vec<ContentBlock>,
124 #[serde(skip_serializing_if = "Option::is_none")]
125 pub stop_reason: Option<String>,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub stop_sequence: Option<String>,
128 #[serde(skip_serializing_if = "Option::is_none")]
129 pub usage: Option<serde_json::Value>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134#[serde(tag = "type", rename_all = "snake_case")]
135pub enum ContentBlock {
136 Text(TextBlock),
137 Thinking(ThinkingBlock),
138 ToolUse(ToolUseBlock),
139 ToolResult(ToolResultBlock),
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct TextBlock {
145 pub text: String,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct ThinkingBlock {
151 pub thinking: String,
152 pub signature: String,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct ToolUseBlock {
158 pub id: String,
159 pub name: String,
160 pub input: Value,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct ToolResultBlock {
166 pub tool_use_id: String,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub content: Option<ToolResultContent>,
169 #[serde(skip_serializing_if = "Option::is_none")]
170 pub is_error: Option<bool>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175#[serde(untagged)]
176pub enum ToolResultContent {
177 Text(String),
178 Structured(Vec<Value>),
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ResultMessage {
184 pub subtype: ResultSubtype,
185 pub is_error: bool,
186 pub duration_ms: u64,
187 pub duration_api_ms: u64,
188 pub num_turns: u32,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
191 pub result: Option<String>,
192
193 pub session_id: String,
194 pub total_cost_usd: f64,
195
196 #[serde(skip_serializing_if = "Option::is_none")]
197 pub usage: Option<UsageInfo>,
198
199 #[serde(default)]
200 pub permission_denials: Vec<Value>,
201
202 #[serde(skip_serializing_if = "Option::is_none")]
203 pub uuid: Option<String>,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208#[serde(rename_all = "snake_case")]
209pub enum ResultSubtype {
210 Success,
211 ErrorMaxTurns,
212 ErrorDuringExecution,
213}
214
215#[derive(Debug, Clone, Serialize, Deserialize)]
217#[serde(tag = "type", rename_all = "snake_case")]
218pub enum McpServerConfig {
219 Stdio(McpStdioServerConfig),
220 Sse(McpSseServerConfig),
221 Http(McpHttpServerConfig),
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct McpStdioServerConfig {
227 pub command: String,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub args: Option<Vec<String>>,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub env: Option<std::collections::HashMap<String, String>>,
232}
233
234#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct McpSseServerConfig {
237 pub url: String,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub headers: Option<std::collections::HashMap<String, String>>,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct McpHttpServerConfig {
245 pub url: String,
246 #[serde(skip_serializing_if = "Option::is_none")]
247 pub headers: Option<std::collections::HashMap<String, String>>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase")]
253pub enum PermissionMode {
254 Default,
255 AcceptEdits,
256 BypassPermissions,
257 Plan,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct UsageInfo {
263 pub input_tokens: u32,
264 pub cache_creation_input_tokens: u32,
265 pub cache_read_input_tokens: u32,
266 pub output_tokens: u32,
267 pub server_tool_use: ServerToolUse,
268 pub service_tier: String,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct ServerToolUse {
274 pub web_search_requests: u32,
275}
276
277impl ClaudeInput {
278 pub fn user_message(text: impl Into<String>, session_id: impl Into<String>) -> Self {
280 ClaudeInput::User(UserMessage {
281 message: MessageContent {
282 role: "user".to_string(),
283 content: vec![ContentBlock::Text(TextBlock { text: text.into() })],
284 },
285 session_id: Some(session_id.into()),
286 })
287 }
288
289 pub fn user_message_blocks(blocks: Vec<ContentBlock>, session_id: impl Into<String>) -> Self {
291 ClaudeInput::User(UserMessage {
292 message: MessageContent {
293 role: "user".to_string(),
294 content: blocks,
295 },
296 session_id: Some(session_id.into()),
297 })
298 }
299}
300
301impl ClaudeOutput {
302 pub fn message_type(&self) -> String {
304 match self {
305 ClaudeOutput::System(_) => "system".to_string(),
306 ClaudeOutput::User(_) => "user".to_string(),
307 ClaudeOutput::Assistant(_) => "assistant".to_string(),
308 ClaudeOutput::Result(_) => "result".to_string(),
309 }
310 }
311
312 pub fn is_error(&self) -> bool {
314 matches!(self, ClaudeOutput::Result(r) if r.is_error)
315 }
316
317 pub fn is_assistant_message(&self) -> bool {
319 matches!(self, ClaudeOutput::Assistant(_))
320 }
321
322 pub fn is_system_message(&self) -> bool {
324 matches!(self, ClaudeOutput::System(_))
325 }
326
327 pub fn parse_json(s: &str) -> Result<ClaudeOutput, ParseError> {
329 debug!("[IO] Attempting to parse JSON: {}", s);
330
331 let value: Value = serde_json::from_str(s).map_err(|e| {
333 debug!("[IO] Failed to parse as JSON Value: {}", e);
334 ParseError {
335 raw_json: Value::String(s.to_string()),
336 error_message: format!("Invalid JSON: {}", e),
337 }
338 })?;
339
340 debug!("[IO] Successfully parsed as JSON Value, attempting to deserialize as ClaudeOutput");
341
342 serde_json::from_value::<ClaudeOutput>(value.clone()).map_err(|e| {
344 debug!("[IO] Failed to deserialize as ClaudeOutput: {}", e);
345 ParseError {
346 raw_json: value,
347 error_message: e.to_string(),
348 }
349 })
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_serialize_user_message() {
359 let input = ClaudeInput::user_message("Hello, Claude!", "session-123");
360 let json = serde_json::to_string(&input).unwrap();
361 assert!(json.contains("\"type\":\"user\""));
362 assert!(json.contains("\"role\":\"user\""));
363 assert!(json.contains("\"text\":\"Hello, Claude!\""));
364 assert!(json.contains("session-123"));
365 }
366
367 #[test]
368 fn test_deserialize_assistant_message() {
369 let json = r#"{
370 "type": "assistant",
371 "message": {
372 "id": "msg_123",
373 "role": "assistant",
374 "model": "claude-3-sonnet",
375 "content": [{"type": "text", "text": "Hello! How can I help you?"}]
376 },
377 "session_id": "123"
378 }"#;
379
380 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
381 assert!(output.is_assistant_message());
382 }
383
384 #[test]
385 fn test_deserialize_result_message() {
386 let json = r#"{
387 "type": "result",
388 "subtype": "success",
389 "is_error": false,
390 "duration_ms": 100,
391 "duration_api_ms": 200,
392 "num_turns": 1,
393 "result": "Done",
394 "session_id": "123",
395 "total_cost_usd": 0.01,
396 "permission_denials": []
397 }"#;
398
399 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
400 assert!(!output.is_error());
401 }
402}