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 tool_result_meta: None,
59 source_tool_use_id: None,
60 source_tool_assistant_uuid: None,
61 image_paste_ids: None,
62 client_platform: None,
63 inbound_origin: None,
64 is_replay: None,
65 file_attachments: None,
66 })
67 }
68
69 pub fn user_message_blocks(blocks: Vec<ContentBlock>, session_id: Uuid) -> Self {
71 ClaudeInput::User(UserMessage {
72 message: MessageContent {
73 role: super::MessageRole::User,
74 content: blocks,
75 },
76 session_id: Some(session_id),
77 parent_tool_use_id: None,
78 uuid: None,
79 timestamp: None,
80 tool_use_result: None,
81 subagent_type: None,
82 task_description: None,
83 origin: None,
84 priority: None,
85 is_synthetic: None,
86 should_query: None,
87 is_meta: None,
88 is_visible_in_transcript_only: None,
89 is_virtual: None,
90 is_compact_summary: None,
91 summarize_metadata: None,
92 mcp_meta: None,
93 tool_result_meta: None,
94 source_tool_use_id: None,
95 source_tool_assistant_uuid: None,
96 image_paste_ids: None,
97 client_platform: None,
98 inbound_origin: None,
99 is_replay: None,
100 file_attachments: None,
101 })
102 }
103
104 pub fn interrupt(request_id: impl Into<String>) -> Self {
115 ClaudeInput::ControlRequest(ControlRequest {
116 request_id: request_id.into(),
117 request: super::ControlRequestPayload::Interrupt,
118 })
119 }
120
121 pub fn user_message_with_image(
124 image_data: String,
125 media_type: super::MediaType,
126 text: Option<String>,
127 session_id: Uuid,
128 ) -> Result<Self, String> {
129 match &media_type {
131 super::MediaType::Jpeg
132 | super::MediaType::Png
133 | super::MediaType::Gif
134 | super::MediaType::Webp => {}
135 other => {
136 return Err(format!(
137 "Invalid media type '{}'. Only JPEG, PNG, GIF, and WebP are supported.",
138 other
139 ));
140 }
141 }
142
143 let mut blocks = vec![ContentBlock::Image(ImageBlock {
144 source: ImageSource {
145 source_type: super::ImageSourceType::Base64,
146 media_type,
147 data: image_data,
148 },
149 })];
150
151 if let Some(text_content) = text {
152 blocks.push(ContentBlock::Text(TextBlock {
153 text: text_content,
154 citations: Vec::new(),
155 }));
156 }
157
158 Ok(Self::user_message_blocks(blocks, session_id))
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_interrupt_serializes_to_control_request_envelope() {
168 let input = ClaudeInput::interrupt("interrupt-abc");
169 assert_eq!(
170 serde_json::to_value(&input).unwrap(),
171 serde_json::json!({
172 "type": "control_request",
173 "request_id": "interrupt-abc",
174 "request": {"subtype": "interrupt"}
175 })
176 );
177 }
178
179 #[test]
180 fn test_serialize_user_message() {
181 let session_uuid = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
182 let input = ClaudeInput::user_message("Hello, Claude!", session_uuid);
183 let json = serde_json::to_string(&input).unwrap();
184 assert!(json.contains("\"type\":\"user\""));
185 assert!(json.contains("\"role\":\"user\""));
186 assert!(json.contains("\"text\":\"Hello, Claude!\""));
187 assert!(json.contains("550e8400-e29b-41d4-a716-446655440000"));
188 }
189}