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