use serde::{Deserialize, Serialize};
use crate::ToolCall;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
System,
Human,
Ai,
Tool,
}
impl std::fmt::Display for Role {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Role::System => write!(f, "system"),
Role::Human => write!(f, "human"),
Role::Ai => write!(f, "ai"),
Role::Tool => write!(f, "tool"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum ImageData {
Url { url: String },
Base64 { mime_type: String, data: String },
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum ContentPart {
Text { text: String },
Image { image: ImageData },
}
impl ContentPart {
pub fn text(text: impl Into<String>) -> Self {
ContentPart::Text { text: text.into() }
}
pub fn image_url(url: impl Into<String>) -> Self {
ContentPart::Image {
image: ImageData::Url { url: url.into() },
}
}
pub fn image_base64(mime_type: impl Into<String>, data: impl Into<String>) -> Self {
ContentPart::Image {
image: ImageData::Base64 {
mime_type: mime_type.into(),
data: data.into(),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageContent {
Text(String),
Parts(Vec<ContentPart>),
}
impl MessageContent {
pub fn as_text(&self) -> &str {
match self {
MessageContent::Text(s) => s.as_str(),
MessageContent::Parts(_) => "",
}
}
pub fn text_content(&self) -> String {
match self {
MessageContent::Text(s) => s.clone(),
MessageContent::Parts(parts) => parts
.iter()
.filter_map(|p| {
if let ContentPart::Text { text } = p {
Some(text.as_str())
} else {
None
}
})
.collect::<Vec<_>>()
.join(" "),
}
}
pub fn is_text(&self) -> bool {
matches!(self, MessageContent::Text(_))
}
}
impl From<String> for MessageContent {
fn from(s: String) -> Self {
MessageContent::Text(s)
}
}
impl From<&str> for MessageContent {
fn from(s: &str) -> Self {
MessageContent::Text(s.to_string())
}
}
impl From<&String> for MessageContent {
fn from(s: &String) -> Self {
MessageContent::Text(s.clone())
}
}
impl std::fmt::Display for MessageContent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.text_content())
}
}
impl PartialEq<str> for MessageContent {
fn eq(&self, other: &str) -> bool {
self.as_text() == other
}
}
impl PartialEq<&str> for MessageContent {
fn eq(&self, other: &&str) -> bool {
self.as_text() == *other
}
}
impl PartialEq<String> for MessageContent {
fn eq(&self, other: &String) -> bool {
self.as_text() == other.as_str()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Message {
pub role: Role,
pub content: MessageContent,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tool_calls: Vec<ToolCall>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tool_call_id: Option<String>,
}
impl Message {
pub fn new(role: Role, content: impl Into<MessageContent>) -> Self {
Self {
role,
content: content.into(),
tool_calls: Vec::new(),
tool_call_id: None,
}
}
pub fn system(content: impl Into<MessageContent>) -> Self {
Self::new(Role::System, content)
}
pub fn human(content: impl Into<MessageContent>) -> Self {
Self::new(Role::Human, content)
}
pub fn ai(content: impl Into<MessageContent>) -> Self {
Self::new(Role::Ai, content)
}
pub fn ai_with_tool_calls(
content: impl Into<MessageContent>,
tool_calls: Vec<ToolCall>,
) -> Self {
Self {
role: Role::Ai,
content: content.into(),
tool_calls,
tool_call_id: None,
}
}
pub fn tool_result(call_id: impl Into<String>, content: impl Into<MessageContent>) -> Self {
Self {
role: Role::Tool,
content: content.into(),
tool_calls: Vec::new(),
tool_call_id: Some(call_id.into()),
}
}
}