use std::{slice, vec};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{Resource, ResourceContents};
use crate::{Arguments, macros::with_meta};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Role {
User,
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ContentBlock {
#[serde(rename = "text")]
Text(TextContent),
#[serde(rename = "image")]
Image(ImageContent),
#[serde(rename = "audio")]
Audio(AudioContent),
#[serde(rename = "resource_link")]
ResourceLink(ResourceLink),
#[serde(rename = "resource")]
EmbeddedResource(EmbeddedResource),
}
impl ContentBlock {
pub fn text(text: impl Into<String>) -> Self {
Self::Text(TextContent::new(text))
}
pub fn image(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Image(ImageContent::new(data, mime_type))
}
pub fn audio(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self::Audio(AudioContent::new(data, mime_type))
}
pub fn resource(resource: ResourceContents) -> Self {
Self::EmbeddedResource(EmbeddedResource {
resource,
annotations: None,
_meta: None,
})
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SamplingMessageContentBlock {
#[serde(rename = "text")]
Text(TextContent),
#[serde(rename = "image")]
Image(ImageContent),
#[serde(rename = "audio")]
Audio(AudioContent),
#[serde(rename = "tool_use")]
ToolUse(ToolUseContent),
#[serde(rename = "tool_result")]
ToolResult(ToolResultContent),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum OneOrMany<T> {
One(T),
Many(Vec<T>),
}
impl<T> OneOrMany<T> {
pub fn iter(&self) -> impl Iterator<Item = &T> {
let s: &[T] = match self {
Self::One(value) => slice::from_ref(value),
Self::Many(values) => values.as_slice(),
};
s.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
let s: &mut [T] = match self {
Self::One(value) => slice::from_mut(value),
Self::Many(values) => values.as_mut_slice(),
};
s.iter_mut()
}
pub fn first(&self) -> Option<&T> {
match self {
Self::One(value) => Some(value),
Self::Many(values) => values.first(),
}
}
pub fn len(&self) -> usize {
match self {
Self::One(_) => 1,
Self::Many(values) => values.len(),
}
}
pub fn is_empty(&self) -> bool {
match self {
Self::One(_) => false,
Self::Many(values) => values.is_empty(),
}
}
pub fn into_vec(self) -> Vec<T> {
match self {
Self::One(value) => vec![value],
Self::Many(values) => values,
}
}
}
impl<T> IntoIterator for OneOrMany<T> {
type Item = T;
type IntoIter = vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
self.into_vec().into_iter()
}
}
impl<'a, T> IntoIterator for &'a OneOrMany<T> {
type Item = &'a T;
type IntoIter = slice::Iter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
match self {
OneOrMany::One(value) => slice::from_ref(value).iter(),
OneOrMany::Many(values) => values.iter(),
}
}
}
impl<T> From<T> for OneOrMany<T> {
fn from(value: T) -> Self {
Self::One(value)
}
}
impl<T> From<Vec<T>> for OneOrMany<T> {
fn from(values: Vec<T>) -> Self {
Self::Many(values)
}
}
#[with_meta]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmbeddedResource {
pub resource: ResourceContents,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceLink {
#[serde(flatten)]
pub resource: Resource,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Annotations {
#[serde(skip_serializing_if = "Option::is_none")]
pub audience: Option<Vec<Role>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<f64>,
#[serde(rename = "lastModified", skip_serializing_if = "Option::is_none")]
pub last_modified: Option<String>,
}
impl Annotations {
pub fn new() -> Self {
Self {
audience: None,
priority: None,
last_modified: None,
}
}
pub fn with_audience(mut self, audience: Vec<Role>) -> Self {
self.audience = Some(audience);
self
}
pub fn with_priority(mut self, priority: f64) -> Self {
self.priority = Some(priority);
self
}
pub fn with_last_modified(mut self, last_modified: impl Into<String>) -> Self {
self.last_modified = Some(last_modified.into());
self
}
}
#[with_meta]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TextContent {
pub text: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
impl TextContent {
pub fn new(text: impl Into<String>) -> Self {
Self {
text: text.into(),
annotations: None,
_meta: None,
}
}
pub fn with_annotations(mut self, annotations: Annotations) -> Self {
self.annotations = Some(annotations);
self
}
}
#[with_meta]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ImageContent {
pub data: String,
#[serde(rename = "mimeType")]
pub mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
impl ImageContent {
pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
_meta: None,
}
}
pub fn with_annotations(mut self, annotations: Annotations) -> Self {
self.annotations = Some(annotations);
self
}
}
#[with_meta]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AudioContent {
pub data: String,
#[serde(rename = "mimeType")]
pub mime_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<Annotations>,
}
impl AudioContent {
pub fn new(data: impl Into<String>, mime_type: impl Into<String>) -> Self {
Self {
data: data.into(),
mime_type: mime_type.into(),
annotations: None,
_meta: None,
}
}
pub fn with_annotations(mut self, annotations: Annotations) -> Self {
self.annotations = Some(annotations);
self
}
}
#[with_meta]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolUseContent {
pub id: String,
pub name: String,
pub input: Arguments,
}
#[with_meta]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResultContent {
#[serde(rename = "toolUseId")]
pub tool_use_id: String,
pub content: Vec<ContentBlock>,
#[serde(rename = "structuredContent", skip_serializing_if = "Option::is_none")]
pub structured_content: Option<Value>,
#[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_content_block_helpers() {
let text = ContentBlock::text("hello");
assert!(matches!(text, ContentBlock::Text(_)));
let image = ContentBlock::image("data", "image/png");
assert!(matches!(image, ContentBlock::Image(_)));
let audio = ContentBlock::audio("data", "audio/mpeg");
assert!(matches!(audio, ContentBlock::Audio(_)));
}
}