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, VmBuiltinArity, VmBuiltinDispatch, VmBuiltinEntry, VmBuiltinKind,
10 VmBuiltinMetadata,
11};
12
13impl Vm {
14 fn builtin_span_kind(name: &str) -> Option<crate::tracing::SpanKind> {
15 match name {
16 "llm_call" | "llm_stream" | "llm_stream_call" | "agent_loop" | "agent_turn" => {
17 Some(crate::tracing::SpanKind::LlmCall)
18 }
19 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
20 _ => None,
21 }
22 }
23
24 fn is_runtime_context_builtin(name: &str) -> bool {
25 matches!(
26 name,
27 "runtime_context"
28 | "task_current"
29 | "runtime_context_values"
30 | "runtime_context_get"
31 | "runtime_context_set"
32 | "runtime_context_clear"
33 )
34 }
35
36 fn resolve_sync_builtin_id_or_name(
37 &self,
38 direct_id: Option<BuiltinId>,
39 name: &str,
40 ) -> Option<Result<VmBuiltinFn, VmError>> {
41 if crate::autonomy::needs_async_side_effect_enforcement(name)
42 || Self::is_runtime_context_builtin(name)
43 {
44 return None;
45 }
46
47 let dispatch = if let Some(id) = direct_id {
48 self.builtins_by_id
49 .get(&id)
50 .filter(|entry| entry.name.as_ref() == name)
51 .map(|entry| entry.dispatch.clone())
52 } else {
53 None
54 }
55 .or_else(|| {
56 self.builtins
57 .get(name)
58 .cloned()
59 .map(VmBuiltinDispatch::Sync)
60 });
61
62 let Some(dispatch) = dispatch else {
63 if self.async_builtins.contains_key(name) || self.bridge.is_some() {
64 return None;
65 }
66 let all_builtins = self
67 .builtins
68 .keys()
69 .chain(self.async_builtins.keys())
70 .map(|s| s.as_str());
71 return Some(
72 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
73 Err(VmError::Runtime(format!(
74 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
75 )))
76 } else {
77 Err(VmError::UndefinedBuiltin(name.to_string()))
78 },
79 );
80 };
81
82 match dispatch {
83 VmBuiltinDispatch::Sync(builtin) => Some(Ok(builtin)),
84 VmBuiltinDispatch::Async(_) => None,
85 }
86 }
87
88 fn validate_sync_builtin_args(&self, name: &str, args: &[VmValue]) -> Result<(), VmError> {
89 if self.denied_builtins.contains(name) {
90 return Err(VmError::CategorizedError {
91 message: format!("Tool '{name}' is not permitted."),
92 category: ErrorCategory::ToolRejected,
93 });
94 }
95 crate::orchestration::enforce_current_policy_for_builtin(name, args)?;
96 crate::typecheck::validate_builtin_call(name, args, None)
97 }
98
99 fn index_builtin_id(&mut self, name: &str, dispatch: VmBuiltinDispatch) {
100 let id = BuiltinId::from_name(name);
101 if self.builtin_id_collisions.contains(&id) {
102 return;
103 }
104 if let Some(existing) = self.builtins_by_id.get(&id) {
105 if existing.name.as_ref() != name {
106 Rc::make_mut(&mut self.builtins_by_id).remove(&id);
107 Rc::make_mut(&mut self.builtin_id_collisions).insert(id);
108 return;
109 }
110 }
111 Rc::make_mut(&mut self.builtins_by_id).insert(
112 id,
113 VmBuiltinEntry {
114 name: Rc::from(name),
115 dispatch,
116 },
117 );
118 }
119
120 fn refresh_builtin_id(&mut self, name: &str) {
121 if let Some(builtin) = self.builtins.get(name).cloned() {
122 self.index_builtin_id(name, VmBuiltinDispatch::Sync(builtin));
123 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
124 self.index_builtin_id(name, VmBuiltinDispatch::Async(async_builtin));
125 } else {
126 let id = BuiltinId::from_name(name);
127 if self
128 .builtins_by_id
129 .get(&id)
130 .is_some_and(|entry| entry.name.as_ref() == name)
131 {
132 Rc::make_mut(&mut self.builtins_by_id).remove(&id);
133 }
134 }
135 }
136
137 pub fn register_builtin<F>(&mut self, name: &str, f: F)
139 where
140 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
141 {
142 Rc::make_mut(&mut self.builtins).insert(name.to_string(), Rc::new(f));
143 Rc::make_mut(&mut self.builtin_metadata)
144 .insert(name.to_string(), VmBuiltinMetadata::sync(name.to_string()));
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 self.refresh_builtin_id(&name);
158 }
159
160 pub fn register_builtin_def(&mut self, def: &'static crate::stdlib::macros::VmBuiltinDef) {
166 use crate::stdlib::macros::VmBuiltinHandler;
167 if def.parser_only {
168 return;
169 }
170 let arity = arity_from_sig(&def.sig);
174 let names = std::iter::once(def.sig.name).chain(def.aliases.iter().copied());
175 for name in names {
176 match def.handler {
177 VmBuiltinHandler::Sync(f) => {
178 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Sync);
179 self.register_builtin_with_metadata(meta, f);
180 }
181 VmBuiltinHandler::Async(f) => {
182 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Async);
183 self.register_async_builtin_with_metadata(meta, f);
187 }
188 VmBuiltinHandler::None => {
189 panic!(
192 "VmBuiltinHandler::None for {name:?} without parser_only=true \
193 on its BuiltinDef"
194 );
195 }
196 }
197 }
198 }
199
200 pub fn unregister_builtin(&mut self, name: &str) {
202 Rc::make_mut(&mut self.builtins).remove(name);
203 if self.async_builtins.contains_key(name) {
204 Rc::make_mut(&mut self.builtin_metadata).insert(
205 name.to_string(),
206 VmBuiltinMetadata::async_builtin(name.to_string()),
207 );
208 } else {
209 Rc::make_mut(&mut self.builtin_metadata).remove(name);
210 }
211 self.refresh_builtin_id(name);
212 }
213
214 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
216 where
217 F: Fn(Vec<VmValue>) -> Fut + 'static,
218 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
219 {
220 Rc::make_mut(&mut self.async_builtins)
221 .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
222 Rc::make_mut(&mut self.builtin_metadata).insert(
223 name.to_string(),
224 VmBuiltinMetadata::async_builtin(name.to_string()),
225 );
226 self.refresh_builtin_id(name);
227 }
228
229 pub fn register_async_builtin_with_metadata<F, Fut>(
231 &mut self,
232 metadata: VmBuiltinMetadata,
233 f: F,
234 ) where
235 F: Fn(Vec<VmValue>) -> Fut + 'static,
236 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
237 {
238 let name = metadata.name().to_string();
239 Rc::make_mut(&mut self.async_builtins)
240 .insert(name.clone(), Rc::new(move |args| Box::pin(f(args))));
241 Rc::make_mut(&mut self.builtin_metadata)
242 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
243 self.refresh_builtin_id(&name);
244 }
245
246 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
247 let id = BuiltinId::from_name(name);
248 if self
249 .builtins_by_id
250 .get(&id)
251 .is_some_and(|entry| entry.name.as_ref() == name)
252 {
253 Some(id)
254 } else {
255 None
256 }
257 }
258
259 pub(crate) async fn call_closure(
280 &mut self,
281 closure: &VmClosure,
282 args: &[VmValue],
283 ) -> Result<VmValue, VmError> {
284 self.call_closure_args(closure, CallArgs::Slice(args)).await
285 }
286
287 pub(crate) async fn call_closure_args(
288 &mut self,
289 closure: &VmClosure,
290 args: CallArgs<'_>,
291 ) -> Result<VmValue, VmError> {
292 let saved_handlers = std::mem::take(&mut self.exception_handlers);
293 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
294 .then(crate::step_runtime::take_active_context);
295
296 let target_frame_depth = self.frames.len();
297 let frame_result = self.push_closure_frame_args(closure, &args);
298 drop(args);
299 let result = match frame_result {
300 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
301 Err(e) => Err(e),
302 };
303
304 self.exception_handlers = saved_handlers;
305 if let Some(ctx) = active_context {
306 crate::step_runtime::restore_active_context(ctx);
307 }
308
309 result
310 }
311
312 pub(crate) async fn call_callable_value(
317 &mut self,
318 callable: &VmValue,
319 args: &[VmValue],
320 ) -> Result<VmValue, VmError> {
321 self.call_callable_args(callable, CallArgs::Slice(args))
322 .await
323 }
324
325 pub(crate) async fn call_callable_owned(
326 &mut self,
327 callable: &VmValue,
328 args: Vec<VmValue>,
329 ) -> Result<VmValue, VmError> {
330 self.call_callable_args(callable, CallArgs::Owned(args))
331 .await
332 }
333
334 pub(crate) async fn call_callable_zero(
335 &mut self,
336 callable: &VmValue,
337 ) -> Result<VmValue, VmError> {
338 self.call_callable_args(callable, CallArgs::Empty).await
339 }
340
341 pub(crate) async fn call_callable_one(
342 &mut self,
343 callable: &VmValue,
344 arg: &VmValue,
345 ) -> Result<VmValue, VmError> {
346 self.call_callable_args(callable, CallArgs::One(arg)).await
347 }
348
349 pub(crate) async fn call_callable_two(
350 &mut self,
351 callable: &VmValue,
352 first: &VmValue,
353 second: &VmValue,
354 ) -> Result<VmValue, VmError> {
355 self.call_callable_args(callable, CallArgs::Two(first, second))
356 .await
357 }
358
359 pub(crate) async fn call_callable_args(
360 &mut self,
361 callable: &VmValue,
362 args: CallArgs<'_>,
363 ) -> Result<VmValue, VmError> {
364 match callable {
365 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
366 VmValue::BuiltinRef(name) => {
367 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
368 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
369 return result;
370 }
371 }
372 self.call_named_builtin(name, args.into_vec()).await
373 }
374 VmValue::BuiltinRefId { id, name } => {
375 if let Some(result) =
376 self.try_call_sync_builtin_id_or_name_args(Some(*id), name, &args)
377 {
378 return result;
379 }
380 self.call_builtin_id_or_name(*id, name, args.into_vec())
381 .await
382 }
383 other => Err(VmError::TypeError(format!(
384 "expected callable, got {}",
385 other.type_name()
386 ))),
387 }
388 }
389
390 fn call_sync_builtin_by_ref_args(
391 &mut self,
392 name: &str,
393 args: &CallArgs<'_>,
394 ) -> Option<Result<VmValue, VmError>> {
395 self.try_call_sync_builtin_id_or_name_args(None, name, args)
396 }
397
398 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
400 matches!(
401 v,
402 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId { .. }
403 )
404 }
405
406 pub async fn call_closure_pub(
409 &mut self,
410 closure: &VmClosure,
411 args: &[VmValue],
412 ) -> Result<VmValue, VmError> {
413 self.cancel_grace_instructions_remaining = None;
414 self.call_closure(closure, args).await
415 }
416
417 pub(crate) async fn call_named_builtin(
420 &mut self,
421 name: &str,
422 args: Vec<VmValue>,
423 ) -> Result<VmValue, VmError> {
424 self.call_builtin_impl(name, args, None).await
425 }
426
427 pub(crate) async fn call_builtin_id_or_name(
428 &mut self,
429 id: BuiltinId,
430 name: &str,
431 args: Vec<VmValue>,
432 ) -> Result<VmValue, VmError> {
433 self.call_builtin_impl(name, args, Some(id)).await
434 }
435
436 pub(crate) fn try_call_sync_builtin_id_or_name_args(
437 &mut self,
438 direct_id: Option<BuiltinId>,
439 name: &str,
440 args: &CallArgs<'_>,
441 ) -> Option<Result<VmValue, VmError>> {
442 if self.denied_builtins.contains(name) {
443 return Some(Err(VmError::CategorizedError {
444 message: format!("Tool '{name}' is not permitted."),
445 category: ErrorCategory::ToolRejected,
446 }));
447 }
448 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
449 Ok(builtin) => builtin,
450 Err(error) => return Some(Err(error)),
451 };
452 let _span =
453 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
454 if let Err(error) = args.with_slice(|slice| self.validate_sync_builtin_args(name, slice)) {
455 return Some(Err(error));
456 }
457
458 Some(args.with_slice(|slice| builtin(slice, &mut self.output)))
459 }
460
461 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
462 &mut self,
463 direct_id: Option<BuiltinId>,
464 name: &str,
465 args_start: usize,
466 ) -> Option<Result<VmValue, VmError>> {
467 if self.denied_builtins.contains(name) {
468 return Some(Err(VmError::CategorizedError {
469 message: format!("Tool '{name}' is not permitted."),
470 category: ErrorCategory::ToolRejected,
471 }));
472 }
473 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
474 Ok(builtin) => builtin,
475 Err(error) => return Some(Err(error)),
476 };
477 if args_start > self.stack.len() {
478 return Some(Err(VmError::Runtime(
479 "call argument stack underflow".to_string(),
480 )));
481 }
482
483 let _span =
484 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
485 let args = &self.stack[args_start..];
486 if let Err(error) = self.validate_sync_builtin_args(name, args) {
487 return Some(Err(error));
488 }
489
490 Some(builtin(args, &mut self.output))
491 }
492
493 async fn call_builtin_impl(
494 &mut self,
495 name: &str,
496 args: Vec<VmValue>,
497 direct_id: Option<BuiltinId>,
498 ) -> Result<VmValue, VmError> {
499 let _span =
501 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
502
503 if self.denied_builtins.contains(name) {
505 return Err(VmError::CategorizedError {
506 message: format!("Tool '{name}' is not permitted."),
507 category: ErrorCategory::ToolRejected,
508 });
509 }
510 let autonomy = if crate::autonomy::needs_async_side_effect_enforcement(name) {
511 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
512 } else {
513 None
514 };
515 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
516 return Ok(value);
517 }
518 if !matches!(
519 autonomy,
520 Some(crate::autonomy::AutonomyDecision::AllowApproved)
521 ) {
522 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
523 }
524 crate::typecheck::validate_builtin_call(name, &args, None)?;
525
526 if let Some(result) =
527 crate::runtime_context::dispatch_runtime_context_builtin(self, name, &args)
528 {
529 return result;
530 }
531
532 if let Some(id) = direct_id {
533 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
534 if entry.name.as_ref() == name {
535 return self.call_builtin_entry(entry.dispatch, args).await;
536 }
537 }
538 }
539
540 if let Some(builtin) = self.builtins.get(name).cloned() {
541 self.call_builtin_entry(VmBuiltinDispatch::Sync(builtin), args)
542 .await
543 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
544 self.call_builtin_entry(VmBuiltinDispatch::Async(async_builtin), args)
545 .await
546 } else if let Some(bridge) = &self.bridge {
547 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
548 let args_json: Vec<serde_json::Value> =
549 args.iter().map(crate::llm::vm_value_to_json).collect();
550 let result = bridge
551 .call(
552 "builtin_call",
553 serde_json::json!({"name": name, "args": args_json}),
554 )
555 .await?;
556 Ok(crate::bridge::json_result_to_vm_value(&result))
557 } else {
558 let all_builtins = self
559 .builtins
560 .keys()
561 .chain(self.async_builtins.keys())
562 .map(|s| s.as_str());
563 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
564 return Err(VmError::Runtime(format!(
565 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
566 )));
567 }
568 Err(VmError::UndefinedBuiltin(name.to_string()))
569 }
570 }
571
572 async fn call_builtin_entry(
573 &mut self,
574 dispatch: VmBuiltinDispatch,
575 args: Vec<VmValue>,
576 ) -> Result<VmValue, VmError> {
577 match dispatch {
578 VmBuiltinDispatch::Sync(builtin) => builtin(&args, &mut self.output),
579 VmBuiltinDispatch::Async(async_builtin) => {
580 CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
581 slot.borrow_mut().push(self.child_vm());
582 });
583 let result = async_builtin(args).await;
584 let captured = CURRENT_ASYNC_BUILTIN_CHILD_VM.with(|slot| {
585 let mut stack = slot.borrow_mut();
586 let mut top = stack.pop();
587 top.as_mut().map(|vm| vm.take_output()).unwrap_or_default()
588 });
589 if !captured.is_empty() {
590 self.output.push_str(&captured);
591 }
592 result
593 }
594 }
595 }
596}
597
598fn builtin_def_metadata(
603 def: &'static crate::stdlib::macros::VmBuiltinDef,
604 name: &'static str,
605 arity: VmBuiltinArity,
606 kind: VmBuiltinKind,
607) -> VmBuiltinMetadata {
608 let mut meta = match kind {
609 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
610 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
611 }
612 .arity(arity);
613 if let Some(category) = def.category {
614 meta = meta.category_static(category);
615 }
616 if let Some(doc) = def.doc {
617 meta = meta.doc_static(doc);
618 }
619 if let Some(sig_text) = def.signature_text {
620 meta = meta.signature_static(sig_text);
621 } else {
622 meta = meta.signature_owned(format!("{}", def.sig));
629 }
630 meta
631}
632
633fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
639 let required = sig.params.iter().filter(|p| !p.optional).count();
640 let total = sig.params.len();
641 if sig.has_rest {
642 if required == 0 {
643 VmBuiltinArity::Variadic
644 } else {
645 VmBuiltinArity::Min(required)
646 }
647 } else if required == total {
648 VmBuiltinArity::Exact(total)
649 } else {
650 VmBuiltinArity::Range {
651 min: required,
652 max: total,
653 }
654 }
655}