use crate::chat::{Binary, ContentPart, ToolCall, ToolResponse};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MessageContent {
parts: Vec<ContentPart>,
}
impl MessageContent {
pub fn from_text(content: impl Into<String>) -> Self {
Self {
parts: vec![ContentPart::Text(content.into())],
}
}
pub fn from_parts(parts: impl Into<Vec<ContentPart>>) -> Self {
Self { parts: parts.into() }
}
pub fn from_tool_calls(tool_calls: Vec<ToolCall>) -> Self {
Self {
parts: tool_calls.into_iter().map(ContentPart::ToolCall).collect(),
}
}
}
impl MessageContent {
pub fn append(mut self, part: impl Into<ContentPart>) -> Self {
self.parts.push(part.into());
self
}
pub fn push(&mut self, part: impl Into<ContentPart>) {
self.parts.push(part.into());
}
pub fn insert(&mut self, index: usize, part: impl Into<ContentPart>) {
self.parts.insert(index, part.into());
}
pub fn prepend(&mut self, part: impl Into<ContentPart>) {
self.parts.insert(0, part.into());
}
pub fn extend_front<I>(&mut self, iter: I)
where
I: IntoIterator<Item = ContentPart>,
{
let collected: Vec<ContentPart> = iter.into_iter().collect();
for part in collected.into_iter().rev() {
self.parts.insert(0, part);
}
}
pub fn extended<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = ContentPart>,
{
self.parts.extend(iter);
self
}
}
impl Extend<ContentPart> for MessageContent {
fn extend<T: IntoIterator<Item = ContentPart>>(&mut self, iter: T) {
self.parts.extend(iter);
}
}
impl MessageContent {
pub fn size(&self) -> usize {
self.parts.iter().map(|p| p.size()).sum()
}
}
use crate::support;
use std::iter::FromIterator;
use std::slice::{Iter, IterMut};
impl IntoIterator for MessageContent {
type Item = ContentPart;
type IntoIter = std::vec::IntoIter<ContentPart>;
fn into_iter(self) -> Self::IntoIter {
self.parts.into_iter()
}
}
impl<'a> IntoIterator for &'a MessageContent {
type Item = &'a ContentPart;
type IntoIter = Iter<'a, ContentPart>;
fn into_iter(self) -> Self::IntoIter {
self.parts.iter()
}
}
impl<'a> IntoIterator for &'a mut MessageContent {
type Item = &'a mut ContentPart;
type IntoIter = IterMut<'a, ContentPart>;
fn into_iter(self) -> Self::IntoIter {
self.parts.iter_mut()
}
}
impl FromIterator<ContentPart> for MessageContent {
fn from_iter<T: IntoIterator<Item = ContentPart>>(iter: T) -> Self {
Self {
parts: iter.into_iter().collect(),
}
}
}
impl MessageContent {
pub fn parts(&self) -> &Vec<ContentPart> {
&self.parts
}
pub fn into_parts(self) -> Vec<ContentPart> {
self.parts
}
pub fn texts(&self) -> Vec<&str> {
self.parts.iter().filter_map(|p| p.as_text()).collect()
}
pub fn into_texts(self) -> Vec<String> {
self.parts.into_iter().filter_map(|p| p.into_text()).collect()
}
pub fn binaries(&self) -> Vec<&Binary> {
self.parts.iter().filter_map(|p| p.as_binary()).collect()
}
pub fn into_binaries(self) -> Vec<Binary> {
self.parts.into_iter().filter_map(|p| p.into_binary()).collect()
}
pub fn tool_calls(&self) -> Vec<&ToolCall> {
self.parts
.iter()
.filter_map(|p| match p {
ContentPart::ToolCall(tc) => Some(tc),
_ => None,
})
.collect()
}
pub fn into_tool_calls(self) -> Vec<ToolCall> {
self.parts
.into_iter()
.filter_map(|p| match p {
ContentPart::ToolCall(tc) => Some(tc),
_ => None,
})
.collect()
}
pub fn tool_responses(&self) -> Vec<&ToolResponse> {
self.parts
.iter()
.filter_map(|p| match p {
ContentPart::ToolResponse(tr) => Some(tr),
_ => None,
})
.collect()
}
pub fn into_tool_responses(self) -> Vec<ToolResponse> {
self.parts
.into_iter()
.filter_map(|p| match p {
ContentPart::ToolResponse(tr) => Some(tr),
_ => None,
})
.collect()
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
pub fn len(&self) -> usize {
self.parts.len()
}
pub fn is_text_empty(&self) -> bool {
if self.parts.is_empty() {
return true;
}
self.parts
.iter()
.all(|p| matches!(p, ContentPart::Text(t) if t.trim().is_empty()))
}
}
impl MessageContent {
pub fn first_text(&self) -> Option<&str> {
let first_text_part = self.parts.iter().find(|p| p.is_text())?;
first_text_part.as_text()
}
pub fn into_first_text(self) -> Option<String> {
let first_text_part = self.parts.into_iter().find(|p| p.is_text())?;
first_text_part.into_text()
}
pub fn joined_texts(&self) -> Option<String> {
let texts = self.texts();
if texts.is_empty() {
return None;
}
if texts.len() == 1 {
return texts.first().map(|s| s.to_string());
}
let mut combined = String::new();
for text in texts {
support::combine_text_with_empty_line(&mut combined, text);
}
Some(combined)
}
pub fn into_joined_texts(self) -> Option<String> {
let texts = self.into_texts();
if texts.is_empty() {
return None;
}
if texts.len() == 1 {
return texts.into_iter().next();
}
let mut combined = String::new();
for text in texts {
support::combine_text_with_empty_line(&mut combined, &text);
}
Some(combined)
}
}
impl MessageContent {
pub fn is_text_only(&self) -> bool {
self.parts.iter().all(|p| p.is_text())
}
pub fn contains_text(&self) -> bool {
self.parts.iter().any(|p| p.is_text())
}
pub fn contains_tool_call(&self) -> bool {
self.parts.iter().any(|p| p.is_tool_call())
}
pub fn contains_tool_response(&self) -> bool {
self.parts.iter().any(|p| p.is_tool_response())
}
}
impl From<&str> for MessageContent {
fn from(s: &str) -> Self {
Self {
parts: vec![ContentPart::Text(s.to_string())],
}
}
}
impl From<&String> for MessageContent {
fn from(s: &String) -> Self {
Self {
parts: vec![ContentPart::Text(s.clone())],
}
}
}
impl From<String> for MessageContent {
fn from(s: String) -> Self {
Self {
parts: vec![ContentPart::Text(s)],
}
}
}
impl From<Vec<ToolCall>> for MessageContent {
fn from(tool_calls: Vec<ToolCall>) -> Self {
Self {
parts: tool_calls.into_iter().map(ContentPart::ToolCall).collect(),
}
}
}
impl From<ToolResponse> for MessageContent {
fn from(tool_response: ToolResponse) -> Self {
Self {
parts: vec![ContentPart::ToolResponse(tool_response)],
}
}
}
impl From<ContentPart> for MessageContent {
fn from(part: ContentPart) -> Self {
Self { parts: vec![part] }
}
}
impl From<Binary> for MessageContent {
fn from(bin: Binary) -> Self {
Self {
parts: vec![bin.into()],
}
}
}
impl From<Vec<ContentPart>> for MessageContent {
fn from(parts: Vec<ContentPart>) -> Self {
Self { parts }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_content_joined_texts_empty() {
assert_eq!(MessageContent::from_parts(vec![]).joined_texts(), None);
}
#[test]
fn test_message_content_joined_texts_single_part() {
assert_eq!(MessageContent::from_parts(vec![ContentPart::Text("Hello".to_string())]).joined_texts(), Some("Hello".to_string()));
}
#[test]
fn test_message_content_joined_texts_two_parts() {
assert_eq!(
MessageContent::from_parts(vec![
ContentPart::Text("Hello".to_string()),
ContentPart::Text("World".to_string()),
]).joined_texts(),
Some("Hello\n\nWorld".to_string())
);
}
#[test]
fn test_message_content_into_joined_texts_empty() {
assert_eq!(MessageContent::from_parts(vec![]).into_joined_texts(), None);
}
#[test]
fn test_message_content_into_joined_texts_single_part() {
assert_eq!(MessageContent::from_parts(vec![ContentPart::Text("Hello".to_string())]).into_joined_texts(), Some("Hello".to_string()));
}
#[test]
fn test_message_content_into_joined_texts_two_parts() {
assert_eq!(
MessageContent::from_parts(vec![
ContentPart::Text("Hello".to_string()),
ContentPart::Text("World".to_string()),
]).into_joined_texts(),
Some("Hello\n\nWorld".to_string())
);
}
}