mod private
{
use serde::{ Serialize, Deserialize };
use serde_json::Value;
#[ derive( Debug, Clone, PartialEq, Serialize, Deserialize ) ]
pub enum Role
{
#[ serde( rename = "user" ) ]
User,
#[ serde( rename = "assistant" ) ]
Assistant,
#[ serde( rename = "system" ) ]
System,
}
#[ derive( Debug, Clone, Serialize, Deserialize, PartialEq ) ]
#[ serde( untagged ) ]
pub enum Content
{
Text
{
r#type : String,
text : String,
},
#[ cfg( feature = "vision" ) ]
Image
{
r#type : String,
source : ImageSource,
},
#[ cfg( feature = "tools" ) ]
ToolUse
{
r#type : String,
id : String,
name : String,
input : Value,
},
#[ cfg( feature = "tools" ) ]
ToolResult
{
r#type : String,
tool_use_id : String,
content : String,
#[ serde( skip_serializing_if = "Option::is_none" ) ]
is_error : Option< bool >,
},
}
impl Content
{
#[ inline ]
#[ must_use ]
pub fn new_text< S : Into< String > >( text : S ) -> Self
{
Self::Text
{
r#type : "text".to_string(),
text : text.into(),
}
}
#[ cfg( feature = "vision" ) ]
#[ inline ]
#[ must_use ]
pub fn image( source : ImageSource ) -> Self
{
Self::Image
{
r#type : "image".to_string(),
source,
}
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn tool_use< S1 : Into< String >, S2 : Into< String > >( id : S1, name : S2, input : Value ) -> Self
{
Self::ToolUse
{
r#type : "tool_use".to_string(),
id : id.into(),
name : name.into(),
input,
}
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn tool_result< S1 : Into< String >, S2 : Into< String > >( tool_use_id : S1, content : S2 ) -> Self
{
Self::ToolResult
{
r#type : "tool_result".to_string(),
tool_use_id : tool_use_id.into(),
content : content.into(),
is_error : None,
}
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn tool_result_error< S1 : Into< String >, S2 : Into< String > >( tool_use_id : S1, content : S2, is_error : bool ) -> Self
{
Self::ToolResult
{
r#type : "tool_result".to_string(),
tool_use_id : tool_use_id.into(),
content : content.into(),
is_error : Some( is_error ),
}
}
#[ inline ]
#[ must_use ]
#[ allow( clippy::match_same_arms ) ] pub fn r#type( &self ) -> &str
{
match self
{
Content::Text { r#type, .. } => r#type,
#[ cfg( feature = "vision" ) ]
Content::Image { r#type, .. } => r#type,
#[ cfg( feature = "tools" ) ]
Content::ToolUse { r#type, .. } => r#type,
#[ cfg( feature = "tools" ) ]
Content::ToolResult { r#type, .. } => r#type,
}
}
#[ inline ]
#[ must_use ]
pub fn text( &self ) -> Option< &str >
{
match self
{
Content::Text { text, .. } => Some( text ),
_ => None,
}
}
#[ inline ]
#[ must_use ]
pub fn is_text( &self ) -> bool
{
matches!( self, Content::Text { .. } )
}
#[ cfg( feature = "vision" ) ]
#[ inline ]
#[ must_use ]
pub fn is_image( &self ) -> bool
{
matches!( self, Content::Image { .. } )
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn is_tool_use( &self ) -> bool
{
matches!( self, Content::ToolUse { .. } )
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn is_tool_result( &self ) -> bool
{
matches!( self, Content::ToolResult { .. } )
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn tool_use_id( &self ) -> Option< &str >
{
match self
{
Content::ToolUse { id, .. } => Some( id ),
_ => None,
}
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn tool_name( &self ) -> Option< &str >
{
match self
{
Content::ToolUse { name, .. } => Some( name ),
_ => None,
}
}
#[ cfg( feature = "tools" ) ]
#[ inline ]
#[ must_use ]
pub fn tool_input( &self ) -> Option< &Value >
{
match self
{
Content::ToolUse { input, .. } => Some( input ),
_ => None,
}
}
}
#[ cfg( feature = "vision" ) ]
#[ derive( Debug, Clone, Serialize, Deserialize ) ]
pub struct ImageContent
{
pub r#type : String,
pub source : ImageSource,
}
#[ cfg( feature = "vision" ) ]
impl ImageContent
{
#[ inline ]
#[ must_use ]
pub fn new( source : ImageSource ) -> Self
{
Self
{
r#type : "image".to_string(),
source,
}
}
#[ inline ]
#[ must_use ]
pub fn jpeg< S : Into< String > >( data : S ) -> Self
{
Self::new( ImageSource::jpeg( data ) )
}
#[ inline ]
#[ must_use ]
pub fn png< S : Into< String > >( data : S ) -> Self
{
Self::new( ImageSource::png( data ) )
}
#[ inline ]
#[ must_use ]
pub fn gif< S : Into< String > >( data : S ) -> Self
{
Self::new( ImageSource::gif( data ) )
}
#[ inline ]
#[ must_use ]
pub fn webp< S : Into< String > >( data : S ) -> Self
{
Self::new( ImageSource::webp( data ) )
}
#[ inline ]
pub fn validate( &self ) -> Result< (), crate::error_tools::Error >
{
if self.r#type != "image"
{
return Err( crate::error_tools::Error::msg( format!( "Invalid image content type : '{}'. Expected 'image'.", self.r#type ) ) );
}
self.source.validate()
}
#[ inline ]
#[ must_use ]
pub fn is_valid( &self ) -> bool
{
self.r#type == "image" && self.source.is_valid_base64()
}
#[ inline ]
#[ must_use ]
pub fn media_type( &self ) -> &str
{
&self.source.media_type
}
#[ inline ]
#[ must_use ]
pub fn estimated_size_bytes( &self ) -> usize
{
self.source.estimated_size_bytes()
}
}
#[ cfg( feature = "vision" ) ]
#[ derive( Debug, Clone, Serialize, Deserialize, PartialEq ) ]
pub struct ImageSource
{
pub r#type : String,
pub media_type : String,
pub data : String,
}
#[ cfg( feature = "vision" ) ]
impl ImageSource
{
#[ inline ]
#[ must_use ]
pub fn base64< S1 : Into< String >, S2 : Into< String > >( media_type : S1, data : S2 ) -> Self
{
Self
{
r#type : "base64".to_string(),
media_type : media_type.into(),
data : data.into(),
}
}
#[ inline ]
#[ must_use ]
pub fn jpeg< S : Into< String > >( data : S ) -> Self
{
Self::base64( "image/jpeg", data )
}
#[ inline ]
#[ must_use ]
pub fn png< S : Into< String > >( data : S ) -> Self
{
Self::base64( "image/png", data )
}
#[ inline ]
#[ must_use ]
pub fn gif< S : Into< String > >( data : S ) -> Self
{
Self::base64( "image/gif", data )
}
#[ inline ]
#[ must_use ]
pub fn webp< S : Into< String > >( data : S ) -> Self
{
Self::base64( "image/webp", data )
}
#[ inline ]
pub fn validate( &self ) -> Result< (), crate::error_tools::Error >
{
if self.r#type != "base64"
{
return Err( crate::error_tools::Error::msg( format!( "Invalid image source type : '{}'. Only 'base64' is supported.", self.r#type ) ) );
}
const VALID_MEDIA_TYPES : &[ &str ] = &[ "image/jpeg", "image/png", "image/gif", "image/webp" ];
if !VALID_MEDIA_TYPES.contains( &self.media_type.as_str() )
{
return Err( crate::error_tools::Error::msg( format!( "Invalid image media type : '{}'. Supported types : {:?}", self.media_type, VALID_MEDIA_TYPES ) ) );
}
if self.data.is_empty()
{
return Err( crate::error_tools::Error::msg( "Image data cannot be empty" ) );
}
if !self.data.chars().all( | c | c.is_alphanumeric() || c == '+' || c == '/' || c == '=' )
{
return Err( crate::error_tools::Error::msg( "Invalid base64 image data format" ) );
}
Ok( () )
}
#[ inline ]
#[ must_use ]
pub fn is_valid_base64( &self ) -> bool
{
!self.data.is_empty() && self.data.chars().all( | c | c.is_alphanumeric() || c == '+' || c == '/' || c == '=' )
}
#[ inline ]
#[ must_use ]
pub fn estimated_size_bytes( &self ) -> usize
{
( self.data.len() * 3 ) / 4
}
}
}
crate::mod_interface!
{
exposed use Role;
exposed use Content;
#[ cfg( feature = "vision" ) ]
exposed use ImageContent;
#[ cfg( feature = "vision" ) ]
exposed use ImageSource;
}