1use std::future::Future;
2use std::pin::Pin;
3use std::rc::Rc;
4
5use crate::chunk::CompiledFunction;
6use crate::value::{ErrorCategory, VmClosure, VmError, VmValue};
7
8use super::async_builtin::CURRENT_ASYNC_BUILTIN_CHILD_VM;
9use super::{ScopeSpan, Vm};
10
11impl Vm {
12 pub fn register_builtin<F>(&mut self, name: &str, f: F)
14 where
15 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
16 {
17 self.builtins.insert(name.to_string(), Rc::new(f));
18 }
19
20 pub fn unregister_builtin(&mut self, name: &str) {
22 self.builtins.remove(name);
23 }
24
25 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
27 where
28 F: Fn(Vec<VmValue>) -> Fut + 'static,
29 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
30 {
31 self.async_builtins
32 .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
33 }
34
35 pub(crate) fn call_closure<'a>(
38 &'a mut self,
39 closure: &'a VmClosure,
40 args: &'a [VmValue],
41 _parent_functions: &'a [CompiledFunction],
42 ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
43 Box::pin(async move {
44 let saved_env = self.env.clone();
45 let saved_frames = std::mem::take(&mut self.frames);
46 let saved_handlers = std::mem::take(&mut self.exception_handlers);
47 let saved_iterators = std::mem::take(&mut self.iterators);
48 let saved_deadlines = std::mem::take(&mut self.deadlines);
49
50 let mut call_env = Self::closure_call_env(&saved_env, closure);
51 call_env.push_scope();
52
53 let default_start = closure
54 .func
55 .default_start
56 .unwrap_or(closure.func.params.len());
57 let param_count = closure.func.params.len();
58 for (i, param) in closure.func.params.iter().enumerate() {
59 if closure.func.has_rest_param && i == param_count - 1 {
60 let rest_args = if i < args.len() {
61 args[i..].to_vec()
62 } else {
63 Vec::new()
64 };
65 let _ =
66 call_env.define(param, VmValue::List(std::rc::Rc::new(rest_args)), false);
67 } else if i < args.len() {
68 let _ = call_env.define(param, args[i].clone(), false);
69 } else if i < default_start {
70 let _ = call_env.define(param, VmValue::Nil, false);
71 }
72 }
73
74 self.env = call_env;
75 let argc = args.len();
76 let saved_source_dir = if let Some(ref dir) = closure.source_dir {
77 let prev = crate::stdlib::process::VM_SOURCE_DIR.with(|sd| sd.borrow().clone());
78 crate::stdlib::set_thread_source_dir(dir);
79 prev
80 } else {
81 None
82 };
83 let result = self
84 .run_chunk_entry(
85 &closure.func.chunk,
86 argc,
87 saved_source_dir,
88 closure.module_functions.clone(),
89 closure.module_state.clone(),
90 )
91 .await;
92
93 self.env = saved_env;
94 self.frames = saved_frames;
95 self.exception_handlers = saved_handlers;
96 self.iterators = saved_iterators;
97 self.deadlines = saved_deadlines;
98
99 result
100 })
101 }
102
103 #[allow(clippy::manual_async_fn)]
108 pub(crate) fn call_callable_value<'a>(
109 &'a mut self,
110 callable: &'a VmValue,
111 args: &'a [VmValue],
112 functions: &'a [CompiledFunction],
113 ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
114 Box::pin(async move {
115 match callable {
116 VmValue::Closure(closure) => self.call_closure(closure, args, functions).await,
117 VmValue::BuiltinRef(name) => {
118 let name_owned = name.to_string();
119 self.call_named_builtin(&name_owned, args.to_vec()).await
120 }
121 other => Err(VmError::TypeError(format!(
122 "expected callable, got {}",
123 other.type_name()
124 ))),
125 }
126 })
127 }
128
129 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
131 matches!(v, VmValue::Closure(_) | VmValue::BuiltinRef(_))
132 }
133
134 pub async fn call_closure_pub(
137 &mut self,
138 closure: &VmClosure,
139 args: &[VmValue],
140 functions: &[CompiledFunction],
141 ) -> Result<VmValue, VmError> {
142 self.call_closure(closure, args, functions).await
143 }
144
145 pub(crate) async fn call_named_builtin(
148 &mut self,
149 name: &str,
150 args: Vec<VmValue>,
151 ) -> Result<VmValue, VmError> {
152 let span_kind = match name {
154 "llm_call" | "llm_stream" | "agent_loop" => Some(crate::tracing::SpanKind::LlmCall),
155 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
156 _ => None,
157 };
158 let _span = span_kind.map(|kind| ScopeSpan::new(kind, name.to_string()));
159
160 if self.denied_builtins.contains(name) {
162 return Err(VmError::CategorizedError {
163 message: format!("Tool '{}' is not permitted.", name),
164 category: ErrorCategory::ToolRejected,
165 });
166 }
167 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
168 if let Some(builtin) = self.builtins.get(name).cloned() {
169 builtin(&args, &mut self.output)
170 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
171 CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
172 slot.borrow_mut().push(self.child_vm());
173 });
174 let result = async_builtin(args).await;
175 CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
176 slot.borrow_mut().pop();
177 });
178 result
179 } else if let Some(bridge) = &self.bridge {
180 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
181 let args_json: Vec<serde_json::Value> =
182 args.iter().map(crate::llm::vm_value_to_json).collect();
183 let result = bridge
184 .call(
185 "builtin_call",
186 serde_json::json!({"name": name, "args": args_json}),
187 )
188 .await?;
189 Ok(crate::bridge::json_result_to_vm_value(&result))
190 } else {
191 let all_builtins = self
192 .builtins
193 .keys()
194 .chain(self.async_builtins.keys())
195 .map(|s| s.as_str());
196 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
197 return Err(VmError::Runtime(format!(
198 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
199 )));
200 }
201 Err(VmError::UndefinedBuiltin(name.to_string()))
202 }
203 }
204}