use crate::functions;
use functions::expression::{
ExpressionError, FromStarlarkValue, ToStarlarkValue, WithExpression,
};
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;
use starlark::values::dict::{
AllocDict as StarlarkAllocDict, DictRef as StarlarkDictRef,
};
use starlark::values::{
Heap as StarlarkHeap, UnpackValue, Value as StarlarkValue,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[serde(untagged)]
#[schemars(rename = "agent.completions.message.RichContent")]
pub enum RichContent {
#[schemars(title = "Text")]
Text(String),
#[schemars(title = "Parts")]
Parts(Vec<RichContentPart>),
}
impl RichContent {
pub fn push(&mut self, other: &RichContent) {
match (&mut *self, other) {
(RichContent::Text(self_text), RichContent::Text(other_text)) => {
self_text.push_str(&other_text);
}
(RichContent::Text(self_text), RichContent::Parts(other_parts)) => {
let mut parts = Vec::with_capacity(1 + other_parts.len());
parts.push(RichContentPart::Text {
text: std::mem::take(self_text),
});
parts.extend(other_parts.iter().cloned());
*self = RichContent::Parts(parts);
}
(RichContent::Parts(self_parts), RichContent::Text(other_text)) => {
self_parts.push(RichContentPart::Text {
text: other_text.clone(),
});
}
(
RichContent::Parts(self_parts),
RichContent::Parts(other_parts),
) => {
self_parts.extend(other_parts.iter().cloned());
}
}
}
pub fn prepare(&mut self) {
let parts = match self {
RichContent::Text(_) => return,
RichContent::Parts(parts) => parts,
};
parts.iter_mut().for_each(RichContentPart::prepare);
let mut final_parts = Vec::with_capacity(parts.len());
let mut buffer: Option<String> = None;
for part in parts.drain(..) {
match part {
part if part.is_empty() => continue,
RichContentPart::Text { text } => {
if let Some(buffer) = &mut buffer {
buffer.push_str(&text);
} else {
buffer = Some(text);
}
}
part => {
if let Some(buffer) = buffer.take() {
final_parts
.push(RichContentPart::Text { text: buffer });
}
final_parts.push(part);
}
}
}
if let Some(buffer) = buffer.take() {
final_parts.push(RichContentPart::Text { text: buffer });
}
if final_parts.len() == 1
&& matches!(&final_parts[0], RichContentPart::Text { .. })
{
match final_parts.into_iter().next() {
Some(RichContentPart::Text { text }) => {
*self = RichContent::Text(text);
}
_ => unreachable!(),
}
} else {
*self = RichContent::Parts(final_parts);
}
}
pub fn is_empty(&self) -> bool {
match self {
RichContent::Text(text) => text.is_empty(),
RichContent::Parts(parts) => parts.is_empty(),
}
}
#[cfg(feature = "filesystem")]
pub fn extract_media(
self,
route_base: &str,
id: &str,
message_index: u64,
) -> (serde_json::Value, Vec<crate::filesystem::logs::LogFile>) {
let parts = match self {
RichContent::Text(text) => return (serde_json::Value::String(text), Vec::new()),
RichContent::Parts(parts) => parts,
};
let mut json_parts = Vec::with_capacity(parts.len());
let mut files = Vec::new();
for (part_idx, part) in parts.into_iter().enumerate() {
let fc_and_type: Option<(super::FileContent, &str)> = match &part {
RichContentPart::ImageUrl { image_url } => {
image_url.file_content().map(|fc| (fc, "image"))
}
RichContentPart::InputAudio { input_audio } => {
input_audio.file_content().map(|fc| (fc, "audio"))
}
RichContentPart::InputVideo { video_url }
| RichContentPart::VideoUrl { video_url } => {
video_url.file_content().map(|fc| (fc, "video"))
}
RichContentPart::File { file } => {
file.file_content().map(|fc| (fc, "file"))
}
_ => None,
};
if let Some((fc, media_type)) = fc_and_type {
if let Ok(decoded) = fc.decode() {
let log_file = crate::filesystem::logs::LogFile {
route: format!("{route_base}/messages/{media_type}"),
id: id.to_string(),
message_index: Some(message_index),
media_index: Some(part_idx as u64),
extension: fc.extension.to_string(),
content: decoded,
};
json_parts.push(serde_json::json!({
"type": "reference",
"path": log_file.path(),
}));
files.push(log_file);
} else {
json_parts.push(serde_json::to_value(&part).unwrap());
}
} else {
json_parts.push(serde_json::to_value(&part).unwrap());
}
}
(serde_json::Value::Array(json_parts), files)
}
pub fn id(&self) -> String {
let mut hasher = twox_hash::XxHash3_128::with_seed(0);
hasher.write(serde_json::to_string(self).unwrap().as_bytes());
format!("{:0>22}", base62::encode(hasher.finish_128()))
}
pub fn validate_text_or_image_only(&self) -> Result<(), String> {
match self {
RichContent::Text(_) => Ok(()),
RichContent::Parts(parts) => {
for (idx, part) in parts.iter().enumerate() {
match part {
RichContentPart::Text { .. }
| RichContentPart::ImageUrl { .. } => {}
RichContentPart::InputAudio { .. } => {
return Err(format!(
"part[{idx}] has unsupported media type `input_audio`; only text and image parts are allowed"
));
}
RichContentPart::InputVideo { .. } => {
return Err(format!(
"part[{idx}] has unsupported media type `input_video`; only text and image parts are allowed"
));
}
RichContentPart::VideoUrl { .. } => {
return Err(format!(
"part[{idx}] has unsupported media type `video_url`; only text and image parts are allowed"
));
}
RichContentPart::File { .. } => {
return Err(format!(
"part[{idx}] has unsupported media type `file`; only text and image parts are allowed"
));
}
}
}
Ok(())
}
}
}
}
impl FromStarlarkValue for RichContent {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
if let Ok(Some(s)) = <&str as UnpackValue>::unpack_value(*value) {
return Ok(RichContent::Text(s.to_owned()));
}
let parts = Vec::<RichContentPart>::from_starlark_value(value)?;
Ok(RichContent::Parts(parts))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[serde(untagged)]
#[schemars(rename = "agent.completions.message.RichContentExpression")]
pub enum RichContentExpression {
#[schemars(title = "Text")]
Text(String),
#[schemars(title = "Parts")]
Parts(
Vec<functions::expression::WithExpression<RichContentPartExpression>>,
),
}
impl RichContentExpression {
pub fn compile(
self,
params: &functions::expression::Params,
) -> Result<RichContent, functions::expression::ExpressionError> {
match self {
RichContentExpression::Text(text) => Ok(RichContent::Text(text)),
RichContentExpression::Parts(parts) => {
let mut compiled_parts = Vec::with_capacity(parts.len());
for part in parts {
match part.compile_one_or_many(params)? {
functions::expression::OneOrMany::One(one_part) => {
compiled_parts.push(one_part.compile(params)?);
}
functions::expression::OneOrMany::Many(many_parts) => {
for part in many_parts {
compiled_parts.push(part.compile(params)?);
}
}
}
}
Ok(RichContent::Parts(compiled_parts))
}
}
}
}
impl From<RichContent> for RichContentExpression {
fn from(content: RichContent) -> Self {
match content {
RichContent::Text(text) => RichContentExpression::Text(text),
RichContent::Parts(parts) => RichContentExpression::Parts(
parts
.into_iter()
.map(RichContentPartExpression::from)
.map(WithExpression::Value)
.collect(),
),
}
}
}
impl FromStarlarkValue for RichContentExpression {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
if let Ok(Some(s)) = <&str as UnpackValue>::unpack_value(*value) {
return Ok(RichContentExpression::Text(s.to_owned()));
}
let parts = Vec::<WithExpression<RichContentPartExpression>>::from_starlark_value(value)?;
Ok(RichContentExpression::Parts(parts))
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[serde(tag = "type", rename_all = "snake_case")]
#[schemars(rename = "agent.completions.message.RichContentPart")]
pub enum RichContentPart {
#[schemars(title = "Text")]
Text { text: String },
#[schemars(title = "ImageUrl")]
ImageUrl { image_url: ImageUrl },
#[schemars(title = "InputAudio")]
InputAudio { input_audio: InputAudio },
#[schemars(title = "InputVideo")]
InputVideo { video_url: VideoUrl },
#[schemars(title = "VideoUrl")]
VideoUrl { video_url: VideoUrl },
#[schemars(title = "File")]
File { file: File },
}
impl RichContentPart {
pub fn prepare(&mut self) {
match self {
RichContentPart::Text { .. } => {}
RichContentPart::ImageUrl { image_url } => {
image_url.prepare();
}
RichContentPart::InputAudio { .. } => {}
RichContentPart::InputVideo { .. } => {}
RichContentPart::VideoUrl { .. } => {}
RichContentPart::File { file } => {
file.prepare();
}
}
}
pub fn is_empty(&self) -> bool {
match self {
RichContentPart::Text { text } => text.is_empty(),
RichContentPart::ImageUrl { image_url } => image_url.is_empty(),
RichContentPart::InputAudio { input_audio } => {
input_audio.is_empty()
}
RichContentPart::InputVideo { video_url } => video_url.is_empty(),
RichContentPart::VideoUrl { video_url } => video_url.is_empty(),
RichContentPart::File { file } => file.is_empty(),
}
}
}
impl ToStarlarkValue for RichContentPart {
fn to_starlark_value<'v>(
&self,
heap: &'v StarlarkHeap,
) -> StarlarkValue<'v> {
match self {
RichContentPart::Text { text } => heap.alloc(StarlarkAllocDict([
("type", "text".to_starlark_value(heap)),
("text", text.to_starlark_value(heap)),
])),
RichContentPart::ImageUrl { image_url } => {
heap.alloc(StarlarkAllocDict([
("type", "image_url".to_starlark_value(heap)),
("image_url", image_url.to_starlark_value(heap)),
]))
}
RichContentPart::InputAudio { input_audio } => {
heap.alloc(StarlarkAllocDict([
("type", "input_audio".to_starlark_value(heap)),
("input_audio", input_audio.to_starlark_value(heap)),
]))
}
RichContentPart::InputVideo { video_url } => {
heap.alloc(StarlarkAllocDict([
("type", "input_video".to_starlark_value(heap)),
("video_url", video_url.to_starlark_value(heap)),
]))
}
RichContentPart::VideoUrl { video_url } => {
heap.alloc(StarlarkAllocDict([
("type", "video_url".to_starlark_value(heap)),
("video_url", video_url.to_starlark_value(heap)),
]))
}
RichContentPart::File { file } => heap.alloc(StarlarkAllocDict([
("type", "file".to_starlark_value(heap)),
("file", file.to_starlark_value(heap)),
])),
}
}
}
impl FromStarlarkValue for RichContentPart {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
let dict = StarlarkDictRef::from_value(*value).ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"RichContentPart: expected dict".into(),
)
})?;
let mut typ = None;
for (k, v) in dict.iter() {
if let Ok(Some("type")) = <&str as UnpackValue>::unpack_value(k) {
typ = Some(
<&str as UnpackValue>::unpack_value(v)
.map_err(|e| {
ExpressionError::StarlarkConversionError(
e.to_string(),
)
})?
.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"RichContentPart: expected string type".into(),
)
})?,
);
break;
}
}
let typ = typ.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"RichContentPart: missing type".into(),
)
})?;
let payload_key = match typ {
"text" => "text",
"image_url" => "image_url",
"input_audio" => "input_audio",
"input_video" | "video_url" => "video_url",
"file" => "file",
_ => {
return Err(ExpressionError::StarlarkConversionError(format!(
"RichContentPart: unknown type: {}",
typ
)));
}
};
let mut payload = None;
for (k, v) in dict.iter() {
if let Ok(Some(key)) = <&str as UnpackValue>::unpack_value(k) {
if key == payload_key {
payload = Some(v);
break;
}
}
}
let v = payload.ok_or_else(|| {
ExpressionError::StarlarkConversionError(format!(
"RichContentPart: missing {}",
payload_key
))
})?;
match typ {
"text" => Ok(RichContentPart::Text {
text: String::from_starlark_value(&v)?,
}),
"image_url" => Ok(RichContentPart::ImageUrl {
image_url: ImageUrl::from_starlark_value(&v)?,
}),
"input_audio" => Ok(RichContentPart::InputAudio {
input_audio: InputAudio::from_starlark_value(&v)?,
}),
"input_video" => Ok(RichContentPart::InputVideo {
video_url: VideoUrl::from_starlark_value(&v)?,
}),
"video_url" => Ok(RichContentPart::VideoUrl {
video_url: VideoUrl::from_starlark_value(&v)?,
}),
"file" => Ok(RichContentPart::File {
file: File::from_starlark_value(&v)?,
}),
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[serde(tag = "type", rename_all = "snake_case")]
#[schemars(rename = "agent.completions.message.RichContentPartExpression")]
pub enum RichContentPartExpression {
#[schemars(title = "Text")]
Text {
text: functions::expression::WithExpression<String>,
},
#[schemars(title = "ImageUrl")]
ImageUrl {
image_url: functions::expression::WithExpression<ImageUrl>,
},
#[schemars(title = "InputAudio")]
InputAudio {
input_audio: functions::expression::WithExpression<InputAudio>,
},
#[schemars(title = "InputVideo")]
InputVideo {
video_url: functions::expression::WithExpression<VideoUrl>,
},
#[schemars(title = "VideoUrl")]
VideoUrl {
video_url: functions::expression::WithExpression<VideoUrl>,
},
#[schemars(title = "File")]
File {
file: functions::expression::WithExpression<File>,
},
}
impl RichContentPartExpression {
pub fn compile(
self,
params: &functions::expression::Params,
) -> Result<RichContentPart, functions::expression::ExpressionError> {
match self {
RichContentPartExpression::Text { text } => {
let text = text.compile_one(params)?;
Ok(RichContentPart::Text { text })
}
RichContentPartExpression::ImageUrl { image_url } => {
let image_url = image_url.compile_one(params)?;
Ok(RichContentPart::ImageUrl { image_url })
}
RichContentPartExpression::InputAudio { input_audio } => {
let input_audio = input_audio.compile_one(params)?;
Ok(RichContentPart::InputAudio { input_audio })
}
RichContentPartExpression::InputVideo { video_url } => {
let video_url = video_url.compile_one(params)?;
Ok(RichContentPart::InputVideo { video_url })
}
RichContentPartExpression::VideoUrl { video_url } => {
let video_url = video_url.compile_one(params)?;
Ok(RichContentPart::VideoUrl { video_url })
}
RichContentPartExpression::File { file } => {
let file = file.compile_one(params)?;
Ok(RichContentPart::File { file })
}
}
}
}
impl From<RichContentPart> for RichContentPartExpression {
fn from(part: RichContentPart) -> Self {
match part {
RichContentPart::Text { text } => RichContentPartExpression::Text {
text: WithExpression::Value(text),
},
RichContentPart::ImageUrl { image_url } => {
RichContentPartExpression::ImageUrl {
image_url: WithExpression::Value(image_url),
}
}
RichContentPart::InputAudio { input_audio } => {
RichContentPartExpression::InputAudio {
input_audio: WithExpression::Value(input_audio),
}
}
RichContentPart::InputVideo { video_url } => {
RichContentPartExpression::InputVideo {
video_url: WithExpression::Value(video_url),
}
}
RichContentPart::VideoUrl { video_url } => {
RichContentPartExpression::VideoUrl {
video_url: WithExpression::Value(video_url),
}
}
RichContentPart::File { file } => RichContentPartExpression::File {
file: WithExpression::Value(file),
},
}
}
}
impl FromStarlarkValue for RichContentPartExpression {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
let part = RichContentPart::from_starlark_value(value)?;
match part {
RichContentPart::Text { text } => {
Ok(RichContentPartExpression::Text {
text: WithExpression::Value(text),
})
}
RichContentPart::ImageUrl { image_url } => {
Ok(RichContentPartExpression::ImageUrl {
image_url: WithExpression::Value(image_url),
})
}
RichContentPart::InputAudio { input_audio } => {
Ok(RichContentPartExpression::InputAudio {
input_audio: WithExpression::Value(input_audio),
})
}
RichContentPart::InputVideo { video_url } => {
Ok(RichContentPartExpression::InputVideo {
video_url: WithExpression::Value(video_url),
})
}
RichContentPart::VideoUrl { video_url } => {
Ok(RichContentPartExpression::VideoUrl {
video_url: WithExpression::Value(video_url),
})
}
RichContentPart::File { file } => {
Ok(RichContentPartExpression::File {
file: WithExpression::Value(file),
})
}
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[schemars(rename = "agent.completions.message.ImageUrl")]
pub struct ImageUrl {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub detail: Option<ImageUrlDetail>,
}
impl ImageUrl {
pub fn prepare(&mut self) {
if matches!(self.detail, Some(ImageUrlDetail::Auto)) {
self.detail = None;
}
}
pub fn is_empty(&self) -> bool {
self.url.is_empty() && self.detail.is_none()
}
pub fn file_content(&self) -> Option<super::FileContent<'_>> {
let (mime, payload) = super::file_content::parse_data_url(&self.url)?;
Some(super::FileContent {
content: payload,
extension: super::file_content::mime_to_ext(mime),
})
}
}
impl ToStarlarkValue for ImageUrl {
fn to_starlark_value<'v>(
&self,
heap: &'v StarlarkHeap,
) -> StarlarkValue<'v> {
heap.alloc(StarlarkAllocDict([
("url", self.url.to_starlark_value(heap)),
("detail", self.detail.to_starlark_value(heap)),
]))
}
}
impl FromStarlarkValue for ImageUrl {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
let dict = StarlarkDictRef::from_value(*value).ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"ImageUrl: expected dict".into(),
)
})?;
let mut url = None;
let mut detail = None;
for (k, v) in dict.iter() {
let key = <&str as UnpackValue>::unpack_value(k)
.map_err(|e| {
ExpressionError::StarlarkConversionError(e.to_string())
})?
.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"ImageUrl: expected string key".into(),
)
})?;
match key {
"url" => url = Some(String::from_starlark_value(&v)?),
"detail" => {
detail = Option::<ImageUrlDetail>::from_starlark_value(&v)?
}
_ => {}
}
if url.is_some() && detail.is_some() {
break;
}
}
Ok(ImageUrl {
url: url.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"ImageUrl: missing url".into(),
)
})?,
detail,
})
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[schemars(rename = "agent.completions.message.ImageUrlDetail")]
pub enum ImageUrlDetail {
#[schemars(title = "Auto")]
#[serde(rename = "auto")]
Auto,
#[schemars(title = "Low")]
#[serde(rename = "low")]
Low,
#[schemars(title = "High")]
#[serde(rename = "high")]
High,
}
impl ToStarlarkValue for ImageUrlDetail {
fn to_starlark_value<'v>(
&self,
heap: &'v StarlarkHeap,
) -> StarlarkValue<'v> {
match self {
ImageUrlDetail::Auto => "auto".to_starlark_value(heap),
ImageUrlDetail::Low => "low".to_starlark_value(heap),
ImageUrlDetail::High => "high".to_starlark_value(heap),
}
}
}
impl FromStarlarkValue for ImageUrlDetail {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
let s = <&str as UnpackValue>::unpack_value(*value)
.map_err(|e| {
ExpressionError::StarlarkConversionError(e.to_string())
})?
.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"ImageUrlDetail: expected string".into(),
)
})?;
match s {
"auto" => Ok(ImageUrlDetail::Auto),
"low" => Ok(ImageUrlDetail::Low),
"high" => Ok(ImageUrlDetail::High),
_ => Err(ExpressionError::StarlarkConversionError(format!(
"ImageUrlDetail: unknown value: {}",
s
))),
}
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[schemars(rename = "agent.completions.message.InputAudio")]
pub struct InputAudio {
pub data: String,
pub format: String,
}
impl InputAudio {
pub fn is_empty(&self) -> bool {
self.data.is_empty() && self.format.is_empty()
}
pub fn file_content(&self) -> Option<super::FileContent<'_>> {
if self.data.is_empty() {
return None;
}
Some(super::FileContent {
content: &self.data,
extension: if self.format.is_empty() { "bin" } else { &self.format },
})
}
}
impl ToStarlarkValue for InputAudio {
fn to_starlark_value<'v>(
&self,
heap: &'v StarlarkHeap,
) -> StarlarkValue<'v> {
heap.alloc(StarlarkAllocDict([
("data", self.data.to_starlark_value(heap)),
("format", self.format.to_starlark_value(heap)),
]))
}
}
impl FromStarlarkValue for InputAudio {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
let dict = StarlarkDictRef::from_value(*value).ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"InputAudio: expected dict".into(),
)
})?;
let mut data = None;
let mut format = None;
for (k, v) in dict.iter() {
let key = <&str as UnpackValue>::unpack_value(k)
.map_err(|e| {
ExpressionError::StarlarkConversionError(e.to_string())
})?
.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"InputAudio: expected string key".into(),
)
})?;
match key {
"data" => data = Some(String::from_starlark_value(&v)?),
"format" => format = Some(String::from_starlark_value(&v)?),
_ => {}
}
if data.is_some() && format.is_some() {
break;
}
}
Ok(InputAudio {
data: data.unwrap_or_default(),
format: format.unwrap_or_default(),
})
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[schemars(rename = "agent.completions.message.VideoUrl")]
pub struct VideoUrl {
pub url: String,
}
impl VideoUrl {
pub fn is_empty(&self) -> bool {
self.url.is_empty()
}
pub fn file_content(&self) -> Option<super::FileContent<'_>> {
let (mime, payload) = super::file_content::parse_data_url(&self.url)?;
Some(super::FileContent {
content: payload,
extension: super::file_content::mime_to_ext(mime),
})
}
}
impl ToStarlarkValue for VideoUrl {
fn to_starlark_value<'v>(
&self,
heap: &'v StarlarkHeap,
) -> StarlarkValue<'v> {
heap.alloc(StarlarkAllocDict([(
"url",
self.url.to_starlark_value(heap),
)]))
}
}
impl FromStarlarkValue for VideoUrl {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
let dict = StarlarkDictRef::from_value(*value).ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"VideoUrl: expected dict".into(),
)
})?;
let mut url = None;
for (k, v) in dict.iter() {
let key = <&str as UnpackValue>::unpack_value(k)
.map_err(|e| {
ExpressionError::StarlarkConversionError(e.to_string())
})?
.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"VideoUrl: expected string key".into(),
)
})?;
if key == "url" {
url = Some(String::from_starlark_value(&v)?);
}
}
Ok(VideoUrl {
url: url.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"VideoUrl: missing url".into(),
)
})?,
})
}
}
#[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
#[schemars(rename = "agent.completions.message.File")]
pub struct File {
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub file_data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub file_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub filename: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[schemars(extend("omitempty" = true))]
pub file_url: Option<String>,
}
impl File {
pub fn prepare(&mut self) {
if self.file_data.as_ref().is_some_and(String::is_empty) {
self.file_data = None;
}
if self.file_id.as_ref().is_some_and(String::is_empty) {
self.file_id = None;
}
if self.filename.as_ref().is_some_and(String::is_empty) {
self.filename = None;
}
if self.file_url.as_ref().is_some_and(String::is_empty) {
self.file_url = None;
}
}
pub fn is_empty(&self) -> bool {
self.file_data.is_none()
&& self.file_id.is_none()
&& self.filename.is_none()
&& self.file_url.is_none()
}
pub fn file_content(&self) -> Option<super::FileContent<'_>> {
let data = self.file_data.as_deref()?;
if data.is_empty() {
return None;
}
let ext = self.filename.as_deref()
.and_then(|name| name.rsplit_once('.'))
.map(|(_, ext)| ext)
.unwrap_or("bin");
Some(super::FileContent {
content: data,
extension: ext,
})
}
}
impl ToStarlarkValue for File {
fn to_starlark_value<'v>(
&self,
heap: &'v StarlarkHeap,
) -> StarlarkValue<'v> {
heap.alloc(StarlarkAllocDict([
("file_data", self.file_data.to_starlark_value(heap)),
("file_id", self.file_id.to_starlark_value(heap)),
("filename", self.filename.to_starlark_value(heap)),
("file_url", self.file_url.to_starlark_value(heap)),
]))
}
}
impl FromStarlarkValue for File {
fn from_starlark_value(
value: &StarlarkValue,
) -> Result<Self, ExpressionError> {
let dict = StarlarkDictRef::from_value(*value).ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"File: expected dict".into(),
)
})?;
let mut file_data = None;
let mut file_id = None;
let mut filename = None;
let mut file_url = None;
for (k, v) in dict.iter() {
let key = <&str as UnpackValue>::unpack_value(k)
.map_err(|e| {
ExpressionError::StarlarkConversionError(e.to_string())
})?
.ok_or_else(|| {
ExpressionError::StarlarkConversionError(
"File: expected string key".into(),
)
})?;
match key {
"file_data" => {
file_data = Option::<String>::from_starlark_value(&v)?
}
"file_id" => {
file_id = Option::<String>::from_starlark_value(&v)?
}
"filename" => {
filename = Option::<String>::from_starlark_value(&v)?
}
"file_url" => {
file_url = Option::<String>::from_starlark_value(&v)?
}
_ => {}
}
}
Ok(File {
file_data,
file_id,
filename,
file_url,
})
}
}
crate::functions::expression::impl_from_special_unsupported!(
RichContentExpression,
RichContentPartExpression,
ImageUrl,
InputAudio,
VideoUrl,
File,
);
impl crate::functions::expression::FromSpecial
for Vec<crate::functions::expression::WithExpression<RichContentExpression>>
{
fn from_special(
_special: &crate::functions::expression::Special,
_params: &crate::functions::expression::Params,
) -> Result<Self, crate::functions::expression::ExpressionError> {
Err(crate::functions::expression::ExpressionError::UnsupportedSpecial)
}
}