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    // Logging builtins
632    // =========================================================================
633
634    vm.register_builtin("log_debug", |args, out| {
635        vm_write_log("debug", 0, args, out);
636        Ok(VmValue::Nil)
637    });
638
639    vm.register_builtin("log_info", |args, out| {
640        vm_write_log("info", 1, args, out);
641        Ok(VmValue::Nil)
642    });
643
644    vm.register_builtin("log_warn", |args, out| {
645        vm_write_log("warn", 2, args, out);
646        Ok(VmValue::Nil)
647    });
648
649    vm.register_builtin("log_error", |args, out| {
650        vm_write_log("error", 3, args, out);
651        Ok(VmValue::Nil)
652    });
653
654    vm.register_builtin("log_set_level", |args, _out| {
655        let level_str = args.first().map(|a| a.display()).unwrap_or_default();
656        match vm_level_to_u8(&level_str) {
657            Some(n) => {
658                VM_MIN_LOG_LEVEL.store(n, Ordering::Relaxed);
659                Ok(VmValue::Nil)
660            }
661            None => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
662                "log_set_level: invalid level '{}'. Expected debug, info, warn, or error",
663                level_str
664            ))))),
665        }
666    });
667
668    // =========================================================================
669    // Tracing builtins
670    // =========================================================================
671
672    vm.register_builtin("trace_start", |args, _out| {
673        use rand::Rng;
674        let name = args.first().map(|a| a.display()).unwrap_or_default();
675        let trace_id = VM_TRACE_STACK.with(|stack| {
676            stack
677                .borrow()
678                .last()
679                .map(|t| t.trace_id.clone())
680                .unwrap_or_else(|| {
681                    let val: u32 = rand::thread_rng().gen();
682                    format!("{val:08x}")
683                })
684        });
685        let span_id = {
686            let val: u32 = rand::thread_rng().gen();
687            format!("{val:08x}")
688        };
689        let start_ms = std::time::SystemTime::now()
690            .duration_since(std::time::UNIX_EPOCH)
691            .unwrap_or_default()
692            .as_millis() as i64;
693
694        VM_TRACE_STACK.with(|stack| {
695            stack.borrow_mut().push(VmTraceContext {
696                trace_id: trace_id.clone(),
697                span_id: span_id.clone(),
698            });
699        });
700
701        let mut span = BTreeMap::new();
702        span.insert(
703            "trace_id".to_string(),
704            VmValue::String(Rc::from(trace_id.as_str())),
705        );
706        span.insert(
707            "span_id".to_string(),
708            VmValue::String(Rc::from(span_id.as_str())),
709        );
710        span.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
711        span.insert("start_ms".to_string(), VmValue::Int(start_ms));
712        Ok(VmValue::Dict(Rc::new(span)))
713    });
714
715    vm.register_builtin("trace_end", |args, out| {
716        let span = match args.first() {
717            Some(VmValue::Dict(d)) => d,
718            _ => {
719                return Err(VmError::Thrown(VmValue::String(Rc::from(
720                    "trace_end: argument must be a span dict from trace_start",
721                ))));
722            }
723        };
724
725        let end_ms = std::time::SystemTime::now()
726            .duration_since(std::time::UNIX_EPOCH)
727            .unwrap_or_default()
728            .as_millis() as i64;
729
730        let start_ms = span
731            .get("start_ms")
732            .and_then(|v| v.as_int())
733            .unwrap_or(end_ms);
734        let duration_ms = end_ms - start_ms;
735        let name = span.get("name").map(|v| v.display()).unwrap_or_default();
736        let trace_id = span
737            .get("trace_id")
738            .map(|v| v.display())
739            .unwrap_or_default();
740        let span_id = span.get("span_id").map(|v| v.display()).unwrap_or_default();
741
742        VM_TRACE_STACK.with(|stack| {
743            stack.borrow_mut().pop();
744        });
745
746        let level_num = 1_u8;
747        if level_num >= VM_MIN_LOG_LEVEL.load(Ordering::Relaxed) {
748            let mut fields = BTreeMap::new();
749            fields.insert(
750                "trace_id".to_string(),
751                VmValue::String(Rc::from(trace_id.as_str())),
752            );
753            fields.insert(
754                "span_id".to_string(),
755                VmValue::String(Rc::from(span_id.as_str())),
756            );
757            fields.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
758            fields.insert("duration_ms".to_string(), VmValue::Int(duration_ms));
759            let line = vm_build_log_line("info", "span_end", Some(&fields));
760            out.push_str(&line);
761        }
762
763        Ok(VmValue::Nil)
764    });
765
766    vm.register_builtin("trace_id", |_args, _out| {
767        let id = VM_TRACE_STACK.with(|stack| stack.borrow().last().map(|t| t.trace_id.clone()));
768        match id {
769            Some(trace_id) => Ok(VmValue::String(Rc::from(trace_id.as_str()))),
770            None => Ok(VmValue::Nil),
771        }
772    });
773
774    // =========================================================================
775    // Tool registry builtins
776    // =========================================================================
777
778    vm.register_builtin("tool_registry", |_args, _out| {
779        let mut registry = BTreeMap::new();
780        registry.insert(
781            "_type".to_string(),
782            VmValue::String(Rc::from("tool_registry")),
783        );
784        registry.insert("tools".to_string(), VmValue::List(Rc::new(Vec::new())));
785        Ok(VmValue::Dict(Rc::new(registry)))
786    });
787
788    vm.register_builtin("tool_add", |args, _out| {
789        if args.len() < 4 {
790            return Err(VmError::Thrown(VmValue::String(Rc::from(
791                "tool_add: requires registry, name, description, and handler",
792            ))));
793        }
794
795        let registry = match &args[0] {
796            VmValue::Dict(map) => (**map).clone(),
797            _ => {
798                return Err(VmError::Thrown(VmValue::String(Rc::from(
799                    "tool_add: first argument must be a tool registry",
800                ))));
801            }
802        };
803
804        match registry.get("_type") {
805            Some(VmValue::String(t)) if &**t == "tool_registry" => {}
806            _ => {
807                return Err(VmError::Thrown(VmValue::String(Rc::from(
808                    "tool_add: first argument must be a tool registry",
809                ))));
810            }
811        }
812
813        let name = args[1].display();
814        let description = args[2].display();
815        let handler = args[3].clone();
816        let parameters = if args.len() > 4 {
817            args[4].clone()
818        } else {
819            VmValue::Dict(Rc::new(BTreeMap::new()))
820        };
821
822        let mut tool_entry = BTreeMap::new();
823        tool_entry.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
824        tool_entry.insert(
825            "description".to_string(),
826            VmValue::String(Rc::from(description.as_str())),
827        );
828        tool_entry.insert("handler".to_string(), handler);
829        tool_entry.insert("parameters".to_string(), parameters);
830
831        let mut tools: Vec<VmValue> = match registry.get("tools") {
832            Some(VmValue::List(list)) => list
833                .iter()
834                .filter(|t| {
835                    if let VmValue::Dict(e) = t {
836                        e.get("name").map(|v| v.display()).as_deref() != Some(name.as_str())
837                    } else {
838                        true
839                    }
840                })
841                .cloned()
842                .collect(),
843            _ => Vec::new(),
844        };
845        tools.push(VmValue::Dict(Rc::new(tool_entry)));
846
847        let mut new_registry = registry;
848        new_registry.insert("tools".to_string(), VmValue::List(Rc::new(tools)));
849        Ok(VmValue::Dict(Rc::new(new_registry)))
850    });
851
852    vm.register_builtin("tool_list", |args, _out| {
853        let registry = match args.first() {
854            Some(VmValue::Dict(map)) => map,
855            _ => {
856                return Err(VmError::Thrown(VmValue::String(Rc::from(
857                    "tool_list: requires a tool registry",
858                ))));
859            }
860        };
861        vm_validate_registry("tool_list", registry)?;
862
863        let tools = vm_get_tools(registry);
864        let mut result = Vec::new();
865        for tool in tools {
866            if let VmValue::Dict(entry) = tool {
867                let mut desc = BTreeMap::new();
868                if let Some(name) = entry.get("name") {
869                    desc.insert("name".to_string(), name.clone());
870                }
871                if let Some(description) = entry.get("description") {
872                    desc.insert("description".to_string(), description.clone());
873                }
874                if let Some(parameters) = entry.get("parameters") {
875                    desc.insert("parameters".to_string(), parameters.clone());
876                }
877                result.push(VmValue::Dict(Rc::new(desc)));
878            }
879        }
880        Ok(VmValue::List(Rc::new(result)))
881    });
882
883    vm.register_builtin("tool_find", |args, _out| {
884        if args.len() < 2 {
885            return Err(VmError::Thrown(VmValue::String(Rc::from(
886                "tool_find: requires registry and name",
887            ))));
888        }
889
890        let registry = match &args[0] {
891            VmValue::Dict(map) => map,
892            _ => {
893                return Err(VmError::Thrown(VmValue::String(Rc::from(
894                    "tool_find: first argument must be a tool registry",
895                ))));
896            }
897        };
898        vm_validate_registry("tool_find", registry)?;
899
900        let target_name = args[1].display();
901        let tools = vm_get_tools(registry);
902
903        for tool in tools {
904            if let VmValue::Dict(entry) = tool {
905                if let Some(VmValue::String(name)) = entry.get("name") {
906                    if &**name == target_name.as_str() {
907                        return Ok(tool.clone());
908                    }
909                }
910            }
911        }
912        Ok(VmValue::Nil)
913    });
914
915    vm.register_builtin("tool_describe", |args, _out| {
916        let registry = match args.first() {
917            Some(VmValue::Dict(map)) => map,
918            _ => {
919                return Err(VmError::Thrown(VmValue::String(Rc::from(
920                    "tool_describe: requires a tool registry",
921                ))));
922            }
923        };
924        vm_validate_registry("tool_describe", registry)?;
925
926        let tools = vm_get_tools(registry);
927
928        if tools.is_empty() {
929            return Ok(VmValue::String(Rc::from("Available tools:\n(none)")));
930        }
931
932        let mut tool_infos: Vec<(String, String, String)> = Vec::new();
933        for tool in tools {
934            if let VmValue::Dict(entry) = tool {
935                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
936                let description = entry
937                    .get("description")
938                    .map(|v| v.display())
939                    .unwrap_or_default();
940                let params_str = vm_format_parameters(entry.get("parameters"));
941                tool_infos.push((name, params_str, description));
942            }
943        }
944
945        tool_infos.sort_by(|a, b| a.0.cmp(&b.0));
946
947        let mut lines = vec!["Available tools:".to_string()];
948        for (name, params, desc) in &tool_infos {
949            lines.push(format!("- {name}({params}): {desc}"));
950        }
951
952        Ok(VmValue::String(Rc::from(lines.join("\n").as_str())))
953    });
954
955    vm.register_builtin("tool_remove", |args, _out| {
956        if args.len() < 2 {
957            return Err(VmError::Thrown(VmValue::String(Rc::from(
958                "tool_remove: requires registry and name",
959            ))));
960        }
961
962        let registry = match &args[0] {
963            VmValue::Dict(map) => (**map).clone(),
964            _ => {
965                return Err(VmError::Thrown(VmValue::String(Rc::from(
966                    "tool_remove: first argument must be a tool registry",
967                ))));
968            }
969        };
970        vm_validate_registry("tool_remove", &registry)?;
971
972        let target_name = args[1].display();
973
974        let tools = match registry.get("tools") {
975            Some(VmValue::List(list)) => (**list).clone(),
976            _ => Vec::new(),
977        };
978
979        let filtered: Vec<VmValue> = tools
980            .into_iter()
981            .filter(|tool| {
982                if let VmValue::Dict(entry) = tool {
983                    if let Some(VmValue::String(name)) = entry.get("name") {
984                        return &**name != target_name.as_str();
985                    }
986                }
987                true
988            })
989            .collect();
990
991        let mut new_registry = registry;
992        new_registry.insert("tools".to_string(), VmValue::List(Rc::new(filtered)));
993        Ok(VmValue::Dict(Rc::new(new_registry)))
994    });
995
996    vm.register_builtin("tool_count", |args, _out| {
997        let registry = match args.first() {
998            Some(VmValue::Dict(map)) => map,
999            _ => {
1000                return Err(VmError::Thrown(VmValue::String(Rc::from(
1001                    "tool_count: requires a tool registry",
1002                ))));
1003            }
1004        };
1005        vm_validate_registry("tool_count", registry)?;
1006        let count = vm_get_tools(registry).len();
1007        Ok(VmValue::Int(count as i64))
1008    });
1009
1010    vm.register_builtin("tool_schema", |args, _out| {
1011        let registry = match args.first() {
1012            Some(VmValue::Dict(map)) => {
1013                vm_validate_registry("tool_schema", map)?;
1014                map
1015            }
1016            _ => {
1017                return Err(VmError::Thrown(VmValue::String(Rc::from(
1018                    "tool_schema: requires a tool registry",
1019                ))));
1020            }
1021        };
1022
1023        let components = args.get(1).and_then(|v| v.as_dict()).cloned();
1024
1025        let tools = match registry.get("tools") {
1026            Some(VmValue::List(list)) => list,
1027            _ => return Ok(VmValue::Dict(Rc::new(vm_build_empty_schema()))),
1028        };
1029
1030        let mut tool_schemas = Vec::new();
1031        for tool in tools.iter() {
1032            if let VmValue::Dict(entry) = tool {
1033                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
1034                let description = entry
1035                    .get("description")
1036                    .map(|v| v.display())
1037                    .unwrap_or_default();
1038
1039                let input_schema =
1040                    vm_build_input_schema(entry.get("parameters"), components.as_ref());
1041
1042                let mut tool_def = BTreeMap::new();
1043                tool_def.insert("name".to_string(), VmValue::String(Rc::from(name.as_str())));
1044                tool_def.insert(
1045                    "description".to_string(),
1046                    VmValue::String(Rc::from(description.as_str())),
1047                );
1048                tool_def.insert("inputSchema".to_string(), input_schema);
1049                tool_schemas.push(VmValue::Dict(Rc::new(tool_def)));
1050            }
1051        }
1052
1053        let mut schema = BTreeMap::new();
1054        schema.insert(
1055            "schema_version".to_string(),
1056            VmValue::String(Rc::from("harn-tools/1.0")),
1057        );
1058
1059        if let Some(comps) = &components {
1060            let mut comp_wrapper = BTreeMap::new();
1061            comp_wrapper.insert("schemas".to_string(), VmValue::Dict(Rc::new(comps.clone())));
1062            schema.insert(
1063                "components".to_string(),
1064                VmValue::Dict(Rc::new(comp_wrapper)),
1065            );
1066        }
1067
1068        schema.insert("tools".to_string(), VmValue::List(Rc::new(tool_schemas)));
1069        Ok(VmValue::Dict(Rc::new(schema)))
1070    });
1071
1072    vm.register_builtin("tool_parse_call", |args, _out| {
1073        let text = args.first().map(|a| a.display()).unwrap_or_default();
1074
1075        let mut results = Vec::new();
1076        let mut search_from = 0;
1077
1078        while let Some(start) = text[search_from..].find("<tool_call>") {
1079            let abs_start = search_from + start + "<tool_call>".len();
1080            if let Some(end) = text[abs_start..].find("</tool_call>") {
1081                let json_str = text[abs_start..abs_start + end].trim();
1082                if let Ok(jv) = serde_json::from_str::<serde_json::Value>(json_str) {
1083                    results.push(json_to_vm_value(&jv));
1084                }
1085                search_from = abs_start + end + "</tool_call>".len();
1086            } else {
1087                break;
1088            }
1089        }
1090
1091        Ok(VmValue::List(Rc::new(results)))
1092    });
1093
1094    vm.register_builtin("tool_format_result", |args, _out| {
1095        if args.len() < 2 {
1096            return Err(VmError::Thrown(VmValue::String(Rc::from(
1097                "tool_format_result: requires name and result",
1098            ))));
1099        }
1100        let name = args[0].display();
1101        let result = args[1].display();
1102
1103        let json_name = vm_escape_json_str(&name);
1104        let json_result = vm_escape_json_str(&result);
1105        Ok(VmValue::String(Rc::from(
1106            format!(
1107                "<tool_result>{{\"name\": \"{json_name}\", \"result\": \"{json_result}\"}}</tool_result>"
1108            )
1109            .as_str(),
1110        )))
1111    });
1112
1113    vm.register_builtin("tool_prompt", |args, _out| {
1114        let registry = match args.first() {
1115            Some(VmValue::Dict(map)) => {
1116                vm_validate_registry("tool_prompt", map)?;
1117                map
1118            }
1119            _ => {
1120                return Err(VmError::Thrown(VmValue::String(Rc::from(
1121                    "tool_prompt: requires a tool registry",
1122                ))));
1123            }
1124        };
1125
1126        let tools = match registry.get("tools") {
1127            Some(VmValue::List(list)) => list,
1128            _ => {
1129                return Ok(VmValue::String(Rc::from("No tools are available.")));
1130            }
1131        };
1132
1133        if tools.is_empty() {
1134            return Ok(VmValue::String(Rc::from("No tools are available.")));
1135        }
1136
1137        let mut prompt = String::from("# Available Tools\n\n");
1138        prompt.push_str("You have access to the following tools. To use a tool, output a tool call in this exact format:\n\n");
1139        prompt.push_str("<tool_call>{\"name\": \"tool_name\", \"arguments\": {\"param\": \"value\"}}</tool_call>\n\n");
1140        prompt.push_str("You may make multiple tool calls in a single response. Wait for tool results before proceeding.\n\n");
1141        prompt.push_str("## Tools\n\n");
1142
1143        let mut tool_infos: Vec<(&BTreeMap<String, VmValue>, String)> = Vec::new();
1144        for tool in tools.iter() {
1145            if let VmValue::Dict(entry) = tool {
1146                let name = entry.get("name").map(|v| v.display()).unwrap_or_default();
1147                tool_infos.push((entry, name));
1148            }
1149        }
1150        tool_infos.sort_by(|a, b| a.1.cmp(&b.1));
1151
1152        for (entry, name) in &tool_infos {
1153            let description = entry
1154                .get("description")
1155                .map(|v| v.display())
1156                .unwrap_or_default();
1157            let params_str = vm_format_parameters(entry.get("parameters"));
1158
1159            prompt.push_str(&format!("### {name}\n"));
1160            prompt.push_str(&format!("{description}\n"));
1161            if !params_str.is_empty() {
1162                prompt.push_str(&format!("Parameters: {params_str}\n"));
1163            }
1164            prompt.push('\n');
1165        }
1166
1167        Ok(VmValue::String(Rc::from(prompt.trim_end())))
1168    });
1169
1170    // =========================================================================
1171    // Channel builtins (sync)
1172    // =========================================================================
1173
1174    vm.register_builtin("channel", |args, _out| {
1175        let name = args
1176            .first()
1177            .map(|a| a.display())
1178            .unwrap_or_else(|| "default".to_string());
1179        let capacity = args.get(1).and_then(|a| a.as_int()).unwrap_or(256) as usize;
1180        let capacity = capacity.max(1);
1181        let (tx, rx) = tokio::sync::mpsc::channel(capacity);
1182        #[allow(clippy::arc_with_non_send_sync)]
1183        Ok(VmValue::Channel(VmChannelHandle {
1184            name,
1185            sender: Arc::new(tx),
1186            receiver: Arc::new(tokio::sync::Mutex::new(rx)),
1187            closed: Arc::new(AtomicBool::new(false)),
1188        }))
1189    });
1190
1191    vm.register_builtin("close_channel", |args, _out| {
1192        if args.is_empty() {
1193            return Err(VmError::Thrown(VmValue::String(Rc::from(
1194                "close_channel: requires a channel",
1195            ))));
1196        }
1197        if let VmValue::Channel(ch) = &args[0] {
1198            ch.closed.store(true, Ordering::SeqCst);
1199            Ok(VmValue::Nil)
1200        } else {
1201            Err(VmError::Thrown(VmValue::String(Rc::from(
1202                "close_channel: first argument must be a channel",
1203            ))))
1204        }
1205    });
1206
1207    vm.register_builtin("try_receive", |args, _out| {
1208        if args.is_empty() {
1209            return Err(VmError::Thrown(VmValue::String(Rc::from(
1210                "try_receive: requires a channel",
1211            ))));
1212        }
1213        if let VmValue::Channel(ch) = &args[0] {
1214            match ch.receiver.try_lock() {
1215                Ok(mut rx) => match rx.try_recv() {
1216                    Ok(val) => Ok(val),
1217                    Err(_) => Ok(VmValue::Nil),
1218                },
1219                Err(_) => Ok(VmValue::Nil),
1220            }
1221        } else {
1222            Err(VmError::Thrown(VmValue::String(Rc::from(
1223                "try_receive: first argument must be a channel",
1224            ))))
1225        }
1226    });
1227
1228    // =========================================================================
1229    // Atomic builtins
1230    // =========================================================================
1231
1232    vm.register_builtin("atomic", |args, _out| {
1233        let initial = match args.first() {
1234            Some(VmValue::Int(n)) => *n,
1235            Some(VmValue::Float(f)) => *f as i64,
1236            Some(VmValue::Bool(b)) => {
1237                if *b {
1238                    1
1239                } else {
1240                    0
1241                }
1242            }
1243            _ => 0,
1244        };
1245        Ok(VmValue::Atomic(VmAtomicHandle {
1246            value: Arc::new(AtomicI64::new(initial)),
1247        }))
1248    });
1249
1250    vm.register_builtin("atomic_get", |args, _out| {
1251        if let Some(VmValue::Atomic(a)) = args.first() {
1252            Ok(VmValue::Int(a.value.load(Ordering::SeqCst)))
1253        } else {
1254            Ok(VmValue::Nil)
1255        }
1256    });
1257
1258    vm.register_builtin("atomic_set", |args, _out| {
1259        if args.len() >= 2 {
1260            if let (VmValue::Atomic(a), Some(val)) = (&args[0], args[1].as_int()) {
1261                let old = a.value.swap(val, Ordering::SeqCst);
1262                return Ok(VmValue::Int(old));
1263            }
1264        }
1265        Ok(VmValue::Nil)
1266    });
1267
1268    vm.register_builtin("atomic_add", |args, _out| {
1269        if args.len() >= 2 {
1270            if let (VmValue::Atomic(a), Some(delta)) = (&args[0], args[1].as_int()) {
1271                let prev = a.value.fetch_add(delta, Ordering::SeqCst);
1272                return Ok(VmValue::Int(prev));
1273            }
1274        }
1275        Ok(VmValue::Nil)
1276    });
1277
1278    vm.register_builtin("atomic_cas", |args, _out| {
1279        if args.len() >= 3 {
1280            if let (VmValue::Atomic(a), Some(expected), Some(new_val)) =
1281                (&args[0], args[1].as_int(), args[2].as_int())
1282            {
1283                let result =
1284                    a.value
1285                        .compare_exchange(expected, new_val, Ordering::SeqCst, Ordering::SeqCst);
1286                return Ok(VmValue::Bool(result.is_ok()));
1287            }
1288        }
1289        Ok(VmValue::Bool(false))
1290    });
1291
1292    // =========================================================================
1293    // Async builtins
1294    // =========================================================================
1295
1296    // sleep(ms)
1297    vm.register_async_builtin("sleep", |args| async move {
1298        let ms = match args.first() {
1299            Some(VmValue::Duration(ms)) => *ms,
1300            Some(VmValue::Int(n)) => *n as u64,
1301            _ => 0,
1302        };
1303        if ms > 0 {
1304            tokio::time::sleep(tokio::time::Duration::from_millis(ms)).await;
1305        }
1306        Ok(VmValue::Nil)
1307    });
1308
1309    // send(channel, value)
1310    vm.register_async_builtin("send", |args| async move {
1311        if args.len() < 2 {
1312            return Err(VmError::Thrown(VmValue::String(Rc::from(
1313                "send: requires channel and value",
1314            ))));
1315        }
1316        if let VmValue::Channel(ch) = &args[0] {
1317            if ch.closed.load(Ordering::SeqCst) {
1318                return Ok(VmValue::Bool(false));
1319            }
1320            let val = args[1].clone();
1321            match ch.sender.send(val).await {
1322                Ok(()) => Ok(VmValue::Bool(true)),
1323                Err(_) => Ok(VmValue::Bool(false)),
1324            }
1325        } else {
1326            Err(VmError::Thrown(VmValue::String(Rc::from(
1327                "send: first argument must be a channel",
1328            ))))
1329        }
1330    });
1331
1332    // receive(channel)
1333    vm.register_async_builtin("receive", |args| async move {
1334        if args.is_empty() {
1335            return Err(VmError::Thrown(VmValue::String(Rc::from(
1336                "receive: requires a channel",
1337            ))));
1338        }
1339        if let VmValue::Channel(ch) = &args[0] {
1340            if ch.closed.load(Ordering::SeqCst) {
1341                let mut rx = ch.receiver.lock().await;
1342                return match rx.try_recv() {
1343                    Ok(val) => Ok(val),
1344                    Err(_) => Ok(VmValue::Nil),
1345                };
1346            }
1347            let mut rx = ch.receiver.lock().await;
1348            match rx.recv().await {
1349                Some(val) => Ok(val),
1350                None => Ok(VmValue::Nil),
1351            }
1352        } else {
1353            Err(VmError::Thrown(VmValue::String(Rc::from(
1354                "receive: first argument must be a channel",
1355            ))))
1356        }
1357    });
1358
1359    // select(channel1, channel2, ...)
1360    vm.register_async_builtin("select", |args| async move {
1361        if args.is_empty() {
1362            return Err(VmError::Thrown(VmValue::String(Rc::from(
1363                "select: requires at least one channel",
1364            ))));
1365        }
1366
1367        let mut channels: Vec<&VmChannelHandle> = Vec::new();
1368        for arg in &args {
1369            if let VmValue::Channel(ch) = arg {
1370                channels.push(ch);
1371            } else {
1372                return Err(VmError::Thrown(VmValue::String(Rc::from(
1373                    "select: all arguments must be channels",
1374                ))));
1375            }
1376        }
1377
1378        loop {
1379            let mut all_closed = true;
1380            for (i, ch) in channels.iter().enumerate() {
1381                if let Ok(mut rx) = ch.receiver.try_lock() {
1382                    match rx.try_recv() {
1383                        Ok(val) => {
1384                            let mut result = BTreeMap::new();
1385                            result.insert("index".to_string(), VmValue::Int(i as i64));
1386                            result.insert("value".to_string(), val);
1387                            result.insert(
1388                                "channel".to_string(),
1389                                VmValue::String(Rc::from(ch.name.as_str())),
1390                            );
1391                            return Ok(VmValue::Dict(Rc::new(result)));
1392                        }
1393                        Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
1394                            all_closed = false;
1395                        }
1396                        Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {}
1397                    }
1398                }
1399            }
1400            if all_closed {
1401                return Ok(VmValue::Nil);
1402            }
1403            tokio::time::sleep(tokio::time::Duration::from_millis(1)).await;
1404        }
1405    });
1406
1407    // =========================================================================
1408    // JSON validation and extraction builtins
1409    // =========================================================================
1410
1411    vm.register_builtin("json_validate", |args, _out| {
1412        if args.len() < 2 {
1413            return Err(VmError::Thrown(VmValue::String(Rc::from(
1414                "json_validate requires 2 arguments: data and schema",
1415            ))));
1416        }
1417        let data = &args[0];
1418        let schema = &args[1];
1419        let schema_dict = match schema.as_dict() {
1420            Some(d) => d,
1421            None => {
1422                return Err(VmError::Thrown(VmValue::String(Rc::from(
1423                    "json_validate: schema must be a dict",
1424                ))));
1425            }
1426        };
1427        let mut errors = Vec::new();
1428        validate_value(data, schema_dict, "", &mut errors);
1429        if errors.is_empty() {
1430            Ok(VmValue::Bool(true))
1431        } else {
1432            Err(VmError::Thrown(VmValue::String(Rc::from(
1433                errors.join("; "),
1434            ))))
1435        }
1436    });
1437
1438    vm.register_builtin("json_extract", |args, _out| {
1439        if args.is_empty() {
1440            return Err(VmError::Thrown(VmValue::String(Rc::from(
1441                "json_extract requires at least 1 argument: text",
1442            ))));
1443        }
1444        let text = args[0].display();
1445        let key = args.get(1).map(|a| a.display());
1446
1447        // Extract JSON from text that may contain markdown code fences
1448        let json_str = extract_json_from_text(&text);
1449        let parsed = match serde_json::from_str::<serde_json::Value>(&json_str) {
1450            Ok(jv) => json_to_vm_value(&jv),
1451            Err(e) => {
1452                return Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1453                    "json_extract: failed to parse JSON: {e}"
1454                )))));
1455            }
1456        };
1457
1458        match key {
1459            Some(k) => match &parsed {
1460                VmValue::Dict(map) => match map.get(&k) {
1461                    Some(val) => Ok(val.clone()),
1462                    None => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1463                        "json_extract: key '{}' not found",
1464                        k
1465                    ))))),
1466                },
1467                _ => Err(VmError::Thrown(VmValue::String(Rc::from(
1468                    "json_extract: parsed value is not a dict, cannot extract key",
1469                )))),
1470            },
1471            None => Ok(parsed),
1472        }
1473    });
1474
1475    // =========================================================================
1476    // HTTP and LLM builtins (registered from separate modules)
1477    // =========================================================================
1478
1479    register_http_builtins(vm);
1480    register_llm_builtins(vm);
1481    register_mcp_builtins(vm);
1482}
1483
1484// =============================================================================
1485// JSON helpers
1486// =============================================================================
1487
1488pub(crate) fn escape_json_string_vm(s: &str) -> String {
1489    let mut out = String::with_capacity(s.len() + 2);
1490    out.push('"');
1491    for ch in s.chars() {
1492        match ch {
1493            '"' => out.push_str("\\\""),
1494            '\\' => out.push_str("\\\\"),
1495            '\n' => out.push_str("\\n"),
1496            '\r' => out.push_str("\\r"),
1497            '\t' => out.push_str("\\t"),
1498            c if c.is_control() => {
1499                out.push_str(&format!("\\u{:04x}", c as u32));
1500            }
1501            c => out.push(c),
1502        }
1503    }
1504    out.push('"');
1505    out
1506}
1507
1508pub(crate) fn vm_value_to_json(val: &VmValue) -> String {
1509    match val {
1510        VmValue::String(s) => escape_json_string_vm(s),
1511        VmValue::Int(n) => n.to_string(),
1512        VmValue::Float(n) => n.to_string(),
1513        VmValue::Bool(b) => b.to_string(),
1514        VmValue::Nil => "null".to_string(),
1515        VmValue::List(items) => {
1516            let inner: Vec<String> = items.iter().map(vm_value_to_json).collect();
1517            format!("[{}]", inner.join(","))
1518        }
1519        VmValue::Dict(map) => {
1520            let inner: Vec<String> = map
1521                .iter()
1522                .map(|(k, v)| format!("{}:{}", escape_json_string_vm(k), vm_value_to_json(v)))
1523                .collect();
1524            format!("{{{}}}", inner.join(","))
1525        }
1526        _ => "null".to_string(),
1527    }
1528}
1529
1530pub(crate) fn json_to_vm_value(jv: &serde_json::Value) -> VmValue {
1531    match jv {
1532        serde_json::Value::Null => VmValue::Nil,
1533        serde_json::Value::Bool(b) => VmValue::Bool(*b),
1534        serde_json::Value::Number(n) => {
1535            if let Some(i) = n.as_i64() {
1536                VmValue::Int(i)
1537            } else {
1538                VmValue::Float(n.as_f64().unwrap_or(0.0))
1539            }
1540        }
1541        serde_json::Value::String(s) => VmValue::String(Rc::from(s.as_str())),
1542        serde_json::Value::Array(arr) => {
1543            VmValue::List(Rc::new(arr.iter().map(json_to_vm_value).collect()))
1544        }
1545        serde_json::Value::Object(map) => {
1546            let mut m = BTreeMap::new();
1547            for (k, v) in map {
1548                m.insert(k.clone(), json_to_vm_value(v));
1549            }
1550            VmValue::Dict(Rc::new(m))
1551        }
1552    }
1553}
1554
1555// =============================================================================
1556// Helper: validate a VmValue against a schema dict
1557// =============================================================================
1558
1559fn validate_value(
1560    value: &VmValue,
1561    schema: &BTreeMap<String, VmValue>,
1562    path: &str,
1563    errors: &mut Vec<String>,
1564) {
1565    // Check "type" constraint
1566    if let Some(VmValue::String(expected_type)) = schema.get("type") {
1567        let actual_type = value.type_name();
1568        let type_str: &str = expected_type;
1569        if type_str != "any" && actual_type != type_str {
1570            let location = if path.is_empty() {
1571                "root".to_string()
1572            } else {
1573                path.to_string()
1574            };
1575            errors.push(format!(
1576                "at {}: expected type '{}', got '{}'",
1577                location, type_str, actual_type
1578            ));
1579            return; // No point checking further if type is wrong
1580        }
1581    }
1582
1583    // Check "required" keys (only for dicts)
1584    if let Some(VmValue::List(required_keys)) = schema.get("required") {
1585        if let VmValue::Dict(map) = value {
1586            for key_val in required_keys.iter() {
1587                let key = key_val.display();
1588                if !map.contains_key(&key) {
1589                    let location = if path.is_empty() {
1590                        "root".to_string()
1591                    } else {
1592                        path.to_string()
1593                    };
1594                    errors.push(format!("at {}: missing required key '{}'", location, key));
1595                }
1596            }
1597        }
1598    }
1599
1600    // Check "properties" (only for dicts)
1601    if let Some(VmValue::Dict(prop_schemas)) = schema.get("properties") {
1602        if let VmValue::Dict(map) = value {
1603            for (key, prop_schema) in prop_schemas.iter() {
1604                if let Some(prop_value) = map.get(key) {
1605                    if let Some(prop_schema_dict) = prop_schema.as_dict() {
1606                        let child_path = if path.is_empty() {
1607                            key.clone()
1608                        } else {
1609                            format!("{}.{}", path, key)
1610                        };
1611                        validate_value(prop_value, prop_schema_dict, &child_path, errors);
1612                    }
1613                }
1614            }
1615        }
1616    }
1617
1618    // Check "items" (only for lists)
1619    if let Some(VmValue::Dict(item_schema)) = schema.get("items") {
1620        if let VmValue::List(items) = value {
1621            for (i, item) in items.iter().enumerate() {
1622                let child_path = if path.is_empty() {
1623                    format!("[{}]", i)
1624                } else {
1625                    format!("{}[{}]", path, i)
1626                };
1627                validate_value(item, item_schema, &child_path, errors);
1628            }
1629        }
1630    }
1631}
1632
1633// =============================================================================
1634// Helper: extract JSON from text with possible markdown fences
1635// =============================================================================
1636
1637fn extract_json_from_text(text: &str) -> String {
1638    let trimmed = text.trim();
1639
1640    // Try to find ```json ... ``` or ``` ... ``` code fences
1641    if let Some(start) = trimmed.find("```") {
1642        let after_backticks = &trimmed[start + 3..];
1643        // Skip optional language tag (e.g., "json")
1644        let content_start = if let Some(nl) = after_backticks.find('\n') {
1645            nl + 1
1646        } else {
1647            0
1648        };
1649        let content = &after_backticks[content_start..];
1650        if let Some(end) = content.find("```") {
1651            return content[..end].trim().to_string();
1652        }
1653    }
1654
1655    // No code fences found; try to find JSON object or array boundaries
1656    // Look for first { or [ and last matching } or ]
1657    if let Some(obj_start) = trimmed.find('{') {
1658        if let Some(obj_end) = trimmed.rfind('}') {
1659            if obj_end > obj_start {
1660                return trimmed[obj_start..=obj_end].to_string();
1661            }
1662        }
1663    }
1664    if let Some(arr_start) = trimmed.find('[') {
1665        if let Some(arr_end) = trimmed.rfind(']') {
1666            if arr_end > arr_start {
1667                return trimmed[arr_start..=arr_end].to_string();
1668            }
1669        }
1670    }
1671
1672    // Fall back to trimmed text as-is
1673    trimmed.to_string()
1674}
1675
1676// =============================================================================
1677// Helper: convert process::Output to VmValue dict
1678// =============================================================================
1679
1680fn vm_output_to_value(output: std::process::Output) -> VmValue {
1681    let mut result = BTreeMap::new();
1682    result.insert(
1683        "stdout".to_string(),
1684        VmValue::String(Rc::from(
1685            String::from_utf8_lossy(&output.stdout).to_string().as_str(),
1686        )),
1687    );
1688    result.insert(
1689        "stderr".to_string(),
1690        VmValue::String(Rc::from(
1691            String::from_utf8_lossy(&output.stderr).to_string().as_str(),
1692        )),
1693    );
1694    result.insert(
1695        "status".to_string(),
1696        VmValue::Int(output.status.code().unwrap_or(-1) as i64),
1697    );
1698    result.insert(
1699        "success".to_string(),
1700        VmValue::Bool(output.status.success()),
1701    );
1702    VmValue::Dict(Rc::new(result))
1703}
1704
1705// =============================================================================
1706// Helper: civil date from timestamp (Howard Hinnant's algorithm)
1707// =============================================================================
1708
1709fn vm_civil_from_timestamp(total_secs: u64) -> (i64, i64, i64, i64, i64, i64, i64) {
1710    let days = total_secs / 86400;
1711    let time_of_day = total_secs % 86400;
1712    let hour = (time_of_day / 3600) as i64;
1713    let minute = ((time_of_day % 3600) / 60) as i64;
1714    let second = (time_of_day % 60) as i64;
1715
1716    let z = days as i64 + 719468;
1717    let era = if z >= 0 { z } else { z - 146096 } / 146097;
1718    let doe = (z - era * 146097) as u64;
1719    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
1720    let y = yoe as i64 + era * 400;
1721    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
1722    let mp = (5 * doy + 2) / 153;
1723    let d = (doy - (153 * mp + 2) / 5 + 1) as i64;
1724    let m = if mp < 10 { mp + 3 } else { mp - 9 } as i64;
1725    let y = if m <= 2 { y + 1 } else { y };
1726    let dow = ((days + 4) % 7) as i64;
1727
1728    (y, m, d, hour, minute, second, dow)
1729}
1730
1731// =============================================================================
1732// Logging helpers for VM
1733// =============================================================================
1734
1735pub(crate) static VM_MIN_LOG_LEVEL: AtomicU8 = AtomicU8::new(0);
1736
1737#[derive(Clone)]
1738pub(crate) struct VmTraceContext {
1739    pub(crate) trace_id: String,
1740    pub(crate) span_id: String,
1741}
1742
1743thread_local! {
1744    pub(crate) static VM_TRACE_STACK: std::cell::RefCell<Vec<VmTraceContext>> = const { std::cell::RefCell::new(Vec::new()) };
1745}
1746
1747fn vm_level_to_u8(level: &str) -> Option<u8> {
1748    match level {
1749        "debug" => Some(0),
1750        "info" => Some(1),
1751        "warn" => Some(2),
1752        "error" => Some(3),
1753        _ => None,
1754    }
1755}
1756
1757fn vm_format_timestamp_utc() -> String {
1758    let now = std::time::SystemTime::now()
1759        .duration_since(std::time::UNIX_EPOCH)
1760        .unwrap_or_default();
1761    let total_secs = now.as_secs();
1762    let millis = now.subsec_millis();
1763
1764    let days = total_secs / 86400;
1765    let time_of_day = total_secs % 86400;
1766    let hour = time_of_day / 3600;
1767    let minute = (time_of_day % 3600) / 60;
1768    let second = time_of_day % 60;
1769
1770    let z = days as i64 + 719468;
1771    let era = if z >= 0 { z } else { z - 146096 } / 146097;
1772    let doe = (z - era * 146097) as u64;
1773    let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
1774    let y = yoe as i64 + era * 400;
1775    let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
1776    let mp = (5 * doy + 2) / 153;
1777    let d = doy - (153 * mp + 2) / 5 + 1;
1778    let m = if mp < 10 { mp + 3 } else { mp - 9 };
1779    let y = if m <= 2 { y + 1 } else { y };
1780
1781    format!("{y:04}-{m:02}-{d:02}T{hour:02}:{minute:02}:{second:02}.{millis:03}Z")
1782}
1783
1784pub(crate) fn vm_escape_json_str(s: &str) -> String {
1785    let mut out = String::with_capacity(s.len());
1786    for ch in s.chars() {
1787        match ch {
1788            '"' => out.push_str("\\\""),
1789            '\\' => out.push_str("\\\\"),
1790            '\n' => out.push_str("\\n"),
1791            '\r' => out.push_str("\\r"),
1792            '\t' => out.push_str("\\t"),
1793            c if c.is_control() => {
1794                out.push_str(&format!("\\u{:04x}", c as u32));
1795            }
1796            c => out.push(c),
1797        }
1798    }
1799    out
1800}
1801
1802fn vm_escape_json_str_quoted(s: &str) -> String {
1803    let mut out = String::with_capacity(s.len() + 2);
1804    out.push('"');
1805    out.push_str(&vm_escape_json_str(s));
1806    out.push('"');
1807    out
1808}
1809
1810fn vm_value_to_json_fragment(val: &VmValue) -> String {
1811    match val {
1812        VmValue::String(s) => vm_escape_json_str_quoted(s),
1813        VmValue::Int(n) => n.to_string(),
1814        VmValue::Float(n) => {
1815            if n.is_finite() {
1816                n.to_string()
1817            } else {
1818                "null".to_string()
1819            }
1820        }
1821        VmValue::Bool(b) => b.to_string(),
1822        VmValue::Nil => "null".to_string(),
1823        _ => vm_escape_json_str_quoted(&val.display()),
1824    }
1825}
1826
1827fn vm_build_log_line(level: &str, msg: &str, fields: Option<&BTreeMap<String, VmValue>>) -> String {
1828    let ts = vm_format_timestamp_utc();
1829    let mut parts: Vec<String> = Vec::new();
1830    parts.push(format!("\"ts\":{}", vm_escape_json_str_quoted(&ts)));
1831    parts.push(format!("\"level\":{}", vm_escape_json_str_quoted(level)));
1832    parts.push(format!("\"msg\":{}", vm_escape_json_str_quoted(msg)));
1833
1834    VM_TRACE_STACK.with(|stack| {
1835        if let Some(trace) = stack.borrow().last() {
1836            parts.push(format!(
1837                "\"trace_id\":{}",
1838                vm_escape_json_str_quoted(&trace.trace_id)
1839            ));
1840            parts.push(format!(
1841                "\"span_id\":{}",
1842                vm_escape_json_str_quoted(&trace.span_id)
1843            ));
1844        }
1845    });
1846
1847    if let Some(dict) = fields {
1848        for (k, v) in dict {
1849            parts.push(format!(
1850                "{}:{}",
1851                vm_escape_json_str_quoted(k),
1852                vm_value_to_json_fragment(v)
1853            ));
1854        }
1855    }
1856
1857    format!("{{{}}}\n", parts.join(","))
1858}
1859
1860fn vm_write_log(level: &str, level_num: u8, args: &[VmValue], out: &mut String) {
1861    if level_num < VM_MIN_LOG_LEVEL.load(Ordering::Relaxed) {
1862        return;
1863    }
1864    let msg = args.first().map(|a| a.display()).unwrap_or_default();
1865    let fields = args.get(1).and_then(|v| {
1866        if let VmValue::Dict(d) = v {
1867            Some(&**d)
1868        } else {
1869            None
1870        }
1871    });
1872    let line = vm_build_log_line(level, &msg, fields);
1873    out.push_str(&line);
1874}
1875
1876// =============================================================================
1877// Tool registry helpers for VM
1878// =============================================================================
1879
1880fn vm_validate_registry(name: &str, dict: &BTreeMap<String, VmValue>) -> Result<(), VmError> {
1881    match dict.get("_type") {
1882        Some(VmValue::String(t)) if &**t == "tool_registry" => Ok(()),
1883        _ => Err(VmError::Thrown(VmValue::String(Rc::from(format!(
1884            "{name}: argument must be a tool registry (created with tool_registry())"
1885        ))))),
1886    }
1887}
1888
1889fn vm_get_tools(dict: &BTreeMap<String, VmValue>) -> &[VmValue] {
1890    match dict.get("tools") {
1891        Some(VmValue::List(list)) => list,
1892        _ => &[],
1893    }
1894}
1895
1896fn vm_format_parameters(params: Option<&VmValue>) -> String {
1897    match params {
1898        Some(VmValue::Dict(map)) if !map.is_empty() => {
1899            let mut pairs: Vec<(String, String)> =
1900                map.iter().map(|(k, v)| (k.clone(), v.display())).collect();
1901            pairs.sort_by(|a, b| a.0.cmp(&b.0));
1902            pairs
1903                .iter()
1904                .map(|(k, v)| format!("{k}: {v}"))
1905                .collect::<Vec<_>>()
1906                .join(", ")
1907        }
1908        _ => String::new(),
1909    }
1910}
1911
1912fn vm_build_empty_schema() -> BTreeMap<String, VmValue> {
1913    let mut schema = BTreeMap::new();
1914    schema.insert(
1915        "schema_version".to_string(),
1916        VmValue::String(Rc::from("harn-tools/1.0")),
1917    );
1918    schema.insert("tools".to_string(), VmValue::List(Rc::new(Vec::new())));
1919    schema
1920}
1921
1922fn vm_build_input_schema(
1923    params: Option<&VmValue>,
1924    components: Option<&BTreeMap<String, VmValue>>,
1925) -> VmValue {
1926    let mut schema = BTreeMap::new();
1927    schema.insert("type".to_string(), VmValue::String(Rc::from("object")));
1928
1929    let params_map = match params {
1930        Some(VmValue::Dict(map)) if !map.is_empty() => map,
1931        _ => {
1932            schema.insert(
1933                "properties".to_string(),
1934                VmValue::Dict(Rc::new(BTreeMap::new())),
1935            );
1936            return VmValue::Dict(Rc::new(schema));
1937        }
1938    };
1939
1940    let mut properties = BTreeMap::new();
1941    let mut required = Vec::new();
1942
1943    for (key, val) in params_map.iter() {
1944        let prop = vm_resolve_param_type(val, components);
1945        properties.insert(key.clone(), prop);
1946        required.push(VmValue::String(Rc::from(key.as_str())));
1947    }
1948
1949    schema.insert("properties".to_string(), VmValue::Dict(Rc::new(properties)));
1950    if !required.is_empty() {
1951        required.sort_by_key(|a| a.display());
1952        schema.insert("required".to_string(), VmValue::List(Rc::new(required)));
1953    }
1954
1955    VmValue::Dict(Rc::new(schema))
1956}
1957
1958fn vm_resolve_param_type(val: &VmValue, components: Option<&BTreeMap<String, VmValue>>) -> VmValue {
1959    match val {
1960        VmValue::String(type_name) => {
1961            let json_type = vm_harn_type_to_json_schema(type_name);
1962            let mut prop = BTreeMap::new();
1963            prop.insert("type".to_string(), VmValue::String(Rc::from(json_type)));
1964            VmValue::Dict(Rc::new(prop))
1965        }
1966        VmValue::Dict(map) => {
1967            if let Some(VmValue::String(ref_name)) = map.get("$ref") {
1968                if let Some(comps) = components {
1969                    if let Some(resolved) = comps.get(&**ref_name) {
1970                        return resolved.clone();
1971                    }
1972                }
1973                let mut prop = BTreeMap::new();
1974                prop.insert(
1975                    "$ref".to_string(),
1976                    VmValue::String(Rc::from(
1977                        format!("#/components/schemas/{ref_name}").as_str(),
1978                    )),
1979                );
1980                VmValue::Dict(Rc::new(prop))
1981            } else {
1982                VmValue::Dict(Rc::new((**map).clone()))
1983            }
1984        }
1985        _ => {
1986            let mut prop = BTreeMap::new();
1987            prop.insert("type".to_string(), VmValue::String(Rc::from("string")));
1988            VmValue::Dict(Rc::new(prop))
1989        }
1990    }
1991}
1992
1993fn vm_harn_type_to_json_schema(harn_type: &str) -> &str {
1994    match harn_type {
1995        "int" => "integer",
1996        "float" => "number",
1997        "bool" | "boolean" => "boolean",
1998        "list" | "array" => "array",
1999        "dict" | "object" => "object",
2000        _ => "string",
2001    }
2002}