Skip to main content

objectiveai_sdk/agent/completions/message/
simple_content.rs

1//! Simple text content types for system/developer messages.
2
3use crate::functions;
4use functions::expression::{
5    ExpressionError, FromStarlarkValue, WithExpression,
6};
7use serde::{Deserialize, Serialize};
8use starlark::values::dict::DictRef as StarlarkDictRef;
9use starlark::values::{UnpackValue, Value as StarlarkValue};
10use schemars::JsonSchema;
11
12/// Simple text content for system/developer messages.
13#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
14#[serde(untagged)]
15#[schemars(rename = "agent.completions.message.SimpleContent")]
16pub enum SimpleContent {
17    /// Plain text content.
18    #[schemars(title = "Text")]
19    Text(String),
20    /// Multi-part text content.
21    #[schemars(title = "Parts")]
22    Parts(Vec<SimpleContentPart>),
23}
24
25impl SimpleContent {
26    pub fn push(&mut self, other: &SimpleContent) {
27        match (&mut *self, other) {
28            (SimpleContent::Text(self_text), SimpleContent::Text(other_text)) => {
29                self_text.push_str(&other_text);
30            }
31            (SimpleContent::Text(self_text), SimpleContent::Parts(other_parts)) => {
32                let mut parts = Vec::with_capacity(1 + other_parts.len());
33                parts.push(SimpleContentPart::Text {
34                    text: std::mem::take(self_text),
35                });
36                parts.extend(other_parts.iter().cloned());
37                *self = SimpleContent::Parts(parts);
38            }
39            (SimpleContent::Parts(self_parts), SimpleContent::Text(other_text)) => {
40                self_parts.push(SimpleContentPart::Text {
41                    text: other_text.clone(),
42                });
43            }
44            (SimpleContent::Parts(self_parts), SimpleContent::Parts(other_parts)) => {
45                self_parts.extend(other_parts.iter().cloned());
46            }
47        }
48    }
49
50    /// Prepares the content by consolidating parts into a single text string.
51    pub fn prepare(&mut self) {
52        match self {
53            SimpleContent::Text(_) => {}
54            SimpleContent::Parts(parts) => {
55                let text_len = parts
56                    .iter()
57                    .map(|part| match part {
58                        SimpleContentPart::Text { text } => text.len(),
59                    })
60                    .sum();
61                let mut text = String::with_capacity(text_len);
62                for part in parts {
63                    match part {
64                        SimpleContentPart::Text { text: part_text } => {
65                            text.push_str(&part_text);
66                        }
67                    }
68                }
69                *self = SimpleContent::Text(text)
70            }
71        }
72    }
73}
74
75impl FromStarlarkValue for SimpleContent {
76    fn from_starlark_value(
77        value: &StarlarkValue,
78    ) -> Result<Self, ExpressionError> {
79        if let Ok(Some(s)) = <&str as UnpackValue>::unpack_value(*value) {
80            return Ok(SimpleContent::Text(s.to_owned()));
81        }
82        let parts = Vec::<SimpleContentPart>::from_starlark_value(value)?;
83        Ok(SimpleContent::Parts(parts))
84    }
85}
86
87/// Expression variant of [`SimpleContent`] for dynamic content.
88#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
89#[serde(untagged)]
90#[schemars(rename = "agent.completions.message.SimpleContentExpression")]
91pub enum SimpleContentExpression {
92    /// Plain text content.
93    #[schemars(title = "Text")]
94    Text(String),
95    /// Multi-part text content expressions.
96    #[schemars(title = "Parts")]
97    Parts(
98        Vec<functions::expression::WithExpression<SimpleContentPartExpression>>,
99    ),
100}
101
102impl SimpleContentExpression {
103    /// Compiles the expression into a concrete [`SimpleContent`].
104    pub fn compile(
105        self,
106        params: &functions::expression::Params,
107    ) -> Result<SimpleContent, functions::expression::ExpressionError> {
108        match self {
109            SimpleContentExpression::Text(text) => {
110                Ok(SimpleContent::Text(text))
111            }
112            SimpleContentExpression::Parts(parts) => {
113                let mut compiled_parts = Vec::with_capacity(parts.len());
114                for part in parts {
115                    match part.compile_one_or_many(params)? {
116                        functions::expression::OneOrMany::One(one_part) => {
117                            compiled_parts.push(one_part.compile(params)?);
118                        }
119                        functions::expression::OneOrMany::Many(many_parts) => {
120                            for part in many_parts {
121                                compiled_parts.push(part.compile(params)?);
122                            }
123                        }
124                    }
125                }
126                Ok(SimpleContent::Parts(compiled_parts))
127            }
128        }
129    }
130}
131
132impl FromStarlarkValue for SimpleContentExpression {
133    fn from_starlark_value(
134        value: &StarlarkValue,
135    ) -> Result<Self, ExpressionError> {
136        if let Ok(Some(s)) = <&str as UnpackValue>::unpack_value(*value) {
137            return Ok(SimpleContentExpression::Text(s.to_owned()));
138        }
139        let parts = Vec::<WithExpression<SimpleContentPartExpression>>::from_starlark_value(value)?;
140        Ok(SimpleContentExpression::Parts(parts))
141    }
142}
143
144/// A part of simple text content.
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
146#[serde(tag = "type", rename_all = "snake_case")]
147#[schemars(rename = "agent.completions.message.SimpleContentPart")]
148pub enum SimpleContentPart {
149    /// A text part.
150    Text {
151        /// The text content.
152        text: String,
153    },
154}
155
156impl FromStarlarkValue for SimpleContentPart {
157    fn from_starlark_value(
158        value: &StarlarkValue,
159    ) -> Result<Self, ExpressionError> {
160        let dict = StarlarkDictRef::from_value(*value).ok_or_else(|| {
161            ExpressionError::StarlarkConversionError(
162                "SimpleContentPart: expected dict".into(),
163            )
164        })?;
165        for (k, v) in dict.iter() {
166            if let Ok(Some("text")) = <&str as UnpackValue>::unpack_value(k) {
167                return Ok(SimpleContentPart::Text {
168                    text: String::from_starlark_value(&v)?,
169                });
170            }
171        }
172        Err(ExpressionError::StarlarkConversionError(
173            "SimpleContentPart: missing text".into(),
174        ))
175    }
176}
177
178/// Expression variant of [`SimpleContentPart`] for dynamic content.
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, arbitrary::Arbitrary)]
180#[serde(tag = "type", rename_all = "snake_case")]
181#[schemars(rename = "agent.completions.message.SimpleContentPartExpression")]
182pub enum SimpleContentPartExpression {
183    /// A text part expression.
184    Text {
185        /// The text expression.
186        text: functions::expression::WithExpression<String>,
187    },
188}
189
190impl SimpleContentPartExpression {
191    /// Compiles the expression into a concrete [`SimpleContentPart`].
192    pub fn compile(
193        self,
194        params: &functions::expression::Params,
195    ) -> Result<SimpleContentPart, functions::expression::ExpressionError> {
196        match self {
197            SimpleContentPartExpression::Text { text } => {
198                let text = text.compile_one(params)?;
199                Ok(SimpleContentPart::Text { text })
200            }
201        }
202    }
203}
204
205impl FromStarlarkValue for SimpleContentPartExpression {
206    fn from_starlark_value(
207        value: &StarlarkValue,
208    ) -> Result<Self, ExpressionError> {
209        let part = SimpleContentPart::from_starlark_value(value)?;
210        match part {
211            SimpleContentPart::Text { text } => {
212                Ok(SimpleContentPartExpression::Text {
213                    text: WithExpression::Value(text),
214                })
215            }
216        }
217    }
218}
219
220crate::functions::expression::impl_from_special_unsupported!(
221    SimpleContentExpression,
222    SimpleContentPartExpression,
223);