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