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