Skip to main content

harn_vm/
stdlib.rs

1use std::cell::RefCell;
2use std::collections::BTreeMap;
3use std::io::BufRead;
4use std::rc::Rc;
5use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU8, Ordering};
6use std::sync::Arc;
7
8use crate::value::{values_equal, VmAtomicHandle, VmChannelHandle, VmError, VmValue};
9use crate::vm::Vm;
10
11// Thread-local regex cache to avoid recompiling patterns on every call.
12thread_local! {
13    static REGEX_CACHE: RefCell<Vec<(String, regex::Regex)>> = const { RefCell::new(Vec::new()) };
14}
15
16fn get_cached_regex(pattern: &str) -> Result<regex::Regex, VmError> {
17    REGEX_CACHE.with(|cache| {
18        let mut cache = cache.borrow_mut();
19        // Check if pattern is already cached
20        if let Some(pos) = cache.iter().position(|(p, _)| p == pattern) {
21            return Ok(cache[pos].1.clone());
22        }
23        // Compile and cache
24        let re = regex::Regex::new(pattern).map_err(|e| {
25            VmError::Thrown(VmValue::String(Rc::from(format!("Invalid regex: {e}"))))
26        })?;
27        cache.push((pattern.to_string(), re.clone()));
28        // Evict oldest if cache grows too large
29        if cache.len() > 64 {
30            cache.remove(0);
31        }
32        Ok(re)
33    })
34}
35
36use crate::http::register_http_builtins;
37use crate::llm::register_llm_builtins;
38use crate::mcp::register_mcp_builtins;
39
40/// Build a select result dict with the given index, value, and channel name.
41fn select_result(index: usize, value: VmValue, channel_name: &str) -> VmValue {
42    let mut result = BTreeMap::new();
43    result.insert("index".to_string(), VmValue::Int(index as i64));
44    result.insert("value".to_string(), value);
45    result.insert(
46        "channel".to_string(),
47        VmValue::String(Rc::from(channel_name)),
48    );
49    VmValue::Dict(Rc::new(result))
50}
51
52/// Build a select result dict indicating no channel was ready (index = -1).
53fn select_none() -> VmValue {
54    let mut result = BTreeMap::new();
55    result.insert("index".to_string(), VmValue::Int(-1));
56    result.insert("value".to_string(), VmValue::Nil);
57    result.insert("channel".to_string(), VmValue::Nil);
58    VmValue::Dict(Rc::new(result))
59}
60
61/// Try to receive from a list of channels (non-blocking).
62/// Returns Some((index, value, channel_name)) if a message was received,
63/// or None. Sets all_closed to true if all channels are disconnected.
64fn try_poll_channels(channels: &[VmValue]) -> (Option<(usize, VmValue, String)>, bool) {
65    let mut all_closed = true;
66    for (i, ch_val) in channels.iter().enumerate() {
67        if let VmValue::Channel(ch) = ch_val {
68            if let Ok(mut rx) = ch.receiver.try_lock() {
69                match rx.try_recv() {
70                    Ok(val) => return (Some((i, val, ch.name.clone())), false),
71                    Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
72                        all_closed = false;
73                    }
74                    Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {}
75                }
76            } else {
77                all_closed = false;
78            }
79        }
80    }
81    (None, all_closed)
82}
83
84/// Register standard builtins on a VM.
85pub fn register_vm_stdlib(vm: &mut Vm) {
86    vm.register_builtin("log", |args, out| {
87        let msg = args.first().map(|a| a.display()).unwrap_or_default();
88        out.push_str(&format!("[harn] {msg}\n"));
89        Ok(VmValue::Nil)
90    });
91    vm.register_builtin("print", |args, out| {
92        let msg = args.first().map(|a| a.display()).unwrap_or_default();
93        out.push_str(&msg);
94        Ok(VmValue::Nil)
95    });
96    vm.register_builtin("println", |args, out| {
97        let msg = args.first().map(|a| a.display()).unwrap_or_default();
98        out.push_str(&format!("{msg}\n"));
99        Ok(VmValue::Nil)
100    });
101    vm.register_builtin("type_of", |args, _out| {
102        let val = args.first().unwrap_or(&VmValue::Nil);
103        Ok(VmValue::String(Rc::from(val.type_name())))
104    });
105    vm.register_builtin("to_string", |args, _out| {
106        let val = args.first().unwrap_or(&VmValue::Nil);
107        Ok(VmValue::String(Rc::from(val.display())))
108    });
109    vm.register_builtin("to_int", |args, _out| {
110        let val = args.first().unwrap_or(&VmValue::Nil);
111        match val {
112            VmValue::Int(n) => Ok(VmValue::Int(*n)),
113            VmValue::Float(n) => Ok(VmValue::Int(*n as i64)),
114            VmValue::String(s) => Ok(s.parse::<i64>().map(VmValue::Int).unwrap_or(VmValue::Nil)),
115            _ => Ok(VmValue::Nil),
116        }
117    });
118    vm.register_builtin("to_float", |args, _out| {
119        let val = args.first().unwrap_or(&VmValue::Nil);
120        match val {
121            VmValue::Float(n) => Ok(VmValue::Float(*n)),
122            VmValue::Int(n) => Ok(VmValue::Float(*n as f64)),
123            VmValue::String(s) => Ok(s.parse::<f64>().map(VmValue::Float).unwrap_or(VmValue::Nil)),
124            _ => Ok(VmValue::Nil),
125        }
126    });
127
128    // --- Result type helpers ---
129    vm.register_builtin("Ok", |args, _out| {
130        let val = args.first().cloned().unwrap_or(VmValue::Nil);
131        Ok(VmValue::EnumVariant {
132            enum_name: "Result".into(),
133            variant: "Ok".into(),
134            fields: vec![val],
135        })
136    });
137    vm.register_builtin("Err", |args, _out| {
138        let val = args.first().cloned().unwrap_or(VmValue::Nil);
139        Ok(VmValue::EnumVariant {
140            enum_name: "Result".into(),
141            variant: "Err".into(),
142            fields: vec![val],
143        })
144    });
145    vm.register_builtin("is_ok", |args, _out| {
146        let val = args.first().unwrap_or(&VmValue::Nil);
147        Ok(VmValue::Bool(matches!(
148            val,
149            VmValue::EnumVariant { enum_name, variant, .. }
150            if enum_name == "Result" && variant == "Ok"
151        )))
152    });
153    vm.register_builtin("is_err", |args, _out| {
154        let val = args.first().unwrap_or(&VmValue::Nil);
155        Ok(VmValue::Bool(matches!(
156            val,
157            VmValue::EnumVariant { enum_name, variant, .. }
158            if enum_name == "Result" && variant == "Err"
159        )))
160    });
161    vm.register_builtin("unwrap", |args, _out| {
162        let val = args.first().unwrap_or(&VmValue::Nil);
163        match val {
164            VmValue::EnumVariant {
165                enum_name,
166                variant,
167                fields,
168            } if enum_name == "Result" && variant == "Ok" => {
169                Ok(fields.first().cloned().unwrap_or(VmValue::Nil))
170            }
171            VmValue::EnumVariant {
172                enum_name,
173                variant,
174                fields,
175            } if enum_name == "Result" && variant == "Err" => {
176                let msg = fields.first().map(|f| f.display()).unwrap_or_default();
177                Err(VmError::Runtime(format!("unwrap called on Err: {msg}")))
178            }
179            _ => Ok(val.clone()),
180        }
181    });
182    vm.register_builtin("unwrap_or", |args, _out| {
183        let val = args.first().unwrap_or(&VmValue::Nil);
184        let default = args.get(1).cloned().unwrap_or(VmValue::Nil);
185        match val {
186            VmValue::EnumVariant {
187                enum_name,
188                variant,
189                fields,
190            } if enum_name == "Result" && variant == "Ok" => {
191                Ok(fields.first().cloned().unwrap_or(VmValue::Nil))
192            }
193            VmValue::EnumVariant {
194                enum_name, variant, ..
195            } if enum_name == "Result" && variant == "Err" => Ok(default),
196            _ => Ok(val.clone()),
197        }
198    });
199    vm.register_builtin("unwrap_err", |args, _out| {
200        let val = args.first().unwrap_or(&VmValue::Nil);
201        match val {
202            VmValue::EnumVariant {
203                enum_name,
204                variant,
205                fields,
206            } if enum_name == "Result" && variant == "Err" => {
207                Ok(fields.first().cloned().unwrap_or(VmValue::Nil))
208            }
209            _ => Err(VmError::Runtime("unwrap_err called on non-Err".into())),
210        }
211    });
212
213    vm.register_builtin("json_stringify", |args, _out| {
214        let val = args.first().unwrap_or(&VmValue::Nil);
215        Ok(VmValue::String(Rc::from(vm_value_to_json(val))))
216    });
217
218    vm.register_builtin("json_parse", |args, _out| {
219        let text = args.first().map(|a| a.display()).unwrap_or_default();
220        match serde_json::from_str::<serde_json::Value>(&text) {
221            Ok(jv) => Ok(json_to_vm_value(&jv)),
222            Err(e) => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
223                "JSON parse error: {e}"
224            ))))),
225        }
226    });
227
228    vm.register_builtin("env", |args, _out| {
229        let name = args.first().map(|a| a.display()).unwrap_or_default();
230        match std::env::var(&name) {
231            Ok(val) => Ok(VmValue::String(Rc::from(val))),
232            Err(_) => Ok(VmValue::Nil),
233        }
234    });
235
236    vm.register_builtin("timestamp", |_args, _out| {
237        use std::time::{SystemTime, UNIX_EPOCH};
238        let secs = SystemTime::now()
239            .duration_since(UNIX_EPOCH)
240            .map(|d| d.as_secs_f64())
241            .unwrap_or(0.0);
242        Ok(VmValue::Float(secs))
243    });
244
245    vm.register_builtin("read_file", |args, _out| {
246        let path = args.first().map(|a| a.display()).unwrap_or_default();
247        match std::fs::read_to_string(&path) {
248            Ok(content) => Ok(VmValue::String(Rc::from(content))),
249            Err(e) => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
250                "Failed to read file {path}: {e}"
251            ))))),
252        }
253    });
254
255    vm.register_builtin("write_file", |args, _out| {
256        if args.len() >= 2 {
257            let path = args[0].display();
258            let content = args[1].display();
259            std::fs::write(&path, &content).map_err(|e| {
260                VmError::Thrown(VmValue::String(Rc::from(format!(
261                    "Failed to write file {path}: {e}"
262                ))))
263            })?;
264        }
265        Ok(VmValue::Nil)
266    });
267
268    vm.register_builtin("exit", |args, _out| {
269        let code = args.first().and_then(|a| a.as_int()).unwrap_or(0);
270        std::process::exit(code as i32);
271    });
272
273    vm.register_builtin("regex_match", |args, _out| {
274        if args.len() >= 2 {
275            let pattern = args[0].display();
276            let text = args[1].display();
277            let re = get_cached_regex(&pattern)?;
278            let matches: Vec<VmValue> = re
279                .find_iter(&text)
280                .map(|m| VmValue::String(Rc::from(m.as_str())))
281                .collect();
282            if matches.is_empty() {
283                return Ok(VmValue::Nil);
284            }
285            return Ok(VmValue::List(Rc::new(matches)));
286        }
287        Ok(VmValue::Nil)
288    });
289
290    vm.register_builtin("regex_replace", |args, _out| {
291        if args.len() >= 3 {
292            let pattern = args[0].display();
293            let replacement = args[1].display();
294            let text = args[2].display();
295            let re = get_cached_regex(&pattern)?;
296            return Ok(VmValue::String(Rc::from(
297                re.replace_all(&text, replacement.as_str()).into_owned(),
298            )));
299        }
300        Ok(VmValue::Nil)
301    });
302
303    vm.register_builtin("regex_captures", |args, _out| {
304        if args.len() < 2 {
305            return Ok(VmValue::List(Rc::new(Vec::new())));
306        }
307        let pattern = args[0].display();
308        let text = args[1].display();
309        let re = get_cached_regex(&pattern)?;
310
311        let mut results: Vec<VmValue> = Vec::new();
312        for caps in re.captures_iter(&text) {
313            let mut dict = BTreeMap::new();
314
315            // "match" → full match (group 0)
316            dict.insert(
317                "match".to_string(),
318                VmValue::String(Rc::from(caps.get(0).map_or("", |m| m.as_str()))),
319            );
320
321            // "groups" → list of capture groups 1..n
322            let groups: Vec<VmValue> = (1..caps.len())
323                .map(|i| match caps.get(i) {
324                    Some(m) => VmValue::String(Rc::from(m.as_str())),
325                    None => VmValue::Nil,
326                })
327                .collect();
328            dict.insert("groups".to_string(), VmValue::List(Rc::new(groups)));
329
330            // Named groups as top-level dict keys
331            for name in re.capture_names().flatten() {
332                if let Some(m) = caps.name(name) {
333                    dict.insert(name.to_string(), VmValue::String(Rc::from(m.as_str())));
334                }
335            }
336
337            results.push(VmValue::Dict(Rc::new(dict)));
338        }
339        Ok(VmValue::List(Rc::new(results)))
340    });
341
342    // --- Struct constructor helper ---
343    vm.register_builtin("__make_struct", |args, _out| {
344        let struct_name = args.first().map(|a| a.display()).unwrap_or_default();
345        let fields_dict = args.get(1).cloned().unwrap_or(VmValue::Nil);
346        match fields_dict {
347            VmValue::Dict(d) => Ok(VmValue::StructInstance {
348                struct_name,
349                fields: (*d).clone(),
350            }),
351            _ => Ok(VmValue::StructInstance {
352                struct_name,
353                fields: BTreeMap::new(),
354            }),
355        }
356    });
357
358    // --- Encoding builtins ---
359    vm.register_builtin("base64_encode", |args, _out| {
360        let val = args.first().map(|a| a.display()).unwrap_or_default();
361        use base64::Engine;
362        Ok(VmValue::String(Rc::from(
363            base64::engine::general_purpose::STANDARD.encode(val.as_bytes()),
364        )))
365    });
366    vm.register_builtin("base64_decode", |args, _out| {
367        let val = args.first().map(|a| a.display()).unwrap_or_default();
368        use base64::Engine;
369        match base64::engine::general_purpose::STANDARD.decode(val.as_bytes()) {
370            Ok(bytes) => Ok(VmValue::String(Rc::from(
371                String::from_utf8_lossy(&bytes).into_owned(),
372            ))),
373            Err(e) => Err(VmError::Runtime(format!("base64 decode error: {e}"))),
374        }
375    });
376
377    // --- Crypto/hash builtins ---
378    vm.register_builtin("sha256", |args, _out| {
379        use sha2::Digest;
380        let val = args.first().map(|a| a.display()).unwrap_or_default();
381        let hash = sha2::Sha256::digest(val.as_bytes());
382        Ok(VmValue::String(Rc::from(format!("{hash:x}"))))
383    });
384    vm.register_builtin("md5", |args, _out| {
385        use md5::Digest;
386        let val = args.first().map(|a| a.display()).unwrap_or_default();
387        let hash = md5::Md5::digest(val.as_bytes());
388        Ok(VmValue::String(Rc::from(format!("{hash:x}"))))
389    });
390
391    vm.register_builtin("prompt_user", |args, out| {
392        let msg = args.first().map(|a| a.display()).unwrap_or_default();
393        out.push_str(&msg);
394        let mut input = String::new();
395        if std::io::stdin().lock().read_line(&mut input).is_ok() {
396            Ok(VmValue::String(Rc::from(input.trim_end())))
397        } else {
398            Ok(VmValue::Nil)
399        }
400    });
401
402    // --- Math builtins ---
403
404    vm.register_builtin("abs", |args, _out| {
405        match args.first().unwrap_or(&VmValue::Nil) {
406            VmValue::Int(n) => Ok(VmValue::Int(n.wrapping_abs())),
407            VmValue::Float(n) => Ok(VmValue::Float(n.abs())),
408            _ => Ok(VmValue::Nil),
409        }
410    });
411
412    vm.register_builtin("min", |args, _out| {
413        if args.len() >= 2 {
414            match (&args[0], &args[1]) {
415                (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(*x.min(y))),
416                (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x.min(*y))),
417                (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float((*x as f64).min(*y))),
418                (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x.min(*y as f64))),
419                _ => Ok(VmValue::Nil),
420            }
421        } else {
422            Ok(VmValue::Nil)
423        }
424    });
425
426    vm.register_builtin("max", |args, _out| {
427        if args.len() >= 2 {
428            match (&args[0], &args[1]) {
429                (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(*x.max(y))),
430                (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x.max(*y))),
431                (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float((*x as f64).max(*y))),
432                (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x.max(*y as f64))),
433                _ => Ok(VmValue::Nil),
434            }
435        } else {
436            Ok(VmValue::Nil)
437        }
438    });
439
440    vm.register_builtin("floor", |args, _out| {
441        match args.first().unwrap_or(&VmValue::Nil) {
442            VmValue::Float(n) => Ok(VmValue::Int(n.floor() as i64)),
443            VmValue::Int(n) => Ok(VmValue::Int(*n)),
444            _ => Ok(VmValue::Nil),
445        }
446    });
447
448    vm.register_builtin("ceil", |args, _out| {
449        match args.first().unwrap_or(&VmValue::Nil) {
450            VmValue::Float(n) => Ok(VmValue::Int(n.ceil() as i64)),
451            VmValue::Int(n) => Ok(VmValue::Int(*n)),
452            _ => Ok(VmValue::Nil),
453        }
454    });
455
456    vm.register_builtin("round", |args, _out| {
457        match args.first().unwrap_or(&VmValue::Nil) {
458            VmValue::Float(n) => Ok(VmValue::Int(n.round() as i64)),
459            VmValue::Int(n) => Ok(VmValue::Int(*n)),
460            _ => Ok(VmValue::Nil),
461        }
462    });
463
464    vm.register_builtin("sqrt", |args, _out| {
465        match args.first().unwrap_or(&VmValue::Nil) {
466            VmValue::Float(n) => Ok(VmValue::Float(n.sqrt())),
467            VmValue::Int(n) => Ok(VmValue::Float((*n as f64).sqrt())),
468            _ => Ok(VmValue::Nil),
469        }
470    });
471
472    vm.register_builtin("pow", |args, _out| {
473        if args.len() >= 2 {
474            match (&args[0], &args[1]) {
475                (VmValue::Int(base), VmValue::Int(exp)) => {
476                    if *exp >= 0 && *exp <= u32::MAX as i64 {
477                        Ok(VmValue::Int(base.wrapping_pow(*exp as u32)))
478                    } else {
479                        Ok(VmValue::Float((*base as f64).powf(*exp as f64)))
480                    }
481                }
482                (VmValue::Float(base), VmValue::Int(exp)) => {
483                    if *exp >= i32::MIN as i64 && *exp <= i32::MAX as i64 {
484                        Ok(VmValue::Float(base.powi(*exp as i32)))
485                    } else {
486                        Ok(VmValue::Float(base.powf(*exp as f64)))
487                    }
488                }
489                (VmValue::Int(base), VmValue::Float(exp)) => {
490                    Ok(VmValue::Float((*base as f64).powf(*exp)))
491                }
492                (VmValue::Float(base), VmValue::Float(exp)) => Ok(VmValue::Float(base.powf(*exp))),
493                _ => Ok(VmValue::Nil),
494            }
495        } else {
496            Ok(VmValue::Nil)
497        }
498    });
499
500    vm.register_builtin("random", |_args, _out| {
501        use rand::Rng;
502        let val: f64 = rand::thread_rng().gen();
503        Ok(VmValue::Float(val))
504    });
505
506    vm.register_builtin("random_int", |args, _out| {
507        use rand::Rng;
508        if args.len() >= 2 {
509            let min = args[0].as_int().unwrap_or(0);
510            let max = args[1].as_int().unwrap_or(0);
511            if min <= max {
512                let val = rand::thread_rng().gen_range(min..=max);
513                return Ok(VmValue::Int(val));
514            }
515        }
516        Ok(VmValue::Nil)
517    });
518
519    // --- Trigonometric and transcendental math ---
520
521    vm.register_builtin("sin", |args, _out| {
522        let n = match args.first().unwrap_or(&VmValue::Nil) {
523            VmValue::Float(n) => *n,
524            VmValue::Int(n) => *n as f64,
525            _ => return Ok(VmValue::Nil),
526        };
527        Ok(VmValue::Float(n.sin()))
528    });
529
530    vm.register_builtin("cos", |args, _out| {
531        let n = match args.first().unwrap_or(&VmValue::Nil) {
532            VmValue::Float(n) => *n,
533            VmValue::Int(n) => *n as f64,
534            _ => return Ok(VmValue::Nil),
535        };
536        Ok(VmValue::Float(n.cos()))
537    });
538
539    vm.register_builtin("tan", |args, _out| {
540        let n = match args.first().unwrap_or(&VmValue::Nil) {
541            VmValue::Float(n) => *n,
542            VmValue::Int(n) => *n as f64,
543            _ => return Ok(VmValue::Nil),
544        };
545        Ok(VmValue::Float(n.tan()))
546    });
547
548    vm.register_builtin("asin", |args, _out| {
549        let n = match args.first().unwrap_or(&VmValue::Nil) {
550            VmValue::Float(n) => *n,
551            VmValue::Int(n) => *n as f64,
552            _ => return Ok(VmValue::Nil),
553        };
554        Ok(VmValue::Float(n.asin()))
555    });
556
557    vm.register_builtin("acos", |args, _out| {
558        let n = match args.first().unwrap_or(&VmValue::Nil) {
559            VmValue::Float(n) => *n,
560            VmValue::Int(n) => *n as f64,
561            _ => return Ok(VmValue::Nil),
562        };
563        Ok(VmValue::Float(n.acos()))
564    });
565
566    vm.register_builtin("atan", |args, _out| {
567        let n = match args.first().unwrap_or(&VmValue::Nil) {
568            VmValue::Float(n) => *n,
569            VmValue::Int(n) => *n as f64,
570            _ => return Ok(VmValue::Nil),
571        };
572        Ok(VmValue::Float(n.atan()))
573    });
574
575    vm.register_builtin("atan2", |args, _out| {
576        if args.len() >= 2 {
577            let y = match &args[0] {
578                VmValue::Float(n) => *n,
579                VmValue::Int(n) => *n as f64,
580                _ => return Ok(VmValue::Nil),
581            };
582            let x = match &args[1] {
583                VmValue::Float(n) => *n,
584                VmValue::Int(n) => *n as f64,
585                _ => return Ok(VmValue::Nil),
586            };
587            Ok(VmValue::Float(y.atan2(x)))
588        } else {
589            Ok(VmValue::Nil)
590        }
591    });
592
593    vm.register_builtin("log2", |args, _out| {
594        let n = match args.first().unwrap_or(&VmValue::Nil) {
595            VmValue::Float(n) => *n,
596            VmValue::Int(n) => *n as f64,
597            _ => return Ok(VmValue::Nil),
598        };
599        Ok(VmValue::Float(n.log2()))
600    });
601
602    vm.register_builtin("log10", |args, _out| {
603        let n = match args.first().unwrap_or(&VmValue::Nil) {
604            VmValue::Float(n) => *n,
605            VmValue::Int(n) => *n as f64,
606            _ => return Ok(VmValue::Nil),
607        };
608        Ok(VmValue::Float(n.log10()))
609    });
610
611    vm.register_builtin("ln", |args, _out| {
612        let n = match args.first().unwrap_or(&VmValue::Nil) {
613            VmValue::Float(n) => *n,
614            VmValue::Int(n) => *n as f64,
615            _ => return Ok(VmValue::Nil),
616        };
617        Ok(VmValue::Float(n.ln()))
618    });
619
620    vm.register_builtin("exp", |args, _out| {
621        let n = match args.first().unwrap_or(&VmValue::Nil) {
622            VmValue::Float(n) => *n,
623            VmValue::Int(n) => *n as f64,
624            _ => return Ok(VmValue::Nil),
625        };
626        Ok(VmValue::Float(n.exp()))
627    });
628
629    vm.register_builtin("sign", |args, _out| {
630        match args.first().unwrap_or(&VmValue::Nil) {
631            VmValue::Int(n) => Ok(VmValue::Int(n.signum())),
632            VmValue::Float(n) => {
633                if n.is_nan() {
634                    Ok(VmValue::Float(f64::NAN))
635                } else if *n == 0.0 {
636                    Ok(VmValue::Int(0))
637                } else if *n > 0.0 {
638                    Ok(VmValue::Int(1))
639                } else {
640                    Ok(VmValue::Int(-1))
641                }
642            }
643            _ => Ok(VmValue::Nil),
644        }
645    });
646
647    vm.register_builtin("pi", |_args, _out| Ok(VmValue::Float(std::f64::consts::PI)));
648
649    vm.register_builtin("e", |_args, _out| Ok(VmValue::Float(std::f64::consts::E)));
650
651    vm.register_builtin("is_nan", |args, _out| {
652        match args.first().unwrap_or(&VmValue::Nil) {
653            VmValue::Float(n) => Ok(VmValue::Bool(n.is_nan())),
654            _ => Ok(VmValue::Bool(false)),
655        }
656    });
657
658    vm.register_builtin("is_infinite", |args, _out| {
659        match args.first().unwrap_or(&VmValue::Nil) {
660            VmValue::Float(n) => Ok(VmValue::Bool(n.is_infinite())),
661            _ => Ok(VmValue::Bool(false)),
662        }
663    });
664
665    // --- Set builtins ---
666
667    // set(...items) or set(list) -> Set
668    vm.register_builtin("set", |args, _out| {
669        let mut items: Vec<VmValue> = Vec::new();
670        for arg in args {
671            match arg {
672                VmValue::List(list) => {
673                    for v in list.iter() {
674                        if !items.iter().any(|x| values_equal(x, v)) {
675                            items.push(v.clone());
676                        }
677                    }
678                }
679                VmValue::Set(s) => {
680                    for v in s.iter() {
681                        if !items.iter().any(|x| values_equal(x, v)) {
682                            items.push(v.clone());
683                        }
684                    }
685                }
686                other => {
687                    if !items.iter().any(|x| values_equal(x, other)) {
688                        items.push(other.clone());
689                    }
690                }
691            }
692        }
693        Ok(VmValue::Set(Rc::new(items)))
694    });
695
696    // set_add(s, value) -> new Set
697    vm.register_builtin("set_add", |args, _out| {
698        let s = match args.first() {
699            Some(VmValue::Set(s)) => s.clone(),
700            _ => {
701                return Err(VmError::Thrown(VmValue::String(Rc::from(
702                    "set_add: first argument must be a set",
703                ))));
704            }
705        };
706        let val = args.get(1).cloned().unwrap_or(VmValue::Nil);
707        let mut items: Vec<VmValue> = (*s).clone();
708        if !items.iter().any(|x| values_equal(x, &val)) {
709            items.push(val);
710        }
711        Ok(VmValue::Set(Rc::new(items)))
712    });
713
714    // set_remove(s, value) -> new Set
715    vm.register_builtin("set_remove", |args, _out| {
716        let s = match args.first() {
717            Some(VmValue::Set(s)) => s.clone(),
718            _ => {
719                return Err(VmError::Thrown(VmValue::String(Rc::from(
720                    "set_remove: first argument must be a set",
721                ))));
722            }
723        };
724        let val = args.get(1).cloned().unwrap_or(VmValue::Nil);
725        let items: Vec<VmValue> = s
726            .iter()
727            .filter(|x| !values_equal(x, &val))
728            .cloned()
729            .collect();
730        Ok(VmValue::Set(Rc::new(items)))
731    });
732
733    // set_contains(s, value) -> bool
734    vm.register_builtin("set_contains", |args, _out| {
735        let s = match args.first() {
736            Some(VmValue::Set(s)) => s,
737            _ => return Ok(VmValue::Bool(false)),
738        };
739        let val = args.get(1).unwrap_or(&VmValue::Nil);
740        Ok(VmValue::Bool(s.iter().any(|x| values_equal(x, val))))
741    });
742
743    // set_union(a, b) -> Set
744    vm.register_builtin("set_union", |args, _out| {
745        let a = match args.first() {
746            Some(VmValue::Set(s)) => s,
747            _ => {
748                return Err(VmError::Thrown(VmValue::String(Rc::from(
749                    "set_union: arguments must be sets",
750                ))));
751            }
752        };
753        let b = match args.get(1) {
754            Some(VmValue::Set(s)) => s,
755            _ => {
756                return Err(VmError::Thrown(VmValue::String(Rc::from(
757                    "set_union: arguments must be sets",
758                ))));
759            }
760        };
761        let mut items: Vec<VmValue> = (**a).clone();
762        for v in b.iter() {
763            if !items.iter().any(|x| values_equal(x, v)) {
764                items.push(v.clone());
765            }
766        }
767        Ok(VmValue::Set(Rc::new(items)))
768    });
769
770    // set_intersect(a, b) -> Set
771    vm.register_builtin("set_intersect", |args, _out| {
772        let a = match args.first() {
773            Some(VmValue::Set(s)) => s,
774            _ => {
775                return Err(VmError::Thrown(VmValue::String(Rc::from(
776                    "set_intersect: arguments must be sets",
777                ))));
778            }
779        };
780        let b = match args.get(1) {
781            Some(VmValue::Set(s)) => s,
782            _ => {
783                return Err(VmError::Thrown(VmValue::String(Rc::from(
784                    "set_intersect: arguments must be sets",
785                ))));
786            }
787        };
788        let items: Vec<VmValue> = a
789            .iter()
790            .filter(|x| b.iter().any(|y| values_equal(x, y)))
791            .cloned()
792            .collect();
793        Ok(VmValue::Set(Rc::new(items)))
794    });
795
796    // set_difference(a, b) -> Set (items in a but not in b)
797    vm.register_builtin("set_difference", |args, _out| {
798        let a = match args.first() {
799            Some(VmValue::Set(s)) => s,
800            _ => {
801                return Err(VmError::Thrown(VmValue::String(Rc::from(
802                    "set_difference: arguments must be sets",
803                ))));
804            }
805        };
806        let b = match args.get(1) {
807            Some(VmValue::Set(s)) => s,
808            _ => {
809                return Err(VmError::Thrown(VmValue::String(Rc::from(
810                    "set_difference: arguments must be sets",
811                ))));
812            }
813        };
814        let items: Vec<VmValue> = a
815            .iter()
816            .filter(|x| !b.iter().any(|y| values_equal(x, y)))
817            .cloned()
818            .collect();
819        Ok(VmValue::Set(Rc::new(items)))
820    });
821
822    // to_list(set) -> list (also works for other types)
823    vm.register_builtin("to_list", |args, _out| {
824        match args.first().unwrap_or(&VmValue::Nil) {
825            VmValue::Set(s) => Ok(VmValue::List(Rc::new(s.to_vec()))),
826            VmValue::List(l) => Ok(VmValue::List(l.clone())),
827            other => Ok(VmValue::List(Rc::new(vec![other.clone()]))),
828        }
829    });
830
831    // --- Assert builtins ---
832
833    vm.register_builtin("assert", |args, _out| {
834        let condition = args.first().unwrap_or(&VmValue::Nil);
835        if !condition.is_truthy() {
836            let msg = args
837                .get(1)
838                .map(|a| a.display())
839                .unwrap_or_else(|| "Assertion failed".to_string());
840            return Err(VmError::Thrown(VmValue::String(Rc::from(msg))));
841        }
842        Ok(VmValue::Nil)
843    });
844
845    vm.register_builtin("assert_eq", |args, _out| {
846        if args.len() >= 2 {
847            if !values_equal(&args[0], &args[1]) {
848                let msg = args.get(2).map(|a| a.display()).unwrap_or_else(|| {
849                    format!(
850                        "Assertion failed: expected {}, got {}",
851                        args[1].display(),
852                        args[0].display()
853                    )
854                });
855                return Err(VmError::Thrown(VmValue::String(Rc::from(msg))));
856            }
857            Ok(VmValue::Nil)
858        } else {
859            Err(VmError::Thrown(VmValue::String(Rc::from(
860                "assert_eq requires at least 2 arguments",
861            ))))
862        }
863    });
864
865    vm.register_builtin("assert_ne", |args, _out| {
866        if args.len() >= 2 {
867            if values_equal(&args[0], &args[1]) {
868                let msg = args.get(2).map(|a| a.display()).unwrap_or_else(|| {
869                    format!(
870                        "Assertion failed: values should not be equal: {}",
871                        args[0].display()
872                    )
873                });
874                return Err(VmError::Thrown(VmValue::String(Rc::from(msg))));
875            }
876            Ok(VmValue::Nil)
877        } else {
878            Err(VmError::Thrown(VmValue::String(Rc::from(
879                "assert_ne requires at least 2 arguments",
880            ))))
881        }
882    });
883
884    vm.register_builtin("__range__", |args, _out| {
885        let start = args.first().and_then(|a| a.as_int()).unwrap_or(0);
886        let end = args.get(1).and_then(|a| a.as_int()).unwrap_or(0);
887        let inclusive = args.get(2).map(|a| a.is_truthy()).unwrap_or(false);
888        let items: Vec<VmValue> = if inclusive {
889            (start..=end).map(VmValue::Int).collect()
890        } else {
891            (start..end).map(VmValue::Int).collect()
892        };
893        Ok(VmValue::List(Rc::new(items)))
894    });
895
896    // =========================================================================
897    // File system builtins
898    // =========================================================================
899
900    vm.register_builtin("file_exists", |args, _out| {
901        let path = args.first().map(|a| a.display()).unwrap_or_default();
902        Ok(VmValue::Bool(std::path::Path::new(&path).exists()))
903    });
904
905    vm.register_builtin("delete_file", |args, _out| {
906        let path = args.first().map(|a| a.display()).unwrap_or_default();
907        let p = std::path::Path::new(&path);
908        if p.is_dir() {
909            std::fs::remove_dir_all(&path).map_err(|e| {
910                VmError::Thrown(VmValue::String(Rc::from(format!(
911                    "Failed to delete directory {path}: {e}"
912                ))))
913            })?;
914        } else {
915            std::fs::remove_file(&path).map_err(|e| {
916                VmError::Thrown(VmValue::String(Rc::from(format!(
917                    "Failed to delete file {path}: {e}"
918                ))))
919            })?;
920        }
921        Ok(VmValue::Nil)
922    });
923
924    vm.register_builtin("append_file", |args, _out| {
925        use std::io::Write;
926        if args.len() >= 2 {
927            let path = args[0].display();
928            let content = args[1].display();
929            let mut file = std::fs::OpenOptions::new()
930                .append(true)
931                .create(true)
932                .open(&path)
933                .map_err(|e| {
934                    VmError::Thrown(VmValue::String(Rc::from(format!(
935                        "Failed to open file {path}: {e}"
936                    ))))
937                })?;
938            file.write_all(content.as_bytes()).map_err(|e| {
939                VmError::Thrown(VmValue::String(Rc::from(format!(
940                    "Failed to append to file {path}: {e}"
941                ))))
942            })?;
943        }
944        Ok(VmValue::Nil)
945    });
946
947    vm.register_builtin("list_dir", |args, _out| {
948        let path = args
949            .first()
950            .map(|a| a.display())
951            .unwrap_or_else(|| ".".to_string());
952        let entries = std::fs::read_dir(&path).map_err(|e| {
953            VmError::Thrown(VmValue::String(Rc::from(format!(
954                "Failed to list directory {path}: {e}"
955            ))))
956        })?;
957        let mut result = Vec::new();
958        for entry in entries {
959            let entry =
960                entry.map_err(|e| VmError::Thrown(VmValue::String(Rc::from(e.to_string()))))?;
961            let name = entry.file_name().to_string_lossy().to_string();
962            result.push(VmValue::String(Rc::from(name.as_str())));
963        }
964        result.sort_by_key(|a| a.display());
965        Ok(VmValue::List(Rc::new(result)))
966    });
967
968    vm.register_builtin("mkdir", |args, _out| {
969        let path = args.first().map(|a| a.display()).unwrap_or_default();
970        std::fs::create_dir_all(&path).map_err(|e| {
971            VmError::Thrown(VmValue::String(Rc::from(format!(
972                "Failed to create directory {path}: {e}"
973            ))))
974        })?;
975        Ok(VmValue::Nil)
976    });
977
978    vm.register_builtin("path_join", |args, _out| {
979        let mut path = std::path::PathBuf::new();
980        for arg in args {
981            path.push(arg.display());
982        }
983        Ok(VmValue::String(Rc::from(
984            path.to_string_lossy().to_string().as_str(),
985        )))
986    });
987
988    vm.register_builtin("copy_file", |args, _out| {
989        if args.len() >= 2 {
990            let src = args[0].display();
991            let dst = args[1].display();
992            std::fs::copy(&src, &dst).map_err(|e| {
993                VmError::Thrown(VmValue::String(Rc::from(format!(
994                    "Failed to copy {src} to {dst}: {e}"
995                ))))
996            })?;
997        }
998        Ok(VmValue::Nil)
999    });
1000
1001    vm.register_builtin("temp_dir", |_args, _out| {
1002        Ok(VmValue::String(Rc::from(
1003            std::env::temp_dir().to_string_lossy().to_string().as_str(),
1004        )))
1005    });
1006
1007    vm.register_builtin("stat", |args, _out| {
1008        let path = args.first().map(|a| a.display()).unwrap_or_default();
1009        let metadata = std::fs::metadata(&path).map_err(|e| {
1010            VmError::Thrown(VmValue::String(Rc::from(format!(
1011                "Failed to stat {path}: {e}"
1012            ))))
1013        })?;
1014        let mut info = BTreeMap::new();
1015        info.insert("size".to_string(), VmValue::Int(metadata.len() as i64));
1016        info.insert("is_file".to_string(), VmValue::Bool(metadata.is_file()));
1017        info.insert("is_dir".to_string(), VmValue::Bool(metadata.is_dir()));
1018        info.insert(
1019            "readonly".to_string(),
1020            VmValue::Bool(metadata.permissions().readonly()),
1021        );
1022        if let Ok(modified) = metadata.modified() {
1023            if let Ok(dur) = modified.duration_since(std::time::UNIX_EPOCH) {
1024                info.insert("modified".to_string(), VmValue::Float(dur.as_secs_f64()));
1025            }
1026        }
1027        Ok(VmValue::Dict(Rc::new(info)))
1028    });
1029
1030    // =========================================================================
1031    // Process execution builtins
1032    // =========================================================================
1033
1034    vm.register_builtin("exec", |args, _out| {
1035        if args.is_empty() {
1036            return Err(VmError::Thrown(VmValue::String(Rc::from(
1037                "exec: command is required",
1038            ))));
1039        }
1040        let cmd = args[0].display();
1041        let cmd_args: Vec<String> = args[1..].iter().map(|a| a.display()).collect();
1042        let output = std::process::Command::new(&cmd)
1043            .args(&cmd_args)
1044            .output()
1045            .map_err(|e| VmError::Thrown(VmValue::String(Rc::from(format!("exec failed: {e}")))))?;
1046        Ok(vm_output_to_value(output))
1047    });
1048
1049    vm.register_builtin("shell", |args, _out| {
1050        let cmd = args.first().map(|a| a.display()).unwrap_or_default();
1051        if cmd.is_empty() {
1052            return Err(VmError::Thrown(VmValue::String(Rc::from(
1053                "shell: command string is required",
1054            ))));
1055        }
1056        let shell = if cfg!(target_os = "windows") {
1057            "cmd"
1058        } else {
1059            "sh"
1060        };
1061        let flag = if cfg!(target_os = "windows") {
1062            "/C"
1063        } else {
1064            "-c"
1065        };
1066        let output = std::process::Command::new(shell)
1067            .arg(flag)
1068            .arg(&cmd)
1069            .output()
1070            .map_err(|e| {
1071                VmError::Thrown(VmValue::String(Rc::from(format!("shell failed: {e}"))))
1072            })?;
1073        Ok(vm_output_to_value(output))
1074    });
1075
1076    // =========================================================================
1077    // Date/time builtins
1078    // =========================================================================
1079
1080    vm.register_builtin("date_now", |_args, _out| {
1081        use std::time::{SystemTime, UNIX_EPOCH};
1082        let now = SystemTime::now()
1083            .duration_since(UNIX_EPOCH)
1084            .unwrap_or_default();
1085        let total_secs = now.as_secs();
1086        let (y, m, d, hour, minute, second, dow) = vm_civil_from_timestamp(total_secs);
1087        let mut result = BTreeMap::new();
1088        result.insert("year".to_string(), VmValue::Int(y));
1089        result.insert("month".to_string(), VmValue::Int(m));
1090        result.insert("day".to_string(), VmValue::Int(d));
1091        result.insert("hour".to_string(), VmValue::Int(hour));
1092        result.insert("minute".to_string(), VmValue::Int(minute));
1093        result.insert("second".to_string(), VmValue::Int(second));
1094        result.insert("weekday".to_string(), VmValue::Int(dow));
1095        result.insert("timestamp".to_string(), VmValue::Float(now.as_secs_f64()));
1096        Ok(VmValue::Dict(Rc::new(result)))
1097    });
1098
1099    vm.register_builtin("date_format", |args, _out| {
1100        let ts = match args.first() {
1101            Some(VmValue::Float(f)) => *f,
1102            Some(VmValue::Int(n)) => *n as f64,
1103            Some(VmValue::Dict(map)) => map
1104                .get("timestamp")
1105                .and_then(|v| match v {
1106                    VmValue::Float(f) => Some(*f),
1107                    VmValue::Int(n) => Some(*n as f64),
1108                    _ => None,
1109                })
1110                .unwrap_or(0.0),
1111            _ => 0.0,
1112        };
1113        let fmt = args
1114            .get(1)
1115            .map(|a| a.display())
1116            .unwrap_or_else(|| "%Y-%m-%d %H:%M:%S".to_string());
1117
1118        let (y, m, d, hour, minute, second, _dow) = vm_civil_from_timestamp(ts as u64);
1119
1120        let result = fmt
1121            .replace("%Y", &format!("{y:04}"))
1122            .replace("%m", &format!("{m:02}"))
1123            .replace("%d", &format!("{d:02}"))
1124            .replace("%H", &format!("{hour:02}"))
1125            .replace("%M", &format!("{minute:02}"))
1126            .replace("%S", &format!("{second:02}"));
1127
1128        Ok(VmValue::String(Rc::from(result.as_str())))
1129    });
1130
1131    vm.register_builtin("date_parse", |args, _out| {
1132        let s = args.first().map(|a| a.display()).unwrap_or_default();
1133        let parts: Vec<&str> = s.split(|c: char| !c.is_ascii_digit()).collect();
1134        let parts: Vec<i64> = parts.iter().filter_map(|p| p.parse().ok()).collect();
1135        if parts.len() < 3 {
1136            return Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1137                "Cannot parse date: {s}"
1138            )))));
1139        }
1140        let (y, m, d) = (parts[0], parts[1], parts[2]);
1141        let hour = parts.get(3).copied().unwrap_or(0);
1142        let minute = parts.get(4).copied().unwrap_or(0);
1143        let second = parts.get(5).copied().unwrap_or(0);
1144
1145        let (y_adj, m_adj) = if m <= 2 {
1146            (y - 1, (m + 9) as u64)
1147        } else {
1148            (y, (m - 3) as u64)
1149        };
1150        let era = if y_adj >= 0 { y_adj } else { y_adj - 399 } / 400;
1151        let yoe = (y_adj - era * 400) as u64;
1152        let doy = (153 * m_adj + 2) / 5 + d as u64 - 1;
1153        let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
1154        let days = era * 146097 + doe as i64 - 719468;
1155        let ts = days * 86400 + hour * 3600 + minute * 60 + second;
1156        Ok(VmValue::Float(ts as f64))
1157    });
1158
1159    // =========================================================================
1160    // String formatting
1161    // =========================================================================
1162
1163    vm.register_builtin("format", |args, _out| {
1164        let template = args.first().map(|a| a.display()).unwrap_or_default();
1165        let mut result = String::with_capacity(template.len());
1166        let mut arg_iter = args.iter().skip(1);
1167        let mut rest = template.as_str();
1168        while let Some(pos) = rest.find("{}") {
1169            result.push_str(&rest[..pos]);
1170            if let Some(arg) = arg_iter.next() {
1171                result.push_str(&arg.display());
1172            } else {
1173                result.push_str("{}");
1174            }
1175            rest = &rest[pos + 2..];
1176        }
1177        result.push_str(rest);
1178        Ok(VmValue::String(Rc::from(result.as_str())))
1179    });
1180
1181    // =========================================================================
1182    // Standalone string function builtins
1183    // =========================================================================
1184
1185    vm.register_builtin("trim", |args, _out| {
1186        let s = args.first().map(|a| a.display()).unwrap_or_default();
1187        Ok(VmValue::String(Rc::from(s.trim())))
1188    });
1189
1190    vm.register_builtin("lowercase", |args, _out| {
1191        let s = args.first().map(|a| a.display()).unwrap_or_default();
1192        Ok(VmValue::String(Rc::from(s.to_lowercase().as_str())))
1193    });
1194
1195    vm.register_builtin("uppercase", |args, _out| {
1196        let s = args.first().map(|a| a.display()).unwrap_or_default();
1197        Ok(VmValue::String(Rc::from(s.to_uppercase().as_str())))
1198    });
1199
1200    vm.register_builtin("split", |args, _out| {
1201        let s = args.first().map(|a| a.display()).unwrap_or_default();
1202        let sep = args
1203            .get(1)
1204            .map(|a| a.display())
1205            .unwrap_or_else(|| " ".to_string());
1206        let parts: Vec<VmValue> = s
1207            .split(&sep)
1208            .map(|p| VmValue::String(Rc::from(p)))
1209            .collect();
1210        Ok(VmValue::List(Rc::new(parts)))
1211    });
1212
1213    vm.register_builtin("starts_with", |args, _out| {
1214        let s = args.first().map(|a| a.display()).unwrap_or_default();
1215        let prefix = args.get(1).map(|a| a.display()).unwrap_or_default();
1216        Ok(VmValue::Bool(s.starts_with(&prefix)))
1217    });
1218
1219    vm.register_builtin("ends_with", |args, _out| {
1220        let s = args.first().map(|a| a.display()).unwrap_or_default();
1221        let suffix = args.get(1).map(|a| a.display()).unwrap_or_default();
1222        Ok(VmValue::Bool(s.ends_with(&suffix)))
1223    });
1224
1225    vm.register_builtin("contains", |args, _out| {
1226        match args.first().unwrap_or(&VmValue::Nil) {
1227            VmValue::String(s) => {
1228                let substr = args.get(1).map(|a| a.display()).unwrap_or_default();
1229                Ok(VmValue::Bool(s.contains(&substr)))
1230            }
1231            VmValue::List(items) => {
1232                let target = args.get(1).unwrap_or(&VmValue::Nil);
1233                Ok(VmValue::Bool(
1234                    items.iter().any(|item| values_equal(item, target)),
1235                ))
1236            }
1237            _ => Ok(VmValue::Bool(false)),
1238        }
1239    });
1240
1241    vm.register_builtin("replace", |args, _out| {
1242        let s = args.first().map(|a| a.display()).unwrap_or_default();
1243        let old = args.get(1).map(|a| a.display()).unwrap_or_default();
1244        let new = args.get(2).map(|a| a.display()).unwrap_or_default();
1245        Ok(VmValue::String(Rc::from(s.replace(&old, &new).as_str())))
1246    });
1247
1248    vm.register_builtin("join", |args, _out| {
1249        let sep = args.get(1).map(|a| a.display()).unwrap_or_default();
1250        match args.first() {
1251            Some(VmValue::List(items)) => {
1252                let parts: Vec<String> = items.iter().map(|v| v.display()).collect();
1253                Ok(VmValue::String(Rc::from(parts.join(&sep).as_str())))
1254            }
1255            _ => Ok(VmValue::String(Rc::from(""))),
1256        }
1257    });
1258
1259    vm.register_builtin("len", |args, _out| {
1260        match args.first().unwrap_or(&VmValue::Nil) {
1261            VmValue::String(s) => Ok(VmValue::Int(s.len() as i64)),
1262            VmValue::List(items) => Ok(VmValue::Int(items.len() as i64)),
1263            VmValue::Dict(map) => Ok(VmValue::Int(map.len() as i64)),
1264            VmValue::Set(s) => Ok(VmValue::Int(s.len() as i64)),
1265            _ => Ok(VmValue::Int(0)),
1266        }
1267    });
1268
1269    vm.register_builtin("substring", |args, _out| {
1270        let s = args.first().map(|a| a.display()).unwrap_or_default();
1271        let start = args.get(1).and_then(|a| a.as_int()).unwrap_or(0) as usize;
1272        let start = start.min(s.len());
1273        match args.get(2).and_then(|a| a.as_int()) {
1274            Some(length) => {
1275                let length = (length as usize).min(s.len() - start);
1276                Ok(VmValue::String(Rc::from(&s[start..start + length])))
1277            }
1278            None => Ok(VmValue::String(Rc::from(&s[start..]))),
1279        }
1280    });
1281
1282    // =========================================================================
1283    // Path builtins
1284    // =========================================================================
1285
1286    vm.register_builtin("dirname", |args, _out| {
1287        let path = args.first().map(|a| a.display()).unwrap_or_default();
1288        let p = std::path::Path::new(&path);
1289        match p.parent() {
1290            Some(parent) => Ok(VmValue::String(Rc::from(parent.to_string_lossy().as_ref()))),
1291            None => Ok(VmValue::String(Rc::from(""))),
1292        }
1293    });
1294
1295    vm.register_builtin("basename", |args, _out| {
1296        let path = args.first().map(|a| a.display()).unwrap_or_default();
1297        let p = std::path::Path::new(&path);
1298        match p.file_name() {
1299            Some(name) => Ok(VmValue::String(Rc::from(name.to_string_lossy().as_ref()))),
1300            None => Ok(VmValue::String(Rc::from(""))),
1301        }
1302    });
1303
1304    vm.register_builtin("extname", |args, _out| {
1305        let path = args.first().map(|a| a.display()).unwrap_or_default();
1306        let p = std::path::Path::new(&path);
1307        match p.extension() {
1308            Some(ext) => Ok(VmValue::String(Rc::from(
1309                format!(".{}", ext.to_string_lossy()).as_str(),
1310            ))),
1311            None => Ok(VmValue::String(Rc::from(""))),
1312        }
1313    });
1314
1315    // =========================================================================
1316    // Prompt template rendering
1317    // =========================================================================
1318
1319    vm.register_builtin("render", |args, _out| {
1320        let path = args.first().map(|a| a.display()).unwrap_or_default();
1321        let template = std::fs::read_to_string(&path).map_err(|e| {
1322            VmError::Thrown(VmValue::String(Rc::from(format!(
1323                "Failed to read template {path}: {e}"
1324            ))))
1325        })?;
1326        if let Some(bindings) = args.get(1).and_then(|a| a.as_dict()) {
1327            let mut result = template;
1328            for (key, val) in bindings.iter() {
1329                result = result.replace(&format!("{{{{{key}}}}}"), &val.display());
1330            }
1331            Ok(VmValue::String(Rc::from(result)))
1332        } else {
1333            Ok(VmValue::String(Rc::from(template)))
1334        }
1335    });
1336
1337    // =========================================================================
1338    // Logging builtins
1339    // =========================================================================
1340
1341    vm.register_builtin("log_debug", |args, out| {
1342        vm_write_log("debug", 0, args, out);
1343        Ok(VmValue::Nil)
1344    });
1345
1346    vm.register_builtin("log_info", |args, out| {
1347        vm_write_log("info", 1, args, out);
1348        Ok(VmValue::Nil)
1349    });
1350
1351    vm.register_builtin("log_warn", |args, out| {
1352        vm_write_log("warn", 2, args, out);
1353        Ok(VmValue::Nil)
1354    });
1355
1356    vm.register_builtin("log_error", |args, out| {
1357        vm_write_log("error", 3, args, out);
1358        Ok(VmValue::Nil)
1359    });
1360
1361    vm.register_builtin("log_set_level", |args, _out| {
1362        let level_str = args.first().map(|a| a.display()).unwrap_or_default();
1363        match vm_level_to_u8(&level_str) {
1364            Some(n) => {
1365                VM_MIN_LOG_LEVEL.store(n, Ordering::Relaxed);
1366                Ok(VmValue::Nil)
1367            }
1368            None => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1369                "log_set_level: invalid level '{}'. Expected debug, info, warn, or error",
1370                level_str
1371            ))))),
1372        }
1373    });
1374
1375    // =========================================================================
1376    // Tracing builtins
1377    // =========================================================================
1378
1379    vm.register_builtin("trace_start", |args, _out| {
1380        use rand::Rng;
1381        let name = args.first().map(|a| a.display()).unwrap_or_default();
1382        let trace_id = VM_TRACE_STACK.with(|stack| {
1383            stack
1384                .borrow()
1385                .last()
1386                .map(|t| t.trace_id.clone())
1387                .unwrap_or_else(|| {
1388                    let val: u32 = rand::thread_rng().gen();
1389                    format!("{val:08x}")
1390                })
1391        });
1392        let span_id = {
1393            let val: u32 = rand::thread_rng().gen();
1394            format!("{val:08x}")
1395        };
1396        let start_ms = std::time::SystemTime::now()
1397            .duration_since(std::time::UNIX_EPOCH)
1398            .unwrap_or_default()
1399            .as_millis() as i64;
1400
1401        VM_TRACE_STACK.with(|stack| {
1402            stack.borrow_mut().push(VmTraceContext {
1403                trace_id: trace_id.clone(),
1404                span_id: span_id.clone(),
1405            });
1406        });
1407
1408        let mut span = BTreeMap::new();
1409        span.insert(
1410            "trace_id".to_string(),
1411            VmValue::String(Rc::from(trace_id.as_str())),
1412        );
1413        span.insert(
1414            "span_id".to_string(),
1415            VmValue::String(Rc::from(span_id.as_str())),
1416        );
1417        span.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
1418        span.insert("start_ms".to_string(), VmValue::Int(start_ms));
1419        Ok(VmValue::Dict(Rc::new(span)))
1420    });
1421
1422    vm.register_builtin("trace_end", |args, out| {
1423        let span = match args.first() {
1424            Some(VmValue::Dict(d)) => d,
1425            _ => {
1426                return Err(VmError::Thrown(VmValue::String(Rc::from(
1427                    "trace_end: argument must be a span dict from trace_start",
1428                ))));
1429            }
1430        };
1431
1432        let end_ms = std::time::SystemTime::now()
1433            .duration_since(std::time::UNIX_EPOCH)
1434            .unwrap_or_default()
1435            .as_millis() as i64;
1436
1437        let start_ms = span
1438            .get("start_ms")
1439            .and_then(|v| v.as_int())
1440            .unwrap_or(end_ms);
1441        let duration_ms = end_ms - start_ms;
1442        let name = span.get("name").map(|v| v.display()).unwrap_or_default();
1443        let trace_id = span
1444            .get("trace_id")
1445            .map(|v| v.display())
1446            .unwrap_or_default();
1447        let span_id = span.get("span_id").map(|v| v.display()).unwrap_or_default();
1448
1449        VM_TRACE_STACK.with(|stack| {
1450            stack.borrow_mut().pop();
1451        });
1452
1453        let level_num = 1_u8;
1454        if level_num >= VM_MIN_LOG_LEVEL.load(Ordering::Relaxed) {
1455            let mut fields = BTreeMap::new();
1456            fields.insert(
1457                "trace_id".to_string(),
1458                VmValue::String(Rc::from(trace_id.as_str())),
1459            );
1460            fields.insert(
1461                "span_id".to_string(),
1462                VmValue::String(Rc::from(span_id.as_str())),
1463            );
1464            fields.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
1465            fields.insert("duration_ms".to_string(), VmValue::Int(duration_ms));
1466            let line = vm_build_log_line("info", "span_end", Some(&fields));
1467            out.push_str(&line);
1468        }
1469
1470        Ok(VmValue::Nil)
1471    });
1472
1473    vm.register_builtin("trace_id", |_args, _out| {
1474        let id = VM_TRACE_STACK.with(|stack| stack.borrow().last().map(|t| t.trace_id.clone()));
1475        match id {
1476            Some(trace_id) => Ok(VmValue::String(Rc::from(trace_id.as_str()))),
1477            None => Ok(VmValue::Nil),
1478        }
1479    });
1480
1481    // =========================================================================
1482    // LLM introspection builtins
1483    // =========================================================================
1484
1485    vm.register_builtin("llm_info", |_args, _out| {
1486        let provider = std::env::var("HARN_LLM_PROVIDER").unwrap_or_default();
1487        let model = std::env::var("HARN_LLM_MODEL").unwrap_or_default();
1488        let api_key_set = std::env::var("HARN_API_KEY")
1489            .or_else(|_| std::env::var("OPENROUTER_API_KEY"))
1490            .or_else(|_| std::env::var("ANTHROPIC_API_KEY"))
1491            .is_ok();
1492        let mut info = BTreeMap::new();
1493        info.insert(
1494            "provider".to_string(),
1495            VmValue::String(Rc::from(provider.as_str())),
1496        );
1497        info.insert(
1498            "model".to_string(),
1499            VmValue::String(Rc::from(model.as_str())),
1500        );
1501        info.insert("api_key_set".to_string(), VmValue::Bool(api_key_set));
1502        Ok(VmValue::Dict(Rc::new(info)))
1503    });
1504
1505    vm.register_builtin("llm_usage", |_args, _out| {
1506        let (total_input, total_output, total_duration, call_count) =
1507            crate::llm::peek_trace_summary();
1508        let mut usage = BTreeMap::new();
1509        usage.insert("input_tokens".to_string(), VmValue::Int(total_input));
1510        usage.insert("output_tokens".to_string(), VmValue::Int(total_output));
1511        usage.insert(
1512            "total_duration_ms".to_string(),
1513            VmValue::Int(total_duration),
1514        );
1515        usage.insert("call_count".to_string(), VmValue::Int(call_count));
1516        Ok(VmValue::Dict(Rc::new(usage)))
1517    });
1518
1519    // =========================================================================
1520    // Timer builtins
1521    // =========================================================================
1522
1523    vm.register_builtin("timer_start", |args, _out| {
1524        let name = args
1525            .first()
1526            .map(|a| a.display())
1527            .unwrap_or_else(|| "default".to_string());
1528        let now_ms = std::time::SystemTime::now()
1529            .duration_since(std::time::UNIX_EPOCH)
1530            .unwrap_or_default()
1531            .as_millis() as i64;
1532        let mut timer = BTreeMap::new();
1533        timer.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
1534        timer.insert("start_ms".to_string(), VmValue::Int(now_ms));
1535        Ok(VmValue::Dict(Rc::new(timer)))
1536    });
1537
1538    vm.register_builtin("timer_end", |args, out| {
1539        let timer = match args.first() {
1540            Some(VmValue::Dict(d)) => d,
1541            _ => {
1542                return Err(VmError::Thrown(VmValue::String(Rc::from(
1543                    "timer_end: argument must be a timer dict from timer_start",
1544                ))));
1545            }
1546        };
1547        let now_ms = std::time::SystemTime::now()
1548            .duration_since(std::time::UNIX_EPOCH)
1549            .unwrap_or_default()
1550            .as_millis() as i64;
1551        let start_ms = timer
1552            .get("start_ms")
1553            .and_then(|v| v.as_int())
1554            .unwrap_or(now_ms);
1555        let elapsed = now_ms - start_ms;
1556        let name = timer.get("name").map(|v| v.display()).unwrap_or_default();
1557        out.push_str(&format!("[timer] {name}: {elapsed}ms\n"));
1558        Ok(VmValue::Int(elapsed))
1559    });
1560
1561    vm.register_builtin("elapsed", |_args, _out| {
1562        // Milliseconds since process start (approximated via monotonic clock).
1563        // Uses a stable epoch: first call anchors to 0.
1564        static START: std::sync::OnceLock<std::time::Instant> = std::sync::OnceLock::new();
1565        let start = START.get_or_init(std::time::Instant::now);
1566        Ok(VmValue::Int(start.elapsed().as_millis() as i64))
1567    });
1568
1569    vm.register_builtin("log_json", |args, out| {
1570        let key = args.first().map(|a| a.display()).unwrap_or_default();
1571        let value = args.get(1).cloned().unwrap_or(VmValue::Nil);
1572        let json_val = vm_value_to_json_fragment(&value);
1573        let ts = vm_format_timestamp_utc();
1574        out.push_str(&format!(
1575            "{{\"ts\":{},\"key\":{},\"value\":{}}}\n",
1576            vm_escape_json_str_quoted(&ts),
1577            vm_escape_json_str_quoted(&key),
1578            json_val,
1579        ));
1580        Ok(VmValue::Nil)
1581    });
1582
1583    // =========================================================================
1584    // Tool registry builtins
1585    // =========================================================================
1586
1587    vm.register_builtin("tool_registry", |_args, _out| {
1588        let mut registry = BTreeMap::new();
1589        registry.insert(
1590            "_type".to_string(),
1591            VmValue::String(Rc::from("tool_registry")),
1592        );
1593        registry.insert("tools".to_string(), VmValue::List(Rc::new(Vec::new())));
1594        Ok(VmValue::Dict(Rc::new(registry)))
1595    });
1596
1597    vm.register_builtin("tool_add", |args, _out| {
1598        if args.len() < 4 {
1599            return Err(VmError::Thrown(VmValue::String(Rc::from(
1600                "tool_add: requires registry, name, description, and handler",
1601            ))));
1602        }
1603
1604        let registry = match &args[0] {
1605            VmValue::Dict(map) => (**map).clone(),
1606            _ => {
1607                return Err(VmError::Thrown(VmValue::String(Rc::from(
1608                    "tool_add: first argument must be a tool registry",
1609                ))));
1610            }
1611        };
1612
1613        match registry.get("_type") {
1614            Some(VmValue::String(t)) if &**t == "tool_registry" => {}
1615            _ => {
1616                return Err(VmError::Thrown(VmValue::String(Rc::from(
1617                    "tool_add: first argument must be a tool registry",
1618                ))));
1619            }
1620        }
1621
1622        let name = args[1].display();
1623        let description = args[2].display();
1624        let handler = args[3].clone();
1625        let parameters = if args.len() > 4 {
1626            args[4].clone()
1627        } else {
1628            VmValue::Dict(Rc::new(BTreeMap::new()))
1629        };
1630
1631        let mut tool_entry = BTreeMap::new();
1632        tool_entry.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
1633        tool_entry.insert(
1634            "description".to_string(),
1635            VmValue::String(Rc::from(description.as_str())),
1636        );
1637        tool_entry.insert("handler".to_string(), handler);
1638        tool_entry.insert("parameters".to_string(), parameters);
1639
1640        let mut tools: Vec<VmValue> = match registry.get("tools") {
1641            Some(VmValue::List(list)) => list
1642                .iter()
1643                .filter(|t| {
1644                    if let VmValue::Dict(e) = t {
1645                        e.get("name").map(|v| v.display()).as_deref() != Some(name.as_str())
1646                    } else {
1647                        true
1648                    }
1649                })
1650                .cloned()
1651                .collect(),
1652            _ => Vec::new(),
1653        };
1654        tools.push(VmValue::Dict(Rc::new(tool_entry)));
1655
1656        let mut new_registry = registry;
1657        new_registry.insert("tools".to_string(), VmValue::List(Rc::new(tools)));
1658        Ok(VmValue::Dict(Rc::new(new_registry)))
1659    });
1660
1661    vm.register_builtin("tool_list", |args, _out| {
1662        let registry = match args.first() {
1663            Some(VmValue::Dict(map)) => map,
1664            _ => {
1665                return Err(VmError::Thrown(VmValue::String(Rc::from(
1666                    "tool_list: requires a tool registry",
1667                ))));
1668            }
1669        };
1670        vm_validate_registry("tool_list", registry)?;
1671
1672        let tools = vm_get_tools(registry);
1673        let mut result = Vec::new();
1674        for tool in tools {
1675            if let VmValue::Dict(entry) = tool {
1676                let mut desc = BTreeMap::new();
1677                if let Some(name) = entry.get("name") {
1678                    desc.insert("name".to_string(), name.clone());
1679                }
1680                if let Some(description) = entry.get("description") {
1681                    desc.insert("description".to_string(), description.clone());
1682                }
1683                if let Some(parameters) = entry.get("parameters") {
1684                    desc.insert("parameters".to_string(), parameters.clone());
1685                }
1686                result.push(VmValue::Dict(Rc::new(desc)));
1687            }
1688        }
1689        Ok(VmValue::List(Rc::new(result)))
1690    });
1691
1692    vm.register_builtin("tool_find", |args, _out| {
1693        if args.len() < 2 {
1694            return Err(VmError::Thrown(VmValue::String(Rc::from(
1695                "tool_find: requires registry and name",
1696            ))));
1697        }
1698
1699        let registry = match &args[0] {
1700            VmValue::Dict(map) => map,
1701            _ => {
1702                return Err(VmError::Thrown(VmValue::String(Rc::from(
1703                    "tool_find: first argument must be a tool registry",
1704                ))));
1705            }
1706        };
1707        vm_validate_registry("tool_find", registry)?;
1708
1709        let target_name = args[1].display();
1710        let tools = vm_get_tools(registry);
1711
1712        for tool in tools {
1713            if let VmValue::Dict(entry) = tool {
1714                if let Some(VmValue::String(name)) = entry.get("name") {
1715                    if &**name == target_name.as_str() {
1716                        return Ok(tool.clone());
1717                    }
1718                }
1719            }
1720        }
1721        Ok(VmValue::Nil)
1722    });
1723
1724    vm.register_builtin("tool_describe", |args, _out| {
1725        let registry = match args.first() {
1726            Some(VmValue::Dict(map)) => map,
1727            _ => {
1728                return Err(VmError::Thrown(VmValue::String(Rc::from(
1729                    "tool_describe: requires a tool registry",
1730                ))));
1731            }
1732        };
1733        vm_validate_registry("tool_describe", registry)?;
1734
1735        let tools = vm_get_tools(registry);
1736
1737        if tools.is_empty() {
1738            return Ok(VmValue::String(Rc::from("Available tools:\n(none)")));
1739        }
1740
1741        let mut tool_infos: Vec<(String, String, String)> = Vec::new();
1742        for tool in tools {
1743            if let VmValue::Dict(entry) = tool {
1744                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
1745                let description = entry
1746                    .get("description")
1747                    .map(|v| v.display())
1748                    .unwrap_or_default();
1749                let params_str = vm_format_parameters(entry.get("parameters"));
1750                tool_infos.push((name, params_str, description));
1751            }
1752        }
1753
1754        tool_infos.sort_by(|a, b| a.0.cmp(&b.0));
1755
1756        let mut lines = vec!["Available tools:".to_string()];
1757        for (name, params, desc) in &tool_infos {
1758            lines.push(format!("- {name}({params}): {desc}"));
1759        }
1760
1761        Ok(VmValue::String(Rc::from(lines.join("\n").as_str())))
1762    });
1763
1764    vm.register_builtin("tool_remove", |args, _out| {
1765        if args.len() < 2 {
1766            return Err(VmError::Thrown(VmValue::String(Rc::from(
1767                "tool_remove: requires registry and name",
1768            ))));
1769        }
1770
1771        let registry = match &args[0] {
1772            VmValue::Dict(map) => (**map).clone(),
1773            _ => {
1774                return Err(VmError::Thrown(VmValue::String(Rc::from(
1775                    "tool_remove: first argument must be a tool registry",
1776                ))));
1777            }
1778        };
1779        vm_validate_registry("tool_remove", &registry)?;
1780
1781        let target_name = args[1].display();
1782
1783        let tools = match registry.get("tools") {
1784            Some(VmValue::List(list)) => (**list).clone(),
1785            _ => Vec::new(),
1786        };
1787
1788        let filtered: Vec<VmValue> = tools
1789            .into_iter()
1790            .filter(|tool| {
1791                if let VmValue::Dict(entry) = tool {
1792                    if let Some(VmValue::String(name)) = entry.get("name") {
1793                        return &**name != target_name.as_str();
1794                    }
1795                }
1796                true
1797            })
1798            .collect();
1799
1800        let mut new_registry = registry;
1801        new_registry.insert("tools".to_string(), VmValue::List(Rc::new(filtered)));
1802        Ok(VmValue::Dict(Rc::new(new_registry)))
1803    });
1804
1805    vm.register_builtin("tool_count", |args, _out| {
1806        let registry = match args.first() {
1807            Some(VmValue::Dict(map)) => map,
1808            _ => {
1809                return Err(VmError::Thrown(VmValue::String(Rc::from(
1810                    "tool_count: requires a tool registry",
1811                ))));
1812            }
1813        };
1814        vm_validate_registry("tool_count", registry)?;
1815        let count = vm_get_tools(registry).len();
1816        Ok(VmValue::Int(count as i64))
1817    });
1818
1819    vm.register_builtin("tool_schema", |args, _out| {
1820        let registry = match args.first() {
1821            Some(VmValue::Dict(map)) => {
1822                vm_validate_registry("tool_schema", map)?;
1823                map
1824            }
1825            _ => {
1826                return Err(VmError::Thrown(VmValue::String(Rc::from(
1827                    "tool_schema: requires a tool registry",
1828                ))));
1829            }
1830        };
1831
1832        let components = args.get(1).and_then(|v| v.as_dict()).cloned();
1833
1834        let tools = match registry.get("tools") {
1835            Some(VmValue::List(list)) => list,
1836            _ => return Ok(VmValue::Dict(Rc::new(vm_build_empty_schema()))),
1837        };
1838
1839        let mut tool_schemas = Vec::new();
1840        for tool in tools.iter() {
1841            if let VmValue::Dict(entry) = tool {
1842                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
1843                let description = entry
1844                    .get("description")
1845                    .map(|v| v.display())
1846                    .unwrap_or_default();
1847
1848                let input_schema =
1849                    vm_build_input_schema(entry.get("parameters"), components.as_ref());
1850
1851                let mut tool_def = BTreeMap::new();
1852                tool_def.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
1853                tool_def.insert(
1854                    "description".to_string(),
1855                    VmValue::String(Rc::from(description.as_str())),
1856                );
1857                tool_def.insert("inputSchema".to_string(), input_schema);
1858                tool_schemas.push(VmValue::Dict(Rc::new(tool_def)));
1859            }
1860        }
1861
1862        let mut schema = BTreeMap::new();
1863        schema.insert(
1864            "schema_version".to_string(),
1865            VmValue::String(Rc::from("harn-tools/1.0")),
1866        );
1867
1868        if let Some(comps) = &components {
1869            let mut comp_wrapper = BTreeMap::new();
1870            comp_wrapper.insert("schemas".to_string(), VmValue::Dict(Rc::new(comps.clone())));
1871            schema.insert(
1872                "components".to_string(),
1873                VmValue::Dict(Rc::new(comp_wrapper)),
1874            );
1875        }
1876
1877        schema.insert("tools".to_string(), VmValue::List(Rc::new(tool_schemas)));
1878        Ok(VmValue::Dict(Rc::new(schema)))
1879    });
1880
1881    vm.register_builtin("tool_parse_call", |args, _out| {
1882        let text = args.first().map(|a| a.display()).unwrap_or_default();
1883
1884        let mut results = Vec::new();
1885        let mut search_from = 0;
1886
1887        while let Some(start) = text[search_from..].find("<tool_call>") {
1888            let abs_start = search_from + start + "<tool_call>".len();
1889            if let Some(end) = text[abs_start..].find("</tool_call>") {
1890                let json_str = text[abs_start..abs_start + end].trim();
1891                if let Ok(jv) = serde_json::from_str::<serde_json::Value>(json_str) {
1892                    results.push(json_to_vm_value(&jv));
1893                }
1894                search_from = abs_start + end + "</tool_call>".len();
1895            } else {
1896                break;
1897            }
1898        }
1899
1900        Ok(VmValue::List(Rc::new(results)))
1901    });
1902
1903    vm.register_builtin("tool_format_result", |args, _out| {
1904        if args.len() < 2 {
1905            return Err(VmError::Thrown(VmValue::String(Rc::from(
1906                "tool_format_result: requires name and result",
1907            ))));
1908        }
1909        let name = args[0].display();
1910        let result = args[1].display();
1911
1912        let json_name = vm_escape_json_str(&name);
1913        let json_result = vm_escape_json_str(&result);
1914        Ok(VmValue::String(Rc::from(
1915            format!(
1916                "<tool_result>{{\"name\": \"{json_name}\", \"result\": \"{json_result}\"}}</tool_result>"
1917            )
1918            .as_str(),
1919        )))
1920    });
1921
1922    vm.register_builtin("tool_prompt", |args, _out| {
1923        let registry = match args.first() {
1924            Some(VmValue::Dict(map)) => {
1925                vm_validate_registry("tool_prompt", map)?;
1926                map
1927            }
1928            _ => {
1929                return Err(VmError::Thrown(VmValue::String(Rc::from(
1930                    "tool_prompt: requires a tool registry",
1931                ))));
1932            }
1933        };
1934
1935        let tools = match registry.get("tools") {
1936            Some(VmValue::List(list)) => list,
1937            _ => {
1938                return Ok(VmValue::String(Rc::from("No tools are available.")));
1939            }
1940        };
1941
1942        if tools.is_empty() {
1943            return Ok(VmValue::String(Rc::from("No tools are available.")));
1944        }
1945
1946        let mut prompt = String::from("# Available Tools\n\n");
1947        prompt.push_str("You have access to the following tools. To use a tool, output a tool call in this exact format:\n\n");
1948        prompt.push_str("<tool_call>{\"name\": \"tool_name\", \"arguments\": {\"param\": \"value\"}}</tool_call>\n\n");
1949        prompt.push_str("You may make multiple tool calls in a single response. Wait for tool results before proceeding.\n\n");
1950        prompt.push_str("## Tools\n\n");
1951
1952        let mut tool_infos: Vec<(&BTreeMap<String, VmValue>, String)> = Vec::new();
1953        for tool in tools.iter() {
1954            if let VmValue::Dict(entry) = tool {
1955                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
1956                tool_infos.push((entry, name));
1957            }
1958        }
1959        tool_infos.sort_by(|a, b| a.1.cmp(&b.1));
1960
1961        for (entry, name) in &tool_infos {
1962            let description = entry
1963                .get("description")
1964                .map(|v| v.display())
1965                .unwrap_or_default();
1966            let params_str = vm_format_parameters(entry.get("parameters"));
1967
1968            prompt.push_str(&format!("### {name}\n"));
1969            prompt.push_str(&format!("{description}\n"));
1970            if !params_str.is_empty() {
1971                prompt.push_str(&format!("Parameters: {params_str}\n"));
1972            }
1973            prompt.push('\n');
1974        }
1975
1976        Ok(VmValue::String(Rc::from(prompt.trim_end())))
1977    });
1978
1979    // =========================================================================
1980    // Channel builtins (sync)
1981    // =========================================================================
1982
1983    vm.register_builtin("channel", |args, _out| {
1984        let name = args
1985            .first()
1986            .map(|a| a.display())
1987            .unwrap_or_else(|| "default".to_string());
1988        let capacity = args.get(1).and_then(|a| a.as_int()).unwrap_or(256) as usize;
1989        let capacity = capacity.max(1);
1990        let (tx, rx) = tokio::sync::mpsc::channel(capacity);
1991        #[allow(clippy::arc_with_non_send_sync)]
1992        Ok(VmValue::Channel(VmChannelHandle {
1993            name,
1994            sender: Arc::new(tx),
1995            receiver: Arc::new(tokio::sync::Mutex::new(rx)),
1996            closed: Arc::new(AtomicBool::new(false)),
1997        }))
1998    });
1999
2000    vm.register_builtin("close_channel", |args, _out| {
2001        if args.is_empty() {
2002            return Err(VmError::Thrown(VmValue::String(Rc::from(
2003                "close_channel: requires a channel",
2004            ))));
2005        }
2006        if let VmValue::Channel(ch) = &args[0] {
2007            ch.closed.store(true, Ordering::SeqCst);
2008            Ok(VmValue::Nil)
2009        } else {
2010            Err(VmError::Thrown(VmValue::String(Rc::from(
2011                "close_channel: first argument must be a channel",
2012            ))))
2013        }
2014    });
2015
2016    vm.register_builtin("try_receive", |args, _out| {
2017        if args.is_empty() {
2018            return Err(VmError::Thrown(VmValue::String(Rc::from(
2019                "try_receive: requires a channel",
2020            ))));
2021        }
2022        if let VmValue::Channel(ch) = &args[0] {
2023            match ch.receiver.try_lock() {
2024                Ok(mut rx) => match rx.try_recv() {
2025                    Ok(val) => Ok(val),
2026                    Err(_) => Ok(VmValue::Nil),
2027                },
2028                Err(_) => Ok(VmValue::Nil),
2029            }
2030        } else {
2031            Err(VmError::Thrown(VmValue::String(Rc::from(
2032                "try_receive: first argument must be a channel",
2033            ))))
2034        }
2035    });
2036
2037    // =========================================================================
2038    // Atomic builtins
2039    // =========================================================================
2040
2041    vm.register_builtin("atomic", |args, _out| {
2042        let initial = match args.first() {
2043            Some(VmValue::Int(n)) => *n,
2044            Some(VmValue::Float(f)) => *f as i64,
2045            Some(VmValue::Bool(b)) => {
2046                if *b {
2047                    1
2048                } else {
2049                    0
2050                }
2051            }
2052            _ => 0,
2053        };
2054        Ok(VmValue::Atomic(VmAtomicHandle {
2055            value: Arc::new(AtomicI64::new(initial)),
2056        }))
2057    });
2058
2059    vm.register_builtin("atomic_get", |args, _out| {
2060        if let Some(VmValue::Atomic(a)) = args.first() {
2061            Ok(VmValue::Int(a.value.load(Ordering::SeqCst)))
2062        } else {
2063            Ok(VmValue::Nil)
2064        }
2065    });
2066
2067    vm.register_builtin("atomic_set", |args, _out| {
2068        if args.len() >= 2 {
2069            if let (VmValue::Atomic(a), Some(val)) = (&args[0], args[1].as_int()) {
2070                let old = a.value.swap(val, Ordering::SeqCst);
2071                return Ok(VmValue::Int(old));
2072            }
2073        }
2074        Ok(VmValue::Nil)
2075    });
2076
2077    vm.register_builtin("atomic_add", |args, _out| {
2078        if args.len() >= 2 {
2079            if let (VmValue::Atomic(a), Some(delta)) = (&args[0], args[1].as_int()) {
2080                let prev = a.value.fetch_add(delta, Ordering::SeqCst);
2081                return Ok(VmValue::Int(prev));
2082            }
2083        }
2084        Ok(VmValue::Nil)
2085    });
2086
2087    vm.register_builtin("atomic_cas", |args, _out| {
2088        if args.len() >= 3 {
2089            if let (VmValue::Atomic(a), Some(expected), Some(new_val)) =
2090                (&args[0], args[1].as_int(), args[2].as_int())
2091            {
2092                let result =
2093                    a.value
2094                        .compare_exchange(expected, new_val, Ordering::SeqCst, Ordering::SeqCst);
2095                return Ok(VmValue::Bool(result.is_ok()));
2096            }
2097        }
2098        Ok(VmValue::Bool(false))
2099    });
2100
2101    // =========================================================================
2102    // Async builtins
2103    // =========================================================================
2104
2105    // sleep(ms)
2106    vm.register_async_builtin("sleep", |args| async move {
2107        let ms = match args.first() {
2108            Some(VmValue::Duration(ms)) => *ms,
2109            Some(VmValue::Int(n)) => *n as u64,
2110            _ => 0,
2111        };
2112        if ms > 0 {
2113            tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await;
2114        }
2115        Ok(VmValue::Nil)
2116    });
2117
2118    // send(channel, value)
2119    vm.register_async_builtin("send", |args| async move {
2120        if args.len() < 2 {
2121            return Err(VmError::Thrown(VmValue::String(Rc::from(
2122                "send: requires channel and value",
2123            ))));
2124        }
2125        if let VmValue::Channel(ch) = &args[0] {
2126            if ch.closed.load(Ordering::SeqCst) {
2127                return Ok(VmValue::Bool(false));
2128            }
2129            let val = args[1].clone();
2130            match ch.sender.send(val).await {
2131                Ok(()) => Ok(VmValue::Bool(true)),
2132                Err(_) => Ok(VmValue::Bool(false)),
2133            }
2134        } else {
2135            Err(VmError::Thrown(VmValue::String(Rc::from(
2136                "send: first argument must be a channel",
2137            ))))
2138        }
2139    });
2140
2141    // receive(channel)
2142    vm.register_async_builtin("receive", |args| async move {
2143        if args.is_empty() {
2144            return Err(VmError::Thrown(VmValue::String(Rc::from(
2145                "receive: requires a channel",
2146            ))));
2147        }
2148        if let VmValue::Channel(ch) = &args[0] {
2149            if ch.closed.load(Ordering::SeqCst) {
2150                let mut rx = ch.receiver.lock().await;
2151                return match rx.try_recv() {
2152                    Ok(val) => Ok(val),
2153                    Err(_) => Ok(VmValue::Nil),
2154                };
2155            }
2156            let mut rx = ch.receiver.lock().await;
2157            match rx.recv().await {
2158                Some(val) => Ok(val),
2159                None => Ok(VmValue::Nil),
2160            }
2161        } else {
2162            Err(VmError::Thrown(VmValue::String(Rc::from(
2163                "receive: first argument must be a channel",
2164            ))))
2165        }
2166    });
2167
2168    // select(channel1, channel2, ...) — blocking multiplexed receive (variadic)
2169    vm.register_async_builtin("select", |args| async move {
2170        if args.is_empty() {
2171            return Err(VmError::Thrown(VmValue::String(Rc::from(
2172                "select: requires at least one channel",
2173            ))));
2174        }
2175        for arg in &args {
2176            if !matches!(arg, VmValue::Channel(_)) {
2177                return Err(VmError::Thrown(VmValue::String(Rc::from(
2178                    "select: all arguments must be channels",
2179                ))));
2180            }
2181        }
2182        loop {
2183            let (found, all_closed) = try_poll_channels(&args);
2184            if let Some((i, val, name)) = found {
2185                return Ok(select_result(i, val, &name));
2186            }
2187            if all_closed {
2188                return Ok(select_none());
2189            }
2190            tokio::task::yield_now().await;
2191        }
2192    });
2193
2194    // __select_timeout(channel_list, timeout_ms) — select with timeout
2195    vm.register_async_builtin("__select_timeout", |args| async move {
2196        if args.len() < 2 {
2197            return Err(VmError::Thrown(VmValue::String(Rc::from(
2198                "__select_timeout: requires channel list and timeout",
2199            ))));
2200        }
2201        let channels = match &args[0] {
2202            VmValue::List(items) => (**items).clone(),
2203            _ => {
2204                return Err(VmError::Thrown(VmValue::String(Rc::from(
2205                    "__select_timeout: first argument must be a list of channels",
2206                ))));
2207            }
2208        };
2209        let timeout_ms = match &args[1] {
2210            VmValue::Int(n) => (*n).max(0) as u64,
2211            VmValue::Duration(ms) => *ms,
2212            _ => 5000,
2213        };
2214        let deadline = tokio::time::Instant::now() + tokio::time::Duration::from_millis(timeout_ms);
2215        loop {
2216            let (found, all_closed) = try_poll_channels(&channels);
2217            if let Some((i, val, name)) = found {
2218                return Ok(select_result(i, val, &name));
2219            }
2220            if all_closed || tokio::time::Instant::now() >= deadline {
2221                return Ok(select_none());
2222            }
2223            tokio::task::yield_now().await;
2224        }
2225    });
2226
2227    // __select_try(channel_list) — non-blocking select (for default case)
2228    vm.register_async_builtin("__select_try", |args| async move {
2229        if args.is_empty() {
2230            return Err(VmError::Thrown(VmValue::String(Rc::from(
2231                "__select_try: requires channel list",
2232            ))));
2233        }
2234        let channels = match &args[0] {
2235            VmValue::List(items) => (**items).clone(),
2236            _ => {
2237                return Err(VmError::Thrown(VmValue::String(Rc::from(
2238                    "__select_try: first argument must be a list of channels",
2239                ))));
2240            }
2241        };
2242        let (found, _) = try_poll_channels(&channels);
2243        if let Some((i, val, name)) = found {
2244            Ok(select_result(i, val, &name))
2245        } else {
2246            Ok(select_none())
2247        }
2248    });
2249
2250    // __select_list(channel_list) — blocking select from a list of channels
2251    vm.register_async_builtin("__select_list", |args| async move {
2252        if args.is_empty() {
2253            return Err(VmError::Thrown(VmValue::String(Rc::from(
2254                "__select_list: requires channel list",
2255            ))));
2256        }
2257        let channels = match &args[0] {
2258            VmValue::List(items) => (**items).clone(),
2259            _ => {
2260                return Err(VmError::Thrown(VmValue::String(Rc::from(
2261                    "__select_list: first argument must be a list of channels",
2262                ))));
2263            }
2264        };
2265        loop {
2266            let (found, all_closed) = try_poll_channels(&channels);
2267            if let Some((i, val, name)) = found {
2268                return Ok(select_result(i, val, &name));
2269            }
2270            if all_closed {
2271                return Ok(select_none());
2272            }
2273            tokio::task::yield_now().await;
2274        }
2275    });
2276
2277    // =========================================================================
2278    // JSON validation and extraction builtins
2279    // =========================================================================
2280
2281    vm.register_builtin("json_validate", |args, _out| {
2282        if args.len() < 2 {
2283            return Err(VmError::Thrown(VmValue::String(Rc::from(
2284                "json_validate requires 2 arguments: data and schema",
2285            ))));
2286        }
2287        let data = &args[0];
2288        let schema = &args[1];
2289        let schema_dict = match schema.as_dict() {
2290            Some(d) => d,
2291            None => {
2292                return Err(VmError::Thrown(VmValue::String(Rc::from(
2293                    "json_validate: schema must be a dict",
2294                ))));
2295            }
2296        };
2297        let mut errors = Vec::new();
2298        validate_value(data, schema_dict, "", &mut errors);
2299        if errors.is_empty() {
2300            Ok(VmValue::Bool(true))
2301        } else {
2302            Err(VmError::Thrown(VmValue::String(Rc::from(
2303                errors.join("; "),
2304            ))))
2305        }
2306    });
2307
2308    vm.register_builtin("json_extract", |args, _out| {
2309        if args.is_empty() {
2310            return Err(VmError::Thrown(VmValue::String(Rc::from(
2311                "json_extract requires at least 1 argument: text",
2312            ))));
2313        }
2314        let text = args[0].display();
2315        let key = args.get(1).map(|a| a.display());
2316
2317        // Extract JSON from text that may contain markdown code fences
2318        let json_str = extract_json_from_text(&text);
2319        let parsed = match serde_json::from_str::<serde_json::Value>(&json_str) {
2320            Ok(jv) => json_to_vm_value(&jv),
2321            Err(e) => {
2322                return Err(VmError::Thrown(VmValue::String(Rc::from(format!(
2323                    "json_extract: failed to parse JSON: {e}"
2324                )))));
2325            }
2326        };
2327
2328        match key {
2329            Some(k) => match &parsed {
2330                VmValue::Dict(map) => match map.get(&k) {
2331                    Some(val) => Ok(val.clone()),
2332                    None => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
2333                        "json_extract: key '{}' not found",
2334                        k
2335                    ))))),
2336                },
2337                _ => Err(VmError::Thrown(VmValue::String(Rc::from(
2338                    "json_extract: parsed value is not a dict, cannot extract key",
2339                )))),
2340            },
2341            None => Ok(parsed),
2342        }
2343    });
2344
2345    // =========================================================================
2346    // HTTP and LLM builtins (registered from separate modules)
2347    // =========================================================================
2348
2349    // =========================================================================
2350    // Internal builtins (used by compiler-generated code)
2351    // =========================================================================
2352
2353    vm.register_builtin("__assert_dict", |args, _out| {
2354        let val = args.first().cloned().unwrap_or(VmValue::Nil);
2355        if matches!(val, VmValue::Dict(_)) {
2356            Ok(VmValue::Nil)
2357        } else {
2358            Err(VmError::TypeError(format!(
2359                "cannot destructure {} with {{...}} pattern — expected dict",
2360                val.type_name()
2361            )))
2362        }
2363    });
2364
2365    vm.register_builtin("__assert_list", |args, _out| {
2366        let val = args.first().cloned().unwrap_or(VmValue::Nil);
2367        if matches!(val, VmValue::List(_)) {
2368            Ok(VmValue::Nil)
2369        } else {
2370            Err(VmError::TypeError(format!(
2371                "cannot destructure {} with [...] pattern — expected list",
2372                val.type_name()
2373            )))
2374        }
2375    });
2376
2377    vm.register_builtin("__assert_shape", |args, _out| {
2378        let val = args.first().cloned().unwrap_or(VmValue::Nil);
2379        let param_name = match args.get(1) {
2380            Some(VmValue::String(s)) => s.to_string(),
2381            _ => "value".to_string(),
2382        };
2383        let spec = match args.get(2) {
2384            Some(VmValue::String(s)) => s.to_string(),
2385            _ => return Ok(VmValue::Nil),
2386        };
2387
2388        // Extract fields from dict or struct instance
2389        let fields: Option<&BTreeMap<String, VmValue>> = match &val {
2390            VmValue::Dict(map) => Some(map.as_ref()),
2391            VmValue::StructInstance { fields, .. } => Some(fields),
2392            _ => None,
2393        };
2394        let fields = match fields {
2395            Some(f) => f,
2396            None => {
2397                return Err(VmError::TypeError(format!(
2398                    "parameter '{}': expected dict or struct, got {}",
2399                    param_name,
2400                    val.type_name()
2401                )));
2402            }
2403        };
2404
2405        assert_shape_fields(fields, &param_name, &spec)
2406    });
2407
2408    vm.register_builtin("__dict_rest", |args, _out| {
2409        // __dict_rest(dict, keys_to_exclude) -> new dict without those keys
2410        let dict = args.first().cloned().unwrap_or(VmValue::Nil);
2411        let keys_list = args.get(1).cloned().unwrap_or(VmValue::Nil);
2412        if let VmValue::Dict(map) = dict {
2413            let exclude: std::collections::HashSet<String> = match keys_list {
2414                VmValue::List(items) => items
2415                    .iter()
2416                    .filter_map(|v| {
2417                        if let VmValue::String(s) = v {
2418                            Some(s.to_string())
2419                        } else {
2420                            None
2421                        }
2422                    })
2423                    .collect(),
2424                _ => std::collections::HashSet::new(),
2425            };
2426            let rest: BTreeMap<String, VmValue> = map
2427                .iter()
2428                .filter(|(k, _)| !exclude.contains(k.as_str()))
2429                .map(|(k, v)| (k.clone(), v.clone()))
2430                .collect();
2431            Ok(VmValue::Dict(Rc::new(rest)))
2432        } else {
2433            Ok(VmValue::Nil)
2434        }
2435    });
2436
2437    register_http_builtins(vm);
2438    register_llm_builtins(vm);
2439    register_mcp_builtins(vm);
2440}
2441
2442// =============================================================================
2443// Shape validation helpers (used by __assert_shape builtin)
2444// =============================================================================
2445
2446/// Parse a shape spec string and validate fields against it.
2447/// Spec format: `name:string,age:int,active:?bool,addr:{city:string,zip:string}`
2448fn assert_shape_fields(
2449    fields: &BTreeMap<String, VmValue>,
2450    param_name: &str,
2451    spec: &str,
2452) -> Result<VmValue, VmError> {
2453    let parsed = parse_shape_spec(spec);
2454    for (field_name, type_spec, optional) in &parsed {
2455        match fields.get(field_name.as_str()) {
2456            None => {
2457                if !optional {
2458                    return Err(VmError::TypeError(format!(
2459                        "parameter '{}': missing field '{}' ({})",
2460                        param_name, field_name, type_spec
2461                    )));
2462                }
2463            }
2464            Some(val) => {
2465                // Check if type_spec is a nested shape (starts with '{')
2466                if type_spec.starts_with('{') && type_spec.ends_with('}') {
2467                    // Nested shape: recursively validate
2468                    let inner_spec = &type_spec[1..type_spec.len() - 1];
2469                    let nested_fields: Option<&BTreeMap<String, VmValue>> = match val {
2470                        VmValue::Dict(map) => Some(map.as_ref()),
2471                        VmValue::StructInstance { fields, .. } => Some(fields),
2472                        _ => None,
2473                    };
2474                    match nested_fields {
2475                        Some(nf) => {
2476                            let nested_param = format!("{}.{}", param_name, field_name);
2477                            assert_shape_fields(nf, &nested_param, inner_spec)?;
2478                        }
2479                        None => {
2480                            return Err(VmError::TypeError(format!(
2481                                "parameter '{}': field '{}' expected dict or struct, got {}",
2482                                param_name,
2483                                field_name,
2484                                val.type_name()
2485                            )));
2486                        }
2487                    }
2488                } else {
2489                    // Simple type check
2490                    let actual_type = val.type_name();
2491                    if actual_type != type_spec.as_str() {
2492                        return Err(VmError::TypeError(format!(
2493                            "parameter '{}': field '{}' expected {}, got {}",
2494                            param_name, field_name, type_spec, actual_type
2495                        )));
2496                    }
2497                }
2498            }
2499        }
2500    }
2501    Ok(VmValue::Nil)
2502}
2503
2504/// Parse a shape spec string into a list of (field_name, type_spec, optional).
2505/// Handles balanced braces for nested shapes.
2506fn parse_shape_spec(spec: &str) -> Vec<(String, String, bool)> {
2507    let mut result = Vec::new();
2508    let chars: Vec<char> = spec.chars().collect();
2509    let len = chars.len();
2510    let mut i = 0;
2511
2512    while i < len {
2513        // Skip leading whitespace
2514        while i < len && chars[i].is_whitespace() {
2515            i += 1;
2516        }
2517        if i >= len {
2518            break;
2519        }
2520
2521        // Read field name (up to ':')
2522        let name_start = i;
2523        while i < len && chars[i] != ':' {
2524            i += 1;
2525        }
2526        if i >= len {
2527            break;
2528        }
2529        let field_name = chars[name_start..i]
2530            .iter()
2531            .collect::<String>()
2532            .trim()
2533            .to_string();
2534        i += 1; // skip ':'
2535
2536        // Skip whitespace after ':'
2537        while i < len && chars[i].is_whitespace() {
2538            i += 1;
2539        }
2540
2541        // Check for optional prefix '?'
2542        let optional = if i < len && chars[i] == '?' {
2543            i += 1;
2544            true
2545        } else {
2546            false
2547        };
2548
2549        // Read type spec, handling balanced braces
2550        let type_start = i;
2551        let mut brace_depth = 0;
2552        while i < len {
2553            match chars[i] {
2554                '{' => {
2555                    brace_depth += 1;
2556                    i += 1;
2557                }
2558                '}' => {
2559                    brace_depth -= 1;
2560                    i += 1;
2561                }
2562                ',' if brace_depth == 0 => break,
2563                _ => {
2564                    i += 1;
2565                }
2566            }
2567        }
2568        let type_spec = chars[type_start..i]
2569            .iter()
2570            .collect::<String>()
2571            .trim()
2572            .to_string();
2573
2574        if !field_name.is_empty() && !type_spec.is_empty() {
2575            result.push((field_name, type_spec, optional));
2576        }
2577
2578        // Skip comma
2579        if i < len && chars[i] == ',' {
2580            i += 1;
2581        }
2582    }
2583
2584    result
2585}
2586
2587// =============================================================================
2588// JSON helpers
2589// =============================================================================
2590
2591pub(crate) fn escape_json_string_vm(s: &str) -> String {
2592    let mut out = String::with_capacity(s.len() + 2);
2593    out.push('"');
2594    for ch in s.chars() {
2595        match ch {
2596            '"' => out.push_str("\\\""),
2597            '\\' => out.push_str("\\\\"),
2598            '\n' => out.push_str("\\n"),
2599            '\r' => out.push_str("\\r"),
2600            '\t' => out.push_str("\\t"),
2601            c if c.is_control() => {
2602                out.push_str(&format!("\\u{:04x}", c as u32));
2603            }
2604            c => out.push(c),
2605        }
2606    }
2607    out.push('"');
2608    out
2609}
2610
2611pub(crate) fn vm_value_to_json(val: &VmValue) -> String {
2612    match val {
2613        VmValue::String(s) => escape_json_string_vm(s),
2614        VmValue::Int(n) => n.to_string(),
2615        VmValue::Float(n) => n.to_string(),
2616        VmValue::Bool(b) => b.to_string(),
2617        VmValue::Nil => "null".to_string(),
2618        VmValue::List(items) => {
2619            let inner: Vec<String> = items.iter().map(vm_value_to_json).collect();
2620            format!("[{}]", inner.join(","))
2621        }
2622        VmValue::Dict(map) => {
2623            let inner: Vec<String> = map
2624                .iter()
2625                .map(|(k, v)| format!("{}:{}", escape_json_string_vm(k), vm_value_to_json(v)))
2626                .collect();
2627            format!("{{{}}}", inner.join(","))
2628        }
2629        VmValue::Set(items) => {
2630            let inner: Vec<String> = items.iter().map(vm_value_to_json).collect();
2631            format!("[{}]", inner.join(","))
2632        }
2633        _ => "null".to_string(),
2634    }
2635}
2636
2637pub(crate) fn json_to_vm_value(jv: &serde_json::Value) -> VmValue {
2638    match jv {
2639        serde_json::Value::Null => VmValue::Nil,
2640        serde_json::Value::Bool(b) => VmValue::Bool(*b),
2641        serde_json::Value::Number(n) => {
2642            if let Some(i) = n.as_i64() {
2643                VmValue::Int(i)
2644            } else {
2645                VmValue::Float(n.as_f64().unwrap_or(0.0))
2646            }
2647        }
2648        serde_json::Value::String(s) => VmValue::String(Rc::from(s.as_str())),
2649        serde_json::Value::Array(arr) => {
2650            VmValue::List(Rc::new(arr.iter().map(json_to_vm_value).collect()))
2651        }
2652        serde_json::Value::Object(map) => {
2653            let mut m = BTreeMap::new();
2654            for (k, v) in map {
2655                m.insert(k.clone(), json_to_vm_value(v));
2656            }
2657            VmValue::Dict(Rc::new(m))
2658        }
2659    }
2660}
2661
2662// =============================================================================
2663// Helper: validate a VmValue against a schema dict
2664// =============================================================================
2665
2666fn validate_value(
2667    value: &VmValue,
2668    schema: &BTreeMap<String, VmValue>,
2669    path: &str,
2670    errors: &mut Vec<String>,
2671) {
2672    // Check "type" constraint
2673    if let Some(VmValue::String(expected_type)) = schema.get("type") {
2674        let actual_type = value.type_name();
2675        let type_str: &str = expected_type;
2676        if type_str != "any" && actual_type != type_str {
2677            let location = if path.is_empty() {
2678                "root".to_string()
2679            } else {
2680                path.to_string()
2681            };
2682            errors.push(format!(
2683                "at {}: expected type '{}', got '{}'",
2684                location, type_str, actual_type
2685            ));
2686            return; // No point checking further if type is wrong
2687        }
2688    }
2689
2690    // Check "required" keys (only for dicts)
2691    if let Some(VmValue::List(required_keys)) = schema.get("required") {
2692        if let VmValue::Dict(map) = value {
2693            for key_val in required_keys.iter() {
2694                let key = key_val.display();
2695                if !map.contains_key(&key) {
2696                    let location = if path.is_empty() {
2697                        "root".to_string()
2698                    } else {
2699                        path.to_string()
2700                    };
2701                    errors.push(format!("at {}: missing required key '{}'", location, key));
2702                }
2703            }
2704        }
2705    }
2706
2707    // Check "properties" (only for dicts)
2708    if let Some(VmValue::Dict(prop_schemas)) = schema.get("properties") {
2709        if let VmValue::Dict(map) = value {
2710            for (key, prop_schema) in prop_schemas.iter() {
2711                if let Some(prop_value) = map.get(key) {
2712                    if let Some(prop_schema_dict) = prop_schema.as_dict() {
2713                        let child_path = if path.is_empty() {
2714                            key.clone()
2715                        } else {
2716                            format!("{}.{}", path, key)
2717                        };
2718                        validate_value(prop_value, prop_schema_dict, &child_path, errors);
2719                    }
2720                }
2721            }
2722        }
2723    }
2724
2725    // Check "items" (only for lists)
2726    if let Some(VmValue::Dict(item_schema)) = schema.get("items") {
2727        if let VmValue::List(items) = value {
2728            for (i, item) in items.iter().enumerate() {
2729                let child_path = if path.is_empty() {
2730                    format!("[{}]", i)
2731                } else {
2732                    format!("{}[{}]", path, i)
2733                };
2734                validate_value(item, item_schema, &child_path, errors);
2735            }
2736        }
2737    }
2738}
2739
2740// =============================================================================
2741// Helper: extract JSON from text with possible markdown fences
2742// =============================================================================
2743
2744fn extract_json_from_text(text: &str) -> String {
2745    let trimmed = text.trim();
2746
2747    // Try to find ```json ... ``` or ``` ... ``` code fences
2748    if let Some(start) = trimmed.find("```") {
2749        let after_backticks = &trimmed[start + 3..];
2750        // Skip optional language tag (e.g., "json")
2751        let content_start = if let Some(nl) = after_backticks.find('\n') {
2752            nl + 1
2753        } else {
2754            0
2755        };
2756        let content = &after_backticks[content_start..];
2757        if let Some(end) = content.find("```") {
2758            return content[..end].trim().to_string();
2759        }
2760    }
2761
2762    // No code fences found; try to find JSON object or array boundaries
2763    // Look for first { or [ and last matching } or ]
2764    if let Some(obj_start) = trimmed.find('{') {
2765        if let Some(obj_end) = trimmed.rfind('}') {
2766            if obj_end > obj_start {
2767                return trimmed[obj_start..=obj_end].to_string();
2768            }
2769        }
2770    }
2771    if let Some(arr_start) = trimmed.find('[') {
2772        if let Some(arr_end) = trimmed.rfind(']') {
2773            if arr_end > arr_start {
2774                return trimmed[arr_start..=arr_end].to_string();
2775            }
2776        }
2777    }
2778
2779    // Fall back to trimmed text as-is
2780    trimmed.to_string()
2781}
2782
2783// =============================================================================
2784// Helper: convert process::Output to VmValue dict
2785// =============================================================================
2786
2787fn vm_output_to_value(output: std::process::Output) -> VmValue {
2788    let mut result = BTreeMap::new();
2789    result.insert(
2790        "stdout".to_string(),
2791        VmValue::String(Rc::from(
2792            String::from_utf8_lossy(&output.stdout).to_string().as_str(),
2793        )),
2794    );
2795    result.insert(
2796        "stderr".to_string(),
2797        VmValue::String(Rc::from(
2798            String::from_utf8_lossy(&output.stderr).to_string().as_str(),
2799        )),
2800    );
2801    result.insert(
2802        "status".to_string(),
2803        VmValue::Int(output.status.code().unwrap_or(-1) as i64),
2804    );
2805    result.insert(
2806        "success".to_string(),
2807        VmValue::Bool(output.status.success()),
2808    );
2809    VmValue::Dict(Rc::new(result))
2810}
2811
2812// =============================================================================
2813// Helper: civil date from timestamp (Howard Hinnant's algorithm)
2814// =============================================================================
2815
2816fn vm_civil_from_timestamp(total_secs: u64) -> (i64, i64, i64, i64, i64, i64, i64) {
2817    let days = total_secs / 86400;
2818    let time_of_day = total_secs % 86400;
2819    let hour = (time_of_day / 3600) as i64;
2820    let minute = ((time_of_day % 3600) / 60) as i64;
2821    let second = (time_of_day % 60) as i64;
2822
2823    let z = days as i64 + 719468;
2824    let era = if z >= 0 { z } else { z - 146096 } / 146097;
2825    let doe = (z - era * 146097) as u64;
2826    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
2827    let y = yoe as i64 + era * 400;
2828    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2829    let mp = (5 * doy + 2) / 153;
2830    let d = (doy - (153 * mp + 2) / 5 + 1) as i64;
2831    let m = if mp < 10 { mp + 3 } else { mp - 9 } as i64;
2832    let y = if m <= 2 { y + 1 } else { y };
2833    let dow = ((days + 4) % 7) as i64;
2834
2835    (y, m, d, hour, minute, second, dow)
2836}
2837
2838// =============================================================================
2839// Logging helpers for VM
2840// =============================================================================
2841
2842pub(crate) static VM_MIN_LOG_LEVEL: AtomicU8 = AtomicU8::new(0);
2843
2844#[derive(Clone)]
2845pub(crate) struct VmTraceContext {
2846    pub(crate) trace_id: String,
2847    pub(crate) span_id: String,
2848}
2849
2850thread_local! {
2851    pub(crate) static VM_TRACE_STACK: std::cell::RefCell<Vec<VmTraceContext>> = const { std::cell::RefCell::new(Vec::new()) };
2852}
2853
2854fn vm_level_to_u8(level: &str) -> Option<u8> {
2855    match level {
2856        "debug" => Some(0),
2857        "info" => Some(1),
2858        "warn" => Some(2),
2859        "error" => Some(3),
2860        _ => None,
2861    }
2862}
2863
2864fn vm_format_timestamp_utc() -> String {
2865    let now = std::time::SystemTime::now()
2866        .duration_since(std::time::UNIX_EPOCH)
2867        .unwrap_or_default();
2868    let total_secs = now.as_secs();
2869    let millis = now.subsec_millis();
2870
2871    let days = total_secs / 86400;
2872    let time_of_day = total_secs % 86400;
2873    let hour = time_of_day / 3600;
2874    let minute = (time_of_day % 3600) / 60;
2875    let second = time_of_day % 60;
2876
2877    let z = days as i64 + 719468;
2878    let era = if z >= 0 { z } else { z - 146096 } / 146097;
2879    let doe = (z - era * 146097) as u64;
2880    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
2881    let y = yoe as i64 + era * 400;
2882    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2883    let mp = (5 * doy + 2) / 153;
2884    let d = doy - (153 * mp + 2) / 5 + 1;
2885    let m = if mp < 10 { mp + 3 } else { mp - 9 };
2886    let y = if m <= 2 { y + 1 } else { y };
2887
2888    format!("{y:04}-{m:02}-{d:02}T{hour:02}:{minute:02}:{second:02}.{millis:03}Z")
2889}
2890
2891pub(crate) fn vm_escape_json_str(s: &str) -> String {
2892    let mut out = String::with_capacity(s.len());
2893    for ch in s.chars() {
2894        match ch {
2895            '"' => out.push_str("\\\""),
2896            '\\' => out.push_str("\\\\"),
2897            '\n' => out.push_str("\\n"),
2898            '\r' => out.push_str("\\r"),
2899            '\t' => out.push_str("\\t"),
2900            c if c.is_control() => {
2901                out.push_str(&format!("\\u{:04x}", c as u32));
2902            }
2903            c => out.push(c),
2904        }
2905    }
2906    out
2907}
2908
2909fn vm_escape_json_str_quoted(s: &str) -> String {
2910    let mut out = String::with_capacity(s.len() + 2);
2911    out.push('"');
2912    out.push_str(&vm_escape_json_str(s));
2913    out.push('"');
2914    out
2915}
2916
2917fn vm_value_to_json_fragment(val: &VmValue) -> String {
2918    match val {
2919        VmValue::String(s) => vm_escape_json_str_quoted(s),
2920        VmValue::Int(n) => n.to_string(),
2921        VmValue::Float(n) => {
2922            if n.is_finite() {
2923                n.to_string()
2924            } else {
2925                "null".to_string()
2926            }
2927        }
2928        VmValue::Bool(b) => b.to_string(),
2929        VmValue::Nil => "null".to_string(),
2930        _ => vm_escape_json_str_quoted(&val.display()),
2931    }
2932}
2933
2934fn vm_build_log_line(level: &str, msg: &str, fields: Option<&BTreeMap<String, VmValue>>) -> String {
2935    let ts = vm_format_timestamp_utc();
2936    let mut parts: Vec<String> = Vec::new();
2937    parts.push(format!("\"ts\":{}", vm_escape_json_str_quoted(&ts)));
2938    parts.push(format!("\"level\":{}", vm_escape_json_str_quoted(level)));
2939    parts.push(format!("\"msg\":{}", vm_escape_json_str_quoted(msg)));
2940
2941    VM_TRACE_STACK.with(|stack| {
2942        if let Some(trace) = stack.borrow().last() {
2943            parts.push(format!(
2944                "\"trace_id\":{}",
2945                vm_escape_json_str_quoted(&trace.trace_id)
2946            ));
2947            parts.push(format!(
2948                "\"span_id\":{}",
2949                vm_escape_json_str_quoted(&trace.span_id)
2950            ));
2951        }
2952    });
2953
2954    if let Some(dict) = fields {
2955        for (k, v) in dict {
2956            parts.push(format!(
2957                "{}:{}",
2958                vm_escape_json_str_quoted(k),
2959                vm_value_to_json_fragment(v)
2960            ));
2961        }
2962    }
2963
2964    format!("{{{}}}\n", parts.join(","))
2965}
2966
2967fn vm_write_log(level: &str, level_num: u8, args: &[VmValue], out: &mut String) {
2968    if level_num < VM_MIN_LOG_LEVEL.load(Ordering::Relaxed) {
2969        return;
2970    }
2971    let msg = args.first().map(|a| a.display()).unwrap_or_default();
2972    let fields = args.get(1).and_then(|v| {
2973        if let VmValue::Dict(d) = v {
2974            Some(&**d)
2975        } else {
2976            None
2977        }
2978    });
2979    let line = vm_build_log_line(level, &msg, fields);
2980    out.push_str(&line);
2981}
2982
2983// =============================================================================
2984// Tool registry helpers for VM
2985// =============================================================================
2986
2987fn vm_validate_registry(name: &str, dict: &BTreeMap<String, VmValue>) -> Result<(), VmError> {
2988    match dict.get("_type") {
2989        Some(VmValue::String(t)) if &**t == "tool_registry" => Ok(()),
2990        _ => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
2991            "{name}: argument must be a tool registry (created with tool_registry())"
2992        ))))),
2993    }
2994}
2995
2996fn vm_get_tools(dict: &BTreeMap<String, VmValue>) -> &[VmValue] {
2997    match dict.get("tools") {
2998        Some(VmValue::List(list)) => list,
2999        _ => &[],
3000    }
3001}
3002
3003fn vm_format_parameters(params: Option<&VmValue>) -> String {
3004    match params {
3005        Some(VmValue::Dict(map)) if !map.is_empty() => {
3006            let mut pairs: Vec<(String, String)> =
3007                map.iter().map(|(k, v)| (k.clone(), v.display())).collect();
3008            pairs.sort_by(|a, b| a.0.cmp(&b.0));
3009            pairs
3010                .iter()
3011                .map(|(k, v)| format!("{k}: {v}"))
3012                .collect::<Vec<_>>()
3013                .join(", ")
3014        }
3015        _ => String::new(),
3016    }
3017}
3018
3019fn vm_build_empty_schema() -> BTreeMap<String, VmValue> {
3020    let mut schema = BTreeMap::new();
3021    schema.insert(
3022        "schema_version".to_string(),
3023        VmValue::String(Rc::from("harn-tools/1.0")),
3024    );
3025    schema.insert("tools".to_string(), VmValue::List(Rc::new(Vec::new())));
3026    schema
3027}
3028
3029fn vm_build_input_schema(
3030    params: Option<&VmValue>,
3031    components: Option<&BTreeMap<String, VmValue>>,
3032) -> VmValue {
3033    let mut schema = BTreeMap::new();
3034    schema.insert("type".to_string(), VmValue::String(Rc::from("object")));
3035
3036    let params_map = match params {
3037        Some(VmValue::Dict(map)) if !map.is_empty() => map,
3038        _ => {
3039            schema.insert(
3040                "properties".to_string(),
3041                VmValue::Dict(Rc::new(BTreeMap::new())),
3042            );
3043            return VmValue::Dict(Rc::new(schema));
3044        }
3045    };
3046
3047    let mut properties = BTreeMap::new();
3048    let mut required = Vec::new();
3049
3050    for (key, val) in params_map.iter() {
3051        let prop = vm_resolve_param_type(val, components);
3052        properties.insert(key.clone(), prop);
3053        required.push(VmValue::String(Rc::from(key.as_str())));
3054    }
3055
3056    schema.insert("properties".to_string(), VmValue::Dict(Rc::new(properties)));
3057    if !required.is_empty() {
3058        required.sort_by_key(|a| a.display());
3059        schema.insert("required".to_string(), VmValue::List(Rc::new(required)));
3060    }
3061
3062    VmValue::Dict(Rc::new(schema))
3063}
3064
3065fn vm_resolve_param_type(val: &VmValue, components: Option<&BTreeMap<String, VmValue>>) -> VmValue {
3066    match val {
3067        VmValue::String(type_name) => {
3068            let json_type = vm_harn_type_to_json_schema(type_name);
3069            let mut prop = BTreeMap::new();
3070            prop.insert("type".to_string(), VmValue::String(Rc::from(json_type)));
3071            VmValue::Dict(Rc::new(prop))
3072        }
3073        VmValue::Dict(map) => {
3074            if let Some(VmValue::String(ref_name)) = map.get("$ref") {
3075                if let Some(comps) = components {
3076                    if let Some(resolved) = comps.get(&**ref_name) {
3077                        return resolved.clone();
3078                    }
3079                }
3080                let mut prop = BTreeMap::new();
3081                prop.insert(
3082                    "$ref".to_string(),
3083                    VmValue::String(Rc::from(
3084                        format!("#/components/schemas/{ref_name}").as_str(),
3085                    )),
3086                );
3087                VmValue::Dict(Rc::new(prop))
3088            } else {
3089                VmValue::Dict(Rc::new((**map).clone()))
3090            }
3091        }
3092        _ => {
3093            let mut prop = BTreeMap::new();
3094            prop.insert("type".to_string(), VmValue::String(Rc::from("string")));
3095            VmValue::Dict(Rc::new(prop))
3096        }
3097    }
3098}
3099
3100fn vm_harn_type_to_json_schema(harn_type: &str) -> &str {
3101    match harn_type {
3102        "int" => "integer",
3103        "float" => "number",
3104        "bool" | "boolean" => "boolean",
3105        "list" | "array" => "array",
3106        "dict" | "object" => "object",
3107        _ => "string",
3108    }
3109}