Skip to main content

harn_vm/
stdlib.rs

1use std::collections::BTreeMap;
2use std::io::BufRead;
3use std::rc::Rc;
4use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU8, Ordering};
5use std::sync::Arc;
6
7use crate::value::{values_equal, VmAtomicHandle, VmChannelHandle, VmError, VmValue};
8use crate::vm::Vm;
9
10use crate::http::register_http_builtins;
11use crate::llm::register_llm_builtins;
12use crate::mcp::register_mcp_builtins;
13
14/// Register standard builtins on a VM.
15pub fn register_vm_stdlib(vm: &mut Vm) {
16    vm.register_builtin("log", |args, out| {
17        let msg = args.first().map(|a| a.display()).unwrap_or_default();
18        out.push_str(&format!("[harn] {msg}\n"));
19        Ok(VmValue::Nil)
20    });
21    vm.register_builtin("print", |args, out| {
22        let msg = args.first().map(|a| a.display()).unwrap_or_default();
23        out.push_str(&msg);
24        Ok(VmValue::Nil)
25    });
26    vm.register_builtin("println", |args, out| {
27        let msg = args.first().map(|a| a.display()).unwrap_or_default();
28        out.push_str(&format!("{msg}\n"));
29        Ok(VmValue::Nil)
30    });
31    vm.register_builtin("type_of", |args, _out| {
32        let val = args.first().unwrap_or(&VmValue::Nil);
33        Ok(VmValue::String(Rc::from(val.type_name())))
34    });
35    vm.register_builtin("to_string", |args, _out| {
36        let val = args.first().unwrap_or(&VmValue::Nil);
37        Ok(VmValue::String(Rc::from(val.display())))
38    });
39    vm.register_builtin("to_int", |args, _out| {
40        let val = args.first().unwrap_or(&VmValue::Nil);
41        match val {
42            VmValue::Int(n) => Ok(VmValue::Int(*n)),
43            VmValue::Float(n) => Ok(VmValue::Int(*n as i64)),
44            VmValue::String(s) => Ok(s.parse::<i64>().map(VmValue::Int).unwrap_or(VmValue::Nil)),
45            _ => Ok(VmValue::Nil),
46        }
47    });
48    vm.register_builtin("to_float", |args, _out| {
49        let val = args.first().unwrap_or(&VmValue::Nil);
50        match val {
51            VmValue::Float(n) => Ok(VmValue::Float(*n)),
52            VmValue::Int(n) => Ok(VmValue::Float(*n as f64)),
53            VmValue::String(s) => Ok(s.parse::<f64>().map(VmValue::Float).unwrap_or(VmValue::Nil)),
54            _ => Ok(VmValue::Nil),
55        }
56    });
57
58    vm.register_builtin("json_stringify", |args, _out| {
59        let val = args.first().unwrap_or(&VmValue::Nil);
60        Ok(VmValue::String(Rc::from(vm_value_to_json(val))))
61    });
62
63    vm.register_builtin("json_parse", |args, _out| {
64        let text = args.first().map(|a| a.display()).unwrap_or_default();
65        match serde_json::from_str::<serde_json::Value>(&text) {
66            Ok(jv) => Ok(json_to_vm_value(&jv)),
67            Err(e) => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
68                "JSON parse error: {e}"
69            ))))),
70        }
71    });
72
73    vm.register_builtin("env", |args, _out| {
74        let name = args.first().map(|a| a.display()).unwrap_or_default();
75        match std::env::var(&name) {
76            Ok(val) => Ok(VmValue::String(Rc::from(val))),
77            Err(_) => Ok(VmValue::Nil),
78        }
79    });
80
81    vm.register_builtin("timestamp", |_args, _out| {
82        use std::time::{SystemTime, UNIX_EPOCH};
83        let secs = SystemTime::now()
84            .duration_since(UNIX_EPOCH)
85            .map(|d| d.as_secs_f64())
86            .unwrap_or(0.0);
87        Ok(VmValue::Float(secs))
88    });
89
90    vm.register_builtin("read_file", |args, _out| {
91        let path = args.first().map(|a| a.display()).unwrap_or_default();
92        match std::fs::read_to_string(&path) {
93            Ok(content) => Ok(VmValue::String(Rc::from(content))),
94            Err(e) => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
95                "Failed to read file {path}: {e}"
96            ))))),
97        }
98    });
99
100    vm.register_builtin("write_file", |args, _out| {
101        if args.len() >= 2 {
102            let path = args[0].display();
103            let content = args[1].display();
104            std::fs::write(&path, &content).map_err(|e| {
105                VmError::Thrown(VmValue::String(Rc::from(format!(
106                    "Failed to write file {path}: {e}"
107                ))))
108            })?;
109        }
110        Ok(VmValue::Nil)
111    });
112
113    vm.register_builtin("exit", |args, _out| {
114        let code = args.first().and_then(|a| a.as_int()).unwrap_or(0);
115        std::process::exit(code as i32);
116    });
117
118    vm.register_builtin("regex_match", |args, _out| {
119        if args.len() >= 2 {
120            let pattern = args[0].display();
121            let text = args[1].display();
122            let re = regex::Regex::new(&pattern).map_err(|e| {
123                VmError::Thrown(VmValue::String(Rc::from(format!("Invalid regex: {e}"))))
124            })?;
125            let matches: Vec<VmValue> = re
126                .find_iter(&text)
127                .map(|m| VmValue::String(Rc::from(m.as_str())))
128                .collect();
129            if matches.is_empty() {
130                return Ok(VmValue::Nil);
131            }
132            return Ok(VmValue::List(Rc::new(matches)));
133        }
134        Ok(VmValue::Nil)
135    });
136
137    vm.register_builtin("regex_replace", |args, _out| {
138        if args.len() >= 3 {
139            let pattern = args[0].display();
140            let replacement = args[1].display();
141            let text = args[2].display();
142            let re = regex::Regex::new(&pattern).map_err(|e| {
143                VmError::Thrown(VmValue::String(Rc::from(format!("Invalid regex: {e}"))))
144            })?;
145            return Ok(VmValue::String(Rc::from(
146                re.replace_all(&text, replacement.as_str()).into_owned(),
147            )));
148        }
149        Ok(VmValue::Nil)
150    });
151
152    vm.register_builtin("prompt_user", |args, out| {
153        let msg = args.first().map(|a| a.display()).unwrap_or_default();
154        out.push_str(&msg);
155        let mut input = String::new();
156        if std::io::stdin().lock().read_line(&mut input).is_ok() {
157            Ok(VmValue::String(Rc::from(input.trim_end())))
158        } else {
159            Ok(VmValue::Nil)
160        }
161    });
162
163    // --- Math builtins ---
164
165    vm.register_builtin("abs", |args, _out| {
166        match args.first().unwrap_or(&VmValue::Nil) {
167            VmValue::Int(n) => Ok(VmValue::Int(n.wrapping_abs())),
168            VmValue::Float(n) => Ok(VmValue::Float(n.abs())),
169            _ => Ok(VmValue::Nil),
170        }
171    });
172
173    vm.register_builtin("min", |args, _out| {
174        if args.len() >= 2 {
175            match (&args[0], &args[1]) {
176                (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(*x.min(y))),
177                (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x.min(*y))),
178                (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float((*x as f64).min(*y))),
179                (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x.min(*y as f64))),
180                _ => Ok(VmValue::Nil),
181            }
182        } else {
183            Ok(VmValue::Nil)
184        }
185    });
186
187    vm.register_builtin("max", |args, _out| {
188        if args.len() >= 2 {
189            match (&args[0], &args[1]) {
190                (VmValue::Int(x), VmValue::Int(y)) => Ok(VmValue::Int(*x.max(y))),
191                (VmValue::Float(x), VmValue::Float(y)) => Ok(VmValue::Float(x.max(*y))),
192                (VmValue::Int(x), VmValue::Float(y)) => Ok(VmValue::Float((*x as f64).max(*y))),
193                (VmValue::Float(x), VmValue::Int(y)) => Ok(VmValue::Float(x.max(*y as f64))),
194                _ => Ok(VmValue::Nil),
195            }
196        } else {
197            Ok(VmValue::Nil)
198        }
199    });
200
201    vm.register_builtin("floor", |args, _out| {
202        match args.first().unwrap_or(&VmValue::Nil) {
203            VmValue::Float(n) => Ok(VmValue::Int(n.floor() as i64)),
204            VmValue::Int(n) => Ok(VmValue::Int(*n)),
205            _ => Ok(VmValue::Nil),
206        }
207    });
208
209    vm.register_builtin("ceil", |args, _out| {
210        match args.first().unwrap_or(&VmValue::Nil) {
211            VmValue::Float(n) => Ok(VmValue::Int(n.ceil() as i64)),
212            VmValue::Int(n) => Ok(VmValue::Int(*n)),
213            _ => Ok(VmValue::Nil),
214        }
215    });
216
217    vm.register_builtin("round", |args, _out| {
218        match args.first().unwrap_or(&VmValue::Nil) {
219            VmValue::Float(n) => Ok(VmValue::Int(n.round() as i64)),
220            VmValue::Int(n) => Ok(VmValue::Int(*n)),
221            _ => Ok(VmValue::Nil),
222        }
223    });
224
225    vm.register_builtin("sqrt", |args, _out| {
226        match args.first().unwrap_or(&VmValue::Nil) {
227            VmValue::Float(n) => Ok(VmValue::Float(n.sqrt())),
228            VmValue::Int(n) => Ok(VmValue::Float((*n as f64).sqrt())),
229            _ => Ok(VmValue::Nil),
230        }
231    });
232
233    vm.register_builtin("pow", |args, _out| {
234        if args.len() >= 2 {
235            match (&args[0], &args[1]) {
236                (VmValue::Int(base), VmValue::Int(exp)) => {
237                    if *exp >= 0 && *exp <= u32::MAX as i64 {
238                        Ok(VmValue::Int(base.wrapping_pow(*exp as u32)))
239                    } else {
240                        Ok(VmValue::Float((*base as f64).powf(*exp as f64)))
241                    }
242                }
243                (VmValue::Float(base), VmValue::Int(exp)) => {
244                    if *exp >= i32::MIN as i64 && *exp <= i32::MAX as i64 {
245                        Ok(VmValue::Float(base.powi(*exp as i32)))
246                    } else {
247                        Ok(VmValue::Float(base.powf(*exp as f64)))
248                    }
249                }
250                (VmValue::Int(base), VmValue::Float(exp)) => {
251                    Ok(VmValue::Float((*base as f64).powf(*exp)))
252                }
253                (VmValue::Float(base), VmValue::Float(exp)) => Ok(VmValue::Float(base.powf(*exp))),
254                _ => Ok(VmValue::Nil),
255            }
256        } else {
257            Ok(VmValue::Nil)
258        }
259    });
260
261    vm.register_builtin("random", |_args, _out| {
262        use rand::Rng;
263        let val: f64 = rand::thread_rng().gen();
264        Ok(VmValue::Float(val))
265    });
266
267    vm.register_builtin("random_int", |args, _out| {
268        use rand::Rng;
269        if args.len() >= 2 {
270            let min = args[0].as_int().unwrap_or(0);
271            let max = args[1].as_int().unwrap_or(0);
272            if min <= max {
273                let val = rand::thread_rng().gen_range(min..=max);
274                return Ok(VmValue::Int(val));
275            }
276        }
277        Ok(VmValue::Nil)
278    });
279
280    // --- Assert builtins ---
281
282    vm.register_builtin("assert", |args, _out| {
283        let condition = args.first().unwrap_or(&VmValue::Nil);
284        if !condition.is_truthy() {
285            let msg = args
286                .get(1)
287                .map(|a| a.display())
288                .unwrap_or_else(|| "Assertion failed".to_string());
289            return Err(VmError::Thrown(VmValue::String(Rc::from(msg))));
290        }
291        Ok(VmValue::Nil)
292    });
293
294    vm.register_builtin("assert_eq", |args, _out| {
295        if args.len() >= 2 {
296            if !values_equal(&args[0], &args[1]) {
297                let msg = args.get(2).map(|a| a.display()).unwrap_or_else(|| {
298                    format!(
299                        "Assertion failed: expected {}, got {}",
300                        args[1].display(),
301                        args[0].display()
302                    )
303                });
304                return Err(VmError::Thrown(VmValue::String(Rc::from(msg))));
305            }
306            Ok(VmValue::Nil)
307        } else {
308            Err(VmError::Thrown(VmValue::String(Rc::from(
309                "assert_eq requires at least 2 arguments",
310            ))))
311        }
312    });
313
314    vm.register_builtin("assert_ne", |args, _out| {
315        if args.len() >= 2 {
316            if values_equal(&args[0], &args[1]) {
317                let msg = args.get(2).map(|a| a.display()).unwrap_or_else(|| {
318                    format!(
319                        "Assertion failed: values should not be equal: {}",
320                        args[0].display()
321                    )
322                });
323                return Err(VmError::Thrown(VmValue::String(Rc::from(msg))));
324            }
325            Ok(VmValue::Nil)
326        } else {
327            Err(VmError::Thrown(VmValue::String(Rc::from(
328                "assert_ne requires at least 2 arguments",
329            ))))
330        }
331    });
332
333    vm.register_builtin("__range__", |args, _out| {
334        let start = args.first().and_then(|a| a.as_int()).unwrap_or(0);
335        let end = args.get(1).and_then(|a| a.as_int()).unwrap_or(0);
336        let inclusive = args.get(2).map(|a| a.is_truthy()).unwrap_or(false);
337        let items: Vec<VmValue> = if inclusive {
338            (start..=end).map(VmValue::Int).collect()
339        } else {
340            (start..end).map(VmValue::Int).collect()
341        };
342        Ok(VmValue::List(Rc::new(items)))
343    });
344
345    // =========================================================================
346    // File system builtins
347    // =========================================================================
348
349    vm.register_builtin("file_exists", |args, _out| {
350        let path = args.first().map(|a| a.display()).unwrap_or_default();
351        Ok(VmValue::Bool(std::path::Path::new(&path).exists()))
352    });
353
354    vm.register_builtin("delete_file", |args, _out| {
355        let path = args.first().map(|a| a.display()).unwrap_or_default();
356        let p = std::path::Path::new(&path);
357        if p.is_dir() {
358            std::fs::remove_dir_all(&path).map_err(|e| {
359                VmError::Thrown(VmValue::String(Rc::from(format!(
360                    "Failed to delete directory {path}: {e}"
361                ))))
362            })?;
363        } else {
364            std::fs::remove_file(&path).map_err(|e| {
365                VmError::Thrown(VmValue::String(Rc::from(format!(
366                    "Failed to delete file {path}: {e}"
367                ))))
368            })?;
369        }
370        Ok(VmValue::Nil)
371    });
372
373    vm.register_builtin("append_file", |args, _out| {
374        use std::io::Write;
375        if args.len() >= 2 {
376            let path = args[0].display();
377            let content = args[1].display();
378            let mut file = std::fs::OpenOptions::new()
379                .append(true)
380                .create(true)
381                .open(&path)
382                .map_err(|e| {
383                    VmError::Thrown(VmValue::String(Rc::from(format!(
384                        "Failed to open file {path}: {e}"
385                    ))))
386                })?;
387            file.write_all(content.as_bytes()).map_err(|e| {
388                VmError::Thrown(VmValue::String(Rc::from(format!(
389                    "Failed to append to file {path}: {e}"
390                ))))
391            })?;
392        }
393        Ok(VmValue::Nil)
394    });
395
396    vm.register_builtin("list_dir", |args, _out| {
397        let path = args
398            .first()
399            .map(|a| a.display())
400            .unwrap_or_else(|| ".".to_string());
401        let entries = std::fs::read_dir(&path).map_err(|e| {
402            VmError::Thrown(VmValue::String(Rc::from(format!(
403                "Failed to list directory {path}: {e}"
404            ))))
405        })?;
406        let mut result = Vec::new();
407        for entry in entries {
408            let entry =
409                entry.map_err(|e| VmError::Thrown(VmValue::String(Rc::from(e.to_string()))))?;
410            let name = entry.file_name().to_string_lossy().to_string();
411            result.push(VmValue::String(Rc::from(name.as_str())));
412        }
413        result.sort_by_key(|a| a.display());
414        Ok(VmValue::List(Rc::new(result)))
415    });
416
417    vm.register_builtin("mkdir", |args, _out| {
418        let path = args.first().map(|a| a.display()).unwrap_or_default();
419        std::fs::create_dir_all(&path).map_err(|e| {
420            VmError::Thrown(VmValue::String(Rc::from(format!(
421                "Failed to create directory {path}: {e}"
422            ))))
423        })?;
424        Ok(VmValue::Nil)
425    });
426
427    vm.register_builtin("path_join", |args, _out| {
428        let mut path = std::path::PathBuf::new();
429        for arg in args {
430            path.push(arg.display());
431        }
432        Ok(VmValue::String(Rc::from(
433            path.to_string_lossy().to_string().as_str(),
434        )))
435    });
436
437    vm.register_builtin("copy_file", |args, _out| {
438        if args.len() >= 2 {
439            let src = args[0].display();
440            let dst = args[1].display();
441            std::fs::copy(&src, &dst).map_err(|e| {
442                VmError::Thrown(VmValue::String(Rc::from(format!(
443                    "Failed to copy {src} to {dst}: {e}"
444                ))))
445            })?;
446        }
447        Ok(VmValue::Nil)
448    });
449
450    vm.register_builtin("temp_dir", |_args, _out| {
451        Ok(VmValue::String(Rc::from(
452            std::env::temp_dir().to_string_lossy().to_string().as_str(),
453        )))
454    });
455
456    vm.register_builtin("stat", |args, _out| {
457        let path = args.first().map(|a| a.display()).unwrap_or_default();
458        let metadata = std::fs::metadata(&path).map_err(|e| {
459            VmError::Thrown(VmValue::String(Rc::from(format!(
460                "Failed to stat {path}: {e}"
461            ))))
462        })?;
463        let mut info = BTreeMap::new();
464        info.insert("size".to_string(), VmValue::Int(metadata.len() as i64));
465        info.insert("is_file".to_string(), VmValue::Bool(metadata.is_file()));
466        info.insert("is_dir".to_string(), VmValue::Bool(metadata.is_dir()));
467        info.insert(
468            "readonly".to_string(),
469            VmValue::Bool(metadata.permissions().readonly()),
470        );
471        if let Ok(modified) = metadata.modified() {
472            if let Ok(dur) = modified.duration_since(std::time::UNIX_EPOCH) {
473                info.insert("modified".to_string(), VmValue::Float(dur.as_secs_f64()));
474            }
475        }
476        Ok(VmValue::Dict(Rc::new(info)))
477    });
478
479    // =========================================================================
480    // Process execution builtins
481    // =========================================================================
482
483    vm.register_builtin("exec", |args, _out| {
484        if args.is_empty() {
485            return Err(VmError::Thrown(VmValue::String(Rc::from(
486                "exec: command is required",
487            ))));
488        }
489        let cmd = args[0].display();
490        let cmd_args: Vec<String> = args[1..].iter().map(|a| a.display()).collect();
491        let output = std::process::Command::new(&cmd)
492            .args(&cmd_args)
493            .output()
494            .map_err(|e| VmError::Thrown(VmValue::String(Rc::from(format!("exec failed: {e}")))))?;
495        Ok(vm_output_to_value(output))
496    });
497
498    vm.register_builtin("shell", |args, _out| {
499        let cmd = args.first().map(|a| a.display()).unwrap_or_default();
500        if cmd.is_empty() {
501            return Err(VmError::Thrown(VmValue::String(Rc::from(
502                "shell: command string is required",
503            ))));
504        }
505        let shell = if cfg!(target_os = "windows") {
506            "cmd"
507        } else {
508            "sh"
509        };
510        let flag = if cfg!(target_os = "windows") {
511            "/C"
512        } else {
513            "-c"
514        };
515        let output = std::process::Command::new(shell)
516            .arg(flag)
517            .arg(&cmd)
518            .output()
519            .map_err(|e| {
520                VmError::Thrown(VmValue::String(Rc::from(format!("shell failed: {e}"))))
521            })?;
522        Ok(vm_output_to_value(output))
523    });
524
525    // =========================================================================
526    // Date/time builtins
527    // =========================================================================
528
529    vm.register_builtin("date_now", |_args, _out| {
530        use std::time::{SystemTime, UNIX_EPOCH};
531        let now = SystemTime::now()
532            .duration_since(UNIX_EPOCH)
533            .unwrap_or_default();
534        let total_secs = now.as_secs();
535        let (y, m, d, hour, minute, second, dow) = vm_civil_from_timestamp(total_secs);
536        let mut result = BTreeMap::new();
537        result.insert("year".to_string(), VmValue::Int(y));
538        result.insert("month".to_string(), VmValue::Int(m));
539        result.insert("day".to_string(), VmValue::Int(d));
540        result.insert("hour".to_string(), VmValue::Int(hour));
541        result.insert("minute".to_string(), VmValue::Int(minute));
542        result.insert("second".to_string(), VmValue::Int(second));
543        result.insert("weekday".to_string(), VmValue::Int(dow));
544        result.insert("timestamp".to_string(), VmValue::Float(now.as_secs_f64()));
545        Ok(VmValue::Dict(Rc::new(result)))
546    });
547
548    vm.register_builtin("date_format", |args, _out| {
549        let ts = match args.first() {
550            Some(VmValue::Float(f)) => *f,
551            Some(VmValue::Int(n)) => *n as f64,
552            Some(VmValue::Dict(map)) => map
553                .get("timestamp")
554                .and_then(|v| match v {
555                    VmValue::Float(f) => Some(*f),
556                    VmValue::Int(n) => Some(*n as f64),
557                    _ => None,
558                })
559                .unwrap_or(0.0),
560            _ => 0.0,
561        };
562        let fmt = args
563            .get(1)
564            .map(|a| a.display())
565            .unwrap_or_else(|| "%Y-%m-%d %H:%M:%S".to_string());
566
567        let (y, m, d, hour, minute, second, _dow) = vm_civil_from_timestamp(ts as u64);
568
569        let result = fmt
570            .replace("%Y", &format!("{y:04}"))
571            .replace("%m", &format!("{m:02}"))
572            .replace("%d", &format!("{d:02}"))
573            .replace("%H", &format!("{hour:02}"))
574            .replace("%M", &format!("{minute:02}"))
575            .replace("%S", &format!("{second:02}"));
576
577        Ok(VmValue::String(Rc::from(result.as_str())))
578    });
579
580    vm.register_builtin("date_parse", |args, _out| {
581        let s = args.first().map(|a| a.display()).unwrap_or_default();
582        let parts: Vec<&str> = s.split(|c: char| !c.is_ascii_digit()).collect();
583        let parts: Vec<i64> = parts.iter().filter_map(|p| p.parse().ok()).collect();
584        if parts.len() < 3 {
585            return Err(VmError::Thrown(VmValue::String(Rc::from(format!(
586                "Cannot parse date: {s}"
587            )))));
588        }
589        let (y, m, d) = (parts[0], parts[1], parts[2]);
590        let hour = parts.get(3).copied().unwrap_or(0);
591        let minute = parts.get(4).copied().unwrap_or(0);
592        let second = parts.get(5).copied().unwrap_or(0);
593
594        let (y_adj, m_adj) = if m <= 2 {
595            (y - 1, (m + 9) as u64)
596        } else {
597            (y, (m - 3) as u64)
598        };
599        let era = if y_adj >= 0 { y_adj } else { y_adj - 399 } / 400;
600        let yoe = (y_adj - era * 400) as u64;
601        let doy = (153 * m_adj + 2) / 5 + d as u64 - 1;
602        let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
603        let days = era * 146097 + doe as i64 - 719468;
604        let ts = days * 86400 + hour * 3600 + minute * 60 + second;
605        Ok(VmValue::Float(ts as f64))
606    });
607
608    // =========================================================================
609    // String formatting
610    // =========================================================================
611
612    vm.register_builtin("format", |args, _out| {
613        let template = args.first().map(|a| a.display()).unwrap_or_default();
614        let mut result = String::with_capacity(template.len());
615        let mut arg_iter = args.iter().skip(1);
616        let mut rest = template.as_str();
617        while let Some(pos) = rest.find("{}") {
618            result.push_str(&rest[..pos]);
619            if let Some(arg) = arg_iter.next() {
620                result.push_str(&arg.display());
621            } else {
622                result.push_str("{}");
623            }
624            rest = &rest[pos + 2..];
625        }
626        result.push_str(rest);
627        Ok(VmValue::String(Rc::from(result.as_str())))
628    });
629
630    // =========================================================================
631    // Standalone string function builtins
632    // =========================================================================
633
634    vm.register_builtin("trim", |args, _out| {
635        let s = args.first().map(|a| a.display()).unwrap_or_default();
636        Ok(VmValue::String(Rc::from(s.trim())))
637    });
638
639    vm.register_builtin("lowercase", |args, _out| {
640        let s = args.first().map(|a| a.display()).unwrap_or_default();
641        Ok(VmValue::String(Rc::from(s.to_lowercase().as_str())))
642    });
643
644    vm.register_builtin("uppercase", |args, _out| {
645        let s = args.first().map(|a| a.display()).unwrap_or_default();
646        Ok(VmValue::String(Rc::from(s.to_uppercase().as_str())))
647    });
648
649    vm.register_builtin("split", |args, _out| {
650        let s = args.first().map(|a| a.display()).unwrap_or_default();
651        let sep = args
652            .get(1)
653            .map(|a| a.display())
654            .unwrap_or_else(|| " ".to_string());
655        let parts: Vec<VmValue> = s
656            .split(&sep)
657            .map(|p| VmValue::String(Rc::from(p)))
658            .collect();
659        Ok(VmValue::List(Rc::new(parts)))
660    });
661
662    // =========================================================================
663    // Logging builtins
664    // =========================================================================
665
666    vm.register_builtin("log_debug", |args, out| {
667        vm_write_log("debug", 0, args, out);
668        Ok(VmValue::Nil)
669    });
670
671    vm.register_builtin("log_info", |args, out| {
672        vm_write_log("info", 1, args, out);
673        Ok(VmValue::Nil)
674    });
675
676    vm.register_builtin("log_warn", |args, out| {
677        vm_write_log("warn", 2, args, out);
678        Ok(VmValue::Nil)
679    });
680
681    vm.register_builtin("log_error", |args, out| {
682        vm_write_log("error", 3, args, out);
683        Ok(VmValue::Nil)
684    });
685
686    vm.register_builtin("log_set_level", |args, _out| {
687        let level_str = args.first().map(|a| a.display()).unwrap_or_default();
688        match vm_level_to_u8(&level_str) {
689            Some(n) => {
690                VM_MIN_LOG_LEVEL.store(n, Ordering::Relaxed);
691                Ok(VmValue::Nil)
692            }
693            None => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
694                "log_set_level: invalid level '{}'. Expected debug, info, warn, or error",
695                level_str
696            ))))),
697        }
698    });
699
700    // =========================================================================
701    // Tracing builtins
702    // =========================================================================
703
704    vm.register_builtin("trace_start", |args, _out| {
705        use rand::Rng;
706        let name = args.first().map(|a| a.display()).unwrap_or_default();
707        let trace_id = VM_TRACE_STACK.with(|stack| {
708            stack
709                .borrow()
710                .last()
711                .map(|t| t.trace_id.clone())
712                .unwrap_or_else(|| {
713                    let val: u32 = rand::thread_rng().gen();
714                    format!("{val:08x}")
715                })
716        });
717        let span_id = {
718            let val: u32 = rand::thread_rng().gen();
719            format!("{val:08x}")
720        };
721        let start_ms = std::time::SystemTime::now()
722            .duration_since(std::time::UNIX_EPOCH)
723            .unwrap_or_default()
724            .as_millis() as i64;
725
726        VM_TRACE_STACK.with(|stack| {
727            stack.borrow_mut().push(VmTraceContext {
728                trace_id: trace_id.clone(),
729                span_id: span_id.clone(),
730            });
731        });
732
733        let mut span = BTreeMap::new();
734        span.insert(
735            "trace_id".to_string(),
736            VmValue::String(Rc::from(trace_id.as_str())),
737        );
738        span.insert(
739            "span_id".to_string(),
740            VmValue::String(Rc::from(span_id.as_str())),
741        );
742        span.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
743        span.insert("start_ms".to_string(), VmValue::Int(start_ms));
744        Ok(VmValue::Dict(Rc::new(span)))
745    });
746
747    vm.register_builtin("trace_end", |args, out| {
748        let span = match args.first() {
749            Some(VmValue::Dict(d)) => d,
750            _ => {
751                return Err(VmError::Thrown(VmValue::String(Rc::from(
752                    "trace_end: argument must be a span dict from trace_start",
753                ))));
754            }
755        };
756
757        let end_ms = std::time::SystemTime::now()
758            .duration_since(std::time::UNIX_EPOCH)
759            .unwrap_or_default()
760            .as_millis() as i64;
761
762        let start_ms = span
763            .get("start_ms")
764            .and_then(|v| v.as_int())
765            .unwrap_or(end_ms);
766        let duration_ms = end_ms - start_ms;
767        let name = span.get("name").map(|v| v.display()).unwrap_or_default();
768        let trace_id = span
769            .get("trace_id")
770            .map(|v| v.display())
771            .unwrap_or_default();
772        let span_id = span.get("span_id").map(|v| v.display()).unwrap_or_default();
773
774        VM_TRACE_STACK.with(|stack| {
775            stack.borrow_mut().pop();
776        });
777
778        let level_num = 1_u8;
779        if level_num >= VM_MIN_LOG_LEVEL.load(Ordering::Relaxed) {
780            let mut fields = BTreeMap::new();
781            fields.insert(
782                "trace_id".to_string(),
783                VmValue::String(Rc::from(trace_id.as_str())),
784            );
785            fields.insert(
786                "span_id".to_string(),
787                VmValue::String(Rc::from(span_id.as_str())),
788            );
789            fields.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
790            fields.insert("duration_ms".to_string(), VmValue::Int(duration_ms));
791            let line = vm_build_log_line("info", "span_end", Some(&fields));
792            out.push_str(&line);
793        }
794
795        Ok(VmValue::Nil)
796    });
797
798    vm.register_builtin("trace_id", |_args, _out| {
799        let id = VM_TRACE_STACK.with(|stack| stack.borrow().last().map(|t| t.trace_id.clone()));
800        match id {
801            Some(trace_id) => Ok(VmValue::String(Rc::from(trace_id.as_str()))),
802            None => Ok(VmValue::Nil),
803        }
804    });
805
806    // =========================================================================
807    // Tool registry builtins
808    // =========================================================================
809
810    vm.register_builtin("tool_registry", |_args, _out| {
811        let mut registry = BTreeMap::new();
812        registry.insert(
813            "_type".to_string(),
814            VmValue::String(Rc::from("tool_registry")),
815        );
816        registry.insert("tools".to_string(), VmValue::List(Rc::new(Vec::new())));
817        Ok(VmValue::Dict(Rc::new(registry)))
818    });
819
820    vm.register_builtin("tool_add", |args, _out| {
821        if args.len() < 4 {
822            return Err(VmError::Thrown(VmValue::String(Rc::from(
823                "tool_add: requires registry, name, description, and handler",
824            ))));
825        }
826
827        let registry = match &args[0] {
828            VmValue::Dict(map) => (**map).clone(),
829            _ => {
830                return Err(VmError::Thrown(VmValue::String(Rc::from(
831                    "tool_add: first argument must be a tool registry",
832                ))));
833            }
834        };
835
836        match registry.get("_type") {
837            Some(VmValue::String(t)) if &**t == "tool_registry" => {}
838            _ => {
839                return Err(VmError::Thrown(VmValue::String(Rc::from(
840                    "tool_add: first argument must be a tool registry",
841                ))));
842            }
843        }
844
845        let name = args[1].display();
846        let description = args[2].display();
847        let handler = args[3].clone();
848        let parameters = if args.len() > 4 {
849            args[4].clone()
850        } else {
851            VmValue::Dict(Rc::new(BTreeMap::new()))
852        };
853
854        let mut tool_entry = BTreeMap::new();
855        tool_entry.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
856        tool_entry.insert(
857            "description".to_string(),
858            VmValue::String(Rc::from(description.as_str())),
859        );
860        tool_entry.insert("handler".to_string(), handler);
861        tool_entry.insert("parameters".to_string(), parameters);
862
863        let mut tools: Vec<VmValue> = match registry.get("tools") {
864            Some(VmValue::List(list)) => list
865                .iter()
866                .filter(|t| {
867                    if let VmValue::Dict(e) = t {
868                        e.get("name").map(|v| v.display()).as_deref() != Some(name.as_str())
869                    } else {
870                        true
871                    }
872                })
873                .cloned()
874                .collect(),
875            _ => Vec::new(),
876        };
877        tools.push(VmValue::Dict(Rc::new(tool_entry)));
878
879        let mut new_registry = registry;
880        new_registry.insert("tools".to_string(), VmValue::List(Rc::new(tools)));
881        Ok(VmValue::Dict(Rc::new(new_registry)))
882    });
883
884    vm.register_builtin("tool_list", |args, _out| {
885        let registry = match args.first() {
886            Some(VmValue::Dict(map)) => map,
887            _ => {
888                return Err(VmError::Thrown(VmValue::String(Rc::from(
889                    "tool_list: requires a tool registry",
890                ))));
891            }
892        };
893        vm_validate_registry("tool_list", registry)?;
894
895        let tools = vm_get_tools(registry);
896        let mut result = Vec::new();
897        for tool in tools {
898            if let VmValue::Dict(entry) = tool {
899                let mut desc = BTreeMap::new();
900                if let Some(name) = entry.get("name") {
901                    desc.insert("name".to_string(), name.clone());
902                }
903                if let Some(description) = entry.get("description") {
904                    desc.insert("description".to_string(), description.clone());
905                }
906                if let Some(parameters) = entry.get("parameters") {
907                    desc.insert("parameters".to_string(), parameters.clone());
908                }
909                result.push(VmValue::Dict(Rc::new(desc)));
910            }
911        }
912        Ok(VmValue::List(Rc::new(result)))
913    });
914
915    vm.register_builtin("tool_find", |args, _out| {
916        if args.len() < 2 {
917            return Err(VmError::Thrown(VmValue::String(Rc::from(
918                "tool_find: requires registry and name",
919            ))));
920        }
921
922        let registry = match &args[0] {
923            VmValue::Dict(map) => map,
924            _ => {
925                return Err(VmError::Thrown(VmValue::String(Rc::from(
926                    "tool_find: first argument must be a tool registry",
927                ))));
928            }
929        };
930        vm_validate_registry("tool_find", registry)?;
931
932        let target_name = args[1].display();
933        let tools = vm_get_tools(registry);
934
935        for tool in tools {
936            if let VmValue::Dict(entry) = tool {
937                if let Some(VmValue::String(name)) = entry.get("name") {
938                    if &**name == target_name.as_str() {
939                        return Ok(tool.clone());
940                    }
941                }
942            }
943        }
944        Ok(VmValue::Nil)
945    });
946
947    vm.register_builtin("tool_describe", |args, _out| {
948        let registry = match args.first() {
949            Some(VmValue::Dict(map)) => map,
950            _ => {
951                return Err(VmError::Thrown(VmValue::String(Rc::from(
952                    "tool_describe: requires a tool registry",
953                ))));
954            }
955        };
956        vm_validate_registry("tool_describe", registry)?;
957
958        let tools = vm_get_tools(registry);
959
960        if tools.is_empty() {
961            return Ok(VmValue::String(Rc::from("Available tools:\n(none)")));
962        }
963
964        let mut tool_infos: Vec<(String, String, String)> = Vec::new();
965        for tool in tools {
966            if let VmValue::Dict(entry) = tool {
967                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
968                let description = entry
969                    .get("description")
970                    .map(|v| v.display())
971                    .unwrap_or_default();
972                let params_str = vm_format_parameters(entry.get("parameters"));
973                tool_infos.push((name, params_str, description));
974            }
975        }
976
977        tool_infos.sort_by(|a, b| a.0.cmp(&b.0));
978
979        let mut lines = vec!["Available tools:".to_string()];
980        for (name, params, desc) in &tool_infos {
981            lines.push(format!("- {name}({params}): {desc}"));
982        }
983
984        Ok(VmValue::String(Rc::from(lines.join("\n").as_str())))
985    });
986
987    vm.register_builtin("tool_remove", |args, _out| {
988        if args.len() < 2 {
989            return Err(VmError::Thrown(VmValue::String(Rc::from(
990                "tool_remove: requires registry and name",
991            ))));
992        }
993
994        let registry = match &args[0] {
995            VmValue::Dict(map) => (**map).clone(),
996            _ => {
997                return Err(VmError::Thrown(VmValue::String(Rc::from(
998                    "tool_remove: first argument must be a tool registry",
999                ))));
1000            }
1001        };
1002        vm_validate_registry("tool_remove", &registry)?;
1003
1004        let target_name = args[1].display();
1005
1006        let tools = match registry.get("tools") {
1007            Some(VmValue::List(list)) => (**list).clone(),
1008            _ => Vec::new(),
1009        };
1010
1011        let filtered: Vec<VmValue> = tools
1012            .into_iter()
1013            .filter(|tool| {
1014                if let VmValue::Dict(entry) = tool {
1015                    if let Some(VmValue::String(name)) = entry.get("name") {
1016                        return &**name != target_name.as_str();
1017                    }
1018                }
1019                true
1020            })
1021            .collect();
1022
1023        let mut new_registry = registry;
1024        new_registry.insert("tools".to_string(), VmValue::List(Rc::new(filtered)));
1025        Ok(VmValue::Dict(Rc::new(new_registry)))
1026    });
1027
1028    vm.register_builtin("tool_count", |args, _out| {
1029        let registry = match args.first() {
1030            Some(VmValue::Dict(map)) => map,
1031            _ => {
1032                return Err(VmError::Thrown(VmValue::String(Rc::from(
1033                    "tool_count: requires a tool registry",
1034                ))));
1035            }
1036        };
1037        vm_validate_registry("tool_count", registry)?;
1038        let count = vm_get_tools(registry).len();
1039        Ok(VmValue::Int(count as i64))
1040    });
1041
1042    vm.register_builtin("tool_schema", |args, _out| {
1043        let registry = match args.first() {
1044            Some(VmValue::Dict(map)) => {
1045                vm_validate_registry("tool_schema", map)?;
1046                map
1047            }
1048            _ => {
1049                return Err(VmError::Thrown(VmValue::String(Rc::from(
1050                    "tool_schema: requires a tool registry",
1051                ))));
1052            }
1053        };
1054
1055        let components = args.get(1).and_then(|v| v.as_dict()).cloned();
1056
1057        let tools = match registry.get("tools") {
1058            Some(VmValue::List(list)) => list,
1059            _ => return Ok(VmValue::Dict(Rc::new(vm_build_empty_schema()))),
1060        };
1061
1062        let mut tool_schemas = Vec::new();
1063        for tool in tools.iter() {
1064            if let VmValue::Dict(entry) = tool {
1065                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
1066                let description = entry
1067                    .get("description")
1068                    .map(|v| v.display())
1069                    .unwrap_or_default();
1070
1071                let input_schema =
1072                    vm_build_input_schema(entry.get("parameters"), components.as_ref());
1073
1074                let mut tool_def = BTreeMap::new();
1075                tool_def.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
1076                tool_def.insert(
1077                    "description".to_string(),
1078                    VmValue::String(Rc::from(description.as_str())),
1079                );
1080                tool_def.insert("inputSchema".to_string(), input_schema);
1081                tool_schemas.push(VmValue::Dict(Rc::new(tool_def)));
1082            }
1083        }
1084
1085        let mut schema = BTreeMap::new();
1086        schema.insert(
1087            "schema_version".to_string(),
1088            VmValue::String(Rc::from("harn-tools/1.0")),
1089        );
1090
1091        if let Some(comps) = &components {
1092            let mut comp_wrapper = BTreeMap::new();
1093            comp_wrapper.insert("schemas".to_string(), VmValue::Dict(Rc::new(comps.clone())));
1094            schema.insert(
1095                "components".to_string(),
1096                VmValue::Dict(Rc::new(comp_wrapper)),
1097            );
1098        }
1099
1100        schema.insert("tools".to_string(), VmValue::List(Rc::new(tool_schemas)));
1101        Ok(VmValue::Dict(Rc::new(schema)))
1102    });
1103
1104    vm.register_builtin("tool_parse_call", |args, _out| {
1105        let text = args.first().map(|a| a.display()).unwrap_or_default();
1106
1107        let mut results = Vec::new();
1108        let mut search_from = 0;
1109
1110        while let Some(start) = text[search_from..].find("<tool_call>") {
1111            let abs_start = search_from + start + "<tool_call>".len();
1112            if let Some(end) = text[abs_start..].find("</tool_call>") {
1113                let json_str = text[abs_start..abs_start + end].trim();
1114                if let Ok(jv) = serde_json::from_str::<serde_json::Value>(json_str) {
1115                    results.push(json_to_vm_value(&jv));
1116                }
1117                search_from = abs_start + end + "</tool_call>".len();
1118            } else {
1119                break;
1120            }
1121        }
1122
1123        Ok(VmValue::List(Rc::new(results)))
1124    });
1125
1126    vm.register_builtin("tool_format_result", |args, _out| {
1127        if args.len() < 2 {
1128            return Err(VmError::Thrown(VmValue::String(Rc::from(
1129                "tool_format_result: requires name and result",
1130            ))));
1131        }
1132        let name = args[0].display();
1133        let result = args[1].display();
1134
1135        let json_name = vm_escape_json_str(&name);
1136        let json_result = vm_escape_json_str(&result);
1137        Ok(VmValue::String(Rc::from(
1138            format!(
1139                "<tool_result>{{\"name\": \"{json_name}\", \"result\": \"{json_result}\"}}</tool_result>"
1140            )
1141            .as_str(),
1142        )))
1143    });
1144
1145    vm.register_builtin("tool_prompt", |args, _out| {
1146        let registry = match args.first() {
1147            Some(VmValue::Dict(map)) => {
1148                vm_validate_registry("tool_prompt", map)?;
1149                map
1150            }
1151            _ => {
1152                return Err(VmError::Thrown(VmValue::String(Rc::from(
1153                    "tool_prompt: requires a tool registry",
1154                ))));
1155            }
1156        };
1157
1158        let tools = match registry.get("tools") {
1159            Some(VmValue::List(list)) => list,
1160            _ => {
1161                return Ok(VmValue::String(Rc::from("No tools are available.")));
1162            }
1163        };
1164
1165        if tools.is_empty() {
1166            return Ok(VmValue::String(Rc::from("No tools are available.")));
1167        }
1168
1169        let mut prompt = String::from("# Available Tools\n\n");
1170        prompt.push_str("You have access to the following tools. To use a tool, output a tool call in this exact format:\n\n");
1171        prompt.push_str("<tool_call>{\"name\": \"tool_name\", \"arguments\": {\"param\": \"value\"}}</tool_call>\n\n");
1172        prompt.push_str("You may make multiple tool calls in a single response. Wait for tool results before proceeding.\n\n");
1173        prompt.push_str("## Tools\n\n");
1174
1175        let mut tool_infos: Vec<(&BTreeMap<String, VmValue>, String)> = Vec::new();
1176        for tool in tools.iter() {
1177            if let VmValue::Dict(entry) = tool {
1178                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
1179                tool_infos.push((entry, name));
1180            }
1181        }
1182        tool_infos.sort_by(|a, b| a.1.cmp(&b.1));
1183
1184        for (entry, name) in &tool_infos {
1185            let description = entry
1186                .get("description")
1187                .map(|v| v.display())
1188                .unwrap_or_default();
1189            let params_str = vm_format_parameters(entry.get("parameters"));
1190
1191            prompt.push_str(&format!("### {name}\n"));
1192            prompt.push_str(&format!("{description}\n"));
1193            if !params_str.is_empty() {
1194                prompt.push_str(&format!("Parameters: {params_str}\n"));
1195            }
1196            prompt.push('\n');
1197        }
1198
1199        Ok(VmValue::String(Rc::from(prompt.trim_end())))
1200    });
1201
1202    // =========================================================================
1203    // Channel builtins (sync)
1204    // =========================================================================
1205
1206    vm.register_builtin("channel", |args, _out| {
1207        let name = args
1208            .first()
1209            .map(|a| a.display())
1210            .unwrap_or_else(|| "default".to_string());
1211        let capacity = args.get(1).and_then(|a| a.as_int()).unwrap_or(256) as usize;
1212        let capacity = capacity.max(1);
1213        let (tx, rx) = tokio::sync::mpsc::channel(capacity);
1214        #[allow(clippy::arc_with_non_send_sync)]
1215        Ok(VmValue::Channel(VmChannelHandle {
1216            name,
1217            sender: Arc::new(tx),
1218            receiver: Arc::new(tokio::sync::Mutex::new(rx)),
1219            closed: Arc::new(AtomicBool::new(false)),
1220        }))
1221    });
1222
1223    vm.register_builtin("close_channel", |args, _out| {
1224        if args.is_empty() {
1225            return Err(VmError::Thrown(VmValue::String(Rc::from(
1226                "close_channel: requires a channel",
1227            ))));
1228        }
1229        if let VmValue::Channel(ch) = &args[0] {
1230            ch.closed.store(true, Ordering::SeqCst);
1231            Ok(VmValue::Nil)
1232        } else {
1233            Err(VmError::Thrown(VmValue::String(Rc::from(
1234                "close_channel: first argument must be a channel",
1235            ))))
1236        }
1237    });
1238
1239    vm.register_builtin("try_receive", |args, _out| {
1240        if args.is_empty() {
1241            return Err(VmError::Thrown(VmValue::String(Rc::from(
1242                "try_receive: requires a channel",
1243            ))));
1244        }
1245        if let VmValue::Channel(ch) = &args[0] {
1246            match ch.receiver.try_lock() {
1247                Ok(mut rx) => match rx.try_recv() {
1248                    Ok(val) => Ok(val),
1249                    Err(_) => Ok(VmValue::Nil),
1250                },
1251                Err(_) => Ok(VmValue::Nil),
1252            }
1253        } else {
1254            Err(VmError::Thrown(VmValue::String(Rc::from(
1255                "try_receive: first argument must be a channel",
1256            ))))
1257        }
1258    });
1259
1260    // =========================================================================
1261    // Atomic builtins
1262    // =========================================================================
1263
1264    vm.register_builtin("atomic", |args, _out| {
1265        let initial = match args.first() {
1266            Some(VmValue::Int(n)) => *n,
1267            Some(VmValue::Float(f)) => *f as i64,
1268            Some(VmValue::Bool(b)) => {
1269                if *b {
1270                    1
1271                } else {
1272                    0
1273                }
1274            }
1275            _ => 0,
1276        };
1277        Ok(VmValue::Atomic(VmAtomicHandle {
1278            value: Arc::new(AtomicI64::new(initial)),
1279        }))
1280    });
1281
1282    vm.register_builtin("atomic_get", |args, _out| {
1283        if let Some(VmValue::Atomic(a)) = args.first() {
1284            Ok(VmValue::Int(a.value.load(Ordering::SeqCst)))
1285        } else {
1286            Ok(VmValue::Nil)
1287        }
1288    });
1289
1290    vm.register_builtin("atomic_set", |args, _out| {
1291        if args.len() >= 2 {
1292            if let (VmValue::Atomic(a), Some(val)) = (&args[0], args[1].as_int()) {
1293                let old = a.value.swap(val, Ordering::SeqCst);
1294                return Ok(VmValue::Int(old));
1295            }
1296        }
1297        Ok(VmValue::Nil)
1298    });
1299
1300    vm.register_builtin("atomic_add", |args, _out| {
1301        if args.len() >= 2 {
1302            if let (VmValue::Atomic(a), Some(delta)) = (&args[0], args[1].as_int()) {
1303                let prev = a.value.fetch_add(delta, Ordering::SeqCst);
1304                return Ok(VmValue::Int(prev));
1305            }
1306        }
1307        Ok(VmValue::Nil)
1308    });
1309
1310    vm.register_builtin("atomic_cas", |args, _out| {
1311        if args.len() >= 3 {
1312            if let (VmValue::Atomic(a), Some(expected), Some(new_val)) =
1313                (&args[0], args[1].as_int(), args[2].as_int())
1314            {
1315                let result =
1316                    a.value
1317                        .compare_exchange(expected, new_val, Ordering::SeqCst, Ordering::SeqCst);
1318                return Ok(VmValue::Bool(result.is_ok()));
1319            }
1320        }
1321        Ok(VmValue::Bool(false))
1322    });
1323
1324    // =========================================================================
1325    // Async builtins
1326    // =========================================================================
1327
1328    // sleep(ms)
1329    vm.register_async_builtin("sleep", |args| async move {
1330        let ms = match args.first() {
1331            Some(VmValue::Duration(ms)) => *ms,
1332            Some(VmValue::Int(n)) => *n as u64,
1333            _ => 0,
1334        };
1335        if ms > 0 {
1336            tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await;
1337        }
1338        Ok(VmValue::Nil)
1339    });
1340
1341    // send(channel, value)
1342    vm.register_async_builtin("send", |args| async move {
1343        if args.len() < 2 {
1344            return Err(VmError::Thrown(VmValue::String(Rc::from(
1345                "send: requires channel and value",
1346            ))));
1347        }
1348        if let VmValue::Channel(ch) = &args[0] {
1349            if ch.closed.load(Ordering::SeqCst) {
1350                return Ok(VmValue::Bool(false));
1351            }
1352            let val = args[1].clone();
1353            match ch.sender.send(val).await {
1354                Ok(()) => Ok(VmValue::Bool(true)),
1355                Err(_) => Ok(VmValue::Bool(false)),
1356            }
1357        } else {
1358            Err(VmError::Thrown(VmValue::String(Rc::from(
1359                "send: first argument must be a channel",
1360            ))))
1361        }
1362    });
1363
1364    // receive(channel)
1365    vm.register_async_builtin("receive", |args| async move {
1366        if args.is_empty() {
1367            return Err(VmError::Thrown(VmValue::String(Rc::from(
1368                "receive: requires a channel",
1369            ))));
1370        }
1371        if let VmValue::Channel(ch) = &args[0] {
1372            if ch.closed.load(Ordering::SeqCst) {
1373                let mut rx = ch.receiver.lock().await;
1374                return match rx.try_recv() {
1375                    Ok(val) => Ok(val),
1376                    Err(_) => Ok(VmValue::Nil),
1377                };
1378            }
1379            let mut rx = ch.receiver.lock().await;
1380            match rx.recv().await {
1381                Some(val) => Ok(val),
1382                None => Ok(VmValue::Nil),
1383            }
1384        } else {
1385            Err(VmError::Thrown(VmValue::String(Rc::from(
1386                "receive: first argument must be a channel",
1387            ))))
1388        }
1389    });
1390
1391    // select(channel1, channel2, ...)
1392    vm.register_async_builtin("select", |args| async move {
1393        if args.is_empty() {
1394            return Err(VmError::Thrown(VmValue::String(Rc::from(
1395                "select: requires at least one channel",
1396            ))));
1397        }
1398
1399        let mut channels: Vec<&VmChannelHandle> = Vec::new();
1400        for arg in &args {
1401            if let VmValue::Channel(ch) = arg {
1402                channels.push(ch);
1403            } else {
1404                return Err(VmError::Thrown(VmValue::String(Rc::from(
1405                    "select: all arguments must be channels",
1406                ))));
1407            }
1408        }
1409
1410        loop {
1411            let mut all_closed = true;
1412            for (i, ch) in channels.iter().enumerate() {
1413                if let Ok(mut rx) = ch.receiver.try_lock() {
1414                    match rx.try_recv() {
1415                        Ok(val) => {
1416                            let mut result = BTreeMap::new();
1417                            result.insert("index".to_string(), VmValue::Int(i as i64));
1418                            result.insert("value".to_string(), val);
1419                            result.insert(
1420                                "channel".to_string(),
1421                                VmValue::String(Rc::from(ch.name.as_str())),
1422                            );
1423                            return Ok(VmValue::Dict(Rc::new(result)));
1424                        }
1425                        Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
1426                            all_closed = false;
1427                        }
1428                        Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {}
1429                    }
1430                }
1431            }
1432            if all_closed {
1433                return Ok(VmValue::Nil);
1434            }
1435            tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1436        }
1437    });
1438
1439    // =========================================================================
1440    // JSON validation and extraction builtins
1441    // =========================================================================
1442
1443    vm.register_builtin("json_validate", |args, _out| {
1444        if args.len() < 2 {
1445            return Err(VmError::Thrown(VmValue::String(Rc::from(
1446                "json_validate requires 2 arguments: data and schema",
1447            ))));
1448        }
1449        let data = &args[0];
1450        let schema = &args[1];
1451        let schema_dict = match schema.as_dict() {
1452            Some(d) => d,
1453            None => {
1454                return Err(VmError::Thrown(VmValue::String(Rc::from(
1455                    "json_validate: schema must be a dict",
1456                ))));
1457            }
1458        };
1459        let mut errors = Vec::new();
1460        validate_value(data, schema_dict, "", &mut errors);
1461        if errors.is_empty() {
1462            Ok(VmValue::Bool(true))
1463        } else {
1464            Err(VmError::Thrown(VmValue::String(Rc::from(
1465                errors.join("; "),
1466            ))))
1467        }
1468    });
1469
1470    vm.register_builtin("json_extract", |args, _out| {
1471        if args.is_empty() {
1472            return Err(VmError::Thrown(VmValue::String(Rc::from(
1473                "json_extract requires at least 1 argument: text",
1474            ))));
1475        }
1476        let text = args[0].display();
1477        let key = args.get(1).map(|a| a.display());
1478
1479        // Extract JSON from text that may contain markdown code fences
1480        let json_str = extract_json_from_text(&text);
1481        let parsed = match serde_json::from_str::<serde_json::Value>(&json_str) {
1482            Ok(jv) => json_to_vm_value(&jv),
1483            Err(e) => {
1484                return Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1485                    "json_extract: failed to parse JSON: {e}"
1486                )))));
1487            }
1488        };
1489
1490        match key {
1491            Some(k) => match &parsed {
1492                VmValue::Dict(map) => match map.get(&k) {
1493                    Some(val) => Ok(val.clone()),
1494                    None => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1495                        "json_extract: key '{}' not found",
1496                        k
1497                    ))))),
1498                },
1499                _ => Err(VmError::Thrown(VmValue::String(Rc::from(
1500                    "json_extract: parsed value is not a dict, cannot extract key",
1501                )))),
1502            },
1503            None => Ok(parsed),
1504        }
1505    });
1506
1507    // =========================================================================
1508    // HTTP and LLM builtins (registered from separate modules)
1509    // =========================================================================
1510
1511    register_http_builtins(vm);
1512    register_llm_builtins(vm);
1513    register_mcp_builtins(vm);
1514}
1515
1516// =============================================================================
1517// JSON helpers
1518// =============================================================================
1519
1520pub(crate) fn escape_json_string_vm(s: &str) -> String {
1521    let mut out = String::with_capacity(s.len() + 2);
1522    out.push('"');
1523    for ch in s.chars() {
1524        match ch {
1525            '"' => out.push_str("\\\""),
1526            '\\' => out.push_str("\\\\"),
1527            '\n' => out.push_str("\\n"),
1528            '\r' => out.push_str("\\r"),
1529            '\t' => out.push_str("\\t"),
1530            c if c.is_control() => {
1531                out.push_str(&format!("\\u{:04x}", c as u32));
1532            }
1533            c => out.push(c),
1534        }
1535    }
1536    out.push('"');
1537    out
1538}
1539
1540pub(crate) fn vm_value_to_json(val: &VmValue) -> String {
1541    match val {
1542        VmValue::String(s) => escape_json_string_vm(s),
1543        VmValue::Int(n) => n.to_string(),
1544        VmValue::Float(n) => n.to_string(),
1545        VmValue::Bool(b) => b.to_string(),
1546        VmValue::Nil => "null".to_string(),
1547        VmValue::List(items) => {
1548            let inner: Vec<String> = items.iter().map(vm_value_to_json).collect();
1549            format!("[{}]", inner.join(","))
1550        }
1551        VmValue::Dict(map) => {
1552            let inner: Vec<String> = map
1553                .iter()
1554                .map(|(k, v)| format!("{}:{}", escape_json_string_vm(k), vm_value_to_json(v)))
1555                .collect();
1556            format!("{{{}}}", inner.join(","))
1557        }
1558        _ => "null".to_string(),
1559    }
1560}
1561
1562pub(crate) fn json_to_vm_value(jv: &serde_json::Value) -> VmValue {
1563    match jv {
1564        serde_json::Value::Null => VmValue::Nil,
1565        serde_json::Value::Bool(b) => VmValue::Bool(*b),
1566        serde_json::Value::Number(n) => {
1567            if let Some(i) = n.as_i64() {
1568                VmValue::Int(i)
1569            } else {
1570                VmValue::Float(n.as_f64().unwrap_or(0.0))
1571            }
1572        }
1573        serde_json::Value::String(s) => VmValue::String(Rc::from(s.as_str())),
1574        serde_json::Value::Array(arr) => {
1575            VmValue::List(Rc::new(arr.iter().map(json_to_vm_value).collect()))
1576        }
1577        serde_json::Value::Object(map) => {
1578            let mut m = BTreeMap::new();
1579            for (k, v) in map {
1580                m.insert(k.clone(), json_to_vm_value(v));
1581            }
1582            VmValue::Dict(Rc::new(m))
1583        }
1584    }
1585}
1586
1587// =============================================================================
1588// Helper: validate a VmValue against a schema dict
1589// =============================================================================
1590
1591fn validate_value(
1592    value: &VmValue,
1593    schema: &BTreeMap<String, VmValue>,
1594    path: &str,
1595    errors: &mut Vec<String>,
1596) {
1597    // Check "type" constraint
1598    if let Some(VmValue::String(expected_type)) = schema.get("type") {
1599        let actual_type = value.type_name();
1600        let type_str: &str = expected_type;
1601        if type_str != "any" && actual_type != type_str {
1602            let location = if path.is_empty() {
1603                "root".to_string()
1604            } else {
1605                path.to_string()
1606            };
1607            errors.push(format!(
1608                "at {}: expected type '{}', got '{}'",
1609                location, type_str, actual_type
1610            ));
1611            return; // No point checking further if type is wrong
1612        }
1613    }
1614
1615    // Check "required" keys (only for dicts)
1616    if let Some(VmValue::List(required_keys)) = schema.get("required") {
1617        if let VmValue::Dict(map) = value {
1618            for key_val in required_keys.iter() {
1619                let key = key_val.display();
1620                if !map.contains_key(&key) {
1621                    let location = if path.is_empty() {
1622                        "root".to_string()
1623                    } else {
1624                        path.to_string()
1625                    };
1626                    errors.push(format!("at {}: missing required key '{}'", location, key));
1627                }
1628            }
1629        }
1630    }
1631
1632    // Check "properties" (only for dicts)
1633    if let Some(VmValue::Dict(prop_schemas)) = schema.get("properties") {
1634        if let VmValue::Dict(map) = value {
1635            for (key, prop_schema) in prop_schemas.iter() {
1636                if let Some(prop_value) = map.get(key) {
1637                    if let Some(prop_schema_dict) = prop_schema.as_dict() {
1638                        let child_path = if path.is_empty() {
1639                            key.clone()
1640                        } else {
1641                            format!("{}.{}", path, key)
1642                        };
1643                        validate_value(prop_value, prop_schema_dict, &child_path, errors);
1644                    }
1645                }
1646            }
1647        }
1648    }
1649
1650    // Check "items" (only for lists)
1651    if let Some(VmValue::Dict(item_schema)) = schema.get("items") {
1652        if let VmValue::List(items) = value {
1653            for (i, item) in items.iter().enumerate() {
1654                let child_path = if path.is_empty() {
1655                    format!("[{}]", i)
1656                } else {
1657                    format!("{}[{}]", path, i)
1658                };
1659                validate_value(item, item_schema, &child_path, errors);
1660            }
1661        }
1662    }
1663}
1664
1665// =============================================================================
1666// Helper: extract JSON from text with possible markdown fences
1667// =============================================================================
1668
1669fn extract_json_from_text(text: &str) -> String {
1670    let trimmed = text.trim();
1671
1672    // Try to find ```json ... ``` or ``` ... ``` code fences
1673    if let Some(start) = trimmed.find("```") {
1674        let after_backticks = &trimmed[start + 3..];
1675        // Skip optional language tag (e.g., "json")
1676        let content_start = if let Some(nl) = after_backticks.find('\n') {
1677            nl + 1
1678        } else {
1679            0
1680        };
1681        let content = &after_backticks[content_start..];
1682        if let Some(end) = content.find("```") {
1683            return content[..end].trim().to_string();
1684        }
1685    }
1686
1687    // No code fences found; try to find JSON object or array boundaries
1688    // Look for first { or [ and last matching } or ]
1689    if let Some(obj_start) = trimmed.find('{') {
1690        if let Some(obj_end) = trimmed.rfind('}') {
1691            if obj_end > obj_start {
1692                return trimmed[obj_start..=obj_end].to_string();
1693            }
1694        }
1695    }
1696    if let Some(arr_start) = trimmed.find('[') {
1697        if let Some(arr_end) = trimmed.rfind(']') {
1698            if arr_end > arr_start {
1699                return trimmed[arr_start..=arr_end].to_string();
1700            }
1701        }
1702    }
1703
1704    // Fall back to trimmed text as-is
1705    trimmed.to_string()
1706}
1707
1708// =============================================================================
1709// Helper: convert process::Output to VmValue dict
1710// =============================================================================
1711
1712fn vm_output_to_value(output: std::process::Output) -> VmValue {
1713    let mut result = BTreeMap::new();
1714    result.insert(
1715        "stdout".to_string(),
1716        VmValue::String(Rc::from(
1717            String::from_utf8_lossy(&output.stdout).to_string().as_str(),
1718        )),
1719    );
1720    result.insert(
1721        "stderr".to_string(),
1722        VmValue::String(Rc::from(
1723            String::from_utf8_lossy(&output.stderr).to_string().as_str(),
1724        )),
1725    );
1726    result.insert(
1727        "status".to_string(),
1728        VmValue::Int(output.status.code().unwrap_or(-1) as i64),
1729    );
1730    result.insert(
1731        "success".to_string(),
1732        VmValue::Bool(output.status.success()),
1733    );
1734    VmValue::Dict(Rc::new(result))
1735}
1736
1737// =============================================================================
1738// Helper: civil date from timestamp (Howard Hinnant's algorithm)
1739// =============================================================================
1740
1741fn vm_civil_from_timestamp(total_secs: u64) -> (i64, i64, i64, i64, i64, i64, i64) {
1742    let days = total_secs / 86400;
1743    let time_of_day = total_secs % 86400;
1744    let hour = (time_of_day / 3600) as i64;
1745    let minute = ((time_of_day % 3600) / 60) as i64;
1746    let second = (time_of_day % 60) as i64;
1747
1748    let z = days as i64 + 719468;
1749    let era = if z >= 0 { z } else { z - 146096 } / 146097;
1750    let doe = (z - era * 146097) as u64;
1751    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
1752    let y = yoe as i64 + era * 400;
1753    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
1754    let mp = (5 * doy + 2) / 153;
1755    let d = (doy - (153 * mp + 2) / 5 + 1) as i64;
1756    let m = if mp < 10 { mp + 3 } else { mp - 9 } as i64;
1757    let y = if m <= 2 { y + 1 } else { y };
1758    let dow = ((days + 4) % 7) as i64;
1759
1760    (y, m, d, hour, minute, second, dow)
1761}
1762
1763// =============================================================================
1764// Logging helpers for VM
1765// =============================================================================
1766
1767pub(crate) static VM_MIN_LOG_LEVEL: AtomicU8 = AtomicU8::new(0);
1768
1769#[derive(Clone)]
1770pub(crate) struct VmTraceContext {
1771    pub(crate) trace_id: String,
1772    pub(crate) span_id: String,
1773}
1774
1775thread_local! {
1776    pub(crate) static VM_TRACE_STACK: std::cell::RefCell<Vec<VmTraceContext>> = const { std::cell::RefCell::new(Vec::new()) };
1777}
1778
1779fn vm_level_to_u8(level: &str) -> Option<u8> {
1780    match level {
1781        "debug" => Some(0),
1782        "info" => Some(1),
1783        "warn" => Some(2),
1784        "error" => Some(3),
1785        _ => None,
1786    }
1787}
1788
1789fn vm_format_timestamp_utc() -> String {
1790    let now = std::time::SystemTime::now()
1791        .duration_since(std::time::UNIX_EPOCH)
1792        .unwrap_or_default();
1793    let total_secs = now.as_secs();
1794    let millis = now.subsec_millis();
1795
1796    let days = total_secs / 86400;
1797    let time_of_day = total_secs % 86400;
1798    let hour = time_of_day / 3600;
1799    let minute = (time_of_day % 3600) / 60;
1800    let second = time_of_day % 60;
1801
1802    let z = days as i64 + 719468;
1803    let era = if z >= 0 { z } else { z - 146096 } / 146097;
1804    let doe = (z - era * 146097) as u64;
1805    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
1806    let y = yoe as i64 + era * 400;
1807    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
1808    let mp = (5 * doy + 2) / 153;
1809    let d = doy - (153 * mp + 2) / 5 + 1;
1810    let m = if mp < 10 { mp + 3 } else { mp - 9 };
1811    let y = if m <= 2 { y + 1 } else { y };
1812
1813    format!("{y:04}-{m:02}-{d:02}T{hour:02}:{minute:02}:{second:02}.{millis:03}Z")
1814}
1815
1816pub(crate) fn vm_escape_json_str(s: &str) -> String {
1817    let mut out = String::with_capacity(s.len());
1818    for ch in s.chars() {
1819        match ch {
1820            '"' => out.push_str("\\\""),
1821            '\\' => out.push_str("\\\\"),
1822            '\n' => out.push_str("\\n"),
1823            '\r' => out.push_str("\\r"),
1824            '\t' => out.push_str("\\t"),
1825            c if c.is_control() => {
1826                out.push_str(&format!("\\u{:04x}", c as u32));
1827            }
1828            c => out.push(c),
1829        }
1830    }
1831    out
1832}
1833
1834fn vm_escape_json_str_quoted(s: &str) -> String {
1835    let mut out = String::with_capacity(s.len() + 2);
1836    out.push('"');
1837    out.push_str(&vm_escape_json_str(s));
1838    out.push('"');
1839    out
1840}
1841
1842fn vm_value_to_json_fragment(val: &VmValue) -> String {
1843    match val {
1844        VmValue::String(s) => vm_escape_json_str_quoted(s),
1845        VmValue::Int(n) => n.to_string(),
1846        VmValue::Float(n) => {
1847            if n.is_finite() {
1848                n.to_string()
1849            } else {
1850                "null".to_string()
1851            }
1852        }
1853        VmValue::Bool(b) => b.to_string(),
1854        VmValue::Nil => "null".to_string(),
1855        _ => vm_escape_json_str_quoted(&val.display()),
1856    }
1857}
1858
1859fn vm_build_log_line(level: &str, msg: &str, fields: Option<&BTreeMap<String, VmValue>>) -> String {
1860    let ts = vm_format_timestamp_utc();
1861    let mut parts: Vec<String> = Vec::new();
1862    parts.push(format!("\"ts\":{}", vm_escape_json_str_quoted(&ts)));
1863    parts.push(format!("\"level\":{}", vm_escape_json_str_quoted(level)));
1864    parts.push(format!("\"msg\":{}", vm_escape_json_str_quoted(msg)));
1865
1866    VM_TRACE_STACK.with(|stack| {
1867        if let Some(trace) = stack.borrow().last() {
1868            parts.push(format!(
1869                "\"trace_id\":{}",
1870                vm_escape_json_str_quoted(&trace.trace_id)
1871            ));
1872            parts.push(format!(
1873                "\"span_id\":{}",
1874                vm_escape_json_str_quoted(&trace.span_id)
1875            ));
1876        }
1877    });
1878
1879    if let Some(dict) = fields {
1880        for (k, v) in dict {
1881            parts.push(format!(
1882                "{}:{}",
1883                vm_escape_json_str_quoted(k),
1884                vm_value_to_json_fragment(v)
1885            ));
1886        }
1887    }
1888
1889    format!("{{{}}}\n", parts.join(","))
1890}
1891
1892fn vm_write_log(level: &str, level_num: u8, args: &[VmValue], out: &mut String) {
1893    if level_num < VM_MIN_LOG_LEVEL.load(Ordering::Relaxed) {
1894        return;
1895    }
1896    let msg = args.first().map(|a| a.display()).unwrap_or_default();
1897    let fields = args.get(1).and_then(|v| {
1898        if let VmValue::Dict(d) = v {
1899            Some(&**d)
1900        } else {
1901            None
1902        }
1903    });
1904    let line = vm_build_log_line(level, &msg, fields);
1905    out.push_str(&line);
1906}
1907
1908// =============================================================================
1909// Tool registry helpers for VM
1910// =============================================================================
1911
1912fn vm_validate_registry(name: &str, dict: &BTreeMap<String, VmValue>) -> Result<(), VmError> {
1913    match dict.get("_type") {
1914        Some(VmValue::String(t)) if &**t == "tool_registry" => Ok(()),
1915        _ => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1916            "{name}: argument must be a tool registry (created with tool_registry())"
1917        ))))),
1918    }
1919}
1920
1921fn vm_get_tools(dict: &BTreeMap<String, VmValue>) -> &[VmValue] {
1922    match dict.get("tools") {
1923        Some(VmValue::List(list)) => list,
1924        _ => &[],
1925    }
1926}
1927
1928fn vm_format_parameters(params: Option<&VmValue>) -> String {
1929    match params {
1930        Some(VmValue::Dict(map)) if !map.is_empty() => {
1931            let mut pairs: Vec<(String, String)> =
1932                map.iter().map(|(k, v)| (k.clone(), v.display())).collect();
1933            pairs.sort_by(|a, b| a.0.cmp(&b.0));
1934            pairs
1935                .iter()
1936                .map(|(k, v)| format!("{k}: {v}"))
1937                .collect::<Vec<_>>()
1938                .join(", ")
1939        }
1940        _ => String::new(),
1941    }
1942}
1943
1944fn vm_build_empty_schema() -> BTreeMap<String, VmValue> {
1945    let mut schema = BTreeMap::new();
1946    schema.insert(
1947        "schema_version".to_string(),
1948        VmValue::String(Rc::from("harn-tools/1.0")),
1949    );
1950    schema.insert("tools".to_string(), VmValue::List(Rc::new(Vec::new())));
1951    schema
1952}
1953
1954fn vm_build_input_schema(
1955    params: Option<&VmValue>,
1956    components: Option<&BTreeMap<String, VmValue>>,
1957) -> VmValue {
1958    let mut schema = BTreeMap::new();
1959    schema.insert("type".to_string(), VmValue::String(Rc::from("object")));
1960
1961    let params_map = match params {
1962        Some(VmValue::Dict(map)) if !map.is_empty() => map,
1963        _ => {
1964            schema.insert(
1965                "properties".to_string(),
1966                VmValue::Dict(Rc::new(BTreeMap::new())),
1967            );
1968            return VmValue::Dict(Rc::new(schema));
1969        }
1970    };
1971
1972    let mut properties = BTreeMap::new();
1973    let mut required = Vec::new();
1974
1975    for (key, val) in params_map.iter() {
1976        let prop = vm_resolve_param_type(val, components);
1977        properties.insert(key.clone(), prop);
1978        required.push(VmValue::String(Rc::from(key.as_str())));
1979    }
1980
1981    schema.insert("properties".to_string(), VmValue::Dict(Rc::new(properties)));
1982    if !required.is_empty() {
1983        required.sort_by_key(|a| a.display());
1984        schema.insert("required".to_string(), VmValue::List(Rc::new(required)));
1985    }
1986
1987    VmValue::Dict(Rc::new(schema))
1988}
1989
1990fn vm_resolve_param_type(val: &VmValue, components: Option<&BTreeMap<String, VmValue>>) -> VmValue {
1991    match val {
1992        VmValue::String(type_name) => {
1993            let json_type = vm_harn_type_to_json_schema(type_name);
1994            let mut prop = BTreeMap::new();
1995            prop.insert("type".to_string(), VmValue::String(Rc::from(json_type)));
1996            VmValue::Dict(Rc::new(prop))
1997        }
1998        VmValue::Dict(map) => {
1999            if let Some(VmValue::String(ref_name)) = map.get("$ref") {
2000                if let Some(comps) = components {
2001                    if let Some(resolved) = comps.get(&**ref_name) {
2002                        return resolved.clone();
2003                    }
2004                }
2005                let mut prop = BTreeMap::new();
2006                prop.insert(
2007                    "$ref".to_string(),
2008                    VmValue::String(Rc::from(
2009                        format!("#/components/schemas/{ref_name}").as_str(),
2010                    )),
2011                );
2012                VmValue::Dict(Rc::new(prop))
2013            } else {
2014                VmValue::Dict(Rc::new((**map).clone()))
2015            }
2016        }
2017        _ => {
2018            let mut prop = BTreeMap::new();
2019            prop.insert("type".to_string(), VmValue::String(Rc::from("string")));
2020            VmValue::Dict(Rc::new(prop))
2021        }
2022    }
2023}
2024
2025fn vm_harn_type_to_json_schema(harn_type: &str) -> &str {
2026    match harn_type {
2027        "int" => "integer",
2028        "float" => "number",
2029        "bool" | "boolean" => "boolean",
2030        "list" | "array" => "array",
2031        "dict" | "object" => "object",
2032        _ => "string",
2033    }
2034}