gent/interpreter/
types.rs

1//! Value types for the GENT interpreter
2
3use crate::parser::ast::{
4    Block, FieldType, OutputType, Param, StructField, TypeName as ParserTypeName,
5};
6use std::collections::HashMap;
7use std::fmt;
8
9/// Runtime representation of an output schema
10#[derive(Debug, Clone, PartialEq)]
11pub struct OutputSchema {
12    pub fields: Vec<StructField>,
13}
14
15impl OutputSchema {
16    pub fn from_output_type(
17        output_type: &OutputType,
18        structs: &HashMap<String, Vec<StructField>>,
19    ) -> Result<Self, String> {
20        match output_type {
21            OutputType::Inline(fields) => {
22                // Resolve any Named types in the inline fields
23                let resolved_fields = resolve_named_types(fields, structs)?;
24                Ok(OutputSchema {
25                    fields: resolved_fields,
26                })
27            }
28            OutputType::Named(name) => {
29                let fields = structs
30                    .get(name)
31                    .ok_or_else(|| format!("Unknown struct: {}", name))?;
32                // Resolve any Named types in the struct fields
33                let resolved_fields = resolve_named_types(fields, structs)?;
34                Ok(OutputSchema {
35                    fields: resolved_fields,
36                })
37            }
38        }
39    }
40
41    pub fn to_json_schema(&self) -> serde_json::Value {
42        use serde_json::json;
43
44        let properties: serde_json::Map<String, serde_json::Value> = self
45            .fields
46            .iter()
47            .map(|f| (f.name.clone(), field_type_to_json_schema(&f.field_type)))
48            .collect();
49
50        let required: Vec<String> = self.fields.iter().map(|f| f.name.clone()).collect();
51
52        json!({
53            "type": "object",
54            "properties": properties,
55            "required": required
56        })
57    }
58}
59
60/// Recursively resolve FieldType::Named references to FieldType::Object
61fn resolve_named_types(
62    fields: &[StructField],
63    structs: &HashMap<String, Vec<StructField>>,
64) -> Result<Vec<StructField>, String> {
65    fields
66        .iter()
67        .map(|field| {
68            let resolved_type = resolve_field_type(&field.field_type, structs)?;
69            Ok(StructField {
70                name: field.name.clone(),
71                field_type: resolved_type,
72                span: field.span.clone(),
73            })
74        })
75        .collect()
76}
77
78/// Resolve a single FieldType, converting Named to Object
79fn resolve_field_type(
80    ft: &FieldType,
81    structs: &HashMap<String, Vec<StructField>>,
82) -> Result<FieldType, String> {
83    match ft {
84        FieldType::String => Ok(FieldType::String),
85        FieldType::Number => Ok(FieldType::Number),
86        FieldType::Boolean => Ok(FieldType::Boolean),
87        FieldType::Array(inner) => {
88            let resolved_inner = resolve_field_type(inner, structs)?;
89            Ok(FieldType::Array(Box::new(resolved_inner)))
90        }
91        FieldType::Object(fields) => {
92            let resolved_fields = resolve_named_types(fields, structs)?;
93            Ok(FieldType::Object(resolved_fields))
94        }
95        FieldType::Named(name) => {
96            let struct_fields = structs
97                .get(name)
98                .ok_or_else(|| format!("Unknown struct: {}", name))?;
99            // Recursively resolve the struct's fields
100            let resolved_fields = resolve_named_types(struct_fields, structs)?;
101            Ok(FieldType::Object(resolved_fields))
102        }
103    }
104}
105
106fn field_type_to_json_schema(ft: &FieldType) -> serde_json::Value {
107    use serde_json::json;
108    match ft {
109        FieldType::String => json!({"type": "string"}),
110        FieldType::Number => json!({"type": "number"}),
111        FieldType::Boolean => json!({"type": "boolean"}),
112        FieldType::Array(inner) => json!({
113            "type": "array",
114            "items": field_type_to_json_schema(inner)
115        }),
116        FieldType::Object(fields) => {
117            let properties: serde_json::Map<String, serde_json::Value> = fields
118                .iter()
119                .map(|f| (f.name.clone(), field_type_to_json_schema(&f.field_type)))
120                .collect();
121            let required: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
122            json!({
123                "type": "object",
124                "properties": properties,
125                "required": required
126            })
127        }
128        FieldType::Named(name) => json!({"$ref": format!("#/definitions/{}", name)}),
129    }
130}
131
132/// Represents a user-defined tool at runtime
133#[derive(Debug, Clone, PartialEq)]
134pub struct UserToolValue {
135    pub name: String,
136    pub params: Vec<Param>,
137    pub return_type: Option<ParserTypeName>,
138    pub body: Block,
139}
140
141/// Represents a user-defined function at runtime (pure, no agent access)
142#[derive(Debug, Clone, PartialEq)]
143pub struct FnValue {
144    pub name: String,
145    pub params: Vec<Param>,
146    pub return_type: Option<ParserTypeName>,
147    pub body: Block,
148}
149
150/// Represents a lambda/closure at runtime
151#[derive(Debug, Clone, PartialEq)]
152pub struct LambdaValue {
153    pub params: Vec<String>,
154    pub body: crate::parser::ast::LambdaBody,
155}
156
157/// Runtime value for a parallel execution block
158#[derive(Debug, Clone, PartialEq)]
159pub struct ParallelValue {
160    pub name: String,
161    pub agents: Vec<crate::parser::ast::Expression>,
162    pub timeout_ms: u64,
163}
164
165/// Definition of an enum type (stored in environment)
166#[derive(Debug, Clone, PartialEq)]
167pub struct EnumDef {
168    pub name: String,
169    pub variants: Vec<EnumVariantDef>,
170}
171
172/// Definition of an enum variant
173#[derive(Debug, Clone, PartialEq)]
174pub struct EnumVariantDef {
175    pub name: String,
176    pub fields: Vec<EnumFieldDef>,
177}
178
179/// Definition of a field in an enum variant
180#[derive(Debug, Clone, PartialEq)]
181pub struct EnumFieldDef {
182    pub name: Option<String>,
183    pub type_name: String,
184}
185
186/// Runtime value of an enum instance
187#[derive(Debug, Clone, PartialEq)]
188pub struct EnumValue {
189    pub enum_name: String,
190    pub variant: String,
191    pub data: Vec<Value>,
192}
193
194/// Intermediate value for enum variant with data (before being called)
195#[derive(Debug, Clone, PartialEq)]
196pub struct EnumConstructor {
197    pub enum_name: String,
198    pub variant: String,
199    pub expected_fields: usize,
200}
201
202/// Runtime values in GENT
203#[derive(Debug, Clone, PartialEq)]
204pub enum Value {
205    /// String value
206    String(String),
207    /// Numeric value (f64)
208    Number(f64),
209    /// Boolean value
210    Boolean(bool),
211    /// Null/none value
212    Null,
213    /// Agent value
214    Agent(AgentValue),
215    /// Array value
216    Array(Vec<Value>),
217    /// Object value (key-value map)
218    Object(HashMap<String, Value>),
219    /// User-defined tool
220    Tool(UserToolValue),
221    /// User-defined function (pure, no agent access)
222    Function(FnValue),
223    /// Lambda/closure value
224    Lambda(LambdaValue),
225    /// Enum value
226    Enum(EnumValue),
227    /// Enum constructor (intermediate value before calling with args)
228    EnumConstructor(EnumConstructor),
229    /// Parallel execution block
230    Parallel(ParallelValue),
231}
232
233/// Represents a defined agent at runtime
234#[derive(Debug, Clone, PartialEq)]
235pub struct AgentValue {
236    /// Name of the agent
237    pub name: String,
238    /// System prompt for the agent
239    pub system_prompt: String,
240    /// User prompt for the agent (optional)
241    pub user_prompt: Option<String>,
242    /// Tools available to this agent
243    pub tools: Vec<String>,
244    /// Maximum steps before stopping (None = default 10)
245    pub max_steps: Option<u32>,
246    /// Model to use (None = default)
247    pub model: Option<String>,
248    /// Output schema for structured responses
249    pub output_schema: Option<OutputSchema>,
250    /// Number of retries for output validation
251    pub output_retries: u32,
252    /// Custom instructions for schema output (None = default)
253    pub output_instructions: Option<String>,
254    /// Custom prompt for validation retries (None = default)
255    pub retry_prompt: Option<String>,
256}
257
258impl AgentValue {
259    /// Create a new agent value
260    pub fn new(name: impl Into<String>, system_prompt: impl Into<String>) -> Self {
261        Self {
262            name: name.into(),
263            system_prompt: system_prompt.into(),
264            user_prompt: None,
265            tools: Vec::new(),
266            max_steps: None,
267            model: None,
268            output_schema: None,
269            output_retries: 1, // default: retry once
270            output_instructions: None,
271            retry_prompt: None,
272        }
273    }
274
275    /// Add tools to the agent
276    pub fn with_tools(mut self, tools: Vec<String>) -> Self {
277        self.tools = tools;
278        self
279    }
280
281    /// Set max steps
282    pub fn with_max_steps(mut self, steps: u32) -> Self {
283        self.max_steps = Some(steps);
284        self
285    }
286
287    /// Set model
288    pub fn with_model(mut self, model: impl Into<String>) -> Self {
289        self.model = Some(model.into());
290        self
291    }
292
293    /// Set output schema
294    pub fn with_output_schema(mut self, schema: OutputSchema) -> Self {
295        self.output_schema = Some(schema);
296        self
297    }
298
299    /// Set output retries
300    pub fn with_output_retries(mut self, retries: u32) -> Self {
301        self.output_retries = retries;
302        self
303    }
304
305    /// Set custom output instructions
306    pub fn with_output_instructions(mut self, instructions: impl Into<String>) -> Self {
307        self.output_instructions = Some(instructions.into());
308        self
309    }
310
311    /// Set custom retry prompt
312    pub fn with_retry_prompt(mut self, prompt: impl Into<String>) -> Self {
313        self.retry_prompt = Some(prompt.into());
314        self
315    }
316
317    /// Set user prompt
318    pub fn with_user_prompt(mut self, prompt: impl Into<String>) -> Self {
319        self.user_prompt = Some(prompt.into());
320        self
321    }
322}
323
324impl fmt::Display for Value {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        match self {
327            Value::String(s) => write!(f, "{}", s),
328            Value::Number(n) => {
329                if n.fract() == 0.0 {
330                    write!(f, "{}", *n as i64)
331                } else {
332                    write!(f, "{}", n)
333                }
334            }
335            Value::Boolean(b) => write!(f, "{}", b),
336            Value::Null => write!(f, "null"),
337            Value::Agent(agent) => write!(f, "<agent {}>", agent.name),
338            Value::Array(items) => {
339                let formatted: Vec<String> = items.iter().map(|v| format!("{}", v)).collect();
340                write!(f, "[{}]", formatted.join(", "))
341            }
342            Value::Object(map) => {
343                let formatted: Vec<String> =
344                    map.iter().map(|(k, v)| format!("{}: {}", k, v)).collect();
345                write!(f, "{{{}}}", formatted.join(", "))
346            }
347            Value::Tool(t) => write!(f, "<tool {}>", t.name),
348            Value::Function(func) => write!(f, "<fn {}>", func.name),
349            Value::Lambda(_) => write!(f, "<lambda>"),
350            Value::Enum(e) => {
351                if e.data.is_empty() {
352                    write!(f, "{}.{}", e.enum_name, e.variant)
353                } else {
354                    let data_str: Vec<String> = e.data.iter().map(|v| v.to_string()).collect();
355                    write!(f, "{}.{}({})", e.enum_name, e.variant, data_str.join(", "))
356                }
357            }
358            Value::EnumConstructor(c) => {
359                write!(f, "<enum constructor {}.{}>", c.enum_name, c.variant)
360            }
361            Value::Parallel(p) => write!(f, "<parallel {}>", p.name),
362        }
363    }
364}
365
366impl fmt::Display for AgentValue {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        write!(f, "<agent {}>", self.name)
369    }
370}
371
372impl Value {
373    /// Check if value is truthy
374    pub fn is_truthy(&self) -> bool {
375        match self {
376            Value::Boolean(b) => *b,
377            Value::Null => false,
378            Value::String(s) => !s.is_empty(),
379            Value::Number(n) => *n != 0.0,
380            Value::Agent(_) => true,
381            Value::Array(items) => !items.is_empty(),
382            Value::Object(map) => !map.is_empty(),
383            Value::Tool(_) => true,
384            Value::Function(_) => true,
385            Value::Lambda(_) => true,
386            Value::Enum(_) => true,
387            Value::EnumConstructor(_) => true,
388            Value::Parallel(_) => true,
389        }
390    }
391
392    /// Get type name for error messages
393    pub fn type_name(&self) -> String {
394        match self {
395            Value::String(_) => "String".to_string(),
396            Value::Number(_) => "Number".to_string(),
397            Value::Boolean(_) => "Boolean".to_string(),
398            Value::Null => "Null".to_string(),
399            Value::Agent(_) => "Agent".to_string(),
400            Value::Array(_) => "Array".to_string(),
401            Value::Object(_) => "Object".to_string(),
402            Value::Tool(_) => "Tool".to_string(),
403            Value::Function(_) => "Function".to_string(),
404            Value::Lambda(_) => "Lambda".to_string(),
405            Value::Enum(e) => format!("{}.{}", e.enum_name, e.variant),
406            Value::EnumConstructor(c) => format!("EnumConstructor({}.{})", c.enum_name, c.variant),
407            Value::Parallel(_) => "parallel".to_string(),
408        }
409    }
410
411    /// Try to get as string
412    pub fn as_string(&self) -> Option<&String> {
413        match self {
414            Value::String(s) => Some(s),
415            _ => None,
416        }
417    }
418
419    /// Try to get as agent
420    pub fn as_agent(&self) -> Option<&AgentValue> {
421        match self {
422            Value::Agent(a) => Some(a),
423            _ => None,
424        }
425    }
426
427    /// Try to get as array
428    pub fn as_array(&self) -> Option<&Vec<Value>> {
429        match self {
430            Value::Array(arr) => Some(arr),
431            _ => None,
432        }
433    }
434
435    /// Try to get as object
436    pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
437        match self {
438            Value::Object(map) => Some(map),
439            _ => None,
440        }
441    }
442
443    /// Try to get as tool
444    pub fn as_tool(&self) -> Option<&UserToolValue> {
445        match self {
446            Value::Tool(t) => Some(t),
447            _ => None,
448        }
449    }
450
451    /// Try to get as function
452    pub fn as_function(&self) -> Option<&FnValue> {
453        match self {
454            Value::Function(f) => Some(f),
455            _ => None,
456        }
457    }
458}