1use serde::{Deserialize, Serialize};
2pub mod request;
3#[allow(unused_imports)]
4pub use request::*;
5pub mod response;
6#[allow(unused_imports)]
7pub use response::*;
8pub mod stream_response;
9#[allow(unused_imports)]
10pub use stream_response::*;
11
12#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
13pub struct Message {
14 pub role: Role,
15 pub content: MessageContent,
16}
17
18impl Message {
19 pub fn is_all_empty(&self) -> bool {
21 self.content.is_all_empty()
22 }
23
24 pub fn validate(&self) -> Result<(), String> {
26 self.content.validate()
27 }
28}
29
30#[derive(Debug, Deserialize, Clone, Default, PartialEq, Serialize)]
31#[serde(rename_all = "lowercase")]
32pub enum Role {
33 #[default]
34 User,
35 Assistant,
36}
37
38#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
39#[serde(untagged)]
40pub enum MessageContent {
41 Text(String),
42 Blocks(Vec<ContentBlock>),
43}
44
45impl MessageContent {
46 pub fn is_all_empty(&self) -> bool {
47 match self {
48 MessageContent::Text(s) => s.trim().is_empty(),
49 MessageContent::Blocks(blocks) => {
50 if blocks.is_empty() {
51 return true;
52 }
53 for block in blocks {
54 if !block.is_empty() {
55 return false;
56 }
57 }
58 true
59 }
60 }
61 }
62
63 pub fn validate(&self) -> Result<(), String> {
65 match self {
66 MessageContent::Text(_) => Ok(()),
67 MessageContent::Blocks(blocks) => {
68 for (i, block) in blocks.iter().enumerate() {
69 if !matches!(block, ContentBlock::Base(_) | ContentBlock::RequestOnly(_)) {
70 return Err(format!(
71 "Invalid content block type at index {}: {:?}. Only Text, Image, ToolUse, ToolResult, Document, Thinking, and RedactedThinking are allowed in request body.",
72 i, block
73 ));
74 }
75 }
76 Ok(())
77 }
78 }
79 }
80}
81
82#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
84#[serde(tag = "type")]
85pub enum BaseContentBlock {
86 #[serde(rename = "text")]
87 Text { text: String },
88 #[serde(rename = "thinking")]
89 Thinking {
90 thinking: String,
91 #[serde(skip_serializing_if = "Option::is_none")]
92 signature: Option<String>,
93 },
94 #[serde(rename = "tool_use")]
95 ToolUse(ToolUseContentBlock),
96}
97
98#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
99pub struct ToolUseContentBlock {
100 pub id: String,
101 pub name: String,
102 pub input: serde_json::Value,
103}
104
105#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
107#[serde(tag = "type")]
108pub enum RequestOnlyContentBlock {
109 #[serde(rename = "image")]
110 Image { source: ImageSource },
111 #[serde(rename = "document")]
112 Document {
113 #[serde(skip_serializing_if = "Option::is_none")]
114 source: Option<String>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 id: Option<String>,
117 },
118 #[serde(rename = "tool_result")]
119 ToolResult {
120 tool_use_id: String,
121 content: String,
122 },
123}
124
125#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
127#[serde(untagged)]
128pub enum ContentBlock {
129 Base(BaseContentBlock),
130 RequestOnly(RequestOnlyContentBlock),
131 RedactedThinking(RedactedThinkingContentBlock),
132}
133
134#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
135#[serde(untagged)]
136pub enum ResponseContentBlock {
137 Base(BaseContentBlock),
138 RedactedThinking(RedactedThinkingContentBlock),
139}
140
141#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
142#[serde(untagged)]
143pub enum RedactedThinkingContentBlock {
144 #[serde(rename = "redacted_thinking")]
145 RedactedThinking { data: String },
146}
147
148impl ContentBlock {
149 pub fn is_empty(&self) -> bool {
150 match self {
151 ContentBlock::Base(base) => match base {
152 BaseContentBlock::Text { text } => text.trim().is_empty(),
153 BaseContentBlock::ToolUse(tool_use) => {
154 tool_use.id.is_empty()
155 || tool_use.name.is_empty()
156 || !tool_use.input.is_object()
157 }
158 BaseContentBlock::Thinking { thinking, .. } => thinking.trim().is_empty(),
159 },
160 ContentBlock::RequestOnly(req_only) => match req_only {
161 RequestOnlyContentBlock::Image { source } => match source {
162 ImageSource::Base64 { media_type, data } => {
163 media_type.trim().is_empty() || data.trim().is_empty()
164 }
165 },
166 RequestOnlyContentBlock::Document { source, id } => {
167 source.is_none() || id.is_none()
168 }
169 RequestOnlyContentBlock::ToolResult {
170 tool_use_id,
171 content,
172 } => tool_use_id.is_empty() || content.trim().is_empty(),
173 },
174 ContentBlock::RedactedThinking(redacted_thinking) => match redacted_thinking {
175 RedactedThinkingContentBlock::RedactedThinking { data } => data.is_empty(),
176 },
177 }
178 }
179}
180
181#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
184#[serde(tag = "type")]
185pub enum DeltaContentBlock {
186 #[serde(rename = "text_delta")]
187 TextDelta { text: String },
188 #[serde(rename = "input_json_delta")]
189 InputJsonDelta { partial_json: String },
190 #[serde(rename = "thinking_delta")]
191 ThinkingDelta { thinking: String },
192 #[serde(rename = "signature_delta")]
193 SignatureDelta { signature: String },
194}
195
196impl DeltaContentBlock {
197 pub fn is_empty(&self) -> bool {
198 match self {
199 DeltaContentBlock::TextDelta { text } => text.trim().is_empty(),
200 DeltaContentBlock::InputJsonDelta { partial_json } => partial_json.is_empty(),
201 DeltaContentBlock::ThinkingDelta { thinking } => thinking.is_empty(),
202 DeltaContentBlock::SignatureDelta { signature } => signature.is_empty(),
203 }
204 }
205}
206
207#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
208#[serde(tag = "type")]
209pub enum ImageSource {
210 #[serde(rename = "base64")]
211 Base64 { media_type: String, data: String },
212}
213
214#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
215#[serde(tag = "type")]
216pub enum DocumentSource {
217 #[serde(rename = "base64")]
218 Base64 { media_type: String, data: String },
219}
220
221#[derive(Debug, Deserialize, Default, Clone, PartialEq, Serialize)]
222pub struct Usage {
223 pub input_tokens: Option<u32>,
224 pub output_tokens: u32,
225}
226
227#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
228#[serde(rename_all = "snake_case")]
229pub enum StopReason {
230 EndTurn,
231 MaxTokens,
232 StopSequence,
233 ToolUse,
234}