1use std::future::Future;
2use std::sync::Arc;
3
4use crate::value::{ErrorCategory, VmBuiltinFn, VmClosure, VmError, VmValue};
5use crate::BuiltinId;
6
7use super::{
8 CallArgs, ScopeSpan, Vm, VmBuiltinArity, VmBuiltinDispatch, VmBuiltinEntry, VmBuiltinKind,
9 VmBuiltinMetadata,
10};
11
12struct BuiltinObservation<'a> {
24 _span: Option<ScopeSpan>,
25 _timer: Option<crate::builtin_profile::BuiltinTimer<'a>>,
26}
27
28impl Vm {
29 fn builtin_span_kind(name: &str) -> Option<crate::tracing::SpanKind> {
30 match name {
31 "llm_call" | "llm_stream" | "llm_stream_call" | "agent_loop" | "agent_turn" => {
32 Some(crate::tracing::SpanKind::LlmCall)
33 }
34 "mcp_call" => Some(crate::tracing::SpanKind::ToolCall),
35 _ => None,
36 }
37 }
38
39 fn observe_builtin_call(name: &str) -> Option<Box<BuiltinObservation<'_>>> {
54 let span = Self::builtin_span_kind(name).map(|kind| ScopeSpan::new(kind, name.to_string()));
55 let timer = crate::builtin_profile::BuiltinTimer::start(name);
56 if span.is_none() && timer.is_none() {
57 return None;
59 }
60 Some(Box::new(BuiltinObservation {
61 _span: span,
62 _timer: timer,
63 }))
64 }
65
66 fn is_runtime_context_builtin(name: &str) -> bool {
67 matches!(
68 name,
69 "runtime_context"
70 | "task_current"
71 | "runtime_context_values"
72 | "runtime_context_get"
73 | "runtime_context_set"
74 | "runtime_context_clear"
75 )
76 }
77
78 fn resolve_sync_builtin_id_or_name(
79 &self,
80 direct_id: Option<BuiltinId>,
81 name: &str,
82 ) -> Option<Result<VmBuiltinFn, VmError>> {
83 if crate::autonomy::needs_async_side_effect_enforcement(name)
84 || Self::is_runtime_context_builtin(name)
85 {
86 return None;
87 }
88
89 let dispatch = if let Some(id) = direct_id {
90 self.builtins_by_id
91 .get(&id)
92 .filter(|entry| entry.name.as_ref() == name)
93 .map(|entry| entry.dispatch.clone())
94 } else {
95 None
96 }
97 .or_else(|| {
98 self.builtins
99 .get(name)
100 .cloned()
101 .map(VmBuiltinDispatch::Sync)
102 });
103
104 let Some(dispatch) = dispatch else {
105 if self.async_builtins.contains_key(name) || self.bridge.is_some() {
106 return None;
107 }
108 let all_builtins = self
109 .builtins
110 .keys()
111 .chain(self.async_builtins.keys())
112 .map(|s| s.as_str());
113 return Some(
114 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
115 Err(VmError::Runtime(format!(
116 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
117 )))
118 } else {
119 Err(VmError::UndefinedBuiltin(name.to_string()))
120 },
121 );
122 };
123
124 match dispatch {
125 VmBuiltinDispatch::Sync(builtin) => Some(Ok(builtin)),
126 VmBuiltinDispatch::Async(_) => None,
127 }
128 }
129
130 fn validate_sync_builtin_args(&self, name: &str, args: &[VmValue]) -> Result<(), VmError> {
131 if self.denied_builtins.contains(name) {
132 return Err(VmError::CategorizedError {
133 message: format!("Tool '{name}' is not permitted."),
134 category: ErrorCategory::ToolRejected,
135 });
136 }
137 crate::orchestration::enforce_current_policy_for_builtin(name, args)?;
138 self.record_builtin_contract_effects(name, args);
139 crate::typecheck::validate_builtin_call(name, args, None)
140 }
141
142 fn index_builtin_id(&mut self, name: &str, dispatch: VmBuiltinDispatch) {
143 let id = BuiltinId::from_name(name);
144 if self.builtin_id_collisions.contains(&id) {
145 return;
146 }
147 if let Some(existing) = self.builtins_by_id.get(&id) {
148 if existing.name.as_ref() != name {
149 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
150 Arc::make_mut(&mut self.builtin_id_collisions).insert(id);
151 return;
152 }
153 }
154 Arc::make_mut(&mut self.builtins_by_id).insert(
155 id,
156 VmBuiltinEntry {
157 name: std::sync::Arc::from(name),
158 dispatch,
159 },
160 );
161 }
162
163 fn refresh_builtin_id(&mut self, name: &str) {
164 if let Some(builtin) = self.builtins.get(name).cloned() {
165 self.index_builtin_id(name, VmBuiltinDispatch::Sync(builtin));
166 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
167 self.index_builtin_id(name, VmBuiltinDispatch::Async(async_builtin));
168 } else {
169 let id = BuiltinId::from_name(name);
170 if self
171 .builtins_by_id
172 .get(&id)
173 .is_some_and(|entry| entry.name.as_ref() == name)
174 {
175 Arc::make_mut(&mut self.builtins_by_id).remove(&id);
176 }
177 }
178 }
179
180 pub fn register_builtin<F>(&mut self, name: &str, f: F)
182 where
183 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
184 {
185 Arc::make_mut(&mut self.builtins).insert(name.to_string(), Arc::new(f));
186 Arc::make_mut(&mut self.builtin_metadata)
187 .insert(name.to_string(), VmBuiltinMetadata::sync(name.to_string()));
188 self.refresh_builtin_id(name);
189 }
190
191 pub fn register_builtin_with_contract<F>(
194 &mut self,
195 name: &str,
196 contract: harn_builtin_meta::BuiltinContract,
197 f: F,
198 ) where
199 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
200 {
201 self.register_builtin_with_metadata(
202 VmBuiltinMetadata::sync(name.to_string()).with_contract(contract),
203 f,
204 );
205 }
206
207 pub fn register_builtin_with_metadata<F>(&mut self, metadata: VmBuiltinMetadata, f: F)
209 where
210 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
211 {
212 let name = metadata.name().to_string();
213 Arc::make_mut(&mut self.builtins).insert(name.clone(), Arc::new(f));
214 Arc::make_mut(&mut self.builtin_metadata)
215 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Sync));
216 self.refresh_builtin_id(&name);
217 }
218
219 pub fn register_builtin_def(&mut self, def: &'static crate::stdlib::macros::VmBuiltinDef) {
225 use crate::stdlib::macros::VmBuiltinHandler;
226 if def.parser_only {
227 return;
228 }
229 let arity = arity_from_sig(&def.sig);
233 let names = std::iter::once(def.sig.name).chain(def.aliases.iter().copied());
234 for name in names {
235 match def.handler {
236 VmBuiltinHandler::Sync(f) => {
237 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Sync);
238 self.register_builtin_with_metadata(meta, f);
239 }
240 VmBuiltinHandler::Async(f) => {
241 let meta = builtin_def_metadata(def, name, arity, VmBuiltinKind::Async);
242 self.register_async_builtin_with_metadata(meta, f);
246 }
247 VmBuiltinHandler::None => {
248 panic!(
251 "VmBuiltinHandler::None for {name:?} without parser_only=true \
252 on its BuiltinDef"
253 );
254 }
255 }
256 }
257 }
258
259 pub(crate) fn project_declared_capability_methods(&mut self) {
267 use harn_builtin_meta::BuiltinExposure;
268
269 let projections = self
270 .builtin_metadata
271 .iter()
272 .filter_map(|(name, metadata)| {
273 let BuiltinExposure::HarnessMethod { capability, method } =
274 metadata.contract().exposure
275 else {
276 return None;
277 };
278 Some((capability, method, name.clone(), metadata.kind()))
279 })
280 .collect::<Vec<_>>();
281
282 for (capability, method, name, kind) in projections {
283 let key = (capability, method.to_string());
284 if self.capability_methods.contains_key(&key) {
285 continue;
286 }
287 let dispatch = match kind {
288 VmBuiltinKind::Sync => self
289 .builtins
290 .get(name.as_str())
291 .cloned()
292 .map(VmBuiltinDispatch::Sync),
293 VmBuiltinKind::Async => self
294 .async_builtins
295 .get(name.as_str())
296 .cloned()
297 .map(VmBuiltinDispatch::Async),
298 }
299 .unwrap_or_else(|| {
300 panic!(
301 "declared capability method harness.{}.{method} has no runtime handler `{name}`",
302 capability.field_name()
303 )
304 });
305 Arc::make_mut(&mut self.capability_methods).insert(key, dispatch);
306 }
307 }
308
309 pub fn unregister_builtin(&mut self, name: &str) {
311 Arc::make_mut(&mut self.builtins).remove(name);
312 if self.async_builtins.contains_key(name) {
313 Arc::make_mut(&mut self.builtin_metadata).insert(
314 name.to_string(),
315 VmBuiltinMetadata::async_builtin(name.to_string()),
316 );
317 } else {
318 Arc::make_mut(&mut self.builtin_metadata).remove(name);
319 }
320 self.refresh_builtin_id(name);
321 }
322
323 pub fn register_async_builtin<F, Fut>(&mut self, name: &str, f: F)
326 where
327 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
328 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
329 {
330 Arc::make_mut(&mut self.async_builtins).insert(
331 name.to_string(),
332 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
333 );
334 Arc::make_mut(&mut self.builtin_metadata).insert(
335 name.to_string(),
336 VmBuiltinMetadata::async_builtin(name.to_string()),
337 );
338 self.refresh_builtin_id(name);
339 }
340
341 pub fn register_async_builtin_with_contract<F, Fut>(
344 &mut self,
345 name: &str,
346 contract: harn_builtin_meta::BuiltinContract,
347 f: F,
348 ) where
349 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
350 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
351 {
352 self.register_async_builtin_with_metadata(
353 VmBuiltinMetadata::async_builtin(name.to_string()).with_contract(contract),
354 f,
355 );
356 }
357
358 pub fn register_async_builtin_with_metadata<F, Fut>(
361 &mut self,
362 metadata: VmBuiltinMetadata,
363 f: F,
364 ) where
365 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
366 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
367 {
368 let name = metadata.name().to_string();
369 Arc::make_mut(&mut self.async_builtins).insert(
370 name.clone(),
371 Arc::new(move |ctx, args| Box::pin(f(ctx, args))),
372 );
373 Arc::make_mut(&mut self.builtin_metadata)
374 .insert(name.clone(), metadata.with_kind(VmBuiltinKind::Async));
375 self.refresh_builtin_id(&name);
376 }
377
378 pub fn register_capability_method<F>(
385 &mut self,
386 capability: harn_builtin_meta::CapabilityId,
387 method: &str,
388 f: F,
389 ) where
390 F: Fn(&[VmValue], &mut String) -> Result<VmValue, VmError> + Send + Sync + 'static,
391 {
392 self.insert_capability_method(capability, method, VmBuiltinDispatch::Sync(Arc::new(f)));
393 }
394
395 pub fn register_async_capability_method<F, Fut>(
397 &mut self,
398 capability: harn_builtin_meta::CapabilityId,
399 method: &str,
400 f: F,
401 ) where
402 F: Fn(crate::vm::AsyncBuiltinCtx, Vec<VmValue>) -> Fut + Send + Sync + 'static,
403 Fut: Future<Output = Result<VmValue, VmError>> + Send + 'static,
404 {
405 self.insert_capability_method(
406 capability,
407 method,
408 VmBuiltinDispatch::Async(Arc::new(move |ctx, args| Box::pin(f(ctx, args)))),
409 );
410 }
411
412 fn insert_capability_method(
413 &mut self,
414 capability: harn_builtin_meta::CapabilityId,
415 method: &str,
416 dispatch: VmBuiltinDispatch,
417 ) {
418 let key = (capability, method.to_string());
419 let replaced = Arc::make_mut(&mut self.capability_methods).insert(key, dispatch);
420 assert!(
421 replaced.is_none(),
422 "capability method harness.{}.{} registered twice",
423 capability.field_name(),
424 method
425 );
426 }
427
428 pub(crate) fn registered_builtin_id(&self, name: &str) -> Option<BuiltinId> {
429 let id = BuiltinId::from_name(name);
430 if self
431 .builtins_by_id
432 .get(&id)
433 .is_some_and(|entry| entry.name.as_ref() == name)
434 {
435 Some(id)
436 } else {
437 None
438 }
439 }
440
441 pub(crate) async fn call_closure(
462 &mut self,
463 closure: &VmClosure,
464 args: &[VmValue],
465 ) -> Result<VmValue, VmError> {
466 self.call_closure_args(closure, CallArgs::Slice(args)).await
467 }
468
469 pub(crate) async fn call_closure_args(
470 &mut self,
471 closure: &VmClosure,
472 args: CallArgs<'_>,
473 ) -> Result<VmValue, VmError> {
474 let saved_handlers = std::mem::take(&mut self.exception_handlers);
475 let active_context = (!crate::step_runtime::is_tracked_function(&closure.func.name))
476 .then(crate::step_runtime::suspend_active_context);
477
478 let target_frame_depth = self.frames.len();
479 let frame_result = self.push_closure_frame_args(closure, &args);
480 drop(args);
481 let result = match frame_result {
482 Ok(()) => self.drive_until_frame_depth(target_frame_depth).await,
483 Err(e) => Err(e),
484 };
485
486 self.exception_handlers = saved_handlers;
487 drop(active_context);
488
489 result
490 }
491
492 pub(crate) async fn call_callable_value(
497 &mut self,
498 callable: &VmValue,
499 args: &[VmValue],
500 ) -> Result<VmValue, VmError> {
501 self.call_callable_args(callable, CallArgs::Slice(args))
502 .await
503 }
504
505 pub(crate) async fn call_callable_owned(
506 &mut self,
507 callable: &VmValue,
508 args: Vec<VmValue>,
509 ) -> Result<VmValue, VmError> {
510 self.call_callable_args(callable, CallArgs::Owned(args))
511 .await
512 }
513
514 pub(crate) async fn call_callable_zero(
515 &mut self,
516 callable: &VmValue,
517 ) -> Result<VmValue, VmError> {
518 self.call_callable_args(callable, CallArgs::Empty).await
519 }
520
521 pub(crate) async fn call_callable_one(
522 &mut self,
523 callable: &VmValue,
524 arg: &VmValue,
525 ) -> Result<VmValue, VmError> {
526 self.call_callable_args(callable, CallArgs::One(arg)).await
527 }
528
529 pub(crate) async fn call_callable_two(
530 &mut self,
531 callable: &VmValue,
532 first: &VmValue,
533 second: &VmValue,
534 ) -> Result<VmValue, VmError> {
535 self.call_callable_args(callable, CallArgs::Two(first, second))
536 .await
537 }
538
539 pub(crate) async fn call_callable_args(
540 &mut self,
541 callable: &VmValue,
542 args: CallArgs<'_>,
543 ) -> Result<VmValue, VmError> {
544 match callable {
545 VmValue::Closure(closure) => self.call_closure_args(closure, args).await,
546 VmValue::Dict(registry) => {
547 let handler =
548 crate::vm::tool_callable::require_single_harn_tool_handler(registry, || {
549 "expected callable, got dict".to_string()
550 })?;
551 self.call_closure_args(&handler, args).await
552 }
553 VmValue::BuiltinRef(name) => {
554 if !crate::autonomy::needs_async_side_effect_enforcement(name) {
555 if let Some(result) = self.call_sync_builtin_by_ref_args(name, &args) {
556 return result;
557 }
558 }
559 self.call_named_builtin(name, args.into_vec()).await
560 }
561 VmValue::BuiltinRefId(r) => {
562 if let Some(result) =
563 self.try_call_sync_builtin_id_or_name_args(Some(r.id), &r.name, &args)
564 {
565 return result;
566 }
567 self.call_builtin_id_or_name(r.id, &r.name, args.into_vec())
568 .await
569 }
570 other => Err(VmError::TypeError(format!(
571 "expected callable, got {}",
572 other.type_name()
573 ))),
574 }
575 }
576
577 fn call_sync_builtin_by_ref_args(
578 &mut self,
579 name: &str,
580 args: &CallArgs<'_>,
581 ) -> Option<Result<VmValue, VmError>> {
582 self.try_call_sync_builtin_id_or_name_args(None, name, args)
583 }
584
585 pub(crate) fn is_callable_value(v: &VmValue) -> bool {
587 matches!(
588 v,
589 VmValue::Closure(_) | VmValue::BuiltinRef(_) | VmValue::BuiltinRefId(_)
590 ) || crate::vm::tool_callable::is_single_harn_tool_registry_value(v)
591 }
592
593 pub async fn call_closure_pub(
596 &mut self,
597 closure: &VmClosure,
598 args: &[VmValue],
599 ) -> Result<VmValue, VmError> {
600 self.ensure_execution_available()?;
601 self.cancel_grace_instructions_remaining = None;
602 self.call_closure(closure, args).await
603 }
604
605 pub(crate) async fn call_named_builtin(
608 &mut self,
609 name: &str,
610 args: Vec<VmValue>,
611 ) -> Result<VmValue, VmError> {
612 self.call_builtin_impl(name, args, None, true).await
613 }
614
615 pub(in crate::vm) async fn call_capability_builtin(
620 &mut self,
621 name: &str,
622 args: Vec<VmValue>,
623 ) -> Result<VmValue, VmError> {
624 self.call_builtin_impl(name, args, None, false).await
625 }
626
627 pub(in crate::vm) fn call_capability_sync_builtin(
636 &mut self,
637 name: &str,
638 args: &[VmValue],
639 ) -> Result<VmValue, VmError> {
640 if self.denied_builtins.contains(name) {
641 return Err(VmError::CategorizedError {
642 message: format!("Tool '{name}' is not permitted."),
643 category: ErrorCategory::ToolRejected,
644 });
645 }
646 let builtin = self
647 .builtins
648 .get(name)
649 .cloned()
650 .ok_or_else(|| VmError::UndefinedBuiltin(name.to_string()))?;
651 let _observe = Self::observe_builtin_call(name);
652 crate::typecheck::validate_builtin_call(name, args, None)?;
658 let _interrupt = self.sync_builtin_interrupt_guard();
659 builtin(args, &mut self.output)
660 }
661
662 pub(crate) async fn call_builtin_id_or_name(
663 &mut self,
664 id: BuiltinId,
665 name: &str,
666 args: Vec<VmValue>,
667 ) -> Result<VmValue, VmError> {
668 self.call_builtin_impl(name, args, Some(id), true).await
669 }
670
671 pub(in crate::vm) fn sync_builtin_interrupt_guard(
678 &self,
679 ) -> Option<crate::op_interrupt::OpInterruptGuard> {
680 let scope_deadline = self.deadlines.last().map(|(deadline, _)| *deadline);
683 let deadline = match (scope_deadline, self.interrupt_handler_deadline) {
684 (Some(scope), Some(interrupt)) => Some(scope.min(interrupt)),
685 (scope, interrupt) => scope.or(interrupt),
686 };
687 if self.cancel_token.is_none() && deadline.is_none() {
688 return None;
689 }
690 Some(crate::op_interrupt::install(
691 self.cancel_token.clone(),
692 deadline,
693 ))
694 }
695
696 pub(crate) fn try_call_sync_builtin_id_or_name_args(
697 &mut self,
698 direct_id: Option<BuiltinId>,
699 name: &str,
700 args: &CallArgs<'_>,
701 ) -> Option<Result<VmValue, VmError>> {
702 if self.denied_builtins.contains(name) {
703 return Some(Err(VmError::CategorizedError {
704 message: format!("Tool '{name}' is not permitted."),
705 category: ErrorCategory::ToolRejected,
706 }));
707 }
708 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
709 Ok(builtin) => builtin,
710 Err(error) => return Some(Err(error)),
711 };
712 let _observe = Self::observe_builtin_call(name);
713 if let Err(error) = args.with_slice(|slice| self.validate_sync_builtin_args(name, slice)) {
714 return Some(Err(error));
715 }
716
717 let _interrupt = self.sync_builtin_interrupt_guard();
718 Some(args.with_slice(|slice| builtin(slice, &mut self.output)))
719 }
720
721 pub(crate) fn try_call_sync_builtin_id_or_name_from_stack_args(
722 &mut self,
723 direct_id: Option<BuiltinId>,
724 name: &str,
725 args_start: usize,
726 ) -> Option<Result<VmValue, VmError>> {
727 if self.denied_builtins.contains(name) {
728 return Some(Err(VmError::CategorizedError {
729 message: format!("Tool '{name}' is not permitted."),
730 category: ErrorCategory::ToolRejected,
731 }));
732 }
733 let builtin = match self.resolve_sync_builtin_id_or_name(direct_id, name)? {
734 Ok(builtin) => builtin,
735 Err(error) => return Some(Err(error)),
736 };
737 if args_start > self.stack.len() {
738 return Some(Err(VmError::Runtime(
739 "call argument stack underflow".to_string(),
740 )));
741 }
742
743 let _observe = Self::observe_builtin_call(name);
744 if let Err(error) = self.validate_sync_builtin_args(name, &self.stack[args_start..]) {
745 return Some(Err(error));
746 }
747
748 let _interrupt = self.sync_builtin_interrupt_guard();
749 Some(builtin(&self.stack[args_start..], &mut self.output))
750 }
751
752 async fn call_builtin_impl(
753 &mut self,
754 name: &str,
755 args: Vec<VmValue>,
756 direct_id: Option<BuiltinId>,
757 enforce_contract: bool,
758 ) -> Result<VmValue, VmError> {
759 let _observe = Self::observe_builtin_call(name);
760
761 if self.denied_builtins.contains(name) {
763 return Err(VmError::CategorizedError {
764 message: format!("Tool '{name}' is not permitted."),
765 category: ErrorCategory::ToolRejected,
766 });
767 }
768 let autonomy =
769 if enforce_contract && crate::autonomy::needs_async_side_effect_enforcement(name) {
770 crate::autonomy::enforce_builtin_side_effect_boxed(name, &args).await?
771 } else {
772 None
773 };
774 if let Some(crate::autonomy::AutonomyDecision::Skip(value)) = autonomy {
775 return Ok(value);
776 }
777 if enforce_contract {
778 if !matches!(
779 autonomy,
780 Some(crate::autonomy::AutonomyDecision::AllowApproved)
781 ) {
782 crate::orchestration::enforce_current_policy_for_builtin(name, &args)?;
783 }
784 self.record_builtin_contract_effects(name, &args);
785 }
786 crate::typecheck::validate_builtin_call(name, &args, None)?;
787
788 if let Some(id) = direct_id {
789 if let Some(entry) = self.builtins_by_id.get(&id).cloned() {
790 if entry.name.as_ref() == name {
791 return self.call_builtin_entry(name, entry.dispatch, args).await;
792 }
793 }
794 }
795
796 if let Some(builtin) = self.builtins.get(name).cloned() {
797 self.call_builtin_entry(name, VmBuiltinDispatch::Sync(builtin), args)
798 .await
799 } else if let Some(async_builtin) = self.async_builtins.get(name).cloned() {
800 self.call_builtin_entry(name, VmBuiltinDispatch::Async(async_builtin), args)
801 .await
802 } else if let Some(bridge) = &self.bridge {
803 if enforce_contract {
804 crate::orchestration::enforce_current_policy_for_bridge_builtin(name)?;
805 }
806 let args_json: Vec<serde_json::Value> =
807 args.iter().map(crate::llm::vm_value_to_json).collect();
808 let result = bridge
809 .call(
810 "builtin_call",
811 serde_json::json!({"name": name, "args": args_json}),
812 )
813 .await?;
814 Ok(crate::bridge::json_result_to_vm_value(&result))
815 } else {
816 let all_builtins = self
817 .builtins
818 .keys()
819 .chain(self.async_builtins.keys())
820 .map(|s| s.as_str());
821 if let Some(suggestion) = crate::value::closest_match(name, all_builtins) {
822 return Err(VmError::Runtime(format!(
823 "Undefined builtin: {name} (did you mean `{suggestion}`?)"
824 )));
825 }
826 Err(VmError::UndefinedBuiltin(name.to_string()))
827 }
828 }
829
830 pub(in crate::vm) async fn call_builtin_entry(
831 &mut self,
832 name: &str,
833 dispatch: VmBuiltinDispatch,
834 args: Vec<VmValue>,
835 ) -> Result<VmValue, VmError> {
836 let result = match dispatch {
837 VmBuiltinDispatch::Sync(builtin) => {
838 let _interrupt = self.sync_builtin_interrupt_guard();
839 builtin(&args, &mut self.output)
840 }
841 VmBuiltinDispatch::Async(async_builtin) => {
842 let (result, captured) =
847 crate::vm::run_async_builtin_with(self.child_vm_inline(), |ctx| {
848 async_builtin(ctx, args)
849 })
850 .await;
851 if !captured.is_empty() {
852 self.output.push_str(&captured);
853 }
854 result
855 }
856 }?;
857 if matches!(
858 name,
859 "sync_mutex_acquire"
860 | "sync_semaphore_acquire"
861 | "sync_gate_acquire"
862 | "sync_rwlock_acquire"
863 ) {
864 if let VmValue::SyncPermit(permit) = &result {
865 self.adopt_sync_permit_for_current_scope(permit.as_ref().clone());
866 }
867 }
868 Ok(result)
869 }
870}
871
872fn builtin_def_metadata(
877 def: &'static crate::stdlib::macros::VmBuiltinDef,
878 name: &'static str,
879 arity: VmBuiltinArity,
880 kind: VmBuiltinKind,
881) -> VmBuiltinMetadata {
882 let mut meta = match kind {
883 VmBuiltinKind::Sync => VmBuiltinMetadata::sync_static(name),
884 VmBuiltinKind::Async => VmBuiltinMetadata::async_static(name),
885 }
886 .arity(arity);
887 if let Some(category) = def.category {
888 meta = meta.category_static(category);
889 }
890 if let Some(doc) = def.doc {
891 meta = meta.doc_static(doc);
892 }
893 if let Some(sig_text) = def.signature_text {
894 meta = meta.signature_static(sig_text);
895 } else {
896 meta = meta.signature_owned(format!("{}", def.sig));
903 }
904 meta.with_contract(def.contract)
905}
906
907fn arity_from_sig(sig: &harn_builtin_meta::BuiltinSignature) -> VmBuiltinArity {
912 let required = sig.params.iter().filter(|p| !p.optional).count();
913 let total = sig.params.len();
914 if sig.has_rest {
915 if required == 0 {
916 VmBuiltinArity::Variadic
917 } else {
918 VmBuiltinArity::Min(required)
919 }
920 } else if required == total {
921 VmBuiltinArity::Exact(total)
922 } else {
923 VmBuiltinArity::Range {
924 min: required,
925 max: total,
926 }
927 }
928}