1use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub struct Slot {
10 pub name: String,
12
13 pub prompt: String,
15
16 pub kind: SlotKind,
18
19 pub constraints: Option<SlotConstraints>,
21
22 pub required: bool,
24
25 pub default: Option<String>,
27
28 pub temperature: Option<f32>,
30
31 pub model: Option<String>,
33
34 pub max_tokens: Option<u32>,
36}
37
38#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
40#[serde(rename_all = "snake_case")]
41pub enum SlotKind {
42 #[default]
44 Raw,
45
46 Function,
48
49 Class,
51
52 Html,
54
55 Css,
57
58 JavaScript,
60
61 Component,
63
64 Custom(String),
66}
67
68#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
70pub struct SlotConstraints {
71 pub max_lines: Option<usize>,
73
74 pub max_chars: Option<usize>,
76
77 pub required_imports: Vec<String>,
79
80 pub forbidden_patterns: Vec<String>,
82
83 pub language: Option<String>,
85
86 pub test_harness: Option<String>,
89
90 pub test_command: Option<String>,
92}
93
94impl Eq for Slot {}
95
96impl std::hash::Hash for Slot {
97 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
98 self.name.hash(state);
99 self.prompt.hash(state);
100 self.kind.hash(state);
101 self.constraints.hash(state);
102 self.required.hash(state);
103 self.default.hash(state);
104 if let Some(temp) = self.temperature {
105 temp.to_bits().hash(state);
106 }
107 self.model.hash(state);
108 self.max_tokens.hash(state);
109 }
110}
111
112impl Slot {
113 pub fn new(name: impl Into<String>, prompt: impl Into<String>) -> Self {
129 Self {
130 name: name.into(),
131 prompt: prompt.into(),
132 kind: SlotKind::default(),
133 constraints: None,
134 required: true,
135 default: None,
136 temperature: None,
137 model: None,
138 max_tokens: None,
139 }
140 }
141
142 pub fn with_temperature(mut self, temp: f32) -> Self {
144 self.temperature = Some(temp.clamp(0.0, 2.0));
145 self
146 }
147
148 pub fn with_model(mut self, model: impl Into<String>) -> Self {
150 self.model = Some(model.into());
151 self
152 }
153
154 pub fn with_max_tokens(mut self, max_tokens: u32) -> Self {
156 self.max_tokens = Some(max_tokens);
157 self
158 }
159
160 pub fn with_kind(mut self, kind: SlotKind) -> Self {
162 self.kind = kind;
163 self
164 }
165
166 pub fn with_constraints(mut self, constraints: SlotConstraints) -> Self {
168 self.constraints = Some(constraints);
169 self
170 }
171
172 pub fn optional(mut self, default: impl Into<String>) -> Self {
174 self.required = false;
175 self.default = Some(default.into());
176 self
177 }
178
179 pub fn validate(&self, code: &str) -> Result<(), Vec<String>> {
181 let mut errors = Vec::new();
182
183 if let Some(ref constraints) = self.constraints {
184 if let Some(max) = constraints.max_lines {
186 let lines = code.lines().count();
187 if lines > max {
188 errors.push(format!("Code exceeds max lines: {} > {}", lines, max));
189 }
190 }
191
192 if let Some(max) = constraints.max_chars {
194 if code.len() > max {
195 errors.push(format!("Code exceeds max chars: {} > {}", code.len(), max));
196 }
197 }
198
199 for pattern in &constraints.forbidden_patterns {
201 if let Ok(re) = regex::Regex::new(pattern) {
202 if re.is_match(code) {
203 errors.push(format!("Code contains forbidden pattern: {}", pattern));
204 }
205 }
206 }
207 }
208
209 if errors.is_empty() {
210 Ok(())
211 } else {
212 Err(errors)
213 }
214 }
215}
216
217impl SlotConstraints {
218 pub fn new() -> Self {
220 Self::default()
221 }
222
223 pub fn max_lines(mut self, lines: usize) -> Self {
225 self.max_lines = Some(lines);
226 self
227 }
228
229 pub fn max_chars(mut self, chars: usize) -> Self {
231 self.max_chars = Some(chars);
232 self
233 }
234
235 pub fn language(mut self, lang: impl Into<String>) -> Self {
237 self.language = Some(lang.into());
238 self
239 }
240
241 pub fn require_import(mut self, import: impl Into<String>) -> Self {
243 self.required_imports.push(import.into());
244 self
245 }
246
247 pub fn forbid_pattern(mut self, pattern: impl Into<String>) -> Self {
249 self.forbidden_patterns.push(pattern.into());
250 self
251 }
252
253 pub fn test_harness(mut self, harness: impl Into<String>) -> Self {
255 self.test_harness = Some(harness.into());
256 self
257 }
258
259 pub fn test_command(mut self, command: impl Into<String>) -> Self {
261 self.test_command = Some(command.into());
262 self
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_slot_creation() {
272 let slot = Slot::new("test", "Generate a test");
273 assert_eq!(slot.name, "test");
274 assert_eq!(slot.prompt, "Generate a test");
275 assert!(slot.required);
276 }
277
278 #[test]
279 fn test_slot_validation() {
280 let slot = Slot::new("test", "")
281 .with_constraints(SlotConstraints::new().max_lines(5));
282
283 assert!(slot.validate("line1\nline2\nline3").is_ok());
284 assert!(slot.validate("1\n2\n3\n4\n5\n6").is_err());
285 }
286}