use crate::model::{
ContentPart, ContentPartKind, Message, MessageHelpers, Prompty, Property, Role, Tool,
ToolResult, ToolResultHelpers,
};
impl Prompty {
pub fn as_inputs(&self) -> Option<&Vec<Property>> {
if self.inputs.is_empty() {
None
} else {
Some(&self.inputs)
}
}
pub fn as_outputs(&self) -> Option<&Vec<Property>> {
if self.outputs.is_empty() {
None
} else {
Some(&self.outputs)
}
}
pub fn as_tools(&self) -> Option<&Vec<Tool>> {
if self.tools.is_empty() {
None
} else {
Some(&self.tools)
}
}
}
impl MessageHelpers for Message {
fn to_text_content(&self) -> serde_json::Value {
let all_text = self
.parts
.iter()
.all(|p| matches!(&p.kind, ContentPartKind::TextPart { .. }));
if all_text {
let text = self
.parts
.iter()
.filter_map(|p| match &p.kind {
ContentPartKind::TextPart { value } => Some(value.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n");
serde_json::Value::String(text)
} else {
use crate::model::context::SaveContext;
let ctx = SaveContext::default();
serde_json::Value::Array(self.parts.iter().map(|p| p.to_value(&ctx)).collect())
}
}
fn text(&self) -> String {
self.parts
.iter()
.filter_map(|p| match &p.kind {
ContentPartKind::TextPart { value } => Some(value.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n")
}
}
impl ToolResultHelpers for ToolResult {
fn text(&self) -> String {
self.parts
.iter()
.filter_map(|p| match &p.kind {
ContentPartKind::TextPart { value } => Some(value.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join("\n")
}
}
impl ContentPart {
pub fn text(value: impl Into<String>) -> Self {
Self {
kind: ContentPartKind::TextPart {
value: value.into(),
},
}
}
pub fn image(
source: impl Into<String>,
detail: Option<String>,
media_type: Option<String>,
) -> Self {
Self {
kind: ContentPartKind::ImagePart {
source: source.into(),
detail,
media_type,
},
}
}
pub fn file(source: impl Into<String>, media_type: Option<String>) -> Self {
Self {
kind: ContentPartKind::FilePart {
source: source.into(),
media_type,
},
}
}
pub fn audio(source: impl Into<String>, media_type: Option<String>) -> Self {
Self {
kind: ContentPartKind::AudioPart {
source: source.into(),
media_type,
},
}
}
}
impl PartialEq for ContentPartKind {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(ContentPartKind::TextPart { value: a }, ContentPartKind::TextPart { value: b }) => {
a == b
}
(
ContentPartKind::ImagePart {
source: a,
detail: ad,
media_type: am,
},
ContentPartKind::ImagePart {
source: b,
detail: bd,
media_type: bm,
},
) => a == b && ad == bd && am == bm,
(
ContentPartKind::FilePart {
source: a,
media_type: am,
},
ContentPartKind::FilePart {
source: b,
media_type: bm,
},
) => a == b && am == bm,
(
ContentPartKind::AudioPart {
source: a,
media_type: am,
},
ContentPartKind::AudioPart {
source: b,
media_type: bm,
},
) => a == b && am == bm,
_ => false,
}
}
}
impl Eq for ContentPartKind {}
impl PartialEq for ContentPart {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind
}
}
impl Eq for ContentPart {}
impl serde::Serialize for ContentPartKind {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let part = ContentPart { kind: self.clone() };
part.serialize(serializer)
}
}
impl serde::Serialize for ContentPart {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use crate::model::context::SaveContext;
let value = self.to_value(&SaveContext::default());
value.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for ContentPart {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use crate::model::context::LoadContext;
let value = serde_json::Value::deserialize(deserializer)?;
Ok(Self::load_from_value(&value, &LoadContext::default()))
}
}
impl Message {
pub fn with_text(role: Role, content: impl Into<String>) -> Self {
Self {
role,
parts: vec![ContentPart::text(content)],
metadata: serde_json::Value::Object(serde_json::Map::new()),
}
}
pub fn tool_result(tool_call_id: impl Into<String>, content: impl Into<String>) -> Self {
Self {
role: Role::Tool,
parts: vec![ContentPart::text(content)],
metadata: serde_json::json!({"tool_call_id": tool_call_id.into()}),
}
}
pub fn text_content(&self) -> String {
self.parts
.iter()
.filter_map(|p| match &p.kind {
ContentPartKind::TextPart { value } => Some(value.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join("")
}
pub fn has_rich_content(&self) -> bool {
self.parts
.iter()
.any(|p| !matches!(&p.kind, ContentPartKind::TextPart { .. }))
}
pub fn metadata_mut(&mut self) -> &mut serde_json::Map<String, serde_json::Value> {
if !self.metadata.is_object() {
self.metadata = serde_json::Value::Object(serde_json::Map::new());
}
self.metadata.as_object_mut().unwrap()
}
}
impl PartialEq for Message {
fn eq(&self, other: &Self) -> bool {
self.role == other.role && self.parts == other.parts && self.metadata == other.metadata
}
}
impl serde::Serialize for Message {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
use crate::model::context::SaveContext;
let value = self.to_value(&SaveContext::default());
value.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for Message {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use crate::model::context::LoadContext;
let value = serde_json::Value::deserialize(deserializer)?;
Ok(Self::load_from_value(&value, &LoadContext::default()))
}
}
impl Role {
pub fn from_str_ignore_case(s: &str) -> Option<Self> {
Self::from_str_opt(s.to_lowercase().as_str())
}
}