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" | "agent_turn" => {
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(
756 &mut self,
757 closure: &VmClosure,
758 args: &[VmValue],
759 ) -> Result<VmValue, VmError> {
760 self.ensure_execution_available()?;
761 self.cancel_grace_instructions_remaining = None;
762 self.call_closure(closure, args).await
763 }
764
765 pub(crate) async fn call_named_builtin(
768 &mut self,
769 name: &str,
770 args: Vec<VmValue>,
771 ) -> Result<VmValue, VmError> {
772 self.call_builtin_impl(name, args, None, true).await
773 }
774
775 pub(in crate::vm) async fn call_capability_builtin(
780 &mut self,
781 name: &str,
782 args: Vec<VmValue>,
783 ) -> Result<VmValue, VmError> {
784 self.call_builtin_impl(name, args, None, false).await
785 }
786
787 pub(in crate::vm) fn call_capability_sync_builtin(
796 &mut self,
797 name: &str,
798 args: &[VmValue],
799 ) -> Result<VmValue, VmError> {
800 if self.denied_builtins.contains(name) {
801 return Err(VmError::CategorizedError {
802 message: format!("Tool '{name}' is not permitted."),
803 category: ErrorCategory::ToolRejected,
804 });
805 }
806 let builtin = self
807 .builtins
808 .get(name)
809 .cloned()
810 .ok_or_else(|| VmError::UndefinedBuiltin(name.to_string()))?;
811 let _observe = Self::observe_builtin_call(name);
812 crate::typecheck::validate_builtin_call(name, args, None)?;
818 let _interrupt = self.sync_builtin_interrupt_guard();
819 builtin(args, &mut self.output)
820 }
821
822 pub(crate) async fn call_builtin_id_or_name(
823 &mut self,
824 id: BuiltinId,
825 name: &str,
826 args: Vec<VmValue>,
827 ) -> Result<VmValue, VmError> {
828 self.call_builtin_impl(name, args, Some(id), true).await
829 }
830
831 pub(in crate::vm) fn sync_builtin_interrupt_guard(
838 &self,
839 ) -> Option<crate::op_interrupt::OpInterruptGuard> {
840 let scope_deadline = self.deadlines.last().map(|(deadline, _)| *deadline);
843 let deadline = match (scope_deadline, self.interrupt_handler_deadline) {
844 (Some(scope), Some(interrupt)) => Some(scope.min(interrupt)),
845 (scope, interrupt) => scope.or(interrupt),
846 };
847 if self.cancel_token.is_none() && deadline.is_none() {
848 return None;
849 }
850 Some(crate::op_interrupt::install(
851 self.cancel_token.clone(),
852 deadline,
853 ))
854 }
855
856 pub(crate) fn try_call_sync_builtin_id_or_name_args(
857 &mut self,
858 direct_id: Option<BuiltinId>,
859 name: &str,
860 args: &CallArgs<'_>,
861 ) -> Option<Result<VmValue, VmError>> {
862 if self.denied_builtins.contains(name) {
863 return Some(Err(VmError::CategorizedError {
864 message: format!("Tool '{name}' is not permitted."),
865 category: ErrorCategory::ToolRejected,
866 }));
867 }
868 let resolved = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
869 Ok(resolved) => resolved,
870 Err(error) => return Some(Err(error)),
871 };
872 let _observe = Self::observe_builtin_call(name);
873 if let Err(error) = args.with_slice(|slice| {
874 Self::validate_sync_builtin_args(
875 &self.denied_builtins,
876 &mut self.runtime_effects,
877 name,
878 slice,
879 resolved.recorded_effects,
880 )
881 }) {
882 return Some(Err(error));
883 }
884
885 let _interrupt = self.sync_builtin_interrupt_guard();
886 Some(args.with_slice(|slice| (resolved.handler)(slice, &mut self.output)))
887 }
888
889 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
890 &mut self,
891 direct_id: Option<BuiltinId>,
892 name: &str,
893 args_start: usize,
894 ) -> Option<Result<VmValue, VmError>> {
895 if self.denied_builtins.contains(name) {
896 return Some(Err(VmError::CategorizedError {
897 message: format!("Tool '{name}' is not permitted."),
898 category: ErrorCategory::ToolRejected,
899 }));
900 }
901 let resolved = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
902 Ok(resolved) => resolved,
903 Err(error) => return Some(Err(error)),
904 };
905 if args_start > self.stack.len() {
906 return Some(Err(VmError::Runtime(
907 "call argument stack underflow".to_string(),
908 )));
909 }
910
911 let _observe = Self::observe_builtin_call(name);
912 if let Err(error) = Self::validate_sync_builtin_args(
913 &self.denied_builtins,
914 &mut self.runtime_effects,
915 name,
916 &self.stack[args_start..],
917 resolved.recorded_effects,
918 ) {
919 return Some(Err(error));
920 }
921
922 let _interrupt = self.sync_builtin_interrupt_guard();
923 Some((resolved.handler)(
924 &self.stack[args_start..],
925 &mut self.output,
926 ))
927 }
928
929 async fn call_builtin_impl(
930 &mut self,
931 name: &str,
932 args: Vec<VmValue>,
933 direct_id: Option<BuiltinId>,
934 enforce_contract: bool,
935 ) -> Result<VmValue, VmError> {
936 let _observe = Self::observe_builtin_call(name);
937
938 if self.denied_builtins.contains(name) {
940 return Err(VmError::CategorizedError {
941 message: format!("Tool '{name}' is not permitted."),
942 category: ErrorCategory::ToolRejected,
943 });
944 }
945 let autonomy =
946 if enforce_contract && crate::autonomy::needs_async_side_effect_enforcement(name) {
947 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
948 } else {
949 None
950 };
951 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
952 return Ok(value);
953 }
954 if enforce_contract {
955 if !matches!(
956 autonomy,
957 Some(crate::autonomy::AutonomyDecision::AllowApproved)
958 ) {
959 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
960 }
961 self.record_builtin_contract_effects(name, &args);
962 }
963 crate::typecheck::validate_builtin_call(name, &args, None)?;
964
965 if let Some(id) = direct_id {
966 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
967 if entry.name.as_ref() == name {
968 return self.call_builtin_entry(name, entry.dispatch, args).await;
969 }
970 }
971 }
972
973 if let Some(builtin) = self.builtins.get(name).cloned() {
974 self.call_builtin_entry(name, VmBuiltinDispatch::Sync(builtin), args)
975 .await
976 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
977 self.call_builtin_entry(name, VmBuiltinDispatch::Async(async_builtin), args)
978 .await
979 } else if let Some(result) = self.try_dispatch_runtime_context_builtin(name, &args) {
980 result
981 } else if let Some(bridge) = &self.bridge {
982 if enforce_contract {
983 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
984 }
985 let args_json: Vec<serde_json::Value> =
986 args.iter().map(crate::llm::vm_value_to_json).collect();
987 let result = bridge
988 .call(
989 "builtin_call",
990 serde_json::json!({"name": name, "args": args_json}),
991 )
992 .await?;
993 Ok(crate::bridge::json_result_to_vm_value(&result))
994 } else {
995 let all_builtins = self
996 .builtins
997 .keys()
998 .chain(self.async_builtins.keys())
999 .map(|s| s.as_str());
1000 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
1001 return Err(VmError::Runtime(format!(
1002 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
1003 )));
1004 }
1005 Err(VmError::UndefinedBuiltin(name.to_string()))
1006 }
1007 }
1008
1009 pub(in crate::vm) async fn call_builtin_entry(
1010 &mut self,
1011 name: &str,
1012 dispatch: VmBuiltinDispatch,
1013 args: Vec<VmValue>,
1014 ) -> Result<VmValue, VmError> {
1015 let result = match dispatch {
1016 VmBuiltinDispatch::Sync(builtin) => {
1017 let _interrupt = self.sync_builtin_interrupt_guard();
1018 builtin(&args, &mut self.output)
1019 }
1020 VmBuiltinDispatch::Async(async_builtin) => {
1021 let (result, captured) =
1026 crate::vm::run_async_builtin_with(self.child_vm_inline(), |ctx| {
1027 async_builtin(ctx, args)
1028 })
1029 .await;
1030 if !captured.is_empty() {
1031 self.output.push_str(&captured);
1032 }
1033 result
1034 }
1035 }?;
1036 if matches!(
1037 name,
1038 "sync_mutex_acquire"
1039 | "sync_semaphore_acquire"
1040 | "sync_gate_acquire"
1041 | "sync_rwlock_acquire"
1042 ) {
1043 if let VmValue::SyncPermit(permit) = &result {
1044 self.adopt_sync_permit_for_current_scope(permit.as_ref().clone());
1045 }
1046 }
1047 Ok(result)
1048 }
1049}
1050
1051fn builtin_def_metadata(
1056 def: &'static crate::stdlib::macros::VmBuiltinDef,
1057 name: &'static str,
1058 arity: VmBuiltinArity,
1059 kind: VmBuiltinKind,
1060) -> VmBuiltinMetadata {
1061 let mut meta = match kind {
1062 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
1063 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
1064 }
1065 .arity(arity);
1066 if let Some(category) = def.category {
1067 meta = meta.category_static(category);
1068 }
1069 if let Some(doc) = def.doc {
1070 meta = meta.doc_static(doc);
1071 }
1072 if let Some(sig_text) = def.signature_text {
1073 meta = meta.signature_static(sig_text);
1074 } else {
1075 meta = meta.signature_owned(format!("{}", def.sig));
1082 }
1083 meta.with_contract(def.contract)
1084}
1085
1086fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
1091 let required = sig.params.iter().filter(|p| !p.optional).count();
1092 let total = sig.params.len();
1093 if sig.has_rest {
1094 if required == 0 {
1095 VmBuiltinArity::Variadic
1096 } else {
1097 VmBuiltinArity::Min(required)
1098 }
1099 } else if required == total {
1100 VmBuiltinArity::Exact(total)
1101 } else {
1102 VmBuiltinArity::Range {
1103 min: required,
1104 max: total,
1105 }
1106 }
1107}