1use std::future::Future;
2use std::pin::Pin;
3use std::rc::Rc;
4
5use crate::value::{ErrorCategory, VmClosure, VmError, VmValue};
6use crate::BuiltinId;
7
8use super::async_builtin::CURRENT_ASYNC_BUILTIN_CHILD_VM;
9use super::{ScopeSpan, Vm, VmBuiltinDispatch, VmBuiltinEntry};
10
11impl Vm {
12 fn index_builtin_id(&mut self, name: &str, dispatch: VmBuiltinDispatch) {
13 let id = BuiltinId::from_name(name);
14 if self.builtin_id_collisions.contains(&id) {
15 return;
16 }
17 if let Some(existing) = self.builtins_by_id.get(&id) {
18 if existing.name.as_ref() != name {
19 self.builtins_by_id.remove(&id);
20 self.builtin_id_collisions.insert(id);
21 return;
22 }
23 }
24 self.builtins_by_id.insert(
25 id,
26 VmBuiltinEntry {
27 name: Rc::from(name),
28 dispatch,
29 },
30 );
31 }
32
33 fn refresh_builtin_id(&mut self, name: &str) {
34 if let Some(builtin) = self.builtins.get(name).cloned() {
35 self.index_builtin_id(name, VmBuiltinDispatch::Sync(builtin));
36 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
37 self.index_builtin_id(name, VmBuiltinDispatch::Async(async_builtin));
38 } else {
39 let id = BuiltinId::from_name(name);
40 if self
41 .builtins_by_id
42 .get(&id)
43 .is_some_and(|entry| entry.name.as_ref() == name)
44 {
45 self.builtins_by_id.remove(&id);
46 }
47 }
48 }
49
50 pub fn register_builtin<F>(&mut self, name: &str, f: F)
52 where
53 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
54 {
55 self.builtins.insert(name.to_string(), Rc::new(f));
56 self.refresh_builtin_id(name);
57 }
58
59 pub fn unregister_builtin(&mut self, name: &str) {
61 self.builtins.remove(name);
62 self.refresh_builtin_id(name);
63 }
64
65 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
67 where
68 F: Fn(Vec<VmValue>) -> Fut + 'static,
69 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
70 {
71 self.async_builtins
72 .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
73 self.refresh_builtin_id(name);
74 }
75
76 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
77 let id = BuiltinId::from_name(name);
78 if self
79 .builtins_by_id
80 .get(&id)
81 .is_some_and(|entry| entry.name.as_ref() == name)
82 {
83 Some(id)
84 } else {
85 None
86 }
87 }
88
89 pub(crate) fn call_closure<'a>(
92 &'a mut self,
93 closure: &'a VmClosure,
94 args: &'a [VmValue],
95 ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
96 Box::pin(async move {
97 let saved_env = self.env.clone();
98 let mut call_env = self.closure_call_env_for_current_frame(closure);
99 let saved_frames = std::mem::take(&mut self.frames);
100 let saved_handlers = std::mem::take(&mut self.exception_handlers);
101 let saved_iterators = std::mem::take(&mut self.iterators);
102 let saved_deadlines = std::mem::take(&mut self.deadlines);
103
104 call_env.push_scope();
105
106 self.env = call_env;
107 let argc = args.len();
108 let mut local_slots = Self::fresh_local_slots(&closure.func.chunk);
109 Self::bind_param_slots(&mut local_slots, &closure.func, args, false);
110 let saved_source_dir = if let Some(ref dir) = closure.source_dir {
111 let prev = crate::stdlib::process::VM_SOURCE_DIR.with(|sd| sd.borrow().clone());
112 crate::stdlib::set_thread_source_dir(dir);
113 prev
114 } else {
115 None
116 };
117 let result = self
118 .run_chunk_ref(
119 Rc::clone(&closure.func.chunk),
120 argc,
121 saved_source_dir,
122 closure.module_functions.clone(),
123 closure.module_state.clone(),
124 Some(local_slots),
125 )
126 .await;
127
128 self.env = saved_env;
129 self.frames = saved_frames;
130 self.exception_handlers = saved_handlers;
131 self.iterators = saved_iterators;
132 self.deadlines = saved_deadlines;
133
134 result
135 })
136 }
137
138 #[allow(clippy::manual_async_fn)]
143 pub(crate) fn call_callable_value<'a>(
144 &'a mut self,
145 callable: &'a VmValue,
146 args: &'a [VmValue],
147 ) -> Pin<Box<dyn Future<Output = Result<VmValue, VmError>> + 'a>> {
148 Box::pin(async move {
149 match callable {
150 VmValue::Closure(closure) => self.call_closure(closure, args).await,
151 VmValue::BuiltinRef(name) => {
152 if let Some(result) = self.call_sync_builtin_by_ref(name, args) {
153 result
154 } else {
155 self.call_named_builtin(name, args.to_vec()).await
156 }
157 }
158 VmValue::BuiltinRefId { id, name } => {
159 self.call_builtin_id_or_name(*id, name, args.to_vec()).await
160 }
161 other => Err(VmError::TypeError(format!(
162 "expected callable, got {}",
163 other.type_name()
164 ))),
165 }
166 })
167 }
168
169 fn call_sync_builtin_by_ref(
170 &mut self,
171 name: &str,
172 args: &[VmValue],
173 ) -> Option<Result<VmValue, VmError>> {
174 let builtin = self.builtins.get(name).cloned()?;
175
176 let span_kind = match name {
177 "llm_call" | "llm_stream" | "agent_loop" => Some(crate::tracing::SpanKind::LlmCall),
178 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
179 _ => None,
180 };
181 let _span = span_kind.map(|kind| ScopeSpan::new(kind, name.to_string()));
182
183 if self.denied_builtins.contains(name) {
184 return Some(Err(VmError::CategorizedError {
185 message: format!("Tool '{}' is not permitted.", name),
186 category: ErrorCategory::ToolRejected,
187 }));
188 }
189 if let Err(err) = crate::orchestration::enforce_current_policy_for_builtin(name, args) {
190 return Some(Err(err));
191 }
192
193 Some(builtin(args, &mut self.output))
194 }
195
196 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
198 matches!(
199 v,
200 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId { .. }
201 )
202 }
203
204 pub async fn call_closure_pub(
207 &mut self,
208 closure: &VmClosure,
209 args: &[VmValue],
210 ) -> Result<VmValue, VmError> {
211 self.call_closure(closure, args).await
212 }
213
214 pub(crate) async fn call_named_builtin(
217 &mut self,
218 name: &str,
219 args: Vec<VmValue>,
220 ) -> Result<VmValue, VmError> {
221 self.call_builtin_impl(name, args, None).await
222 }
223
224 pub(crate) async fn call_builtin_id_or_name(
225 &mut self,
226 id: BuiltinId,
227 name: &str,
228 args: Vec<VmValue>,
229 ) -> Result<VmValue, VmError> {
230 self.call_builtin_impl(name, args, Some(id)).await
231 }
232
233 async fn call_builtin_impl(
234 &mut self,
235 name: &str,
236 args: Vec<VmValue>,
237 direct_id: Option<BuiltinId>,
238 ) -> Result<VmValue, VmError> {
239 let span_kind = match name {
241 "llm_call" | "llm_stream" | "agent_loop" => Some(crate::tracing::SpanKind::LlmCall),
242 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
243 _ => None,
244 };
245 let _span = span_kind.map(|kind| ScopeSpan::new(kind, name.to_string()));
246
247 if self.denied_builtins.contains(name) {
249 return Err(VmError::CategorizedError {
250 message: format!("Tool '{}' is not permitted.", name),
251 category: ErrorCategory::ToolRejected,
252 });
253 }
254 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
255
256 if let Some(id) = direct_id {
257 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
258 if entry.name.as_ref() == name {
259 return self.call_builtin_entry(entry.dispatch, args).await;
260 }
261 }
262 }
263
264 if let Some(builtin) = self.builtins.get(name).cloned() {
265 self.call_builtin_entry(VmBuiltinDispatch::Sync(builtin), args)
266 .await
267 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
268 self.call_builtin_entry(VmBuiltinDispatch::Async(async_builtin), args)
269 .await
270 } else if let Some(bridge) = &self.bridge {
271 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
272 let args_json: Vec<serde_json::Value> =
273 args.iter().map(crate::llm::vm_value_to_json).collect();
274 let result = bridge
275 .call(
276 "builtin_call",
277 serde_json::json!({"name": name, "args": args_json}),
278 )
279 .await?;
280 Ok(crate::bridge::json_result_to_vm_value(&result))
281 } else {
282 let all_builtins = self
283 .builtins
284 .keys()
285 .chain(self.async_builtins.keys())
286 .map(|s| s.as_str());
287 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
288 return Err(VmError::Runtime(format!(
289 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
290 )));
291 }
292 Err(VmError::UndefinedBuiltin(name.to_string()))
293 }
294 }
295
296 async fn call_builtin_entry(
297 &mut self,
298 dispatch: VmBuiltinDispatch,
299 args: Vec<VmValue>,
300 ) -> Result<VmValue, VmError> {
301 match dispatch {
302 VmBuiltinDispatch::Sync(builtin) => builtin(&args, &mut self.output),
303 VmBuiltinDispatch::Async(async_builtin) => {
304 CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
305 slot.borrow_mut().push(self.child_vm());
306 });
307 let result = async_builtin(args).await;
308 CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
309 slot.borrow_mut().pop();
310 });
311 result
312 }
313 }
314 }
315}