#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Role {
System,
User,
Assistant,
Tool,
Other(String),
}
impl Role {
pub fn as_wire(&self) -> &str {
match self {
Role::System => "system",
Role::User => "user",
Role::Assistant => "assistant",
Role::Tool => "tool",
Role::Other(s) => s,
}
}
pub fn from_wire(s: &str) -> Option<Self> {
match s {
"system" => Some(Role::System),
"user" => Some(Role::User),
"assistant" => Some(Role::Assistant),
"tool" => Some(Role::Tool),
_ => None,
}
}
}
impl From<&str> for Role {
fn from(s: &str) -> Self {
Self::from_wire(s).unwrap_or_else(|| Role::Other(s.to_string()))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Message {
pub role: Role,
pub content: Vec<ContentPart>,
pub tool_calls: Vec<ToolCall>,
pub tool_call_id: Option<String>,
pub name: Option<String>,
pub reasoning: Option<Reasoning>,
pub tool_error: bool,
}
impl Message {
pub fn synthetic_system(text: String) -> Self {
Message {
role: Role::System,
content: vec![ContentPart::Text(text)],
tool_calls: Vec::new(),
tool_call_id: None,
name: None,
reasoning: None,
tool_error: false,
}
}
pub fn prepend_text(&mut self, prefix: &str) {
for part in &mut self.content {
if let ContentPart::Text(t) = part {
*t = format!("{prefix}{t}");
return;
}
}
self.content.push(ContentPart::Text(prefix.to_string()));
}
pub fn text(&self) -> String {
let mut out = String::new();
for part in &self.content {
if let ContentPart::Text(t) = part {
out.push_str(t);
}
}
out
}
pub fn media_kinds(&self) -> Vec<MediaKind> {
self.content
.iter()
.filter_map(|p| match p {
ContentPart::Image(_) => Some(MediaKind::Image),
ContentPart::Video(_) => Some(MediaKind::Video),
ContentPart::Text(_) => None,
})
.collect()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MediaKind {
Image,
Video,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ContentPart {
Text(String),
Image(ImageSource),
Video(VideoSource),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VideoSource {
pub data: ImageData,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImageSource {
pub data: ImageData,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImageData {
Base64(String),
Url(String),
}
impl ImageData {
pub fn from_uri(s: String) -> Self {
if s.starts_with("http://") || s.starts_with("https://") {
ImageData::Url(s)
} else {
ImageData::Base64(s)
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ToolCall {
pub id: String,
pub name: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Reasoning {
pub text: String,
}