1use std::future::Future;
2use std::sync::Arc;
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
12struct BuiltinObservation<'a> {
24 _span: Option<ScopeSpan>,
25 _timer: Option<crate::builtin_profile::BuiltinTimer<'a>>,
26}
27
28impl Vm {
29 fn builtin_span_kind(name: &str) -> Option<crate::tracing::SpanKind> {
30 match name {
31 "llm_call" | "llm_stream" | "llm_stream_call" | "agent_loop" | "agent_turn" => {
32 Some(crate::tracing::SpanKind::LlmCall)
33 }
34 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
35 _ => None,
36 }
37 }
38
39 fn observe_builtin_call(name: &str) -> Option<Box<BuiltinObservation<'_>>> {
54 let span = Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
55 let timer = crate::builtin_profile::BuiltinTimer::start(name);
56 if span.is_none() && timer.is_none() {
57 return None;
59 }
60 Some(Box::new(BuiltinObservation {
61 _span: span,
62 _timer: timer,
63 }))
64 }
65
66 fn is_runtime_context_builtin(name: &str) -> bool {
67 matches!(
68 name,
69 "runtime_context"
70 | "task_current"
71 | "runtime_context_values"
72 | "runtime_context_get"
73 | "runtime_context_set"
74 | "runtime_context_clear"
75 )
76 }
77
78 fn resolve_sync_builtin_id_or_name(
79 &self,
80 direct_id: Option<BuiltinId>,
81 name: &str,
82 ) -> Option<Result<VmBuiltinFn, VmError>> {
83 if crate::autonomy::needs_async_side_effect_enforcement(name)
84 || Self::is_runtime_context_builtin(name)
85 {
86 return None;
87 }
88
89 let dispatch = if let Some(id) = direct_id {
90 self.builtins_by_id
91 .get(&id)
92 .filter(|entry| entry.name.as_ref() == name)
93 .map(|entry| entry.dispatch.clone())
94 } else {
95 None
96 }
97 .or_else(|| {
98 self.builtins
99 .get(name)
100 .cloned()
101 .map(VmBuiltinDispatch::Sync)
102 });
103
104 let Some(dispatch) = dispatch else {
105 if self.async_builtins.contains_key(name) || self.bridge.is_some() {
106 return None;
107 }
108 let all_builtins = self
109 .builtins
110 .keys()
111 .chain(self.async_builtins.keys())
112 .map(|s| s.as_str());
113 return Some(
114 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
115 Err(VmError::Runtime(format!(
116 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
117 )))
118 } else {
119 Err(VmError::UndefinedBuiltin(name.to_string()))
120 },
121 );
122 };
123
124 match dispatch {
125 VmBuiltinDispatch::Sync(builtin) => Some(Ok(builtin)),
126 VmBuiltinDispatch::Async(_) => None,
127 }
128 }
129
130 fn validate_sync_builtin_args(&self, name: &str, args: &[VmValue]) -> Result<(), VmError> {
131 if self.denied_builtins.contains(name) {
132 return Err(VmError::CategorizedError {
133 message: format!("Tool '{name}' is not permitted."),
134 category: ErrorCategory::ToolRejected,
135 });
136 }
137 crate::orchestration::enforce_current_policy_for_builtin(name, args)?;
138 crate::typecheck::validate_builtin_call(name, args, None)
139 }
140
141 fn index_builtin_id(&mut self, name: &str, dispatch: VmBuiltinDispatch) {
142 let id = BuiltinId::from_name(name);
143 if self.builtin_id_collisions.contains(&id) {
144 return;
145 }
146 if let Some(existing) = self.builtins_by_id.get(&id) {
147 if existing.name.as_ref() != name {
148 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
149 Arc::make_mut(&mut self.builtin_id_collisions).insert(id);
150 return;
151 }
152 }
153 Arc::make_mut(&mut self.builtins_by_id).insert(
154 id,
155 VmBuiltinEntry {
156 name: std::sync::Arc::from(name),
157 dispatch,
158 },
159 );
160 }
161
162 fn refresh_builtin_id(&mut self, name: &str) {
163 if let Some(builtin) = self.builtins.get(name).cloned() {
164 self.index_builtin_id(name, VmBuiltinDispatch::Sync(builtin));
165 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
166 self.index_builtin_id(name, VmBuiltinDispatch::Async(async_builtin));
167 } else {
168 let id = BuiltinId::from_name(name);
169 if self
170 .builtins_by_id
171 .get(&id)
172 .is_some_and(|entry| entry.name.as_ref() == name)
173 {
174 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
175 }
176 }
177 }
178
179 pub fn register_builtin<F>(&mut self, name: &str, f: F)
181 where
182 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
183 {
184 Arc::make_mut(&mut self.builtins).insert(name.to_string(), Arc::new(f));
185 Arc::make_mut(&mut self.builtin_metadata)
186 .insert(name.to_string(), VmBuiltinMetadata::sync(name.to_string()));
187 self.refresh_builtin_id(name);
188 }
189
190 pub fn register_builtin_with_metadata<F>(&mut self, metadata: VmBuiltinMetadata, f: F)
192 where
193 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
194 {
195 let name = metadata.name().to_string();
196 Arc::make_mut(&mut self.builtins).insert(name.clone(), Arc::new(f));
197 Arc::make_mut(&mut self.builtin_metadata)
198 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Sync));
199 self.refresh_builtin_id(&name);
200 }
201
202 pub fn register_builtin_def(&mut self, def: &'static crate::stdlib::macros::VmBuiltinDef) {
208 use crate::stdlib::macros::VmBuiltinHandler;
209 if def.parser_only {
210 return;
211 }
212 let arity = arity_from_sig(&def.sig);
216 let names = std::iter::once(def.sig.name).chain(def.aliases.iter().copied());
217 for name in names {
218 match def.handler {
219 VmBuiltinHandler::Sync(f) => {
220 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Sync);
221 self.register_builtin_with_metadata(meta, f);
222 }
223 VmBuiltinHandler::Async(f) => {
224 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Async);
225 self.register_async_builtin_with_metadata(meta, f);
229 }
230 VmBuiltinHandler::None => {
231 panic!(
234 "VmBuiltinHandler::None for {name:?} without parser_only=true \
235 on its BuiltinDef"
236 );
237 }
238 }
239 }
240 }
241
242 pub fn unregister_builtin(&mut self, name: &str) {
244 Arc::make_mut(&mut self.builtins).remove(name);
245 if self.async_builtins.contains_key(name) {
246 Arc::make_mut(&mut self.builtin_metadata).insert(
247 name.to_string(),
248 VmBuiltinMetadata::async_builtin(name.to_string()),
249 );
250 } else {
251 Arc::make_mut(&mut self.builtin_metadata).remove(name);
252 }
253 self.refresh_builtin_id(name);
254 }
255
256 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
259 where
260 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
261 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
262 {
263 Arc::make_mut(&mut self.async_builtins).insert(
264 name.to_string(),
265 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
266 );
267 Arc::make_mut(&mut self.builtin_metadata).insert(
268 name.to_string(),
269 VmBuiltinMetadata::async_builtin(name.to_string()),
270 );
271 self.refresh_builtin_id(name);
272 }
273
274 pub fn register_async_builtin_with_metadata<F, Fut>(
277 &mut self,
278 metadata: VmBuiltinMetadata,
279 f: F,
280 ) where
281 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
282 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
283 {
284 let name = metadata.name().to_string();
285 Arc::make_mut(&mut self.async_builtins).insert(
286 name.clone(),
287 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
288 );
289 Arc::make_mut(&mut self.builtin_metadata)
290 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
291 self.refresh_builtin_id(&name);
292 }
293
294 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
295 let id = BuiltinId::from_name(name);
296 if self
297 .builtins_by_id
298 .get(&id)
299 .is_some_and(|entry| entry.name.as_ref() == name)
300 {
301 Some(id)
302 } else {
303 None
304 }
305 }
306
307 pub(crate) async fn call_closure(
328 &mut self,
329 closure: &VmClosure,
330 args: &[VmValue],
331 ) -> Result<VmValue, VmError> {
332 self.call_closure_args(closure, CallArgs::Slice(args)).await
333 }
334
335 pub(crate) async fn call_closure_args(
336 &mut self,
337 closure: &VmClosure,
338 args: CallArgs<'_>,
339 ) -> Result<VmValue, VmError> {
340 let saved_handlers = std::mem::take(&mut self.exception_handlers);
341 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
342 .then(crate::step_runtime::take_active_context);
343
344 let target_frame_depth = self.frames.len();
345 let frame_result = self.push_closure_frame_args(closure, &args);
346 drop(args);
347 let result = match frame_result {
348 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
349 Err(e) => Err(e),
350 };
351
352 self.exception_handlers = saved_handlers;
353 if let Some(ctx) = active_context {
354 crate::step_runtime::restore_active_context(ctx);
355 }
356
357 result
358 }
359
360 pub(crate) async fn call_callable_value(
365 &mut self,
366 callable: &VmValue,
367 args: &[VmValue],
368 ) -> Result<VmValue, VmError> {
369 self.call_callable_args(callable, CallArgs::Slice(args))
370 .await
371 }
372
373 pub(crate) async fn call_callable_owned(
374 &mut self,
375 callable: &VmValue,
376 args: Vec<VmValue>,
377 ) -> Result<VmValue, VmError> {
378 self.call_callable_args(callable, CallArgs::Owned(args))
379 .await
380 }
381
382 pub(crate) async fn call_callable_zero(
383 &mut self,
384 callable: &VmValue,
385 ) -> Result<VmValue, VmError> {
386 self.call_callable_args(callable, CallArgs::Empty).await
387 }
388
389 pub(crate) async fn call_callable_one(
390 &mut self,
391 callable: &VmValue,
392 arg: &VmValue,
393 ) -> Result<VmValue, VmError> {
394 self.call_callable_args(callable, CallArgs::One(arg)).await
395 }
396
397 pub(crate) async fn call_callable_two(
398 &mut self,
399 callable: &VmValue,
400 first: &VmValue,
401 second: &VmValue,
402 ) -> Result<VmValue, VmError> {
403 self.call_callable_args(callable, CallArgs::Two(first, second))
404 .await
405 }
406
407 pub(crate) async fn call_callable_args(
408 &mut self,
409 callable: &VmValue,
410 args: CallArgs<'_>,
411 ) -> Result<VmValue, VmError> {
412 match callable {
413 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
414 VmValue::Dict(registry) => {
415 let handler =
416 crate::vm::tool_callable::require_single_harn_tool_handler(registry, || {
417 "expected callable, got dict".to_string()
418 })?;
419 self.call_closure_args(&handler, args).await
420 }
421 VmValue::BuiltinRef(name) => {
422 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
423 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
424 return result;
425 }
426 }
427 self.call_named_builtin(name, args.into_vec()).await
428 }
429 VmValue::BuiltinRefId(r) => {
430 if let Some(result) =
431 self.try_call_sync_builtin_id_or_name_args(Some(r.id), &r.name, &args)
432 {
433 return result;
434 }
435 self.call_builtin_id_or_name(r.id, &r.name, args.into_vec())
436 .await
437 }
438 other => Err(VmError::TypeError(format!(
439 "expected callable, got {}",
440 other.type_name()
441 ))),
442 }
443 }
444
445 fn call_sync_builtin_by_ref_args(
446 &mut self,
447 name: &str,
448 args: &CallArgs<'_>,
449 ) -> Option<Result<VmValue, VmError>> {
450 self.try_call_sync_builtin_id_or_name_args(None, name, args)
451 }
452
453 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
455 matches!(
456 v,
457 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId(_)
458 ) || crate::vm::tool_callable::is_single_harn_tool_registry_value(v)
459 }
460
461 pub async fn call_closure_pub(
464 &mut self,
465 closure: &VmClosure,
466 args: &[VmValue],
467 ) -> Result<VmValue, VmError> {
468 self.ensure_execution_available()?;
469 self.cancel_grace_instructions_remaining = None;
470 self.call_closure(closure, args).await
471 }
472
473 pub(crate) async fn call_named_builtin(
476 &mut self,
477 name: &str,
478 args: Vec<VmValue>,
479 ) -> Result<VmValue, VmError> {
480 self.call_builtin_impl(name, args, None).await
481 }
482
483 pub(crate) async fn call_builtin_id_or_name(
484 &mut self,
485 id: BuiltinId,
486 name: &str,
487 args: Vec<VmValue>,
488 ) -> Result<VmValue, VmError> {
489 self.call_builtin_impl(name, args, Some(id)).await
490 }
491
492 pub(in crate::vm) fn sync_builtin_interrupt_guard(
499 &self,
500 ) -> Option<crate::op_interrupt::OpInterruptGuard> {
501 let scope_deadline = self.deadlines.last().map(|(deadline, _)| *deadline);
504 let deadline = match (scope_deadline, self.interrupt_handler_deadline) {
505 (Some(scope), Some(interrupt)) => Some(scope.min(interrupt)),
506 (scope, interrupt) => scope.or(interrupt),
507 };
508 if self.cancel_token.is_none() && deadline.is_none() {
509 return None;
510 }
511 Some(crate::op_interrupt::install(
512 self.cancel_token.clone(),
513 deadline,
514 ))
515 }
516
517 pub(crate) fn try_call_sync_builtin_id_or_name_args(
518 &mut self,
519 direct_id: Option<BuiltinId>,
520 name: &str,
521 args: &CallArgs<'_>,
522 ) -> Option<Result<VmValue, VmError>> {
523 if self.denied_builtins.contains(name) {
524 return Some(Err(VmError::CategorizedError {
525 message: format!("Tool '{name}' is not permitted."),
526 category: ErrorCategory::ToolRejected,
527 }));
528 }
529 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
530 Ok(builtin) => builtin,
531 Err(error) => return Some(Err(error)),
532 };
533 let _observe = Self::observe_builtin_call(name);
534 if let Err(error) = args.with_slice(|slice| self.validate_sync_builtin_args(name, slice)) {
535 return Some(Err(error));
536 }
537
538 let _interrupt = self.sync_builtin_interrupt_guard();
539 Some(args.with_slice(|slice| builtin(slice, &mut self.output)))
540 }
541
542 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
543 &mut self,
544 direct_id: Option<BuiltinId>,
545 name: &str,
546 args_start: usize,
547 ) -> Option<Result<VmValue, VmError>> {
548 if self.denied_builtins.contains(name) {
549 return Some(Err(VmError::CategorizedError {
550 message: format!("Tool '{name}' is not permitted."),
551 category: ErrorCategory::ToolRejected,
552 }));
553 }
554 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
555 Ok(builtin) => builtin,
556 Err(error) => return Some(Err(error)),
557 };
558 if args_start > self.stack.len() {
559 return Some(Err(VmError::Runtime(
560 "call argument stack underflow".to_string(),
561 )));
562 }
563
564 let _observe = Self::observe_builtin_call(name);
565 if let Err(error) = self.validate_sync_builtin_args(name, &self.stack[args_start..]) {
566 return Some(Err(error));
567 }
568
569 let _interrupt = self.sync_builtin_interrupt_guard();
570 Some(builtin(&self.stack[args_start..], &mut self.output))
571 }
572
573 async fn call_builtin_impl(
574 &mut self,
575 name: &str,
576 args: Vec<VmValue>,
577 direct_id: Option<BuiltinId>,
578 ) -> Result<VmValue, VmError> {
579 let _observe = Self::observe_builtin_call(name);
580
581 if self.denied_builtins.contains(name) {
583 return Err(VmError::CategorizedError {
584 message: format!("Tool '{name}' is not permitted."),
585 category: ErrorCategory::ToolRejected,
586 });
587 }
588 let autonomy = if crate::autonomy::needs_async_side_effect_enforcement(name) {
589 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
590 } else {
591 None
592 };
593 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
594 return Ok(value);
595 }
596 if !matches!(
597 autonomy,
598 Some(crate::autonomy::AutonomyDecision::AllowApproved)
599 ) {
600 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
601 }
602 crate::typecheck::validate_builtin_call(name, &args, None)?;
603
604 if let Some(result) =
605 crate::runtime_context::dispatch_runtime_context_builtin(self, name, &args)
606 {
607 return result;
608 }
609
610 if let Some(id) = direct_id {
611 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
612 if entry.name.as_ref() == name {
613 return self.call_builtin_entry(name, entry.dispatch, args).await;
614 }
615 }
616 }
617
618 if let Some(builtin) = self.builtins.get(name).cloned() {
619 self.call_builtin_entry(name, VmBuiltinDispatch::Sync(builtin), args)
620 .await
621 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
622 self.call_builtin_entry(name, VmBuiltinDispatch::Async(async_builtin), args)
623 .await
624 } else if let Some(bridge) = &self.bridge {
625 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
626 let args_json: Vec<serde_json::Value> =
627 args.iter().map(crate::llm::vm_value_to_json).collect();
628 let result = bridge
629 .call(
630 "builtin_call",
631 serde_json::json!({"name": name, "args": args_json}),
632 )
633 .await?;
634 Ok(crate::bridge::json_result_to_vm_value(&result))
635 } else {
636 let all_builtins = self
637 .builtins
638 .keys()
639 .chain(self.async_builtins.keys())
640 .map(|s| s.as_str());
641 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
642 return Err(VmError::Runtime(format!(
643 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
644 )));
645 }
646 Err(VmError::UndefinedBuiltin(name.to_string()))
647 }
648 }
649
650 async fn call_builtin_entry(
651 &mut self,
652 name: &str,
653 dispatch: VmBuiltinDispatch,
654 args: Vec<VmValue>,
655 ) -> Result<VmValue, VmError> {
656 let result = match dispatch {
657 VmBuiltinDispatch::Sync(builtin) => {
658 let _interrupt = self.sync_builtin_interrupt_guard();
659 builtin(&args, &mut self.output)
660 }
661 VmBuiltinDispatch::Async(async_builtin) => {
662 let (result, captured) =
667 crate::vm::run_async_builtin_with(self.child_vm_inline(), |ctx| {
668 async_builtin(ctx, args)
669 })
670 .await;
671 if !captured.is_empty() {
672 self.output.push_str(&captured);
673 }
674 result
675 }
676 }?;
677 if matches!(
678 name,
679 "sync_mutex_acquire"
680 | "sync_semaphore_acquire"
681 | "sync_gate_acquire"
682 | "sync_rwlock_acquire"
683 ) {
684 if let VmValue::SyncPermit(permit) = &result {
685 self.adopt_sync_permit_for_current_scope(permit.as_ref().clone());
686 }
687 }
688 Ok(result)
689 }
690}
691
692fn builtin_def_metadata(
697 def: &'static crate::stdlib::macros::VmBuiltinDef,
698 name: &'static str,
699 arity: VmBuiltinArity,
700 kind: VmBuiltinKind,
701) -> VmBuiltinMetadata {
702 let mut meta = match kind {
703 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
704 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
705 }
706 .arity(arity);
707 if let Some(category) = def.category {
708 meta = meta.category_static(category);
709 }
710 if let Some(doc) = def.doc {
711 meta = meta.doc_static(doc);
712 }
713 if let Some(sig_text) = def.signature_text {
714 meta = meta.signature_static(sig_text);
715 } else {
716 meta = meta.signature_owned(format!("{}", def.sig));
723 }
724 meta
725}
726
727fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
732 let required = sig.params.iter().filter(|p| !p.optional).count();
733 let total = sig.params.len();
734 if sig.has_rest {
735 if required == 0 {
736 VmBuiltinArity::Variadic
737 } else {
738 VmBuiltinArity::Min(required)
739 }
740 } else if required == total {
741 VmBuiltinArity::Exact(total)
742 } else {
743 VmBuiltinArity::Range {
744 min: required,
745 max: total,
746 }
747 }
748}