Skip to main content

claude_api/messages/
input.rs

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