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#[allow(clippy::large_enum_variant)]
13#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(tag = "type", rename_all = "snake_case")]
15pub enum ClaudeInput {
16 User(UserMessage),
18
19 ControlRequest(ControlRequest),
21
22 ControlResponse(ControlResponse),
24
25 #[serde(untagged)]
27 Raw(Value),
28}
29
30impl ClaudeInput {
31 pub fn user_message(text: impl Into<String>, session_id: Uuid) -> Self {
33 ClaudeInput::User(UserMessage {
34 message: MessageContent {
35 role: super::MessageRole::User,
36 content: vec![ContentBlock::Text(TextBlock {
37 text: text.into(),
38 citations: Vec::new(),
39 })],
40 },
41 session_id: Some(session_id),
42 parent_tool_use_id: None,
43 uuid: None,
44 timestamp: None,
45 tool_use_result: None,
46 subagent_type: None,
47 task_description: None,
48 origin: None,
49 priority: None,
50 is_synthetic: None,
51 should_query: None,
52 is_meta: None,
53 is_visible_in_transcript_only: None,
54 is_virtual: None,
55 is_compact_summary: None,
56 summarize_metadata: None,
57 mcp_meta: None,
58 source_tool_use_id: None,
59 source_tool_assistant_uuid: None,
60 image_paste_ids: None,
61 client_platform: None,
62 inbound_origin: None,
63 is_replay: None,
64 file_attachments: None,
65 })
66 }
67
68 pub fn user_message_blocks(blocks: Vec<ContentBlock>, session_id: Uuid) -> Self {
70 ClaudeInput::User(UserMessage {
71 message: MessageContent {
72 role: super::MessageRole::User,
73 content: blocks,
74 },
75 session_id: Some(session_id),
76 parent_tool_use_id: None,
77 uuid: None,
78 timestamp: None,
79 tool_use_result: None,
80 subagent_type: None,
81 task_description: None,
82 origin: None,
83 priority: None,
84 is_synthetic: None,
85 should_query: None,
86 is_meta: None,
87 is_visible_in_transcript_only: None,
88 is_virtual: None,
89 is_compact_summary: None,
90 summarize_metadata: None,
91 mcp_meta: None,
92 source_tool_use_id: None,
93 source_tool_assistant_uuid: None,
94 image_paste_ids: None,
95 client_platform: None,
96 inbound_origin: None,
97 is_replay: None,
98 file_attachments: None,
99 })
100 }
101
102 pub fn interrupt() -> Self {
108 ClaudeInput::Raw(serde_json::to_value(super::SDKControlInterruptRequest::new()).unwrap())
109 }
110
111 pub fn user_message_with_image(
114 image_data: String,
115 media_type: super::MediaType,
116 text: Option<String>,
117 session_id: Uuid,
118 ) -> Result<Self, String> {
119 match &media_type {
121 super::MediaType::Jpeg
122 | super::MediaType::Png
123 | super::MediaType::Gif
124 | super::MediaType::Webp => {}
125 other => {
126 return Err(format!(
127 "Invalid media type '{}'. Only JPEG, PNG, GIF, and WebP are supported.",
128 other
129 ));
130 }
131 }
132
133 let mut blocks = vec![ContentBlock::Image(ImageBlock {
134 source: ImageSource {
135 source_type: super::ImageSourceType::Base64,
136 media_type,
137 data: image_data,
138 },
139 })];
140
141 if let Some(text_content) = text {
142 blocks.push(ContentBlock::Text(TextBlock {
143 text: text_content,
144 citations: Vec::new(),
145 }));
146 }
147
148 Ok(Self::user_message_blocks(blocks, session_id))
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_serialize_user_message() {
158 let session_uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
159 let input = ClaudeInput::user_message("Hello, Claude!", session_uuid);
160 let json = serde_json::to_string(&input).unwrap();
161 assert!(json.contains("\"type\":\"user\""));
162 assert!(json.contains("\"role\":\"user\""));
163 assert!(json.contains("\"text\":\"Hello, Claude!\""));
164 assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
165 }
166}