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
12pub(in crate::vm) struct 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 let resolved = crate::stdlib::builtin_for_harness_path(name).unwrap_or(name);
40 match resolved.strip_prefix("__cap_").unwrap_or(resolved) {
41 "llm_call" | "llm_stream" | "llm_stream_call" | "agent_loop" => {
42 Some(crate::tracing::SpanKind::LlmCall)
43 }
44 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
45 _ => None,
46 }
47 }
48
49 pub(in crate::vm) fn observe_builtin_call(name: &str) -> Option<Box<BuiltinObservation<'_>>> {
64 let span = Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
65 let timer = crate::builtin_profile::BuiltinTimer::start(name);
66 if span.is_none() && timer.is_none() {
67 return None;
69 }
70 Some(Box::new(BuiltinObservation {
71 _span: span,
72 _timer: timer,
73 }))
74 }
75
76 fn is_runtime_context_builtin(name: &str) -> bool {
77 matches!(
78 name,
79 "runtime_context"
80 | "task_current"
81 | "runtime_context_values"
82 | "runtime_context_get"
83 | "runtime_context_set"
84 | "runtime_context_clear"
85 )
86 }
87
88 fn resolve_sync_builtin_id_or_name(
89 &self,
90 direct_id: Option<BuiltinId>,
91 name: &str,
92 ) -> Option<Result<ResolvedSyncBuiltin, VmError>> {
93 if crate::autonomy::needs_async_side_effect_enforcement(name)
94 || Self::is_runtime_context_builtin(name)
95 {
96 return None;
97 }
98
99 let dispatch = if let Some(id) = direct_id {
100 self.builtins_by_id
101 .get(&id)
102 .filter(|entry| entry.name.as_ref() == name)
103 .map(|entry| (entry.dispatch.clone(), entry.recorded_effects))
104 } else {
105 None
106 }
107 .or_else(|| {
108 self.builtins.get(name).cloned().map(|builtin| {
109 let recorded_effects = crate::stdlib::recorded_effect_builtin_manifest_entry(name)
110 .map(|entry| entry.contract.effects);
111 (VmBuiltinDispatch::Sync(builtin), recorded_effects)
112 })
113 });
114
115 let Some(dispatch) = dispatch else {
116 if self.async_builtins.contains_key(name) || self.bridge.is_some() {
117 return None;
118 }
119 let all_builtins = self
120 .builtins
121 .keys()
122 .chain(self.async_builtins.keys())
123 .map(|s| s.as_str());
124 return Some(
125 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
126 Err(VmError::Runtime(format!(
127 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
128 )))
129 } else {
130 Err(VmError::UndefinedBuiltin(name.to_string()))
131 },
132 );
133 };
134
135 match dispatch {
136 (VmBuiltinDispatch::Sync(builtin), recorded_effects) => Some(Ok(ResolvedSyncBuiltin {
137 handler: builtin,
138 recorded_effects,
139 })),
140 (VmBuiltinDispatch::Async(_), _) => None,
141 }
142 }
143
144 fn validate_sync_builtin_args(
145 denied_builtins: &std::collections::HashSet<String>,
146 runtime_effects: &mut crate::orchestration::RuntimeEffectState,
147 name: &str,
148 args: &[VmValue],
149 recorded_effects: Option<&'static [harn_builtin_meta::EffectSpec]>,
150 ) -> Result<(), VmError> {
151 if denied_builtins.contains(name) {
152 return Err(VmError::CategorizedError {
153 message: format!("Tool '{name}' is not permitted."),
154 category: ErrorCategory::ToolRejected,
155 });
156 }
157 crate::orchestration::enforce_current_policy_for_builtin(name, args)?;
158 if let Some(specs) = recorded_effects {
159 runtime_effects.record_specs(specs, args);
160 }
161 crate::typecheck::validate_builtin_call(name, args, None)
162 }
163
164 fn index_builtin_id(&mut self, name: &str, dispatch: VmBuiltinDispatch) {
165 let id = BuiltinId::from_name(name);
166 if self.builtin_id_collisions.contains(&id) {
167 return;
168 }
169 if let Some(existing) = self.builtins_by_id.get(&id) {
170 if existing.name.as_ref() != name {
171 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
172 Arc::make_mut(&mut self.builtin_id_collisions).insert(id);
173 return;
174 }
175 }
176 Arc::make_mut(&mut self.builtins_by_id).insert(
177 id,
178 VmBuiltinEntry {
179 name: std::sync::Arc::from(name),
180 dispatch,
181 recorded_effects: crate::stdlib::recorded_effect_builtin_manifest_entry(name)
182 .map(|entry| entry.contract.effects),
183 },
184 );
185 }
186
187 fn refresh_builtin_id(&mut self, name: &str) {
188 if let Some(builtin) = self.builtins.get(name).cloned() {
189 self.index_builtin_id(name, VmBuiltinDispatch::Sync(builtin));
190 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
191 self.index_builtin_id(name, VmBuiltinDispatch::Async(async_builtin));
192 } else {
193 let id = BuiltinId::from_name(name);
194 if self
195 .builtins_by_id
196 .get(&id)
197 .is_some_and(|entry| entry.name.as_ref() == name)
198 {
199 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
200 }
201 }
202 }
203
204 pub fn register_builtin<F>(&mut self, name: &str, f: F)
206 where
207 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
208 {
209 Arc::make_mut(&mut self.builtins).insert(name.to_string(), Arc::new(f));
210 Arc::make_mut(&mut self.builtin_metadata)
211 .insert(name.to_string(), VmBuiltinMetadata::sync(name.to_string()));
212 self.refresh_builtin_id(name);
213 }
214
215 pub fn register_builtin_with_contract<F>(
218 &mut self,
219 name: &str,
220 contract: harn_builtin_meta::BuiltinContract,
221 f: F,
222 ) where
223 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
224 {
225 self.register_builtin_with_metadata(
226 VmBuiltinMetadata::sync(name.to_string()).with_contract(contract),
227 f,
228 );
229 }
230
231 pub fn register_builtin_with_metadata<F>(&mut self, metadata: VmBuiltinMetadata, f: F)
233 where
234 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
235 {
236 let name = metadata.name().to_string();
237 Arc::make_mut(&mut self.builtins).insert(name.clone(), Arc::new(f));
238 Arc::make_mut(&mut self.builtin_metadata)
239 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Sync));
240 self.refresh_builtin_id(&name);
241 }
242
243 pub fn register_builtin_def(&mut self, def: &'static crate::stdlib::macros::VmBuiltinDef) {
249 use crate::stdlib::macros::VmBuiltinHandler;
250 if def.parser_only {
251 return;
252 }
253 let arity = arity_from_sig(&def.sig);
257 let names = std::iter::once(def.sig.name).chain(def.aliases.iter().copied());
258 for name in names {
259 match def.handler {
260 VmBuiltinHandler::Sync(f) => {
261 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Sync);
262 self.register_builtin_with_metadata(meta, f);
263 }
264 VmBuiltinHandler::Async(f) => {
265 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Async);
266 self.register_async_builtin_with_metadata(meta, f);
270 }
271 VmBuiltinHandler::None => {
272 panic!(
275 "VmBuiltinHandler::None for {name:?} without parser_only=true \
276 on its BuiltinDef"
277 );
278 }
279 }
280 }
281 }
282
283 pub(crate) fn project_declared_capability_methods(&mut self) {
291 use harn_builtin_meta::BuiltinExposure;
292
293 let projections = self
294 .builtin_metadata
295 .iter()
296 .filter_map(|(name, metadata)| {
297 let BuiltinExposure::HarnessMethod { capability, method } =
298 metadata.contract().exposure
299 else {
300 return None;
301 };
302 Some((capability, method, name.clone(), metadata.kind()))
303 })
304 .collect::<Vec<_>>();
305
306 for (capability, method, name, kind) in projections {
307 if self
308 .capability_methods
309 .get(&capability)
310 .is_some_and(|methods| methods.contains_key(method))
311 {
312 continue;
313 }
314 let dispatch = match kind {
315 VmBuiltinKind::Sync => self
316 .builtins
317 .get(name.as_str())
318 .cloned()
319 .map(VmBuiltinDispatch::Sync),
320 VmBuiltinKind::Async => self
321 .async_builtins
322 .get(name.as_str())
323 .cloned()
324 .map(VmBuiltinDispatch::Async),
325 }
326 .unwrap_or_else(|| {
327 panic!(
328 "declared capability method harness.{}.{method} has no runtime handler `{name}`",
329 capability.field_name()
330 )
331 });
332 Arc::make_mut(&mut self.capability_methods)
333 .entry(capability)
334 .or_default()
335 .insert(method.to_string(), dispatch);
336 }
337 }
338
339 pub(crate) fn project_legacy_capability_globals(&mut self) {
354 if self.global("harness").is_none() {
355 return;
356 }
357 let mut projections =
358 std::collections::BTreeMap::<String, Option<VmBuiltinDispatch>>::new();
359 for methods in self.capability_methods.values() {
360 for (method, dispatch) in methods {
361 projections
362 .entry(method.clone())
363 .and_modify(|entry| *entry = None)
364 .or_insert_with(|| Some(dispatch.clone()));
365 }
366 }
367 for (method, dispatch) in projections {
368 if self.builtins.contains_key(&method) || self.async_builtins.contains_key(&method) {
369 continue;
370 }
371 match dispatch {
372 Some(VmBuiltinDispatch::Sync(handler)) => {
373 self.register_builtin(&method, move |args, output| handler(args, output));
374 }
375 Some(VmBuiltinDispatch::Async(handler)) => {
376 self.register_async_builtin(&method, move |ctx, args| handler(ctx, args));
377 }
378 None => {}
379 }
380 }
381 self.project_legacy_host_internal_globals();
382 }
383
384 fn project_legacy_host_internal_globals(&mut self) {
385 if !harn_parser::legacy_ambient_capabilities_enabled() {
386 return;
387 }
388 let mut projections = Vec::new();
389 for (name, handler) in self.builtins.iter() {
390 if let Some(ambient) = name.strip_prefix("__host_") {
391 projections.push((
392 ambient.to_string(),
393 VmBuiltinDispatch::Sync(handler.clone()),
394 ));
395 }
396 }
397 for (name, handler) in self.async_builtins.iter() {
398 if let Some(ambient) = name.strip_prefix("__host_") {
399 projections.push((
400 ambient.to_string(),
401 VmBuiltinDispatch::Async(handler.clone()),
402 ));
403 }
404 }
405 for (ambient, dispatch) in projections {
406 if self.builtins.contains_key(&ambient) || self.async_builtins.contains_key(&ambient) {
407 continue;
408 }
409 match dispatch {
410 VmBuiltinDispatch::Sync(handler) => {
411 self.register_builtin(&ambient, move |args, output| handler(args, output));
412 }
413 VmBuiltinDispatch::Async(handler) => {
414 self.register_async_builtin(&ambient, move |ctx, args| handler(ctx, args));
415 }
416 }
417 }
418 }
419
420 fn try_dispatch_runtime_context_builtin(
421 &mut self,
422 name: &str,
423 args: &[VmValue],
424 ) -> Option<Result<VmValue, VmError>> {
425 if !Self::is_runtime_context_builtin(name) {
426 return None;
427 }
428 Some(match name {
429 "runtime_context" | "task_current" => {
430 Ok(crate::runtime_context::runtime_context_value(self))
431 }
432 "runtime_context_values" => Ok(VmValue::dict(self.runtime_context.values.clone())),
433 "runtime_context_get" => crate::runtime_context::runtime_context_get(self, args),
434 "runtime_context_set" => crate::runtime_context::runtime_context_set(self, args),
435 "runtime_context_clear" => crate::runtime_context::runtime_context_clear(self, args),
436 _ => Err(VmError::UndefinedBuiltin(name.to_string())),
437 })
438 }
439
440 pub fn unregister_builtin(&mut self, name: &str) {
442 Arc::make_mut(&mut self.builtins).remove(name);
443 if self.async_builtins.contains_key(name) {
444 Arc::make_mut(&mut self.builtin_metadata).insert(
445 name.to_string(),
446 VmBuiltinMetadata::async_builtin(name.to_string()),
447 );
448 } else {
449 Arc::make_mut(&mut self.builtin_metadata).remove(name);
450 }
451 self.refresh_builtin_id(name);
452 }
453
454 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
457 where
458 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
459 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
460 {
461 Arc::make_mut(&mut self.async_builtins).insert(
462 name.to_string(),
463 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
464 );
465 Arc::make_mut(&mut self.builtin_metadata).insert(
466 name.to_string(),
467 VmBuiltinMetadata::async_builtin(name.to_string()),
468 );
469 self.refresh_builtin_id(name);
470 }
471
472 pub fn register_async_builtin_with_contract<F, Fut>(
475 &mut self,
476 name: &str,
477 contract: harn_builtin_meta::BuiltinContract,
478 f: F,
479 ) where
480 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
481 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
482 {
483 self.register_async_builtin_with_metadata(
484 VmBuiltinMetadata::async_builtin(name.to_string()).with_contract(contract),
485 f,
486 );
487 }
488
489 pub fn register_async_builtin_with_metadata<F, Fut>(
492 &mut self,
493 metadata: VmBuiltinMetadata,
494 f: F,
495 ) where
496 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
497 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
498 {
499 let name = metadata.name().to_string();
500 Arc::make_mut(&mut self.async_builtins).insert(
501 name.clone(),
502 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
503 );
504 Arc::make_mut(&mut self.builtin_metadata)
505 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
506 self.refresh_builtin_id(&name);
507 }
508
509 pub fn register_capability_method<F>(
516 &mut self,
517 capability: harn_builtin_meta::CapabilityId,
518 method: &str,
519 f: F,
520 ) where
521 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
522 {
523 self.insert_capability_method(
524 capability,
525 method,
526 VmBuiltinDispatch::Sync(Arc::new(f)),
527 false,
528 );
529 }
530
531 pub fn override_capability_method<F>(
536 &mut self,
537 capability: harn_builtin_meta::CapabilityId,
538 method: &str,
539 f: F,
540 ) where
541 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
542 {
543 self.insert_capability_method(
544 capability,
545 method,
546 VmBuiltinDispatch::Sync(Arc::new(f)),
547 true,
548 );
549 }
550
551 pub fn register_async_capability_method<F, Fut>(
553 &mut self,
554 capability: harn_builtin_meta::CapabilityId,
555 method: &str,
556 f: F,
557 ) where
558 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
559 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
560 {
561 self.insert_capability_method(
562 capability,
563 method,
564 VmBuiltinDispatch::Async(Arc::new(move |ctx, args| Box::pin(f(ctx, args)))),
565 false,
566 );
567 }
568
569 fn insert_capability_method(
570 &mut self,
571 capability: harn_builtin_meta::CapabilityId,
572 method: &str,
573 dispatch: VmBuiltinDispatch,
574 allow_override: bool,
575 ) {
576 let replaced = Arc::make_mut(&mut self.capability_methods)
577 .entry(capability)
578 .or_default()
579 .insert(method.to_string(), dispatch);
580 assert!(
581 allow_override || replaced.is_none(),
582 "capability method harness.{}.{} registered twice",
583 capability.field_name(),
584 method
585 );
586 }
587
588 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
589 let id = BuiltinId::from_name(name);
590 if self
591 .builtins_by_id
592 .get(&id)
593 .is_some_and(|entry| entry.name.as_ref() == name)
594 {
595 Some(id)
596 } else {
597 None
598 }
599 }
600
601 pub(crate) async fn call_closure(
622 &mut self,
623 closure: &VmClosure,
624 args: &[VmValue],
625 ) -> Result<VmValue, VmError> {
626 self.call_closure_args(closure, CallArgs::Slice(args)).await
627 }
628
629 pub(crate) async fn call_closure_args(
630 &mut self,
631 closure: &VmClosure,
632 args: CallArgs<'_>,
633 ) -> Result<VmValue, VmError> {
634 let saved_handlers = std::mem::take(&mut self.exception_handlers);
635 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
636 .then(crate::step_runtime::suspend_active_context);
637
638 let target_frame_depth = self.frames.len();
639 let frame_result = self.push_closure_frame_args(closure, &args);
640 drop(args);
641 let result = match frame_result {
642 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
643 Err(e) => Err(e),
644 };
645
646 self.exception_handlers = saved_handlers;
647 drop(active_context);
648
649 result
650 }
651
652 pub(crate) async fn call_callable_value(
657 &mut self,
658 callable: &VmValue,
659 args: &[VmValue],
660 ) -> Result<VmValue, VmError> {
661 self.call_callable_args(callable, CallArgs::Slice(args))
662 .await
663 }
664
665 pub(crate) async fn call_callable_owned(
666 &mut self,
667 callable: &VmValue,
668 args: Vec<VmValue>,
669 ) -> Result<VmValue, VmError> {
670 self.call_callable_args(callable, CallArgs::Owned(args))
671 .await
672 }
673
674 pub(crate) async fn call_callable_zero(
675 &mut self,
676 callable: &VmValue,
677 ) -> Result<VmValue, VmError> {
678 self.call_callable_args(callable, CallArgs::Empty).await
679 }
680
681 pub(crate) async fn call_callable_one(
682 &mut self,
683 callable: &VmValue,
684 arg: &VmValue,
685 ) -> Result<VmValue, VmError> {
686 self.call_callable_args(callable, CallArgs::One(arg)).await
687 }
688
689 pub(crate) async fn call_callable_two(
690 &mut self,
691 callable: &VmValue,
692 first: &VmValue,
693 second: &VmValue,
694 ) -> Result<VmValue, VmError> {
695 self.call_callable_args(callable, CallArgs::Two(first, second))
696 .await
697 }
698
699 pub(crate) async fn call_callable_args(
700 &mut self,
701 callable: &VmValue,
702 args: CallArgs<'_>,
703 ) -> Result<VmValue, VmError> {
704 match callable {
705 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
706 VmValue::Dict(registry) => {
707 let handler =
708 crate::vm::tool_callable::require_single_harn_tool_handler(registry, || {
709 "expected callable, got dict".to_string()
710 })?;
711 self.call_closure_args(&handler, args).await
712 }
713 VmValue::BuiltinRef(name) => {
714 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
715 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
716 return result;
717 }
718 }
719 self.call_named_builtin(name, args.into_vec()).await
720 }
721 VmValue::BuiltinRefId(r) => {
722 if let Some(result) =
723 self.try_call_sync_builtin_id_or_name_args(Some(r.id), &r.name, &args)
724 {
725 return result;
726 }
727 self.call_builtin_id_or_name(r.id, &r.name, args.into_vec())
728 .await
729 }
730 other => Err(VmError::TypeError(format!(
731 "expected callable, got {}",
732 other.type_name()
733 ))),
734 }
735 }
736
737 fn call_sync_builtin_by_ref_args(
738 &mut self,
739 name: &str,
740 args: &CallArgs<'_>,
741 ) -> Option<Result<VmValue, VmError>> {
742 self.try_call_sync_builtin_id_or_name_args(None, name, args)
743 }
744
745 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
747 matches!(
748 v,
749 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId(_)
750 ) || crate::vm::tool_callable::is_single_harn_tool_registry_value(v)
751 }
752
753 pub async fn call_closure_pub(
758 &mut self,
759 closure: &VmClosure,
760 args: &[VmValue],
761 ) -> Result<VmValue, VmError> {
762 self.ensure_execution_available()?;
763 self.cancel_grace_instructions_remaining = None;
764 if crate::current_execution_scope().as_ref() == Some(&self.execution_id) {
765 return self.call_closure(closure, args).await;
766 }
767
768 let registry = self.pool_registry.clone();
769 let ambient = self.prepare_top_level_ambient();
770 let call = crate::stdlib::pool::with_pool_registry_scope(registry, async {
771 self.call_closure(closure, args).await
772 });
773 Box::pin(crate::orchestration::scope_ambient(ambient, call)).await
774 }
775
776 pub(crate) async fn call_named_builtin(
779 &mut self,
780 name: &str,
781 args: Vec<VmValue>,
782 ) -> Result<VmValue, VmError> {
783 self.call_builtin_impl(name, args, None, true).await
784 }
785
786 pub(in crate::vm) async fn call_capability_builtin(
791 &mut self,
792 name: &str,
793 args: Vec<VmValue>,
794 ) -> Result<VmValue, VmError> {
795 self.call_builtin_impl(name, args, None, false).await
796 }
797
798 pub(in crate::vm) fn call_capability_sync_builtin(
807 &mut self,
808 name: &str,
809 args: &[VmValue],
810 ) -> Result<VmValue, VmError> {
811 if self.denied_builtins.contains(name) {
812 return Err(VmError::CategorizedError {
813 message: format!("Tool '{name}' is not permitted."),
814 category: ErrorCategory::ToolRejected,
815 });
816 }
817 let builtin = self
818 .builtins
819 .get(name)
820 .cloned()
821 .ok_or_else(|| VmError::UndefinedBuiltin(name.to_string()))?;
822 let _observe = Self::observe_builtin_call(name);
823 crate::typecheck::validate_builtin_call(name, args, None)?;
829 let _interrupt = self.sync_builtin_interrupt_guard();
830 builtin(args, &mut self.output)
831 }
832
833 pub(crate) async fn call_builtin_id_or_name(
834 &mut self,
835 id: BuiltinId,
836 name: &str,
837 args: Vec<VmValue>,
838 ) -> Result<VmValue, VmError> {
839 self.call_builtin_impl(name, args, Some(id), true).await
840 }
841
842 pub(in crate::vm) fn sync_builtin_interrupt_guard(
849 &self,
850 ) -> Option<crate::op_interrupt::OpInterruptGuard> {
851 let scope_deadline = self.deadlines.last().map(|(deadline, _)| *deadline);
854 let deadline = match (scope_deadline, self.interrupt_handler_deadline) {
855 (Some(scope), Some(interrupt)) => Some(scope.min(interrupt)),
856 (scope, interrupt) => scope.or(interrupt),
857 };
858 if self.cancel_token.is_none() && deadline.is_none() {
859 return None;
860 }
861 Some(crate::op_interrupt::install(
862 self.cancel_token.clone(),
863 deadline,
864 ))
865 }
866
867 pub(crate) fn try_call_sync_builtin_id_or_name_args(
868 &mut self,
869 direct_id: Option<BuiltinId>,
870 name: &str,
871 args: &CallArgs<'_>,
872 ) -> Option<Result<VmValue, VmError>> {
873 if self.denied_builtins.contains(name) {
874 return Some(Err(VmError::CategorizedError {
875 message: format!("Tool '{name}' is not permitted."),
876 category: ErrorCategory::ToolRejected,
877 }));
878 }
879 let resolved = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
880 Ok(resolved) => resolved,
881 Err(error) => return Some(Err(error)),
882 };
883 let _observe = Self::observe_builtin_call(name);
884 if let Err(error) = args.with_slice(|slice| {
885 Self::validate_sync_builtin_args(
886 &self.denied_builtins,
887 &mut self.runtime_effects,
888 name,
889 slice,
890 resolved.recorded_effects,
891 )
892 }) {
893 return Some(Err(error));
894 }
895
896 let _interrupt = self.sync_builtin_interrupt_guard();
897 Some(args.with_slice(|slice| (resolved.handler)(slice, &mut self.output)))
898 }
899
900 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
901 &mut self,
902 direct_id: Option<BuiltinId>,
903 name: &str,
904 args_start: usize,
905 ) -> Option<Result<VmValue, VmError>> {
906 if self.denied_builtins.contains(name) {
907 return Some(Err(VmError::CategorizedError {
908 message: format!("Tool '{name}' is not permitted."),
909 category: ErrorCategory::ToolRejected,
910 }));
911 }
912 let resolved = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
913 Ok(resolved) => resolved,
914 Err(error) => return Some(Err(error)),
915 };
916 if args_start > self.stack.len() {
917 return Some(Err(VmError::Runtime(
918 "call argument stack underflow".to_string(),
919 )));
920 }
921
922 let _observe = Self::observe_builtin_call(name);
923 if let Err(error) = Self::validate_sync_builtin_args(
924 &self.denied_builtins,
925 &mut self.runtime_effects,
926 name,
927 &self.stack[args_start..],
928 resolved.recorded_effects,
929 ) {
930 return Some(Err(error));
931 }
932
933 let _interrupt = self.sync_builtin_interrupt_guard();
934 Some((resolved.handler)(
935 &self.stack[args_start..],
936 &mut self.output,
937 ))
938 }
939
940 async fn call_builtin_impl(
941 &mut self,
942 name: &str,
943 args: Vec<VmValue>,
944 direct_id: Option<BuiltinId>,
945 enforce_contract: bool,
946 ) -> Result<VmValue, VmError> {
947 let _observe = Self::observe_builtin_call(name);
948
949 if self.denied_builtins.contains(name) {
951 return Err(VmError::CategorizedError {
952 message: format!("Tool '{name}' is not permitted."),
953 category: ErrorCategory::ToolRejected,
954 });
955 }
956 let autonomy =
957 if enforce_contract && crate::autonomy::needs_async_side_effect_enforcement(name) {
958 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
959 } else {
960 None
961 };
962 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
963 return Ok(value);
964 }
965 if enforce_contract {
966 if !matches!(
967 autonomy,
968 Some(crate::autonomy::AutonomyDecision::AllowApproved)
969 ) {
970 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
971 }
972 self.record_builtin_contract_effects(name, &args);
973 }
974 crate::typecheck::validate_builtin_call(name, &args, None)?;
975
976 if let Some(id) = direct_id {
977 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
978 if entry.name.as_ref() == name {
979 return self.call_builtin_entry(name, entry.dispatch, args).await;
980 }
981 }
982 }
983
984 if let Some(builtin) = self.builtins.get(name).cloned() {
985 self.call_builtin_entry(name, VmBuiltinDispatch::Sync(builtin), args)
986 .await
987 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
988 self.call_builtin_entry(name, VmBuiltinDispatch::Async(async_builtin), args)
989 .await
990 } else if let Some(result) = self.try_dispatch_runtime_context_builtin(name, &args) {
991 result
992 } else if let Some(bridge) = &self.bridge {
993 if enforce_contract {
994 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
995 }
996 let args_json: Vec<serde_json::Value> =
997 args.iter().map(crate::llm::vm_value_to_json).collect();
998 let result = bridge
999 .call(
1000 "builtin_call",
1001 serde_json::json!({"name": name, "args": args_json}),
1002 )
1003 .await?;
1004 Ok(crate::bridge::json_result_to_vm_value(&result))
1005 } else {
1006 let all_builtins = self
1007 .builtins
1008 .keys()
1009 .chain(self.async_builtins.keys())
1010 .map(|s| s.as_str());
1011 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
1012 return Err(VmError::Runtime(format!(
1013 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
1014 )));
1015 }
1016 Err(VmError::UndefinedBuiltin(name.to_string()))
1017 }
1018 }
1019
1020 pub(in crate::vm) async fn call_builtin_entry(
1021 &mut self,
1022 name: &str,
1023 dispatch: VmBuiltinDispatch,
1024 args: Vec<VmValue>,
1025 ) -> Result<VmValue, VmError> {
1026 let result = match dispatch {
1027 VmBuiltinDispatch::Sync(builtin) => {
1028 let _interrupt = self.sync_builtin_interrupt_guard();
1029 builtin(&args, &mut self.output)
1030 }
1031 VmBuiltinDispatch::Async(async_builtin) => {
1032 let (result, captured) =
1037 crate::vm::run_async_builtin_with(self.child_vm_inline(), |ctx| {
1038 async_builtin(ctx, args)
1039 })
1040 .await;
1041 if !captured.is_empty() {
1042 self.output.push_str(&captured);
1043 }
1044 result
1045 }
1046 }?;
1047 if matches!(
1048 name,
1049 "sync_mutex_acquire"
1050 | "sync_semaphore_acquire"
1051 | "sync_gate_acquire"
1052 | "sync_rwlock_acquire"
1053 ) {
1054 if let VmValue::SyncPermit(permit) = &result {
1055 self.adopt_sync_permit_for_current_scope(permit.as_ref().clone());
1056 }
1057 }
1058 Ok(result)
1059 }
1060}
1061
1062fn builtin_def_metadata(
1067 def: &'static crate::stdlib::macros::VmBuiltinDef,
1068 name: &'static str,
1069 arity: VmBuiltinArity,
1070 kind: VmBuiltinKind,
1071) -> VmBuiltinMetadata {
1072 let mut meta = match kind {
1073 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
1074 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
1075 }
1076 .arity(arity);
1077 if let Some(category) = def.category {
1078 meta = meta.category_static(category);
1079 }
1080 if let Some(doc) = def.doc {
1081 meta = meta.doc_static(doc);
1082 }
1083 if let Some(sig_text) = def.signature_text {
1084 meta = meta.signature_static(sig_text);
1085 } else {
1086 meta = meta.signature_owned(format!("{}", def.sig));
1093 }
1094 meta.with_contract(def.contract)
1095}
1096
1097fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
1102 let required = sig.params.iter().filter(|p| !p.optional).count();
1103 let total = sig.params.len();
1104 if sig.has_rest {
1105 if required == 0 {
1106 VmBuiltinArity::Variadic
1107 } else {
1108 VmBuiltinArity::Min(required)
1109 }
1110 } else if required == total {
1111 VmBuiltinArity::Exact(total)
1112 } else {
1113 VmBuiltinArity::Range {
1114 min: required,
1115 max: total,
1116 }
1117 }
1118}