1use serde::{Deserialize, Deserializer, Serialize, Serializer};
33use serde_json::Value;
34use std::fmt;
35use tracing::debug;
36use uuid::Uuid;
37
38fn serialize_optional_uuid<S>(uuid: &Option<Uuid>, serializer: S) -> Result<S::Ok, S::Error>
40where
41 S: Serializer,
42{
43 match uuid {
44 Some(id) => serializer.serialize_str(&id.to_string()),
45 None => serializer.serialize_none(),
46 }
47}
48
49fn deserialize_optional_uuid<'de, D>(deserializer: D) -> Result<Option<Uuid>, D::Error>
51where
52 D: Deserializer<'de>,
53{
54 let opt_str: Option<String> = Option::deserialize(deserializer)?;
55 match opt_str {
56 Some(s) => Uuid::parse_str(&s)
57 .map(Some)
58 .map_err(serde::de::Error::custom),
59 None => Ok(None),
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(tag = "type", rename_all = "snake_case")]
66pub enum ClaudeInput {
67 User(UserMessage),
69
70 #[serde(untagged)]
72 Raw(Value),
73}
74
75#[derive(Debug, Clone)]
77pub struct ParseError {
78 pub raw_json: Value,
80 pub error_message: String,
82}
83
84impl fmt::Display for ParseError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 write!(f, "Failed to parse ClaudeOutput: {}", self.error_message)
87 }
88}
89
90impl std::error::Error for ParseError {}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94#[serde(tag = "type", rename_all = "snake_case")]
95pub enum ClaudeOutput {
96 System(SystemMessage),
98
99 User(UserMessage),
101
102 Assistant(AssistantMessage),
104
105 Result(ResultMessage),
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct UserMessage {
112 pub message: MessageContent,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 #[serde(
115 serialize_with = "serialize_optional_uuid",
116 deserialize_with = "deserialize_optional_uuid"
117 )]
118 pub session_id: Option<Uuid>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct MessageContent {
124 pub role: String,
125 pub content: Vec<ContentBlock>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct SystemMessage {
131 pub subtype: String,
132 #[serde(flatten)]
133 pub data: Value, }
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct AssistantMessage {
139 pub message: AssistantMessageContent,
140 pub session_id: String,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub uuid: Option<String>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub parent_tool_use_id: Option<String>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct AssistantMessageContent {
150 pub id: String,
151 pub role: String,
152 pub model: String,
153 pub content: Vec<ContentBlock>,
154 #[serde(skip_serializing_if = "Option::is_none")]
155 pub stop_reason: Option<String>,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub stop_sequence: Option<String>,
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub usage: Option<serde_json::Value>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
164#[serde(tag = "type", rename_all = "snake_case")]
165pub enum ContentBlock {
166 Text(TextBlock),
167 Image(ImageBlock),
168 Thinking(ThinkingBlock),
169 ToolUse(ToolUseBlock),
170 ToolResult(ToolResultBlock),
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct TextBlock {
176 pub text: String,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ImageBlock {
182 pub source: ImageSource,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct ImageSource {
188 #[serde(rename = "type")]
189 pub source_type: String, pub media_type: String, pub data: String, }
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct ThinkingBlock {
197 pub thinking: String,
198 pub signature: String,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct ToolUseBlock {
204 pub id: String,
205 pub name: String,
206 pub input: Value,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct ToolResultBlock {
212 pub tool_use_id: String,
213 #[serde(skip_serializing_if = "Option::is_none")]
214 pub content: Option<ToolResultContent>,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub is_error: Option<bool>,
217}
218
219#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(untagged)]
222pub enum ToolResultContent {
223 Text(String),
224 Structured(Vec<Value>),
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229pub struct ResultMessage {
230 pub subtype: ResultSubtype,
231 pub is_error: bool,
232 pub duration_ms: u64,
233 pub duration_api_ms: u64,
234 pub num_turns: u32,
235
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub result: Option<String>,
238
239 pub session_id: String,
240 pub total_cost_usd: f64,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
243 pub usage: Option<UsageInfo>,
244
245 #[serde(default)]
246 pub permission_denials: Vec<Value>,
247
248 #[serde(skip_serializing_if = "Option::is_none")]
249 pub uuid: Option<String>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(rename_all = "snake_case")]
255pub enum ResultSubtype {
256 Success,
257 ErrorMaxTurns,
258 ErrorDuringExecution,
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263#[serde(tag = "type", rename_all = "snake_case")]
264pub enum McpServerConfig {
265 Stdio(McpStdioServerConfig),
266 Sse(McpSseServerConfig),
267 Http(McpHttpServerConfig),
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct McpStdioServerConfig {
273 pub command: String,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub args: Option<Vec<String>>,
276 #[serde(skip_serializing_if = "Option::is_none")]
277 pub env: Option<std::collections::HashMap<String, String>>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct McpSseServerConfig {
283 pub url: String,
284 #[serde(skip_serializing_if = "Option::is_none")]
285 pub headers: Option<std::collections::HashMap<String, String>>,
286}
287
288#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct McpHttpServerConfig {
291 pub url: String,
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub headers: Option<std::collections::HashMap<String, String>>,
294}
295
296#[derive(Debug, Clone, Serialize, Deserialize)]
298#[serde(rename_all = "camelCase")]
299pub enum PermissionMode {
300 Default,
301 AcceptEdits,
302 BypassPermissions,
303 Plan,
304}
305
306#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct UsageInfo {
309 pub input_tokens: u32,
310 pub cache_creation_input_tokens: u32,
311 pub cache_read_input_tokens: u32,
312 pub output_tokens: u32,
313 pub server_tool_use: ServerToolUse,
314 pub service_tier: String,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct ServerToolUse {
320 pub web_search_requests: u32,
321}
322
323impl ClaudeInput {
324 pub fn user_message(text: impl Into<String>, session_id: Uuid) -> Self {
326 ClaudeInput::User(UserMessage {
327 message: MessageContent {
328 role: "user".to_string(),
329 content: vec![ContentBlock::Text(TextBlock { text: text.into() })],
330 },
331 session_id: Some(session_id),
332 })
333 }
334
335 pub fn user_message_blocks(blocks: Vec<ContentBlock>, session_id: Uuid) -> Self {
337 ClaudeInput::User(UserMessage {
338 message: MessageContent {
339 role: "user".to_string(),
340 content: blocks,
341 },
342 session_id: Some(session_id),
343 })
344 }
345
346 pub fn user_message_with_image(
349 image_data: String,
350 media_type: String,
351 text: Option<String>,
352 session_id: Uuid,
353 ) -> Result<Self, String> {
354 let valid_types = ["image/jpeg", "image/png", "image/gif", "image/webp"];
356
357 if !valid_types.contains(&media_type.as_str()) {
358 return Err(format!(
359 "Invalid media type '{}'. Only JPEG, PNG, GIF, and WebP are supported.",
360 media_type
361 ));
362 }
363
364 let mut blocks = vec![ContentBlock::Image(ImageBlock {
365 source: ImageSource {
366 source_type: "base64".to_string(),
367 media_type,
368 data: image_data,
369 },
370 })];
371
372 if let Some(text_content) = text {
373 blocks.push(ContentBlock::Text(TextBlock { text: text_content }));
374 }
375
376 Ok(Self::user_message_blocks(blocks, session_id))
377 }
378}
379
380impl ClaudeOutput {
381 pub fn message_type(&self) -> String {
383 match self {
384 ClaudeOutput::System(_) => "system".to_string(),
385 ClaudeOutput::User(_) => "user".to_string(),
386 ClaudeOutput::Assistant(_) => "assistant".to_string(),
387 ClaudeOutput::Result(_) => "result".to_string(),
388 }
389 }
390
391 pub fn is_error(&self) -> bool {
393 matches!(self, ClaudeOutput::Result(r) if r.is_error)
394 }
395
396 pub fn is_assistant_message(&self) -> bool {
398 matches!(self, ClaudeOutput::Assistant(_))
399 }
400
401 pub fn is_system_message(&self) -> bool {
403 matches!(self, ClaudeOutput::System(_))
404 }
405
406 pub fn parse_json(s: &str) -> Result<ClaudeOutput, ParseError> {
408 debug!("[IO] Attempting to parse JSON: {}", s);
409
410 let value: Value = serde_json::from_str(s).map_err(|e| {
412 debug!("[IO] Failed to parse as JSON Value: {}", e);
413 ParseError {
414 raw_json: Value::String(s.to_string()),
415 error_message: format!("Invalid JSON: {}", e),
416 }
417 })?;
418
419 debug!("[IO] Successfully parsed as JSON Value, attempting to deserialize as ClaudeOutput");
420
421 serde_json::from_value::<ClaudeOutput>(value.clone()).map_err(|e| {
423 debug!("[IO] Failed to deserialize as ClaudeOutput: {}", e);
424 ParseError {
425 raw_json: value,
426 error_message: e.to_string(),
427 }
428 })
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_serialize_user_message() {
438 let session_uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
439 let input = ClaudeInput::user_message("Hello, Claude!", session_uuid);
440 let json = serde_json::to_string(&input).unwrap();
441 assert!(json.contains("\"type\":\"user\""));
442 assert!(json.contains("\"role\":\"user\""));
443 assert!(json.contains("\"text\":\"Hello, Claude!\""));
444 assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
445 }
446
447 #[test]
448 fn test_deserialize_assistant_message() {
449 let json = r#"{
450 "type": "assistant",
451 "message": {
452 "id": "msg_123",
453 "role": "assistant",
454 "model": "claude-3-sonnet",
455 "content": [{"type": "text", "text": "Hello! How can I help you?"}]
456 },
457 "session_id": "123"
458 }"#;
459
460 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
461 assert!(output.is_assistant_message());
462 }
463
464 #[test]
465 fn test_deserialize_result_message() {
466 let json = r#"{
467 "type": "result",
468 "subtype": "success",
469 "is_error": false,
470 "duration_ms": 100,
471 "duration_api_ms": 200,
472 "num_turns": 1,
473 "result": "Done",
474 "session_id": "123",
475 "total_cost_usd": 0.01,
476 "permission_denials": []
477 }"#;
478
479 let output: ClaudeOutput = serde_json::from_str(json).unwrap();
480 assert!(!output.is_error());
481 }
482}