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 '{name}' is not permitted."),
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 register_builtin_def(&mut self, def: &'static crate::stdlib::macros::VmBuiltinDef) {
167 use crate::stdlib::macros::VmBuiltinHandler;
168 if def.parser_only {
169 return;
170 }
171 let names = std::iter::once(def.sig.name).chain(def.aliases.iter().copied());
172 for name in names {
173 match def.handler {
174 VmBuiltinHandler::Sync(f) => {
175 let mut meta = VmBuiltinMetadata::sync_static(name);
176 if let Some(category) = def.category {
177 meta = meta.category_static(category);
178 }
179 if let Some(doc) = def.doc {
180 meta = meta.doc_static(doc);
181 }
182 self.register_builtin_with_metadata(meta, f);
183 }
184 VmBuiltinHandler::Async(f) => {
185 let mut meta = VmBuiltinMetadata::async_static(name);
186 if let Some(category) = def.category {
187 meta = meta.category_static(category);
188 }
189 if let Some(doc) = def.doc {
190 meta = meta.doc_static(doc);
191 }
192 self.register_async_builtin_with_metadata(meta, f);
196 }
197 VmBuiltinHandler::None => {
198 panic!(
201 "VmBuiltinHandler::None for {name:?} without parser_only=true \
202 on its BuiltinDef"
203 );
204 }
205 }
206 }
207 }
208
209 pub fn unregister_builtin(&mut self, name: &str) {
211 Rc::make_mut(&mut self.builtins).remove(name);
212 if self.async_builtins.contains_key(name) {
213 Rc::make_mut(&mut self.builtin_metadata).insert(
214 name.to_string(),
215 VmBuiltinMetadata::async_builtin(name.to_string()),
216 );
217 } else {
218 Rc::make_mut(&mut self.builtin_metadata).remove(name);
219 }
220 self.refresh_builtin_id(name);
221 }
222
223 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
225 where
226 F: Fn(Vec<VmValue>) -> Fut + 'static,
227 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
228 {
229 Rc::make_mut(&mut self.async_builtins)
230 .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
231 Rc::make_mut(&mut self.builtin_metadata).insert(
232 name.to_string(),
233 VmBuiltinMetadata::async_builtin(name.to_string()),
234 );
235 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(name);
236 self.refresh_builtin_id(name);
237 }
238
239 pub fn register_async_builtin_with_metadata<F, Fut>(
241 &mut self,
242 metadata: VmBuiltinMetadata,
243 f: F,
244 ) where
245 F: Fn(Vec<VmValue>) -> Fut + 'static,
246 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
247 {
248 let name = metadata.name().to_string();
249 Rc::make_mut(&mut self.async_builtins)
250 .insert(name.clone(), Rc::new(move |args| Box::pin(f(args))));
251 Rc::make_mut(&mut self.builtin_metadata)
252 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
253 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(&name);
254 self.refresh_builtin_id(&name);
255 }
256
257 pub(crate) fn register_deferred_builtin(&mut self, name: &str, registrar: fn(&mut Vm)) {
260 if self.builtins.contains_key(name) || self.async_builtins.contains_key(name) {
261 return;
262 }
263 Rc::make_mut(&mut self.deferred_builtin_registrars).insert(name.to_string(), registrar);
264 }
265
266 pub(crate) fn ensure_deferred_builtin(&mut self, name: &str) -> bool {
267 let Some(registrar) = self.deferred_builtin_registrars.get(name).copied() else {
268 return false;
269 };
270 registrar(self);
271 Rc::make_mut(&mut self.deferred_builtin_registrars).remove(name);
272 self.builtins.contains_key(name) || self.async_builtins.contains_key(name)
273 }
274
275 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
276 let id = BuiltinId::from_name(name);
277 if self
278 .builtins_by_id
279 .get(&id)
280 .is_some_and(|entry| entry.name.as_ref() == name)
281 {
282 Some(id)
283 } else {
284 None
285 }
286 }
287
288 pub(crate) async fn call_closure(
309 &mut self,
310 closure: &VmClosure,
311 args: &[VmValue],
312 ) -> Result<VmValue, VmError> {
313 self.call_closure_args(closure, CallArgs::Slice(args)).await
314 }
315
316 pub(crate) async fn call_closure_args(
317 &mut self,
318 closure: &VmClosure,
319 args: CallArgs<'_>,
320 ) -> Result<VmValue, VmError> {
321 let saved_handlers = std::mem::take(&mut self.exception_handlers);
322 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
323 .then(crate::step_runtime::take_active_context);
324
325 let target_frame_depth = self.frames.len();
326 let frame_result = self.push_closure_frame_args(closure, &args);
327 drop(args);
328 let result = match frame_result {
329 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
330 Err(e) => Err(e),
331 };
332
333 self.exception_handlers = saved_handlers;
334 if let Some(ctx) = active_context {
335 crate::step_runtime::restore_active_context(ctx);
336 }
337
338 result
339 }
340
341 pub(crate) async fn call_callable_value(
346 &mut self,
347 callable: &VmValue,
348 args: &[VmValue],
349 ) -> Result<VmValue, VmError> {
350 self.call_callable_args(callable, CallArgs::Slice(args))
351 .await
352 }
353
354 pub(crate) async fn call_callable_owned(
355 &mut self,
356 callable: &VmValue,
357 args: Vec<VmValue>,
358 ) -> Result<VmValue, VmError> {
359 self.call_callable_args(callable, CallArgs::Owned(args))
360 .await
361 }
362
363 pub(crate) async fn call_callable_zero(
364 &mut self,
365 callable: &VmValue,
366 ) -> Result<VmValue, VmError> {
367 self.call_callable_args(callable, CallArgs::Empty).await
368 }
369
370 pub(crate) async fn call_callable_one(
371 &mut self,
372 callable: &VmValue,
373 arg: &VmValue,
374 ) -> Result<VmValue, VmError> {
375 self.call_callable_args(callable, CallArgs::One(arg)).await
376 }
377
378 pub(crate) async fn call_callable_two(
379 &mut self,
380 callable: &VmValue,
381 first: &VmValue,
382 second: &VmValue,
383 ) -> Result<VmValue, VmError> {
384 self.call_callable_args(callable, CallArgs::Two(first, second))
385 .await
386 }
387
388 pub(crate) async fn call_callable_args(
389 &mut self,
390 callable: &VmValue,
391 args: CallArgs<'_>,
392 ) -> Result<VmValue, VmError> {
393 match callable {
394 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
395 VmValue::BuiltinRef(name) => {
396 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
397 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
398 return result;
399 }
400 }
401 self.call_named_builtin(name, args.into_vec()).await
402 }
403 VmValue::BuiltinRefId { id, name } => {
404 if let Some(result) =
405 self.try_call_sync_builtin_id_or_name_args(Some(*id), name, &args)
406 {
407 return result;
408 }
409 self.call_builtin_id_or_name(*id, name, args.into_vec())
410 .await
411 }
412 other => Err(VmError::TypeError(format!(
413 "expected callable, got {}",
414 other.type_name()
415 ))),
416 }
417 }
418
419 fn call_sync_builtin_by_ref_args(
420 &mut self,
421 name: &str,
422 args: &CallArgs<'_>,
423 ) -> Option<Result<VmValue, VmError>> {
424 self.try_call_sync_builtin_id_or_name_args(None, name, args)
425 }
426
427 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
429 matches!(
430 v,
431 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId { .. }
432 )
433 }
434
435 pub async fn call_closure_pub(
438 &mut self,
439 closure: &VmClosure,
440 args: &[VmValue],
441 ) -> Result<VmValue, VmError> {
442 self.cancel_grace_instructions_remaining = None;
443 self.call_closure(closure, args).await
444 }
445
446 pub(crate) async fn call_named_builtin(
449 &mut self,
450 name: &str,
451 args: Vec<VmValue>,
452 ) -> Result<VmValue, VmError> {
453 self.call_builtin_impl(name, args, None).await
454 }
455
456 pub(crate) async fn call_builtin_id_or_name(
457 &mut self,
458 id: BuiltinId,
459 name: &str,
460 args: Vec<VmValue>,
461 ) -> Result<VmValue, VmError> {
462 self.call_builtin_impl(name, args, Some(id)).await
463 }
464
465 pub(crate) fn try_call_sync_builtin_id_or_name_args(
466 &mut self,
467 direct_id: Option<BuiltinId>,
468 name: &str,
469 args: &CallArgs<'_>,
470 ) -> Option<Result<VmValue, VmError>> {
471 if self.denied_builtins.contains(name) {
472 return Some(Err(VmError::CategorizedError {
473 message: format!("Tool '{name}' is not permitted."),
474 category: ErrorCategory::ToolRejected,
475 }));
476 }
477 self.ensure_deferred_builtin(name);
478 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
479 Ok(builtin) => builtin,
480 Err(error) => return Some(Err(error)),
481 };
482 let _span =
483 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
484 if let Err(error) = args.with_slice(|slice| self.validate_sync_builtin_args(name, slice)) {
485 return Some(Err(error));
486 }
487
488 Some(args.with_slice(|slice| builtin(slice, &mut self.output)))
489 }
490
491 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
492 &mut self,
493 direct_id: Option<BuiltinId>,
494 name: &str,
495 args_start: usize,
496 ) -> Option<Result<VmValue, VmError>> {
497 if self.denied_builtins.contains(name) {
498 return Some(Err(VmError::CategorizedError {
499 message: format!("Tool '{name}' is not permitted."),
500 category: ErrorCategory::ToolRejected,
501 }));
502 }
503 self.ensure_deferred_builtin(name);
504 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
505 Ok(builtin) => builtin,
506 Err(error) => return Some(Err(error)),
507 };
508 if args_start > self.stack.len() {
509 return Some(Err(VmError::Runtime(
510 "call argument stack underflow".to_string(),
511 )));
512 }
513
514 let _span =
515 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
516 let args = &self.stack[args_start..];
517 if let Err(error) = self.validate_sync_builtin_args(name, args) {
518 return Some(Err(error));
519 }
520
521 Some(builtin(args, &mut self.output))
522 }
523
524 async fn call_builtin_impl(
525 &mut self,
526 name: &str,
527 args: Vec<VmValue>,
528 direct_id: Option<BuiltinId>,
529 ) -> Result<VmValue, VmError> {
530 let _span =
532 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
533
534 if self.denied_builtins.contains(name) {
536 return Err(VmError::CategorizedError {
537 message: format!("Tool '{name}' is not permitted."),
538 category: ErrorCategory::ToolRejected,
539 });
540 }
541 let autonomy = if crate::autonomy::needs_async_side_effect_enforcement(name) {
542 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
543 } else {
544 None
545 };
546 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
547 return Ok(value);
548 }
549 if !matches!(
550 autonomy,
551 Some(crate::autonomy::AutonomyDecision::AllowApproved)
552 ) {
553 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
554 }
555 crate::typecheck::validate_builtin_call(name, &args, None)?;
556
557 if let Some(result) =
558 crate::runtime_context::dispatch_runtime_context_builtin(self, name, &args)
559 {
560 return result;
561 }
562
563 self.ensure_deferred_builtin(name);
564
565 if let Some(id) = direct_id {
566 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
567 if entry.name.as_ref() == name {
568 return self.call_builtin_entry(entry.dispatch, args).await;
569 }
570 }
571 }
572
573 if let Some(builtin) = self.builtins.get(name).cloned() {
574 self.call_builtin_entry(VmBuiltinDispatch::Sync(builtin), args)
575 .await
576 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
577 self.call_builtin_entry(VmBuiltinDispatch::Async(async_builtin), args)
578 .await
579 } else if let Some(bridge) = &self.bridge {
580 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
581 let args_json: Vec<serde_json::Value> =
582 args.iter().map(crate::llm::vm_value_to_json).collect();
583 let result = bridge
584 .call(
585 "builtin_call",
586 serde_json::json!({"name": name, "args": args_json}),
587 )
588 .await?;
589 Ok(crate::bridge::json_result_to_vm_value(&result))
590 } else {
591 let all_builtins = self
592 .builtins
593 .keys()
594 .chain(self.async_builtins.keys())
595 .chain(self.deferred_builtin_registrars.keys())
596 .map(|s| s.as_str());
597 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
598 return Err(VmError::Runtime(format!(
599 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
600 )));
601 }
602 Err(VmError::UndefinedBuiltin(name.to_string()))
603 }
604 }
605
606 async fn call_builtin_entry(
607 &mut self,
608 dispatch: VmBuiltinDispatch,
609 args: Vec<VmValue>,
610 ) -> Result<VmValue, VmError> {
611 match dispatch {
612 VmBuiltinDispatch::Sync(builtin) => builtin(&args, &mut self.output),
613 VmBuiltinDispatch::Async(async_builtin) => {
614 CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
615 slot.borrow_mut().push(self.child_vm());
616 });
617 let result = async_builtin(args).await;
618 let captured = CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
619 let mut stack = slot.borrow_mut();
620 let mut top = stack.pop();
621 top.as_mut().map(|vm| vm.take_output()).unwrap_or_default()
622 });
623 if !captured.is_empty() {
624 self.output.push_str(&captured);
625 }
626 result
627 }
628 }
629 }
630}