1use std::future::Future;
2use std::rc::Rc;
3
4use crate::value::{ErrorCategory, VmBuiltinFn, VmClosure, VmError, VmValue};
5use crate::BuiltinId;
6
7use super::async_builtin::CURRENT_ASYNC_BUILTIN_CHILD_VM;
8use super::{
9 CallArgs, ScopeSpan, Vm, VmBuiltinDispatch, VmBuiltinEntry, VmBuiltinKind, VmBuiltinMetadata,
10};
11
12impl Vm {
13 fn builtin_span_kind(name: &str) -> Option<crate::tracing::SpanKind> {
14 match name {
15 "llm_call" | "llm_stream" | "llm_stream_call" | "agent_loop" | "agent_turn" => {
16 Some(crate::tracing::SpanKind::LlmCall)
17 }
18 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
19 _ => None,
20 }
21 }
22
23 fn is_runtime_context_builtin(name: &str) -> bool {
24 matches!(
25 name,
26 "runtime_context"
27 | "task_current"
28 | "runtime_context_values"
29 | "runtime_context_get"
30 | "runtime_context_set"
31 | "runtime_context_clear"
32 )
33 }
34
35 fn resolve_sync_builtin_id_or_name(
36 &self,
37 direct_id: Option<BuiltinId>,
38 name: &str,
39 ) -> Option<Result<VmBuiltinFn, VmError>> {
40 if crate::autonomy::needs_async_side_effect_enforcement(name)
41 || Self::is_runtime_context_builtin(name)
42 {
43 return None;
44 }
45
46 let dispatch = if let Some(id) = direct_id {
47 self.builtins_by_id
48 .get(&id)
49 .filter(|entry| entry.name.as_ref() == name)
50 .map(|entry| entry.dispatch.clone())
51 } else {
52 None
53 }
54 .or_else(|| {
55 self.builtins
56 .get(name)
57 .cloned()
58 .map(VmBuiltinDispatch::Sync)
59 });
60
61 let Some(dispatch) = dispatch else {
62 if self.async_builtins.contains_key(name) || self.bridge.is_some() {
63 return None;
64 }
65 let all_builtins = self
66 .builtins
67 .keys()
68 .chain(self.async_builtins.keys())
69 .map(|s| s.as_str());
70 return Some(
71 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
72 Err(VmError::Runtime(format!(
73 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
74 )))
75 } else {
76 Err(VmError::UndefinedBuiltin(name.to_string()))
77 },
78 );
79 };
80
81 match dispatch {
82 VmBuiltinDispatch::Sync(builtin) => Some(Ok(builtin)),
83 VmBuiltinDispatch::Async(_) => None,
84 }
85 }
86
87 fn validate_sync_builtin_args(&self, name: &str, args: &[VmValue]) -> Result<(), VmError> {
88 if self.denied_builtins.contains(name) {
89 return Err(VmError::CategorizedError {
90 message: format!("Tool '{}' is not permitted.", name),
91 category: ErrorCategory::ToolRejected,
92 });
93 }
94 crate::orchestration::enforce_current_policy_for_builtin(name, args)?;
95 crate::typecheck::validate_builtin_call(name, args, None)
96 }
97
98 fn index_builtin_id(&mut self, name: &str, dispatch: VmBuiltinDispatch) {
99 let id = BuiltinId::from_name(name);
100 if self.builtin_id_collisions.contains(&id) {
101 return;
102 }
103 if let Some(existing) = self.builtins_by_id.get(&id) {
104 if existing.name.as_ref() != name {
105 Rc::make_mut(&mut self.builtins_by_id).remove(&id);
106 Rc::make_mut(&mut self.builtin_id_collisions).insert(id);
107 return;
108 }
109 }
110 Rc::make_mut(&mut self.builtins_by_id).insert(
111 id,
112 VmBuiltinEntry {
113 name: Rc::from(name),
114 dispatch,
115 },
116 );
117 }
118
119 fn refresh_builtin_id(&mut self, name: &str) {
120 if let Some(builtin) = self.builtins.get(name).cloned() {
121 self.index_builtin_id(name, VmBuiltinDispatch::Sync(builtin));
122 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
123 self.index_builtin_id(name, VmBuiltinDispatch::Async(async_builtin));
124 } else {
125 let id = BuiltinId::from_name(name);
126 if self
127 .builtins_by_id
128 .get(&id)
129 .is_some_and(|entry| entry.name.as_ref() == name)
130 {
131 Rc::make_mut(&mut self.builtins_by_id).remove(&id);
132 }
133 }
134 }
135
136 pub fn register_builtin<F>(&mut self, name: &str, f: F)
138 where
139 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
140 {
141 Rc::make_mut(&mut self.builtins).insert(name.to_string(), Rc::new(f));
142 Rc::make_mut(&mut self.builtin_metadata)
143 .insert(name.to_string(), VmBuiltinMetadata::sync(name.to_string()));
144 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(name);
145 self.refresh_builtin_id(name);
146 }
147
148 pub fn register_builtin_with_metadata<F>(&mut self, metadata: VmBuiltinMetadata, f: F)
150 where
151 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
152 {
153 let name = metadata.name().to_string();
154 Rc::make_mut(&mut self.builtins).insert(name.clone(), Rc::new(f));
155 Rc::make_mut(&mut self.builtin_metadata)
156 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Sync));
157 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(&name);
158 self.refresh_builtin_id(&name);
159 }
160
161 pub fn unregister_builtin(&mut self, name: &str) {
163 Rc::make_mut(&mut self.builtins).remove(name);
164 if self.async_builtins.contains_key(name) {
165 Rc::make_mut(&mut self.builtin_metadata).insert(
166 name.to_string(),
167 VmBuiltinMetadata::async_builtin(name.to_string()),
168 );
169 } else {
170 Rc::make_mut(&mut self.builtin_metadata).remove(name);
171 }
172 self.refresh_builtin_id(name);
173 }
174
175 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
177 where
178 F: Fn(Vec<VmValue>) -> Fut + 'static,
179 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
180 {
181 Rc::make_mut(&mut self.async_builtins)
182 .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
183 Rc::make_mut(&mut self.builtin_metadata).insert(
184 name.to_string(),
185 VmBuiltinMetadata::async_builtin(name.to_string()),
186 );
187 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(name);
188 self.refresh_builtin_id(name);
189 }
190
191 pub fn register_async_builtin_with_metadata<F, Fut>(
193 &mut self,
194 metadata: VmBuiltinMetadata,
195 f: F,
196 ) where
197 F: Fn(Vec<VmValue>) -> Fut + 'static,
198 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
199 {
200 let name = metadata.name().to_string();
201 Rc::make_mut(&mut self.async_builtins)
202 .insert(name.clone(), Rc::new(move |args| Box::pin(f(args))));
203 Rc::make_mut(&mut self.builtin_metadata)
204 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
205 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(&name);
206 self.refresh_builtin_id(&name);
207 }
208
209 pub(crate) fn register_deferred_builtin(&mut self, name: &str, registrar: fn(&mut Vm)) {
212 if self.builtins.contains_key(name) || self.async_builtins.contains_key(name) {
213 return;
214 }
215 Rc::make_mut(&mut self.deferred_builtin_registrars).insert(name.to_string(), registrar);
216 }
217
218 pub(crate) fn ensure_deferred_builtin(&mut self, name: &str) -> bool {
219 let Some(registrar) = self.deferred_builtin_registrars.get(name).copied() else {
220 return false;
221 };
222 registrar(self);
223 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(name);
224 self.builtins.contains_key(name) || self.async_builtins.contains_key(name)
225 }
226
227 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
228 let id = BuiltinId::from_name(name);
229 if self
230 .builtins_by_id
231 .get(&id)
232 .is_some_and(|entry| entry.name.as_ref() == name)
233 {
234 Some(id)
235 } else {
236 None
237 }
238 }
239
240 pub(crate) async fn call_closure(
261 &mut self,
262 closure: &VmClosure,
263 args: &[VmValue],
264 ) -> Result<VmValue, VmError> {
265 self.call_closure_args(closure, CallArgs::Slice(args)).await
266 }
267
268 pub(crate) async fn call_closure_args(
269 &mut self,
270 closure: &VmClosure,
271 args: CallArgs<'_>,
272 ) -> Result<VmValue, VmError> {
273 let saved_handlers = std::mem::take(&mut self.exception_handlers);
274 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
275 .then(crate::step_runtime::take_active_context);
276
277 let target_frame_depth = self.frames.len();
278 let frame_result = self.push_closure_frame_args(closure, &args);
279 drop(args);
280 let result = match frame_result {
281 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
282 Err(e) => Err(e),
283 };
284
285 self.exception_handlers = saved_handlers;
286 if let Some(ctx) = active_context {
287 crate::step_runtime::restore_active_context(ctx);
288 }
289
290 result
291 }
292
293 pub(crate) async fn call_callable_value(
298 &mut self,
299 callable: &VmValue,
300 args: &[VmValue],
301 ) -> Result<VmValue, VmError> {
302 self.call_callable_args(callable, CallArgs::Slice(args))
303 .await
304 }
305
306 pub(crate) async fn call_callable_owned(
307 &mut self,
308 callable: &VmValue,
309 args: Vec<VmValue>,
310 ) -> Result<VmValue, VmError> {
311 self.call_callable_args(callable, CallArgs::Owned(args))
312 .await
313 }
314
315 pub(crate) async fn call_callable_zero(
316 &mut self,
317 callable: &VmValue,
318 ) -> Result<VmValue, VmError> {
319 self.call_callable_args(callable, CallArgs::Empty).await
320 }
321
322 pub(crate) async fn call_callable_one(
323 &mut self,
324 callable: &VmValue,
325 arg: &VmValue,
326 ) -> Result<VmValue, VmError> {
327 self.call_callable_args(callable, CallArgs::One(arg)).await
328 }
329
330 pub(crate) async fn call_callable_two(
331 &mut self,
332 callable: &VmValue,
333 first: &VmValue,
334 second: &VmValue,
335 ) -> Result<VmValue, VmError> {
336 self.call_callable_args(callable, CallArgs::Two(first, second))
337 .await
338 }
339
340 pub(crate) async fn call_callable_args(
341 &mut self,
342 callable: &VmValue,
343 args: CallArgs<'_>,
344 ) -> Result<VmValue, VmError> {
345 match callable {
346 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
347 VmValue::BuiltinRef(name) => {
348 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
349 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
350 return result;
351 }
352 }
353 self.call_named_builtin(name, args.into_vec()).await
354 }
355 VmValue::BuiltinRefId { id, name } => {
356 if let Some(result) =
357 self.try_call_sync_builtin_id_or_name_args(Some(*id), name, &args)
358 {
359 return result;
360 }
361 self.call_builtin_id_or_name(*id, name, args.into_vec())
362 .await
363 }
364 other => Err(VmError::TypeError(format!(
365 "expected callable, got {}",
366 other.type_name()
367 ))),
368 }
369 }
370
371 fn call_sync_builtin_by_ref_args(
372 &mut self,
373 name: &str,
374 args: &CallArgs<'_>,
375 ) -> Option<Result<VmValue, VmError>> {
376 self.try_call_sync_builtin_id_or_name_args(None, name, args)
377 }
378
379 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
381 matches!(
382 v,
383 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId { .. }
384 )
385 }
386
387 pub async fn call_closure_pub(
390 &mut self,
391 closure: &VmClosure,
392 args: &[VmValue],
393 ) -> Result<VmValue, VmError> {
394 self.cancel_grace_instructions_remaining = None;
395 self.call_closure(closure, args).await
396 }
397
398 pub(crate) async fn call_named_builtin(
401 &mut self,
402 name: &str,
403 args: Vec<VmValue>,
404 ) -> Result<VmValue, VmError> {
405 self.call_builtin_impl(name, args, None).await
406 }
407
408 pub(crate) async fn call_builtin_id_or_name(
409 &mut self,
410 id: BuiltinId,
411 name: &str,
412 args: Vec<VmValue>,
413 ) -> Result<VmValue, VmError> {
414 self.call_builtin_impl(name, args, Some(id)).await
415 }
416
417 pub(crate) fn try_call_sync_builtin_id_or_name_args(
418 &mut self,
419 direct_id: Option<BuiltinId>,
420 name: &str,
421 args: &CallArgs<'_>,
422 ) -> Option<Result<VmValue, VmError>> {
423 if self.denied_builtins.contains(name) {
424 return Some(Err(VmError::CategorizedError {
425 message: format!("Tool '{}' is not permitted.", name),
426 category: ErrorCategory::ToolRejected,
427 }));
428 }
429 self.ensure_deferred_builtin(name);
430 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
431 Ok(builtin) => builtin,
432 Err(error) => return Some(Err(error)),
433 };
434 let _span =
435 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
436 if let Err(error) = args.with_slice(|slice| self.validate_sync_builtin_args(name, slice)) {
437 return Some(Err(error));
438 }
439
440 Some(args.with_slice(|slice| builtin(slice, &mut self.output)))
441 }
442
443 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
444 &mut self,
445 direct_id: Option<BuiltinId>,
446 name: &str,
447 args_start: usize,
448 ) -> Option<Result<VmValue, VmError>> {
449 if self.denied_builtins.contains(name) {
450 return Some(Err(VmError::CategorizedError {
451 message: format!("Tool '{}' is not permitted.", name),
452 category: ErrorCategory::ToolRejected,
453 }));
454 }
455 self.ensure_deferred_builtin(name);
456 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
457 Ok(builtin) => builtin,
458 Err(error) => return Some(Err(error)),
459 };
460 if args_start > self.stack.len() {
461 return Some(Err(VmError::Runtime(
462 "call argument stack underflow".to_string(),
463 )));
464 }
465
466 let _span =
467 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
468 let args = &self.stack[args_start..];
469 if let Err(error) = self.validate_sync_builtin_args(name, args) {
470 return Some(Err(error));
471 }
472
473 Some(builtin(args, &mut self.output))
474 }
475
476 async fn call_builtin_impl(
477 &mut self,
478 name: &str,
479 args: Vec<VmValue>,
480 direct_id: Option<BuiltinId>,
481 ) -> Result<VmValue, VmError> {
482 let _span =
484 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
485
486 if self.denied_builtins.contains(name) {
488 return Err(VmError::CategorizedError {
489 message: format!("Tool '{}' is not permitted.", name),
490 category: ErrorCategory::ToolRejected,
491 });
492 }
493 let autonomy = if crate::autonomy::needs_async_side_effect_enforcement(name) {
494 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
495 } else {
496 None
497 };
498 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
499 return Ok(value);
500 }
501 if !matches!(
502 autonomy,
503 Some(crate::autonomy::AutonomyDecision::AllowApproved)
504 ) {
505 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
506 }
507 crate::typecheck::validate_builtin_call(name, &args, None)?;
508
509 if let Some(result) =
510 crate::runtime_context::dispatch_runtime_context_builtin(self, name, &args)
511 {
512 return result;
513 }
514
515 self.ensure_deferred_builtin(name);
516
517 if let Some(id) = direct_id {
518 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
519 if entry.name.as_ref() == name {
520 return self.call_builtin_entry(entry.dispatch, args).await;
521 }
522 }
523 }
524
525 if let Some(builtin) = self.builtins.get(name).cloned() {
526 self.call_builtin_entry(VmBuiltinDispatch::Sync(builtin), args)
527 .await
528 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
529 self.call_builtin_entry(VmBuiltinDispatch::Async(async_builtin), args)
530 .await
531 } else if let Some(bridge) = &self.bridge {
532 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
533 let args_json: Vec<serde_json::Value> =
534 args.iter().map(crate::llm::vm_value_to_json).collect();
535 let result = bridge
536 .call(
537 "builtin_call",
538 serde_json::json!({"name": name, "args": args_json}),
539 )
540 .await?;
541 Ok(crate::bridge::json_result_to_vm_value(&result))
542 } else {
543 let all_builtins = self
544 .builtins
545 .keys()
546 .chain(self.async_builtins.keys())
547 .chain(self.deferred_builtin_registrars.keys())
548 .map(|s| s.as_str());
549 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
550 return Err(VmError::Runtime(format!(
551 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
552 )));
553 }
554 Err(VmError::UndefinedBuiltin(name.to_string()))
555 }
556 }
557
558 async fn call_builtin_entry(
559 &mut self,
560 dispatch: VmBuiltinDispatch,
561 args: Vec<VmValue>,
562 ) -> Result<VmValue, VmError> {
563 match dispatch {
564 VmBuiltinDispatch::Sync(builtin) => builtin(&args, &mut self.output),
565 VmBuiltinDispatch::Async(async_builtin) => {
566 CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
567 slot.borrow_mut().push(self.child_vm());
568 });
569 let result = async_builtin(args).await;
570 let captured = CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
571 let mut stack = slot.borrow_mut();
572 let mut top = stack.pop();
573 top.as_mut().map(|vm| vm.take_output()).unwrap_or_default()
574 });
575 if !captured.is_empty() {
576 self.output.push_str(&captured);
577 }
578 result
579 }
580 }
581 }
582}