Skip to main content

claude_codes/io/
claude_input.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use uuid::Uuid;
4
5use super::content_blocks::{ContentBlock, ImageBlock, ImageSource, TextBlock};
6use super::control::{ControlRequest, ControlResponse};
7use super::message_types::{MessageContent, UserMessage};
8
9/// Top-level enum for all possible Claude input messages
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(tag = "type", rename_all = "snake_case")]
12pub enum ClaudeInput {
13    /// User message input
14    User(UserMessage),
15
16    /// Control request (for initialization handshake)
17    ControlRequest(ControlRequest),
18
19    /// Control response (for tool permission responses)
20    ControlResponse(ControlResponse),
21
22    /// Raw JSON for untyped messages
23    #[serde(untagged)]
24    Raw(Value),
25}
26
27impl ClaudeInput {
28    /// Create a simple text user message
29    pub fn user_message(text: impl Into<String>, session_id: Uuid) -> Self {
30        ClaudeInput::User(UserMessage {
31            message: MessageContent {
32                role: "user".to_string(),
33                content: vec![ContentBlock::Text(TextBlock { text: text.into() })],
34            },
35            session_id: Some(session_id),
36        })
37    }
38
39    /// Create a user message with content blocks
40    pub fn user_message_blocks(blocks: Vec<ContentBlock>, session_id: Uuid) -> Self {
41        ClaudeInput::User(UserMessage {
42            message: MessageContent {
43                role: "user".to_string(),
44                content: blocks,
45            },
46            session_id: Some(session_id),
47        })
48    }
49
50    /// Create a user message with an image and optional text
51    /// Only supports JPEG, PNG, GIF, and WebP media types
52    pub fn user_message_with_image(
53        image_data: String,
54        media_type: String,
55        text: Option<String>,
56        session_id: Uuid,
57    ) -> Result<Self, String> {
58        // Validate media type
59        let valid_types = ["image/jpeg", "image/png", "image/gif", "image/webp"];
60
61        if !valid_types.contains(&media_type.as_str()) {
62            return Err(format!(
63                "Invalid media type '{}'. Only JPEG, PNG, GIF, and WebP are supported.",
64                media_type
65            ));
66        }
67
68        let mut blocks = vec![ContentBlock::Image(ImageBlock {
69            source: ImageSource {
70                source_type: "base64".to_string(),
71                media_type,
72                data: image_data,
73            },
74        })];
75
76        if let Some(text_content) = text {
77            blocks.push(ContentBlock::Text(TextBlock { text: text_content }));
78        }
79
80        Ok(Self::user_message_blocks(blocks, session_id))
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_serialize_user_message() {
90        let session_uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
91        let input = ClaudeInput::user_message("Hello, Claude!", session_uuid);
92        let json = serde_json::to_string(&input).unwrap();
93        assert!(json.contains("\"type\":\"user\""));
94        assert!(json.contains("\"role\":\"user\""));
95        assert!(json.contains("\"text\":\"Hello, Claude!\""));
96        assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
97    }
98}