Skip to main content

claude_api/messages/
input.rs

1//! Request-side message structures: [`MessageInput`], [`MessageContent`],
2//! [`SystemPrompt`].
3
4use serde::{Deserialize, Serialize};
5
6use crate::messages::content::ContentBlock;
7use crate::types::Role;
8
9/// One turn in the conversation history sent to the API.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[non_exhaustive]
12pub struct MessageInput {
13    /// Author of the turn.
14    pub role: Role,
15    /// Body of the turn.
16    pub content: MessageContent,
17}
18
19impl MessageInput {
20    /// A user-authored turn.
21    pub fn user(content: impl Into<MessageContent>) -> Self {
22        Self {
23            role: Role::User,
24            content: content.into(),
25        }
26    }
27
28    /// An assistant-authored turn (used to seed prefill).
29    pub fn assistant(content: impl Into<MessageContent>) -> Self {
30        Self {
31            role: Role::Assistant,
32            content: content.into(),
33        }
34    }
35}
36
37/// Content body of a request-side message: either a plain string or a
38/// sequence of [`ContentBlock`]s.
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
40#[serde(untagged)]
41pub enum MessageContent {
42    /// Plain text content.
43    Text(String),
44    /// Structured content composed of multiple blocks (text + image, etc.).
45    Blocks(Vec<ContentBlock>),
46}
47
48impl From<&str> for MessageContent {
49    fn from(s: &str) -> Self {
50        Self::Text(s.to_owned())
51    }
52}
53
54impl From<String> for MessageContent {
55    fn from(s: String) -> Self {
56        Self::Text(s)
57    }
58}
59
60impl From<Vec<ContentBlock>> for MessageContent {
61    fn from(v: Vec<ContentBlock>) -> Self {
62        Self::Blocks(v)
63    }
64}
65
66impl From<ContentBlock> for MessageContent {
67    fn from(b: ContentBlock) -> Self {
68        Self::Blocks(vec![b])
69    }
70}
71
72/// System prompt passed alongside a Messages request.
73///
74/// A plain string is the common case. The `Blocks` variant lets you apply
75/// `cache_control` to specific spans of the system prompt.
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77#[serde(untagged)]
78pub enum SystemPrompt {
79    /// Single-string system prompt.
80    Text(String),
81    /// Multi-block system prompt with optional per-block cache breakpoints.
82    Blocks(Vec<ContentBlock>),
83}
84
85impl From<&str> for SystemPrompt {
86    fn from(s: &str) -> Self {
87        Self::Text(s.to_owned())
88    }
89}
90
91impl From<String> for SystemPrompt {
92    fn from(s: String) -> Self {
93        Self::Text(s)
94    }
95}
96
97impl From<Vec<ContentBlock>> for SystemPrompt {
98    fn from(v: Vec<ContentBlock>) -> Self {
99        Self::Blocks(v)
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106    use pretty_assertions::assert_eq;
107    use serde_json::json;
108
109    #[test]
110    fn message_input_user_with_string_content() {
111        let m = MessageInput::user("hello");
112        assert_eq!(
113            serde_json::to_value(&m).unwrap(),
114            json!({"role": "user", "content": "hello"})
115        );
116    }
117
118    #[test]
119    fn message_input_assistant_with_blocks_content() {
120        let m = MessageInput::assistant(vec![ContentBlock::text("hi")]);
121        assert_eq!(
122            serde_json::to_value(&m).unwrap(),
123            json!({
124                "role": "assistant",
125                "content": [{"type": "text", "text": "hi"}]
126            })
127        );
128    }
129
130    #[test]
131    fn message_content_round_trips_text_and_blocks() {
132        let t: MessageContent = "x".into();
133        assert_eq!(serde_json::to_value(&t).unwrap(), json!("x"));
134
135        let b: MessageContent = ContentBlock::text("y").into();
136        assert_eq!(
137            serde_json::to_value(&b).unwrap(),
138            json!([{"type": "text", "text": "y"}])
139        );
140    }
141
142    #[test]
143    fn system_prompt_text_serializes_as_string() {
144        let s: SystemPrompt = "be concise".into();
145        assert_eq!(serde_json::to_value(&s).unwrap(), json!("be concise"));
146    }
147
148    #[test]
149    fn system_prompt_blocks_serializes_as_array() {
150        let s: SystemPrompt = vec![ContentBlock::text("be concise")].into();
151        assert_eq!(
152            serde_json::to_value(&s).unwrap(),
153            json!([{"type": "text", "text": "be concise"}])
154        );
155    }
156}