1use std::future::Future;
2use std::rc::Rc;
3
4use crate::value::{ErrorCategory, VmBuiltinFn, VmClosure, VmError, VmValue};
5use crate::BuiltinId;
6
7use super::{
8 CallArgs, ScopeSpan, Vm, VmBuiltinArity, VmBuiltinDispatch, VmBuiltinEntry, VmBuiltinKind,
9 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 self.refresh_builtin_id(name);
145 }
146
147 pub fn register_builtin_with_metadata<F>(&mut self, metadata: VmBuiltinMetadata, f: F)
149 where
150 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + 'static,
151 {
152 let name = metadata.name().to_string();
153 Rc::make_mut(&mut self.builtins).insert(name.clone(), Rc::new(f));
154 Rc::make_mut(&mut self.builtin_metadata)
155 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Sync));
156 self.refresh_builtin_id(&name);
157 }
158
159 pub fn register_builtin_def(&mut self, def: &'static crate::stdlib::macros::VmBuiltinDef) {
165 use crate::stdlib::macros::VmBuiltinHandler;
166 if def.parser_only {
167 return;
168 }
169 let arity = arity_from_sig(&def.sig);
173 let names = std::iter::once(def.sig.name).chain(def.aliases.iter().copied());
174 for name in names {
175 match def.handler {
176 VmBuiltinHandler::Sync(f) => {
177 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Sync);
178 self.register_builtin_with_metadata(meta, f);
179 }
180 VmBuiltinHandler::Async(f) => {
181 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Async);
182 self.register_async_builtin_with_metadata(meta, f);
186 }
187 VmBuiltinHandler::None => {
188 panic!(
191 "VmBuiltinHandler::None for {name:?} without parser_only=true \
192 on its BuiltinDef"
193 );
194 }
195 }
196 }
197 }
198
199 pub fn unregister_builtin(&mut self, name: &str) {
201 Rc::make_mut(&mut self.builtins).remove(name);
202 if self.async_builtins.contains_key(name) {
203 Rc::make_mut(&mut self.builtin_metadata).insert(
204 name.to_string(),
205 VmBuiltinMetadata::async_builtin(name.to_string()),
206 );
207 } else {
208 Rc::make_mut(&mut self.builtin_metadata).remove(name);
209 }
210 self.refresh_builtin_id(name);
211 }
212
213 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
215 where
216 F: Fn(Vec<VmValue>) -> Fut + 'static,
217 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
218 {
219 Rc::make_mut(&mut self.async_builtins)
220 .insert(name.to_string(), Rc::new(move |args| Box::pin(f(args))));
221 Rc::make_mut(&mut self.builtin_metadata).insert(
222 name.to_string(),
223 VmBuiltinMetadata::async_builtin(name.to_string()),
224 );
225 self.refresh_builtin_id(name);
226 }
227
228 pub fn register_async_builtin_with_metadata<F, Fut>(
230 &mut self,
231 metadata: VmBuiltinMetadata,
232 f: F,
233 ) where
234 F: Fn(Vec<VmValue>) -> Fut + 'static,
235 Fut: Future<Output = Result<VmValue, VmError>> + 'static,
236 {
237 let name = metadata.name().to_string();
238 Rc::make_mut(&mut self.async_builtins)
239 .insert(name.clone(), Rc::new(move |args| Box::pin(f(args))));
240 Rc::make_mut(&mut self.builtin_metadata)
241 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
242 self.refresh_builtin_id(&name);
243 }
244
245 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
246 let id = BuiltinId::from_name(name);
247 if self
248 .builtins_by_id
249 .get(&id)
250 .is_some_and(|entry| entry.name.as_ref() == name)
251 {
252 Some(id)
253 } else {
254 None
255 }
256 }
257
258 pub(crate) async fn call_closure(
279 &mut self,
280 closure: &VmClosure,
281 args: &[VmValue],
282 ) -> Result<VmValue, VmError> {
283 self.call_closure_args(closure, CallArgs::Slice(args)).await
284 }
285
286 pub(crate) async fn call_closure_args(
287 &mut self,
288 closure: &VmClosure,
289 args: CallArgs<'_>,
290 ) -> Result<VmValue, VmError> {
291 let saved_handlers = std::mem::take(&mut self.exception_handlers);
292 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
293 .then(crate::step_runtime::take_active_context);
294
295 let target_frame_depth = self.frames.len();
296 let frame_result = self.push_closure_frame_args(closure, &args);
297 drop(args);
298 let result = match frame_result {
299 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
300 Err(e) => Err(e),
301 };
302
303 self.exception_handlers = saved_handlers;
304 if let Some(ctx) = active_context {
305 crate::step_runtime::restore_active_context(ctx);
306 }
307
308 result
309 }
310
311 pub(crate) async fn call_callable_value(
316 &mut self,
317 callable: &VmValue,
318 args: &[VmValue],
319 ) -> Result<VmValue, VmError> {
320 self.call_callable_args(callable, CallArgs::Slice(args))
321 .await
322 }
323
324 pub(crate) async fn call_callable_owned(
325 &mut self,
326 callable: &VmValue,
327 args: Vec<VmValue>,
328 ) -> Result<VmValue, VmError> {
329 self.call_callable_args(callable, CallArgs::Owned(args))
330 .await
331 }
332
333 pub(crate) async fn call_callable_zero(
334 &mut self,
335 callable: &VmValue,
336 ) -> Result<VmValue, VmError> {
337 self.call_callable_args(callable, CallArgs::Empty).await
338 }
339
340 pub(crate) async fn call_callable_one(
341 &mut self,
342 callable: &VmValue,
343 arg: &VmValue,
344 ) -> Result<VmValue, VmError> {
345 self.call_callable_args(callable, CallArgs::One(arg)).await
346 }
347
348 pub(crate) async fn call_callable_two(
349 &mut self,
350 callable: &VmValue,
351 first: &VmValue,
352 second: &VmValue,
353 ) -> Result<VmValue, VmError> {
354 self.call_callable_args(callable, CallArgs::Two(first, second))
355 .await
356 }
357
358 pub(crate) async fn call_callable_args(
359 &mut self,
360 callable: &VmValue,
361 args: CallArgs<'_>,
362 ) -> Result<VmValue, VmError> {
363 match callable {
364 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
365 VmValue::BuiltinRef(name) => {
366 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
367 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
368 return result;
369 }
370 }
371 self.call_named_builtin(name, args.into_vec()).await
372 }
373 VmValue::BuiltinRefId { id, name } => {
374 if let Some(result) =
375 self.try_call_sync_builtin_id_or_name_args(Some(*id), name, &args)
376 {
377 return result;
378 }
379 self.call_builtin_id_or_name(*id, name, args.into_vec())
380 .await
381 }
382 other => Err(VmError::TypeError(format!(
383 "expected callable, got {}",
384 other.type_name()
385 ))),
386 }
387 }
388
389 fn call_sync_builtin_by_ref_args(
390 &mut self,
391 name: &str,
392 args: &CallArgs<'_>,
393 ) -> Option<Result<VmValue, VmError>> {
394 self.try_call_sync_builtin_id_or_name_args(None, name, args)
395 }
396
397 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
399 matches!(
400 v,
401 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId { .. }
402 )
403 }
404
405 pub async fn call_closure_pub(
408 &mut self,
409 closure: &VmClosure,
410 args: &[VmValue],
411 ) -> Result<VmValue, VmError> {
412 self.cancel_grace_instructions_remaining = None;
413 self.call_closure(closure, args).await
414 }
415
416 pub(crate) async fn call_named_builtin(
419 &mut self,
420 name: &str,
421 args: Vec<VmValue>,
422 ) -> Result<VmValue, VmError> {
423 self.call_builtin_impl(name, args, None).await
424 }
425
426 pub(crate) async fn call_builtin_id_or_name(
427 &mut self,
428 id: BuiltinId,
429 name: &str,
430 args: Vec<VmValue>,
431 ) -> Result<VmValue, VmError> {
432 self.call_builtin_impl(name, args, Some(id)).await
433 }
434
435 pub(crate) fn try_call_sync_builtin_id_or_name_args(
436 &mut self,
437 direct_id: Option<BuiltinId>,
438 name: &str,
439 args: &CallArgs<'_>,
440 ) -> Option<Result<VmValue, VmError>> {
441 if self.denied_builtins.contains(name) {
442 return Some(Err(VmError::CategorizedError {
443 message: format!("Tool '{name}' is not permitted."),
444 category: ErrorCategory::ToolRejected,
445 }));
446 }
447 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
448 Ok(builtin) => builtin,
449 Err(error) => return Some(Err(error)),
450 };
451 let _span =
452 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
453 if let Err(error) = args.with_slice(|slice| self.validate_sync_builtin_args(name, slice)) {
454 return Some(Err(error));
455 }
456
457 Some(args.with_slice(|slice| builtin(slice, &mut self.output)))
458 }
459
460 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
461 &mut self,
462 direct_id: Option<BuiltinId>,
463 name: &str,
464 args_start: usize,
465 ) -> Option<Result<VmValue, VmError>> {
466 if self.denied_builtins.contains(name) {
467 return Some(Err(VmError::CategorizedError {
468 message: format!("Tool '{name}' is not permitted."),
469 category: ErrorCategory::ToolRejected,
470 }));
471 }
472 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
473 Ok(builtin) => builtin,
474 Err(error) => return Some(Err(error)),
475 };
476 if args_start > self.stack.len() {
477 return Some(Err(VmError::Runtime(
478 "call argument stack underflow".to_string(),
479 )));
480 }
481
482 let _span =
483 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
484 let args = &self.stack[args_start..];
485 if let Err(error) = self.validate_sync_builtin_args(name, args) {
486 return Some(Err(error));
487 }
488
489 Some(builtin(args, &mut self.output))
490 }
491
492 async fn call_builtin_impl(
493 &mut self,
494 name: &str,
495 args: Vec<VmValue>,
496 direct_id: Option<BuiltinId>,
497 ) -> Result<VmValue, VmError> {
498 let _span =
500 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
501
502 if self.denied_builtins.contains(name) {
504 return Err(VmError::CategorizedError {
505 message: format!("Tool '{name}' is not permitted."),
506 category: ErrorCategory::ToolRejected,
507 });
508 }
509 let autonomy = if crate::autonomy::needs_async_side_effect_enforcement(name) {
510 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
511 } else {
512 None
513 };
514 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
515 return Ok(value);
516 }
517 if !matches!(
518 autonomy,
519 Some(crate::autonomy::AutonomyDecision::AllowApproved)
520 ) {
521 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
522 }
523 crate::typecheck::validate_builtin_call(name, &args, None)?;
524
525 if let Some(result) =
526 crate::runtime_context::dispatch_runtime_context_builtin(self, name, &args)
527 {
528 return result;
529 }
530
531 if let Some(id) = direct_id {
532 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
533 if entry.name.as_ref() == name {
534 return self.call_builtin_entry(entry.dispatch, args).await;
535 }
536 }
537 }
538
539 if let Some(builtin) = self.builtins.get(name).cloned() {
540 self.call_builtin_entry(VmBuiltinDispatch::Sync(builtin), args)
541 .await
542 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
543 self.call_builtin_entry(VmBuiltinDispatch::Async(async_builtin), args)
544 .await
545 } else if let Some(bridge) = &self.bridge {
546 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
547 let args_json: Vec<serde_json::Value> =
548 args.iter().map(crate::llm::vm_value_to_json).collect();
549 let result = bridge
550 .call(
551 "builtin_call",
552 serde_json::json!({"name": name, "args": args_json}),
553 )
554 .await?;
555 Ok(crate::bridge::json_result_to_vm_value(&result))
556 } else {
557 let all_builtins = self
558 .builtins
559 .keys()
560 .chain(self.async_builtins.keys())
561 .map(|s| s.as_str());
562 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
563 return Err(VmError::Runtime(format!(
564 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
565 )));
566 }
567 Err(VmError::UndefinedBuiltin(name.to_string()))
568 }
569 }
570
571 async fn call_builtin_entry(
572 &mut self,
573 dispatch: VmBuiltinDispatch,
574 args: Vec<VmValue>,
575 ) -> Result<VmValue, VmError> {
576 match dispatch {
577 VmBuiltinDispatch::Sync(builtin) => builtin(&args, &mut self.output),
578 VmBuiltinDispatch::Async(async_builtin) => {
579 let (result, captured) =
584 crate::vm::run_async_builtin_with(self.child_vm(), async_builtin(args)).await;
585 if !captured.is_empty() {
586 self.output.push_str(&captured);
587 }
588 result
589 }
590 }
591 }
592}
593
594fn builtin_def_metadata(
599 def: &'static crate::stdlib::macros::VmBuiltinDef,
600 name: &'static str,
601 arity: VmBuiltinArity,
602 kind: VmBuiltinKind,
603) -> VmBuiltinMetadata {
604 let mut meta = match kind {
605 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
606 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
607 }
608 .arity(arity);
609 if let Some(category) = def.category {
610 meta = meta.category_static(category);
611 }
612 if let Some(doc) = def.doc {
613 meta = meta.doc_static(doc);
614 }
615 if let Some(sig_text) = def.signature_text {
616 meta = meta.signature_static(sig_text);
617 } else {
618 meta = meta.signature_owned(format!("{}", def.sig));
625 }
626 meta
627}
628
629fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
635 let required = sig.params.iter().filter(|p| !p.optional).count();
636 let total = sig.params.len();
637 if sig.has_rest {
638 if required == 0 {
639 VmBuiltinArity::Variadic
640 } else {
641 VmBuiltinArity::Min(required)
642 }
643 } else if required == total {
644 VmBuiltinArity::Exact(total)
645 } else {
646 VmBuiltinArity::Range {
647 min: required,
648 max: total,
649 }
650 }
651}