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
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 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
106 Arc::make_mut(&mut self.builtin_id_collisions).insert(id);
107 return;
108 }
109 }
110 Arc::make_mut(&mut self.builtins_by_id).insert(
111 id,
112 VmBuiltinEntry {
113 name: std::sync::Arc::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 Arc::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> + Send + Sync + 'static,
140 {
141 Arc::make_mut(&mut self.builtins).insert(name.to_string(), Arc::new(f));
142 Arc::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> + Send + Sync + 'static,
151 {
152 let name = metadata.name().to_string();
153 Arc::make_mut(&mut self.builtins).insert(name.clone(), Arc::new(f));
154 Arc::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 Arc::make_mut(&mut self.builtins).remove(name);
202 if self.async_builtins.contains_key(name) {
203 Arc::make_mut(&mut self.builtin_metadata).insert(
204 name.to_string(),
205 VmBuiltinMetadata::async_builtin(name.to_string()),
206 );
207 } else {
208 Arc::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)
216 where
217 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
218 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
219 {
220 Arc::make_mut(&mut self.async_builtins).insert(
221 name.to_string(),
222 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
223 );
224 Arc::make_mut(&mut self.builtin_metadata).insert(
225 name.to_string(),
226 VmBuiltinMetadata::async_builtin(name.to_string()),
227 );
228 self.refresh_builtin_id(name);
229 }
230
231 pub fn register_async_builtin_with_metadata<F, Fut>(
234 &mut self,
235 metadata: VmBuiltinMetadata,
236 f: F,
237 ) where
238 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
239 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
240 {
241 let name = metadata.name().to_string();
242 Arc::make_mut(&mut self.async_builtins).insert(
243 name.clone(),
244 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
245 );
246 Arc::make_mut(&mut self.builtin_metadata)
247 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
248 self.refresh_builtin_id(&name);
249 }
250
251 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
252 let id = BuiltinId::from_name(name);
253 if self
254 .builtins_by_id
255 .get(&id)
256 .is_some_and(|entry| entry.name.as_ref() == name)
257 {
258 Some(id)
259 } else {
260 None
261 }
262 }
263
264 pub(crate) async fn call_closure(
285 &mut self,
286 closure: &VmClosure,
287 args: &[VmValue],
288 ) -> Result<VmValue, VmError> {
289 self.call_closure_args(closure, CallArgs::Slice(args)).await
290 }
291
292 pub(crate) async fn call_closure_args(
293 &mut self,
294 closure: &VmClosure,
295 args: CallArgs<'_>,
296 ) -> Result<VmValue, VmError> {
297 let saved_handlers = std::mem::take(&mut self.exception_handlers);
298 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
299 .then(crate::step_runtime::take_active_context);
300
301 let target_frame_depth = self.frames.len();
302 let frame_result = self.push_closure_frame_args(closure, &args);
303 drop(args);
304 let result = match frame_result {
305 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
306 Err(e) => Err(e),
307 };
308
309 self.exception_handlers = saved_handlers;
310 if let Some(ctx) = active_context {
311 crate::step_runtime::restore_active_context(ctx);
312 }
313
314 result
315 }
316
317 pub(crate) async fn call_callable_value(
322 &mut self,
323 callable: &VmValue,
324 args: &[VmValue],
325 ) -> Result<VmValue, VmError> {
326 self.call_callable_args(callable, CallArgs::Slice(args))
327 .await
328 }
329
330 pub(crate) async fn call_callable_owned(
331 &mut self,
332 callable: &VmValue,
333 args: Vec<VmValue>,
334 ) -> Result<VmValue, VmError> {
335 self.call_callable_args(callable, CallArgs::Owned(args))
336 .await
337 }
338
339 pub(crate) async fn call_callable_zero(
340 &mut self,
341 callable: &VmValue,
342 ) -> Result<VmValue, VmError> {
343 self.call_callable_args(callable, CallArgs::Empty).await
344 }
345
346 pub(crate) async fn call_callable_one(
347 &mut self,
348 callable: &VmValue,
349 arg: &VmValue,
350 ) -> Result<VmValue, VmError> {
351 self.call_callable_args(callable, CallArgs::One(arg)).await
352 }
353
354 pub(crate) async fn call_callable_two(
355 &mut self,
356 callable: &VmValue,
357 first: &VmValue,
358 second: &VmValue,
359 ) -> Result<VmValue, VmError> {
360 self.call_callable_args(callable, CallArgs::Two(first, second))
361 .await
362 }
363
364 pub(crate) async fn call_callable_args(
365 &mut self,
366 callable: &VmValue,
367 args: CallArgs<'_>,
368 ) -> Result<VmValue, VmError> {
369 match callable {
370 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
371 VmValue::Dict(registry) => {
372 let handler =
373 crate::vm::tool_callable::require_single_harn_tool_handler(registry, || {
374 "expected callable, got dict".to_string()
375 })?;
376 self.call_closure_args(&handler, args).await
377 }
378 VmValue::BuiltinRef(name) => {
379 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
380 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
381 return result;
382 }
383 }
384 self.call_named_builtin(name, args.into_vec()).await
385 }
386 VmValue::BuiltinRefId(r) => {
387 if let Some(result) =
388 self.try_call_sync_builtin_id_or_name_args(Some(r.id), &r.name, &args)
389 {
390 return result;
391 }
392 self.call_builtin_id_or_name(r.id, &r.name, args.into_vec())
393 .await
394 }
395 other => Err(VmError::TypeError(format!(
396 "expected callable, got {}",
397 other.type_name()
398 ))),
399 }
400 }
401
402 fn call_sync_builtin_by_ref_args(
403 &mut self,
404 name: &str,
405 args: &CallArgs<'_>,
406 ) -> Option<Result<VmValue, VmError>> {
407 self.try_call_sync_builtin_id_or_name_args(None, name, args)
408 }
409
410 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
412 matches!(
413 v,
414 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId(_)
415 ) || crate::vm::tool_callable::is_single_harn_tool_registry_value(v)
416 }
417
418 pub async fn call_closure_pub(
421 &mut self,
422 closure: &VmClosure,
423 args: &[VmValue],
424 ) -> Result<VmValue, VmError> {
425 self.ensure_execution_available()?;
426 self.cancel_grace_instructions_remaining = None;
427 self.call_closure(closure, args).await
428 }
429
430 pub(crate) async fn call_named_builtin(
433 &mut self,
434 name: &str,
435 args: Vec<VmValue>,
436 ) -> Result<VmValue, VmError> {
437 self.call_builtin_impl(name, args, None).await
438 }
439
440 pub(crate) async fn call_builtin_id_or_name(
441 &mut self,
442 id: BuiltinId,
443 name: &str,
444 args: Vec<VmValue>,
445 ) -> Result<VmValue, VmError> {
446 self.call_builtin_impl(name, args, Some(id)).await
447 }
448
449 pub(in crate::vm) fn sync_builtin_interrupt_guard(
456 &self,
457 ) -> Option<crate::op_interrupt::OpInterruptGuard> {
458 let scope_deadline = self.deadlines.last().map(|(deadline, _)| *deadline);
461 let deadline = match (scope_deadline, self.interrupt_handler_deadline) {
462 (Some(scope), Some(interrupt)) => Some(scope.min(interrupt)),
463 (scope, interrupt) => scope.or(interrupt),
464 };
465 if self.cancel_token.is_none() && deadline.is_none() {
466 return None;
467 }
468 Some(crate::op_interrupt::install(
469 self.cancel_token.clone(),
470 deadline,
471 ))
472 }
473
474 pub(crate) fn try_call_sync_builtin_id_or_name_args(
475 &mut self,
476 direct_id: Option<BuiltinId>,
477 name: &str,
478 args: &CallArgs<'_>,
479 ) -> Option<Result<VmValue, VmError>> {
480 if self.denied_builtins.contains(name) {
481 return Some(Err(VmError::CategorizedError {
482 message: format!("Tool '{name}' is not permitted."),
483 category: ErrorCategory::ToolRejected,
484 }));
485 }
486 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
487 Ok(builtin) => builtin,
488 Err(error) => return Some(Err(error)),
489 };
490 let _span =
491 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
492 if let Err(error) = args.with_slice(|slice| self.validate_sync_builtin_args(name, slice)) {
493 return Some(Err(error));
494 }
495
496 let _interrupt = self.sync_builtin_interrupt_guard();
497 Some(args.with_slice(|slice| builtin(slice, &mut self.output)))
498 }
499
500 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
501 &mut self,
502 direct_id: Option<BuiltinId>,
503 name: &str,
504 args_start: usize,
505 ) -> Option<Result<VmValue, VmError>> {
506 if self.denied_builtins.contains(name) {
507 return Some(Err(VmError::CategorizedError {
508 message: format!("Tool '{name}' is not permitted."),
509 category: ErrorCategory::ToolRejected,
510 }));
511 }
512 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
513 Ok(builtin) => builtin,
514 Err(error) => return Some(Err(error)),
515 };
516 if args_start > self.stack.len() {
517 return Some(Err(VmError::Runtime(
518 "call argument stack underflow".to_string(),
519 )));
520 }
521
522 let _span =
523 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
524 if let Err(error) = self.validate_sync_builtin_args(name, &self.stack[args_start..]) {
525 return Some(Err(error));
526 }
527
528 let _interrupt = self.sync_builtin_interrupt_guard();
529 Some(builtin(&self.stack[args_start..], &mut self.output))
530 }
531
532 async fn call_builtin_impl(
533 &mut self,
534 name: &str,
535 args: Vec<VmValue>,
536 direct_id: Option<BuiltinId>,
537 ) -> Result<VmValue, VmError> {
538 let _span =
540 Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
541
542 if self.denied_builtins.contains(name) {
544 return Err(VmError::CategorizedError {
545 message: format!("Tool '{name}' is not permitted."),
546 category: ErrorCategory::ToolRejected,
547 });
548 }
549 let autonomy = if crate::autonomy::needs_async_side_effect_enforcement(name) {
550 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
551 } else {
552 None
553 };
554 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
555 return Ok(value);
556 }
557 if !matches!(
558 autonomy,
559 Some(crate::autonomy::AutonomyDecision::AllowApproved)
560 ) {
561 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
562 }
563 crate::typecheck::validate_builtin_call(name, &args, None)?;
564
565 if let Some(result) =
566 crate::runtime_context::dispatch_runtime_context_builtin(self, name, &args)
567 {
568 return result;
569 }
570
571 if let Some(id) = direct_id {
572 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
573 if entry.name.as_ref() == name {
574 return self.call_builtin_entry(name, entry.dispatch, args).await;
575 }
576 }
577 }
578
579 if let Some(builtin) = self.builtins.get(name).cloned() {
580 self.call_builtin_entry(name, VmBuiltinDispatch::Sync(builtin), args)
581 .await
582 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
583 self.call_builtin_entry(name, VmBuiltinDispatch::Async(async_builtin), args)
584 .await
585 } else if let Some(bridge) = &self.bridge {
586 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
587 let args_json: Vec<serde_json::Value> =
588 args.iter().map(crate::llm::vm_value_to_json).collect();
589 let result = bridge
590 .call(
591 "builtin_call",
592 serde_json::json!({"name": name, "args": args_json}),
593 )
594 .await?;
595 Ok(crate::bridge::json_result_to_vm_value(&result))
596 } else {
597 let all_builtins = self
598 .builtins
599 .keys()
600 .chain(self.async_builtins.keys())
601 .map(|s| s.as_str());
602 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
603 return Err(VmError::Runtime(format!(
604 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
605 )));
606 }
607 Err(VmError::UndefinedBuiltin(name.to_string()))
608 }
609 }
610
611 async fn call_builtin_entry(
612 &mut self,
613 name: &str,
614 dispatch: VmBuiltinDispatch,
615 args: Vec<VmValue>,
616 ) -> Result<VmValue, VmError> {
617 let result = match dispatch {
618 VmBuiltinDispatch::Sync(builtin) => {
619 let _interrupt = self.sync_builtin_interrupt_guard();
620 builtin(&args, &mut self.output)
621 }
622 VmBuiltinDispatch::Async(async_builtin) => {
623 let (result, captured) =
628 crate::vm::run_async_builtin_with(self.child_vm_inline(), |ctx| {
629 async_builtin(ctx, args)
630 })
631 .await;
632 if !captured.is_empty() {
633 self.output.push_str(&captured);
634 }
635 result
636 }
637 }?;
638 if matches!(
639 name,
640 "sync_mutex_acquire"
641 | "sync_semaphore_acquire"
642 | "sync_gate_acquire"
643 | "sync_rwlock_acquire"
644 ) {
645 if let VmValue::SyncPermit(permit) = &result {
646 self.adopt_sync_permit_for_current_scope(permit.as_ref().clone());
647 }
648 }
649 Ok(result)
650 }
651}
652
653fn builtin_def_metadata(
658 def: &'static crate::stdlib::macros::VmBuiltinDef,
659 name: &'static str,
660 arity: VmBuiltinArity,
661 kind: VmBuiltinKind,
662) -> VmBuiltinMetadata {
663 let mut meta = match kind {
664 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
665 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
666 }
667 .arity(arity);
668 if let Some(category) = def.category {
669 meta = meta.category_static(category);
670 }
671 if let Some(doc) = def.doc {
672 meta = meta.doc_static(doc);
673 }
674 if let Some(sig_text) = def.signature_text {
675 meta = meta.signature_static(sig_text);
676 } else {
677 meta = meta.signature_owned(format!("{}", def.sig));
684 }
685 meta
686}
687
688fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
693 let required = sig.params.iter().filter(|p| !p.optional).count();
694 let total = sig.params.len();
695 if sig.has_rest {
696 if required == 0 {
697 VmBuiltinArity::Variadic
698 } else {
699 VmBuiltinArity::Min(required)
700 }
701 } else if required == total {
702 VmBuiltinArity::Exact(total)
703 } else {
704 VmBuiltinArity::Range {
705 min: required,
706 max: total,
707 }
708 }
709}