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: super::MessageRole::User,
33                content: vec![ContentBlock::Text(TextBlock {
34                    text: text.into(),
35                    citations: Vec::new(),
36                })],
37            },
38            session_id: Some(session_id),
39            parent_tool_use_id: None,
40            uuid: None,
41            timestamp: None,
42            tool_use_result: None,
43            subagent_type: None,
44            task_description: None,
45        })
46    }
47
48    /// Create a user message with content blocks
49    pub fn user_message_blocks(blocks: Vec<ContentBlock>, session_id: Uuid) -> Self {
50        ClaudeInput::User(UserMessage {
51            message: MessageContent {
52                role: super::MessageRole::User,
53                content: blocks,
54            },
55            session_id: Some(session_id),
56            parent_tool_use_id: None,
57            uuid: None,
58            timestamp: None,
59            tool_use_result: None,
60            subagent_type: None,
61            task_description: None,
62        })
63    }
64
65    /// Create an interrupt control message.
66    ///
67    /// Sends `{ "subtype": "interrupt" }` to the CLI subprocess's stdin,
68    /// telling Claude to stop its current response and return control
69    /// without killing the session.
70    pub fn interrupt() -> Self {
71        ClaudeInput::Raw(serde_json::to_value(super::SDKControlInterruptRequest::new()).unwrap())
72    }
73
74    /// Create a user message with an image and optional text
75    /// Only supports JPEG, PNG, GIF, and WebP media types
76    pub fn user_message_with_image(
77        image_data: String,
78        media_type: super::MediaType,
79        text: Option<String>,
80        session_id: Uuid,
81    ) -> Result<Self, String> {
82        // Validate media type
83        match &media_type {
84            super::MediaType::Jpeg
85            | super::MediaType::Png
86            | super::MediaType::Gif
87            | super::MediaType::Webp => {}
88            other => {
89                return Err(format!(
90                    "Invalid media type '{}'. Only JPEG, PNG, GIF, and WebP are supported.",
91                    other
92                ));
93            }
94        }
95
96        let mut blocks = vec![ContentBlock::Image(ImageBlock {
97            source: ImageSource {
98                source_type: super::ImageSourceType::Base64,
99                media_type,
100                data: image_data,
101            },
102        })];
103
104        if let Some(text_content) = text {
105            blocks.push(ContentBlock::Text(TextBlock {
106                text: text_content,
107                citations: Vec::new(),
108            }));
109        }
110
111        Ok(Self::user_message_blocks(blocks, session_id))
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    #[test]
120    fn test_serialize_user_message() {
121        let session_uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
122        let input = ClaudeInput::user_message("Hello, Claude!", session_uuid);
123        let json = serde_json::to_string(&input).unwrap();
124        assert!(json.contains("\"type\":\"user\""));
125        assert!(json.contains("\"role\":\"user\""));
126        assert!(json.contains("\"text\":\"Hello, Claude!\""));
127        assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
128    }
129}