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
14pub 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 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 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 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 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 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 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 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 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 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", ®istry)?;
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 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 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 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 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 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 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 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 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 register_http_builtins(vm);
1480 register_llm_builtins(vm);
1481 register_mcp_builtins(vm);
1482}
1483
1484pub(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
1555fn validate_value(
1560 value: &VmValue,
1561 schema: &BTreeMap<String, VmValue>,
1562 path: &str,
1563 errors: &mut Vec<String>,
1564) {
1565 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; }
1581 }
1582
1583 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 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 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
1633fn extract_json_from_text(text: &str) -> String {
1638 let trimmed = text.trim();
1639
1640 if let Some(start) = trimmed.find("```") {
1642 let after_backticks = &trimmed[start + 3..];
1643 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 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 trimmed.to_string()
1674}
1675
1676fn 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
1705fn 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
1731pub(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
1876fn 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}