use std::fmt;
use crate::ToolCall;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChatRole {
User,
Assistant,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ImageMime {
JPEG,
PNG,
GIF,
WEBP,
}
impl ImageMime {
pub fn mime_type(&self) -> &'static str {
match self {
ImageMime::JPEG => "image/jpeg",
ImageMime::PNG => "image/png",
ImageMime::GIF => "image/gif",
ImageMime::WEBP => "image/webp",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum MessageType {
#[default]
Text,
Image((ImageMime, Vec<u8>)),
Pdf(Vec<u8>),
Audio(Vec<u8>),
ImageURL(String),
ToolUse(Vec<ToolCall>),
ToolResult(Vec<ToolCall>),
}
pub enum ReasoningEffort {
Low,
Medium,
High,
}
impl fmt::Display for ReasoningEffort {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = match self {
ReasoningEffort::Low => "low",
ReasoningEffort::Medium => "medium",
ReasoningEffort::High => "high",
};
write!(f, "{name}")
}
}
#[derive(Debug, Clone)]
pub struct ChatMessage {
pub role: ChatRole,
pub message_type: MessageType,
pub content: String,
}
impl ChatMessage {
pub fn user() -> ChatMessageBuilder {
ChatMessageBuilder::new(ChatRole::User)
}
pub fn assistant() -> ChatMessageBuilder {
ChatMessageBuilder::new(ChatRole::Assistant)
}
pub fn has_audio(&self) -> bool {
matches!(self.message_type, MessageType::Audio(_))
}
pub fn audio_data(&self) -> Option<&[u8]> {
match &self.message_type {
MessageType::Audio(data) => Some(data),
_ => None,
}
}
}
#[derive(Debug)]
pub struct ChatMessageBuilder {
role: ChatRole,
message_type: MessageType,
content: String,
}
impl ChatMessageBuilder {
pub fn new(role: ChatRole) -> Self {
Self {
role,
message_type: MessageType::default(),
content: String::new(),
}
}
pub fn content(mut self, content: impl Into<String>) -> Self {
self.content = content.into();
self
}
pub fn image(mut self, image_mime: ImageMime, raw_bytes: Vec<u8>) -> Self {
self.message_type = MessageType::Image((image_mime, raw_bytes));
self
}
pub fn pdf(mut self, raw_bytes: Vec<u8>) -> Self {
self.message_type = MessageType::Pdf(raw_bytes);
self
}
pub fn audio(mut self, audio_data: Vec<u8>) -> Self {
self.message_type = MessageType::Audio(audio_data);
self
}
pub fn image_url(mut self, url: impl Into<String>) -> Self {
self.message_type = MessageType::ImageURL(url.into());
self
}
pub fn tool_use(mut self, tools: Vec<ToolCall>) -> Self {
self.message_type = MessageType::ToolUse(tools);
self
}
pub fn tool_result(mut self, tools: Vec<ToolCall>) -> Self {
self.message_type = MessageType::ToolResult(tools);
self
}
pub fn build(self) -> ChatMessage {
ChatMessage {
role: self.role,
message_type: self.message_type,
content: self.content,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn audio_builder_sets_audio_type() {
let msg = ChatMessage::user().audio(vec![1, 2, 3]).build();
assert!(msg.has_audio());
assert_eq!(msg.audio_data(), Some([1, 2, 3].as_slice()));
}
#[test]
fn audio_accessors_ignore_non_audio() {
let msg = ChatMessage::assistant().content("hi").build();
assert!(!msg.has_audio());
assert_eq!(msg.audio_data(), None);
}
}