use serde::{Deserialize, Serialize};
use crate::types::{ContentBlock, MessageRole, Model, StopReason, Usage};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct ContainerInfo {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_at: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Message {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub container: Option<ContainerInfo>,
pub content: Vec<ContentBlock>,
pub model: Model,
pub role: MessageRole,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<StopReason>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequence: Option<String>,
pub r#type: String,
pub usage: Usage,
}
impl Message {
pub fn new(id: String, content: Vec<ContentBlock>, model: Model, usage: Usage) -> Self {
Self {
id,
container: None,
content,
model,
role: MessageRole::Assistant,
stop_reason: None,
stop_sequence: None,
r#type: "message".to_string(),
usage,
}
}
pub fn with_stop_reason(mut self, stop_reason: StopReason) -> Self {
self.stop_reason = Some(stop_reason);
self
}
pub fn with_stop_sequence(mut self, stop_sequence: String) -> Self {
self.stop_sequence = Some(stop_sequence);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::TextBlock;
use serde_json::{json, to_value};
#[test]
fn message_serialization() {
let text_block = TextBlock::new("Hello, I'm Claude.".to_string());
let content = vec![ContentBlock::Text(text_block)];
let model = Model::Known(crate::types::KnownModel::ClaudeSonnet46);
let usage = Usage::new(50, 100);
let message = Message::new("msg_012345".to_string(), content, model, usage);
let json = to_value(&message).unwrap();
assert_eq!(
json,
json!({
"id": "msg_012345",
"content": [
{
"text": "Hello, I'm Claude.",
"type": "text"
}
],
"model": "claude-sonnet-4-6",
"role": "assistant",
"type": "message",
"usage": {
"input_tokens": 50,
"output_tokens": 100
}
})
);
}
#[test]
fn message_with_stop_reason() {
let text_block = TextBlock::new("Hello, I'm Claude.".to_string());
let content = vec![ContentBlock::Text(text_block)];
let model = Model::Known(crate::types::KnownModel::ClaudeSonnet46);
let usage = Usage::new(50, 100);
let message = Message::new("msg_012345".to_string(), content, model, usage)
.with_stop_reason(StopReason::EndTurn);
let json = to_value(&message).unwrap();
assert_eq!(
json,
json!({
"id": "msg_012345",
"content": [
{
"text": "Hello, I'm Claude.",
"type": "text"
}
],
"model": "claude-sonnet-4-6",
"role": "assistant",
"stop_reason": "end_turn",
"type": "message",
"usage": {
"input_tokens": 50,
"output_tokens": 100
}
})
);
}
#[test]
fn message_deserialization() {
let json = json!({
"id": "msg_012345",
"content": [
{
"text": "Hello, I'm Claude.",
"type": "text"
}
],
"model": "claude-sonnet-4-6",
"role": "assistant",
"stop_reason": "end_turn",
"stop_sequence": "###",
"type": "message",
"usage": {
"input_tokens": 50,
"output_tokens": 100,
"server_tool_use": {
"web_search_requests": 5
}
}
});
let message: Message = serde_json::from_value(json).unwrap();
assert_eq!(message.id, "msg_012345");
assert_eq!(message.role, MessageRole::Assistant);
assert_eq!(message.r#type, "message");
assert_eq!(message.stop_reason, Some(StopReason::EndTurn));
assert_eq!(message.stop_sequence, Some("###".to_string()));
assert_eq!(message.content.len(), 1);
match &message.content[0] {
ContentBlock::Text(text_block) => {
assert_eq!(text_block.text, "Hello, I'm Claude.");
}
_ => panic!("Expected Text variant"),
}
match message.model {
Model::Known(model) => {
assert_eq!(model, crate::types::KnownModel::ClaudeSonnet46);
}
_ => panic!("Expected Known model variant"),
}
assert_eq!(message.usage.input_tokens, 50);
assert_eq!(message.usage.output_tokens, 100);
}
}