1use crate::{CacheControl, ContentBlock};
20
21#[derive(Debug, Clone)]
25struct PromptLayer {
26 text: String,
27 cache_control: Option<CacheControl>,
28}
29
30#[derive(Debug, Clone)]
55pub struct Prompt {
56 layers: Vec<PromptLayer>,
57}
58
59impl Prompt {
60 pub fn plain(text: impl Into<String>) -> Self {
62 Self {
63 layers: vec![PromptLayer {
64 text: text.into(),
65 cache_control: None,
66 }],
67 }
68 }
69
70 pub fn builder() -> PromptBuilder {
72 PromptBuilder::new()
73 }
74
75 pub fn to_content_blocks(&self) -> Vec<ContentBlock> {
79 self.layers
80 .iter()
81 .map(|layer| match layer.cache_control {
82 Some(cache) => ContentBlock::text_with_cache(layer.text.clone(), cache),
83 None => ContentBlock::text(&layer.text),
84 })
85 .collect()
86 }
87
88 pub fn build_text(&self) -> String {
92 self.layers
93 .iter()
94 .map(|layer| layer.text.clone())
95 .collect::<Vec<_>>()
96 .join("\n\n")
97 }
98
99 pub fn is_empty(&self) -> bool {
101 self.layers.iter().all(|l| l.text.is_empty())
102 }
103}
104
105impl From<String> for Prompt {
106 fn from(s: String) -> Self {
107 Self::plain(s)
108 }
109}
110
111impl From<&str> for Prompt {
112 fn from(s: &str) -> Self {
113 Self::plain(s)
114 }
115}
116
117impl From<&String> for Prompt {
118 fn from(s: &String) -> Self {
119 Self::plain(s.clone())
120 }
121}
122
123impl Default for Prompt {
124 fn default() -> Self {
125 Self { layers: vec![] }
126 }
127}
128
129#[derive(Debug, Clone, Default)]
154pub struct PromptBuilder {
155 layers: Vec<PromptLayer>,
156}
157
158impl PromptBuilder {
159 pub fn new() -> Self {
160 Self::default()
161 }
162
163 pub fn layer_cached(mut self, text: impl Into<String>) -> Self {
168 self.layers.push(PromptLayer {
169 text: text.into(),
170 cache_control: Some(CacheControl::Breakpoint),
171 });
172 self
173 }
174
175 pub fn layer_dynamic(mut self, text: impl Into<String>) -> Self {
179 self.layers.push(PromptLayer {
180 text: text.into(),
181 cache_control: None,
182 });
183 self
184 }
185
186 pub fn layer(mut self, text: impl Into<String>, cache: Option<CacheControl>) -> Self {
190 self.layers.push(PromptLayer {
191 text: text.into(),
192 cache_control: cache,
193 });
194 self
195 }
196
197 pub fn build(self) -> Prompt {
199 Prompt {
200 layers: self.layers,
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_prompt_from_string() {
211 let prompt: Prompt = "hello".into();
212 let blocks = prompt.to_content_blocks();
213 assert_eq!(blocks.len(), 1);
214 assert_eq!(blocks[0].as_text(), Some("hello"));
215 if let ContentBlock::Text(t) = &blocks[0] {
216 assert!(t.cache_control.is_none());
217 }
218 }
219
220 #[test]
221 fn test_prompt_from_str() {
222 let s = "world";
223 let prompt: Prompt = s.into();
224 assert_eq!(prompt.build_text(), "world");
225 }
226
227 #[test]
228 fn test_prompt_plain() {
229 let prompt = Prompt::plain("plain text");
230 assert_eq!(prompt.build_text(), "plain text");
231 let blocks = prompt.to_content_blocks();
232 assert_eq!(blocks.len(), 1);
233 assert!(matches!(
234 &blocks[0],
235 ContentBlock::Text(t) if t.cache_control.is_none()
236 ));
237 }
238
239 #[test]
240 fn test_prompt_builder_layered() {
241 let prompt = Prompt::builder()
242 .layer_cached("layer1")
243 .layer_cached("layer2")
244 .layer_dynamic("dynamic")
245 .build();
246
247 let blocks = prompt.to_content_blocks();
248 assert_eq!(blocks.len(), 3);
249 assert_eq!(blocks.len(), 3);
250
251 if let ContentBlock::Text(t) = &blocks[0] {
253 assert_eq!(t.text, "layer1");
254 assert!(t.cache_control.is_some());
255 } else {
256 panic!("expected Text block");
257 }
258
259 if let ContentBlock::Text(t) = &blocks[1] {
261 assert_eq!(t.text, "layer2");
262 assert!(t.cache_control.is_some());
263 } else {
264 panic!("expected Text block");
265 }
266
267 if let ContentBlock::Text(t) = &blocks[2] {
269 assert_eq!(t.text, "dynamic");
270 assert!(t.cache_control.is_none());
271 } else {
272 panic!("expected Text block");
273 }
274 }
275
276 #[test]
277 fn test_build_text_joins_with_double_newline() {
278 let prompt = Prompt::builder()
279 .layer_cached("A")
280 .layer_cached("B")
281 .build();
282
283 assert_eq!(prompt.build_text(), "A\n\nB");
284 }
285
286 #[test]
287 fn test_build_text_single_layer() {
288 let prompt = Prompt::builder().layer_cached("only").build();
289
290 assert_eq!(prompt.build_text(), "only");
291 }
292
293 #[test]
294 fn test_prompt_is_empty() {
295 let empty = Prompt::default();
296 assert!(empty.is_empty());
297
298 let nonempty: Prompt = "x".into();
299 assert!(!nonempty.is_empty());
300 }
301
302 #[test]
303 fn test_prompt_layer_custom_cache() {
304 let prompt = Prompt::builder()
305 .layer("cached", Some(CacheControl::Breakpoint))
306 .layer("no cache", None)
307 .build();
308
309 let blocks = prompt.to_content_blocks();
310 if let ContentBlock::Text(t) = &blocks[0] {
311 assert!(t.cache_control.is_some());
312 } else {
313 panic!("expected Text");
314 }
315 if let ContentBlock::Text(t) = &blocks[1] {
316 assert!(t.cache_control.is_none());
317 } else {
318 panic!("expected Text");
319 }
320 }
321
322 #[test]
323 fn test_empty_layers_produce_empty_blocks() {
324 let prompt = Prompt::builder().build();
325 assert!(prompt.to_content_blocks().is_empty());
326 assert_eq!(prompt.build_text(), "");
327 }
328}