use super::cache::CacheControl;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Message {
pub role: Role,
#[serde(with = "content_serde")]
pub content: MessageContent,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub provider_options: Option<MessageProviderOptions>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MessageProviderOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub anthropic: Option<AnthropicMessageOptions>,
}
impl MessageProviderOptions {
pub fn anthropic_cache(cache_control: CacheControl) -> Self {
Self {
anthropic: Some(AnthropicMessageOptions {
cache_control: Some(cache_control),
}),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AnthropicMessageOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_control: Option<CacheControl>,
}
#[derive(Debug, Clone)]
pub enum MessageContent {
Text(String),
Parts(Vec<ContentPart>),
}
impl MessageContent {
pub fn parts(&self) -> Vec<ContentPart> {
match self {
MessageContent::Text(text) => vec![ContentPart::text(text.clone())],
MessageContent::Parts(parts) => parts.clone(),
}
}
pub fn text(&self) -> Option<String> {
match self {
MessageContent::Text(text) => Some(text.clone()),
MessageContent::Parts(parts) => parts
.iter()
.filter_map(|part| match part {
ContentPart::Text { text, .. } => Some(text.clone()),
_ => None,
})
.reduce(|mut acc, text| {
acc.push_str(&text);
acc
}),
}
}
}
mod content_serde {
use super::*;
use serde::{Deserializer, Serializer};
pub fn serialize<S>(content: &MessageContent, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match content {
MessageContent::Text(text) => serializer.serialize_str(text),
MessageContent::Parts(parts) => parts.serialize(serializer),
}
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<MessageContent, D::Error>
where
D: Deserializer<'de>,
{
use serde::de::Error;
let value = serde_json::Value::deserialize(deserializer)?;
match value {
serde_json::Value::String(s) => Ok(MessageContent::Text(s)),
serde_json::Value::Array(_) => {
let parts: Vec<ContentPart> = serde_json::from_value(value)
.map_err(|e| D::Error::custom(format!("Invalid content parts: {}", e)))?;
Ok(MessageContent::Parts(parts))
}
_ => Err(D::Error::custom("Content must be a string or array")),
}
}
}
impl Message {
pub fn new(role: Role, content: impl Into<MessageContent>) -> Self {
Self {
role,
content: content.into(),
name: None,
provider_options: None,
}
}
pub fn text(&self) -> Option<String> {
self.content.text()
}
pub fn parts(&self) -> Vec<ContentPart> {
self.content.parts()
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_cache_control(mut self, cache_control: CacheControl) -> Self {
self.provider_options = Some(MessageProviderOptions::anthropic_cache(cache_control));
self
}
pub fn with_provider_options(mut self, options: MessageProviderOptions) -> Self {
self.provider_options = Some(options);
self
}
pub fn cache_control(&self) -> Option<&CacheControl> {
self.provider_options
.as_ref()
.and_then(|opts| opts.anthropic.as_ref())
.and_then(|anthropic| anthropic.cache_control.as_ref())
}
}
impl From<String> for MessageContent {
fn from(text: String) -> Self {
MessageContent::Text(text)
}
}
impl From<&str> for MessageContent {
fn from(text: &str) -> Self {
MessageContent::Text(text.to_string())
}
}
impl From<Vec<ContentPart>> for MessageContent {
fn from(parts: Vec<ContentPart>) -> Self {
MessageContent::Parts(parts)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
System,
User,
Assistant,
Tool,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ContentPartProviderOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub anthropic: Option<AnthropicContentPartOptions>,
}
impl ContentPartProviderOptions {
pub fn anthropic_cache(cache_control: CacheControl) -> Self {
Self {
anthropic: Some(AnthropicContentPartOptions {
cache_control: Some(cache_control),
}),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AnthropicContentPartOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_control: Option<CacheControl>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ContentPart {
Text {
text: String,
#[serde(skip_serializing_if = "Option::is_none")]
provider_options: Option<ContentPartProviderOptions>,
},
Image {
url: String,
#[serde(skip_serializing_if = "Option::is_none")]
detail: Option<ImageDetail>,
#[serde(skip_serializing_if = "Option::is_none")]
provider_options: Option<ContentPartProviderOptions>,
},
ToolCall {
id: String,
name: String,
arguments: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
provider_options: Option<ContentPartProviderOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
metadata: Option<serde_json::Value>,
},
ToolResult {
tool_call_id: String,
content: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
provider_options: Option<ContentPartProviderOptions>,
},
}
impl ContentPart {
pub fn text(text: impl Into<String>) -> Self {
Self::Text {
text: text.into(),
provider_options: None,
}
}
pub fn image(url: impl Into<String>) -> Self {
Self::Image {
url: url.into(),
detail: None,
provider_options: None,
}
}
pub fn image_with_detail(url: impl Into<String>, detail: ImageDetail) -> Self {
Self::Image {
url: url.into(),
detail: Some(detail),
provider_options: None,
}
}
pub fn tool_call(
id: impl Into<String>,
name: impl Into<String>,
arguments: serde_json::Value,
) -> Self {
Self::ToolCall {
id: id.into(),
name: name.into(),
arguments,
provider_options: None,
metadata: None,
}
}
pub fn tool_result(tool_call_id: impl Into<String>, content: serde_json::Value) -> Self {
Self::ToolResult {
tool_call_id: tool_call_id.into(),
content,
provider_options: None,
}
}
pub fn with_cache_control(self, cache_control: CacheControl) -> Self {
let provider_options = Some(ContentPartProviderOptions::anthropic_cache(cache_control));
match self {
Self::Text { text, .. } => Self::Text {
text,
provider_options,
},
Self::Image { url, detail, .. } => Self::Image {
url,
detail,
provider_options,
},
Self::ToolCall {
id,
name,
arguments,
metadata,
..
} => Self::ToolCall {
id,
name,
arguments,
provider_options,
metadata,
},
Self::ToolResult {
tool_call_id,
content,
..
} => Self::ToolResult {
tool_call_id,
content,
provider_options,
},
}
}
pub fn with_provider_options(self, options: ContentPartProviderOptions) -> Self {
let provider_options = Some(options);
match self {
Self::Text { text, .. } => Self::Text {
text,
provider_options,
},
Self::Image { url, detail, .. } => Self::Image {
url,
detail,
provider_options,
},
Self::ToolCall {
id,
name,
arguments,
metadata,
..
} => Self::ToolCall {
id,
name,
arguments,
provider_options,
metadata,
},
Self::ToolResult {
tool_call_id,
content,
..
} => Self::ToolResult {
tool_call_id,
content,
provider_options,
},
}
}
pub fn provider_options(&self) -> Option<&ContentPartProviderOptions> {
match self {
Self::Text {
provider_options, ..
} => provider_options.as_ref(),
Self::Image {
provider_options, ..
} => provider_options.as_ref(),
Self::ToolCall {
provider_options, ..
} => provider_options.as_ref(),
Self::ToolResult {
provider_options, ..
} => provider_options.as_ref(),
}
}
pub fn cache_control(&self) -> Option<&CacheControl> {
self.provider_options()
.and_then(|opts| opts.anthropic.as_ref())
.and_then(|anthropic| anthropic.cache_control.as_ref())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ImageDetail {
Low,
High,
Auto,
}