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
28struct ResolvedSyncBuiltin {
29 handler: VmBuiltinFn,
30 recorded_effects: Option<&'static [harn_builtin_meta::EffectSpec]>,
31}
32
33impl Vm {
34 fn builtin_span_kind(name: &str) -> Option<crate::tracing::SpanKind> {
35 match name {
36 "llm_call" | "llm_stream" | "llm_stream_call" | "agent_loop" | "agent_turn" => {
37 Some(crate::tracing::SpanKind::LlmCall)
38 }
39 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
40 _ => None,
41 }
42 }
43
44 fn observe_builtin_call(name: &str) -> Option<Box<BuiltinObservation<'_>>> {
59 let span = Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
60 let timer = crate::builtin_profile::BuiltinTimer::start(name);
61 if span.is_none() && timer.is_none() {
62 return None;
64 }
65 Some(Box::new(BuiltinObservation {
66 _span: span,
67 _timer: timer,
68 }))
69 }
70
71 fn is_runtime_context_builtin(name: &str) -> bool {
72 matches!(
73 name,
74 "runtime_context"
75 | "task_current"
76 | "runtime_context_values"
77 | "runtime_context_get"
78 | "runtime_context_set"
79 | "runtime_context_clear"
80 )
81 }
82
83 fn resolve_sync_builtin_id_or_name(
84 &self,
85 direct_id: Option<BuiltinId>,
86 name: &str,
87 ) -> Option<Result<ResolvedSyncBuiltin, VmError>> {
88 if crate::autonomy::needs_async_side_effect_enforcement(name)
89 || Self::is_runtime_context_builtin(name)
90 {
91 return None;
92 }
93
94 let dispatch = if let Some(id) = direct_id {
95 self.builtins_by_id
96 .get(&id)
97 .filter(|entry| entry.name.as_ref() == name)
98 .map(|entry| (entry.dispatch.clone(), entry.recorded_effects))
99 } else {
100 None
101 }
102 .or_else(|| {
103 self.builtins.get(name).cloned().map(|builtin| {
104 let recorded_effects = crate::stdlib::recorded_effect_builtin_manifest_entry(name)
105 .map(|entry| entry.contract.effects);
106 (VmBuiltinDispatch::Sync(builtin), recorded_effects)
107 })
108 });
109
110 let Some(dispatch) = dispatch else {
111 if self.async_builtins.contains_key(name) || self.bridge.is_some() {
112 return None;
113 }
114 let all_builtins = self
115 .builtins
116 .keys()
117 .chain(self.async_builtins.keys())
118 .map(|s| s.as_str());
119 return Some(
120 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
121 Err(VmError::Runtime(format!(
122 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
123 )))
124 } else {
125 Err(VmError::UndefinedBuiltin(name.to_string()))
126 },
127 );
128 };
129
130 match dispatch {
131 (VmBuiltinDispatch::Sync(builtin), recorded_effects) => Some(Ok(ResolvedSyncBuiltin {
132 handler: builtin,
133 recorded_effects,
134 })),
135 (VmBuiltinDispatch::Async(_), _) => None,
136 }
137 }
138
139 fn validate_sync_builtin_args(
140 &self,
141 name: &str,
142 args: &[VmValue],
143 recorded_effects: Option<&'static [harn_builtin_meta::EffectSpec]>,
144 ) -> Result<(), VmError> {
145 if self.denied_builtins.contains(name) {
146 return Err(VmError::CategorizedError {
147 message: format!("Tool '{name}' is not permitted."),
148 category: ErrorCategory::ToolRejected,
149 });
150 }
151 crate::orchestration::enforce_current_policy_for_builtin(name, args)?;
152 if let Some(specs) = recorded_effects {
153 self.record_builtin_effect_specs(specs, args);
154 }
155 crate::typecheck::validate_builtin_call(name, args, None)
156 }
157
158 fn index_builtin_id(&mut self, name: &str, dispatch: VmBuiltinDispatch) {
159 let id = BuiltinId::from_name(name);
160 if self.builtin_id_collisions.contains(&id) {
161 return;
162 }
163 if let Some(existing) = self.builtins_by_id.get(&id) {
164 if existing.name.as_ref() != name {
165 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
166 Arc::make_mut(&mut self.builtin_id_collisions).insert(id);
167 return;
168 }
169 }
170 Arc::make_mut(&mut self.builtins_by_id).insert(
171 id,
172 VmBuiltinEntry {
173 name: std::sync::Arc::from(name),
174 dispatch,
175 recorded_effects: crate::stdlib::recorded_effect_builtin_manifest_entry(name)
176 .map(|entry| entry.contract.effects),
177 },
178 );
179 }
180
181 fn refresh_builtin_id(&mut self, name: &str) {
182 if let Some(builtin) = self.builtins.get(name).cloned() {
183 self.index_builtin_id(name, VmBuiltinDispatch::Sync(builtin));
184 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
185 self.index_builtin_id(name, VmBuiltinDispatch::Async(async_builtin));
186 } else {
187 let id = BuiltinId::from_name(name);
188 if self
189 .builtins_by_id
190 .get(&id)
191 .is_some_and(|entry| entry.name.as_ref() == name)
192 {
193 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
194 }
195 }
196 }
197
198 pub fn register_builtin<F>(&mut self, name: &str, f: F)
200 where
201 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
202 {
203 Arc::make_mut(&mut self.builtins).insert(name.to_string(), Arc::new(f));
204 Arc::make_mut(&mut self.builtin_metadata)
205 .insert(name.to_string(), VmBuiltinMetadata::sync(name.to_string()));
206 self.refresh_builtin_id(name);
207 }
208
209 pub fn register_builtin_with_contract<F>(
212 &mut self,
213 name: &str,
214 contract: harn_builtin_meta::BuiltinContract,
215 f: F,
216 ) where
217 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
218 {
219 self.register_builtin_with_metadata(
220 VmBuiltinMetadata::sync(name.to_string()).with_contract(contract),
221 f,
222 );
223 }
224
225 pub fn register_builtin_with_metadata<F>(&mut self, metadata: VmBuiltinMetadata, f: F)
227 where
228 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
229 {
230 let name = metadata.name().to_string();
231 Arc::make_mut(&mut self.builtins).insert(name.clone(), Arc::new(f));
232 Arc::make_mut(&mut self.builtin_metadata)
233 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Sync));
234 self.refresh_builtin_id(&name);
235 }
236
237 pub fn register_builtin_def(&mut self, def: &'static crate::stdlib::macros::VmBuiltinDef) {
243 use crate::stdlib::macros::VmBuiltinHandler;
244 if def.parser_only {
245 return;
246 }
247 let arity = arity_from_sig(&def.sig);
251 let names = std::iter::once(def.sig.name).chain(def.aliases.iter().copied());
252 for name in names {
253 match def.handler {
254 VmBuiltinHandler::Sync(f) => {
255 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Sync);
256 self.register_builtin_with_metadata(meta, f);
257 }
258 VmBuiltinHandler::Async(f) => {
259 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Async);
260 self.register_async_builtin_with_metadata(meta, f);
264 }
265 VmBuiltinHandler::None => {
266 panic!(
269 "VmBuiltinHandler::None for {name:?} without parser_only=true \
270 on its BuiltinDef"
271 );
272 }
273 }
274 }
275 }
276
277 pub(crate) fn project_declared_capability_methods(&mut self) {
285 use harn_builtin_meta::BuiltinExposure;
286
287 let projections = self
288 .builtin_metadata
289 .iter()
290 .filter_map(|(name, metadata)| {
291 let BuiltinExposure::HarnessMethod { capability, method } =
292 metadata.contract().exposure
293 else {
294 return None;
295 };
296 Some((capability, method, name.clone(), metadata.kind()))
297 })
298 .collect::<Vec<_>>();
299
300 for (capability, method, name, kind) in projections {
301 if self
302 .capability_methods
303 .get(&capability)
304 .is_some_and(|methods| methods.contains_key(method))
305 {
306 continue;
307 }
308 let dispatch = match kind {
309 VmBuiltinKind::Sync => self
310 .builtins
311 .get(name.as_str())
312 .cloned()
313 .map(VmBuiltinDispatch::Sync),
314 VmBuiltinKind::Async => self
315 .async_builtins
316 .get(name.as_str())
317 .cloned()
318 .map(VmBuiltinDispatch::Async),
319 }
320 .unwrap_or_else(|| {
321 panic!(
322 "declared capability method harness.{}.{method} has no runtime handler `{name}`",
323 capability.field_name()
324 )
325 });
326 Arc::make_mut(&mut self.capability_methods)
327 .entry(capability)
328 .or_default()
329 .insert(method.to_string(), dispatch);
330 }
331 }
332
333 pub(crate) fn project_legacy_capability_globals(&mut self) {
337 if self.global("harness").is_none() {
338 return;
339 }
340 let mut projections =
341 std::collections::BTreeMap::<String, Option<VmBuiltinDispatch>>::new();
342 for methods in self.capability_methods.values() {
343 for (method, dispatch) in methods {
344 projections
345 .entry(method.clone())
346 .and_modify(|entry| *entry = None)
347 .or_insert_with(|| Some(dispatch.clone()));
348 }
349 }
350 for (method, dispatch) in projections {
351 if self.builtins.contains_key(&method) || self.async_builtins.contains_key(&method) {
352 continue;
353 }
354 match dispatch {
355 Some(VmBuiltinDispatch::Sync(handler)) => {
356 self.register_builtin(&method, move |args, output| handler(args, output));
357 }
358 Some(VmBuiltinDispatch::Async(handler)) => {
359 self.register_async_builtin(&method, move |ctx, args| handler(ctx, args));
360 }
361 None => {}
362 }
363 }
364 }
365
366 pub fn unregister_builtin(&mut self, name: &str) {
368 Arc::make_mut(&mut self.builtins).remove(name);
369 if self.async_builtins.contains_key(name) {
370 Arc::make_mut(&mut self.builtin_metadata).insert(
371 name.to_string(),
372 VmBuiltinMetadata::async_builtin(name.to_string()),
373 );
374 } else {
375 Arc::make_mut(&mut self.builtin_metadata).remove(name);
376 }
377 self.refresh_builtin_id(name);
378 }
379
380 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
383 where
384 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
385 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
386 {
387 Arc::make_mut(&mut self.async_builtins).insert(
388 name.to_string(),
389 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
390 );
391 Arc::make_mut(&mut self.builtin_metadata).insert(
392 name.to_string(),
393 VmBuiltinMetadata::async_builtin(name.to_string()),
394 );
395 self.refresh_builtin_id(name);
396 }
397
398 pub fn register_async_builtin_with_contract<F, Fut>(
401 &mut self,
402 name: &str,
403 contract: harn_builtin_meta::BuiltinContract,
404 f: F,
405 ) where
406 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
407 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
408 {
409 self.register_async_builtin_with_metadata(
410 VmBuiltinMetadata::async_builtin(name.to_string()).with_contract(contract),
411 f,
412 );
413 }
414
415 pub fn register_async_builtin_with_metadata<F, Fut>(
418 &mut self,
419 metadata: VmBuiltinMetadata,
420 f: F,
421 ) where
422 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
423 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
424 {
425 let name = metadata.name().to_string();
426 Arc::make_mut(&mut self.async_builtins).insert(
427 name.clone(),
428 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
429 );
430 Arc::make_mut(&mut self.builtin_metadata)
431 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
432 self.refresh_builtin_id(&name);
433 }
434
435 pub fn register_capability_method<F>(
442 &mut self,
443 capability: harn_builtin_meta::CapabilityId,
444 method: &str,
445 f: F,
446 ) where
447 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
448 {
449 self.insert_capability_method(capability, method, VmBuiltinDispatch::Sync(Arc::new(f)));
450 }
451
452 pub fn register_async_capability_method<F, Fut>(
454 &mut self,
455 capability: harn_builtin_meta::CapabilityId,
456 method: &str,
457 f: F,
458 ) where
459 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
460 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
461 {
462 self.insert_capability_method(
463 capability,
464 method,
465 VmBuiltinDispatch::Async(Arc::new(move |ctx, args| Box::pin(f(ctx, args)))),
466 );
467 }
468
469 fn insert_capability_method(
470 &mut self,
471 capability: harn_builtin_meta::CapabilityId,
472 method: &str,
473 dispatch: VmBuiltinDispatch,
474 ) {
475 let replaced = Arc::make_mut(&mut self.capability_methods)
476 .entry(capability)
477 .or_default()
478 .insert(method.to_string(), dispatch);
479 assert!(
480 replaced.is_none(),
481 "capability method harness.{}.{} registered twice",
482 capability.field_name(),
483 method
484 );
485 }
486
487 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
488 let id = BuiltinId::from_name(name);
489 if self
490 .builtins_by_id
491 .get(&id)
492 .is_some_and(|entry| entry.name.as_ref() == name)
493 {
494 Some(id)
495 } else {
496 None
497 }
498 }
499
500 pub(crate) async fn call_closure(
521 &mut self,
522 closure: &VmClosure,
523 args: &[VmValue],
524 ) -> Result<VmValue, VmError> {
525 self.call_closure_args(closure, CallArgs::Slice(args)).await
526 }
527
528 pub(crate) async fn call_closure_args(
529 &mut self,
530 closure: &VmClosure,
531 args: CallArgs<'_>,
532 ) -> Result<VmValue, VmError> {
533 let saved_handlers = std::mem::take(&mut self.exception_handlers);
534 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
535 .then(crate::step_runtime::suspend_active_context);
536
537 let target_frame_depth = self.frames.len();
538 let frame_result = self.push_closure_frame_args(closure, &args);
539 drop(args);
540 let result = match frame_result {
541 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
542 Err(e) => Err(e),
543 };
544
545 self.exception_handlers = saved_handlers;
546 drop(active_context);
547
548 result
549 }
550
551 pub(crate) async fn call_callable_value(
556 &mut self,
557 callable: &VmValue,
558 args: &[VmValue],
559 ) -> Result<VmValue, VmError> {
560 self.call_callable_args(callable, CallArgs::Slice(args))
561 .await
562 }
563
564 pub(crate) async fn call_callable_owned(
565 &mut self,
566 callable: &VmValue,
567 args: Vec<VmValue>,
568 ) -> Result<VmValue, VmError> {
569 self.call_callable_args(callable, CallArgs::Owned(args))
570 .await
571 }
572
573 pub(crate) async fn call_callable_zero(
574 &mut self,
575 callable: &VmValue,
576 ) -> Result<VmValue, VmError> {
577 self.call_callable_args(callable, CallArgs::Empty).await
578 }
579
580 pub(crate) async fn call_callable_one(
581 &mut self,
582 callable: &VmValue,
583 arg: &VmValue,
584 ) -> Result<VmValue, VmError> {
585 self.call_callable_args(callable, CallArgs::One(arg)).await
586 }
587
588 pub(crate) async fn call_callable_two(
589 &mut self,
590 callable: &VmValue,
591 first: &VmValue,
592 second: &VmValue,
593 ) -> Result<VmValue, VmError> {
594 self.call_callable_args(callable, CallArgs::Two(first, second))
595 .await
596 }
597
598 pub(crate) async fn call_callable_args(
599 &mut self,
600 callable: &VmValue,
601 args: CallArgs<'_>,
602 ) -> Result<VmValue, VmError> {
603 match callable {
604 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
605 VmValue::Dict(registry) => {
606 let handler =
607 crate::vm::tool_callable::require_single_harn_tool_handler(registry, || {
608 "expected callable, got dict".to_string()
609 })?;
610 self.call_closure_args(&handler, args).await
611 }
612 VmValue::BuiltinRef(name) => {
613 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
614 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
615 return result;
616 }
617 }
618 self.call_named_builtin(name, args.into_vec()).await
619 }
620 VmValue::BuiltinRefId(r) => {
621 if let Some(result) =
622 self.try_call_sync_builtin_id_or_name_args(Some(r.id), &r.name, &args)
623 {
624 return result;
625 }
626 self.call_builtin_id_or_name(r.id, &r.name, args.into_vec())
627 .await
628 }
629 other => Err(VmError::TypeError(format!(
630 "expected callable, got {}",
631 other.type_name()
632 ))),
633 }
634 }
635
636 fn call_sync_builtin_by_ref_args(
637 &mut self,
638 name: &str,
639 args: &CallArgs<'_>,
640 ) -> Option<Result<VmValue, VmError>> {
641 self.try_call_sync_builtin_id_or_name_args(None, name, args)
642 }
643
644 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
646 matches!(
647 v,
648 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId(_)
649 ) || crate::vm::tool_callable::is_single_harn_tool_registry_value(v)
650 }
651
652 pub async fn call_closure_pub(
655 &mut self,
656 closure: &VmClosure,
657 args: &[VmValue],
658 ) -> Result<VmValue, VmError> {
659 self.ensure_execution_available()?;
660 self.cancel_grace_instructions_remaining = None;
661 self.call_closure(closure, args).await
662 }
663
664 pub(crate) async fn call_named_builtin(
667 &mut self,
668 name: &str,
669 args: Vec<VmValue>,
670 ) -> Result<VmValue, VmError> {
671 self.call_builtin_impl(name, args, None, true).await
672 }
673
674 pub(in crate::vm) async fn call_capability_builtin(
679 &mut self,
680 name: &str,
681 args: Vec<VmValue>,
682 ) -> Result<VmValue, VmError> {
683 self.call_builtin_impl(name, args, None, false).await
684 }
685
686 pub(in crate::vm) fn call_capability_sync_builtin(
695 &mut self,
696 name: &str,
697 args: &[VmValue],
698 ) -> Result<VmValue, VmError> {
699 if self.denied_builtins.contains(name) {
700 return Err(VmError::CategorizedError {
701 message: format!("Tool '{name}' is not permitted."),
702 category: ErrorCategory::ToolRejected,
703 });
704 }
705 let builtin = self
706 .builtins
707 .get(name)
708 .cloned()
709 .ok_or_else(|| VmError::UndefinedBuiltin(name.to_string()))?;
710 let _observe = Self::observe_builtin_call(name);
711 crate::typecheck::validate_builtin_call(name, args, None)?;
717 let _interrupt = self.sync_builtin_interrupt_guard();
718 builtin(args, &mut self.output)
719 }
720
721 pub(crate) async fn call_builtin_id_or_name(
722 &mut self,
723 id: BuiltinId,
724 name: &str,
725 args: Vec<VmValue>,
726 ) -> Result<VmValue, VmError> {
727 self.call_builtin_impl(name, args, Some(id), true).await
728 }
729
730 pub(in crate::vm) fn sync_builtin_interrupt_guard(
737 &self,
738 ) -> Option<crate::op_interrupt::OpInterruptGuard> {
739 let scope_deadline = self.deadlines.last().map(|(deadline, _)| *deadline);
742 let deadline = match (scope_deadline, self.interrupt_handler_deadline) {
743 (Some(scope), Some(interrupt)) => Some(scope.min(interrupt)),
744 (scope, interrupt) => scope.or(interrupt),
745 };
746 if self.cancel_token.is_none() && deadline.is_none() {
747 return None;
748 }
749 Some(crate::op_interrupt::install(
750 self.cancel_token.clone(),
751 deadline,
752 ))
753 }
754
755 pub(crate) fn try_call_sync_builtin_id_or_name_args(
756 &mut self,
757 direct_id: Option<BuiltinId>,
758 name: &str,
759 args: &CallArgs<'_>,
760 ) -> Option<Result<VmValue, VmError>> {
761 if self.denied_builtins.contains(name) {
762 return Some(Err(VmError::CategorizedError {
763 message: format!("Tool '{name}' is not permitted."),
764 category: ErrorCategory::ToolRejected,
765 }));
766 }
767 let resolved = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
768 Ok(resolved) => resolved,
769 Err(error) => return Some(Err(error)),
770 };
771 let _observe = Self::observe_builtin_call(name);
772 if let Err(error) = args.with_slice(|slice| {
773 self.validate_sync_builtin_args(name, slice, resolved.recorded_effects)
774 }) {
775 return Some(Err(error));
776 }
777
778 let _interrupt = self.sync_builtin_interrupt_guard();
779 Some(args.with_slice(|slice| (resolved.handler)(slice, &mut self.output)))
780 }
781
782 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
783 &mut self,
784 direct_id: Option<BuiltinId>,
785 name: &str,
786 args_start: usize,
787 ) -> Option<Result<VmValue, VmError>> {
788 if self.denied_builtins.contains(name) {
789 return Some(Err(VmError::CategorizedError {
790 message: format!("Tool '{name}' is not permitted."),
791 category: ErrorCategory::ToolRejected,
792 }));
793 }
794 let resolved = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
795 Ok(resolved) => resolved,
796 Err(error) => return Some(Err(error)),
797 };
798 if args_start > self.stack.len() {
799 return Some(Err(VmError::Runtime(
800 "call argument stack underflow".to_string(),
801 )));
802 }
803
804 let _observe = Self::observe_builtin_call(name);
805 if let Err(error) = self.validate_sync_builtin_args(
806 name,
807 &self.stack[args_start..],
808 resolved.recorded_effects,
809 ) {
810 return Some(Err(error));
811 }
812
813 let _interrupt = self.sync_builtin_interrupt_guard();
814 Some((resolved.handler)(
815 &self.stack[args_start..],
816 &mut self.output,
817 ))
818 }
819
820 async fn call_builtin_impl(
821 &mut self,
822 name: &str,
823 args: Vec<VmValue>,
824 direct_id: Option<BuiltinId>,
825 enforce_contract: bool,
826 ) -> Result<VmValue, VmError> {
827 let _observe = Self::observe_builtin_call(name);
828
829 if self.denied_builtins.contains(name) {
831 return Err(VmError::CategorizedError {
832 message: format!("Tool '{name}' is not permitted."),
833 category: ErrorCategory::ToolRejected,
834 });
835 }
836 let autonomy =
837 if enforce_contract && crate::autonomy::needs_async_side_effect_enforcement(name) {
838 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
839 } else {
840 None
841 };
842 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
843 return Ok(value);
844 }
845 if enforce_contract {
846 if !matches!(
847 autonomy,
848 Some(crate::autonomy::AutonomyDecision::AllowApproved)
849 ) {
850 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
851 }
852 self.record_builtin_contract_effects(name, &args);
853 }
854 crate::typecheck::validate_builtin_call(name, &args, None)?;
855
856 if let Some(id) = direct_id {
857 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
858 if entry.name.as_ref() == name {
859 return self.call_builtin_entry(name, entry.dispatch, args).await;
860 }
861 }
862 }
863
864 if let Some(builtin) = self.builtins.get(name).cloned() {
865 self.call_builtin_entry(name, VmBuiltinDispatch::Sync(builtin), args)
866 .await
867 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
868 self.call_builtin_entry(name, VmBuiltinDispatch::Async(async_builtin), args)
869 .await
870 } else if let Some(bridge) = &self.bridge {
871 if enforce_contract {
872 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
873 }
874 let args_json: Vec<serde_json::Value> =
875 args.iter().map(crate::llm::vm_value_to_json).collect();
876 let result = bridge
877 .call(
878 "builtin_call",
879 serde_json::json!({"name": name, "args": args_json}),
880 )
881 .await?;
882 Ok(crate::bridge::json_result_to_vm_value(&result))
883 } else {
884 let all_builtins = self
885 .builtins
886 .keys()
887 .chain(self.async_builtins.keys())
888 .map(|s| s.as_str());
889 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
890 return Err(VmError::Runtime(format!(
891 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
892 )));
893 }
894 Err(VmError::UndefinedBuiltin(name.to_string()))
895 }
896 }
897
898 pub(in crate::vm) async fn call_builtin_entry(
899 &mut self,
900 name: &str,
901 dispatch: VmBuiltinDispatch,
902 args: Vec<VmValue>,
903 ) -> Result<VmValue, VmError> {
904 let result = match dispatch {
905 VmBuiltinDispatch::Sync(builtin) => {
906 let _interrupt = self.sync_builtin_interrupt_guard();
907 builtin(&args, &mut self.output)
908 }
909 VmBuiltinDispatch::Async(async_builtin) => {
910 let (result, captured) =
915 crate::vm::run_async_builtin_with(self.child_vm_inline(), |ctx| {
916 async_builtin(ctx, args)
917 })
918 .await;
919 if !captured.is_empty() {
920 self.output.push_str(&captured);
921 }
922 result
923 }
924 }?;
925 if matches!(
926 name,
927 "sync_mutex_acquire"
928 | "sync_semaphore_acquire"
929 | "sync_gate_acquire"
930 | "sync_rwlock_acquire"
931 ) {
932 if let VmValue::SyncPermit(permit) = &result {
933 self.adopt_sync_permit_for_current_scope(permit.as_ref().clone());
934 }
935 }
936 Ok(result)
937 }
938}
939
940fn builtin_def_metadata(
945 def: &'static crate::stdlib::macros::VmBuiltinDef,
946 name: &'static str,
947 arity: VmBuiltinArity,
948 kind: VmBuiltinKind,
949) -> VmBuiltinMetadata {
950 let mut meta = match kind {
951 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
952 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
953 }
954 .arity(arity);
955 if let Some(category) = def.category {
956 meta = meta.category_static(category);
957 }
958 if let Some(doc) = def.doc {
959 meta = meta.doc_static(doc);
960 }
961 if let Some(sig_text) = def.signature_text {
962 meta = meta.signature_static(sig_text);
963 } else {
964 meta = meta.signature_owned(format!("{}", def.sig));
971 }
972 meta.with_contract(def.contract)
973}
974
975fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
980 let required = sig.params.iter().filter(|p| !p.optional).count();
981 let total = sig.params.len();
982 if sig.has_rest {
983 if required == 0 {
984 VmBuiltinArity::Variadic
985 } else {
986 VmBuiltinArity::Min(required)
987 }
988 } else if required == total {
989 VmBuiltinArity::Exact(total)
990 } else {
991 VmBuiltinArity::Range {
992 min: required,
993 max: total,
994 }
995 }
996}