harn_vm/stdlib/host/
bridge.rs1use std::cell::RefCell;
4use std::future::Future;
5use std::marker::PhantomData;
6use std::pin::Pin;
7use std::rc::Rc;
8use std::sync::Arc;
9
10use crate::value::{VmError, VmValue};
11
12use super::turn_cache;
13
14pub type HostCallDispatchFuture<'a> =
23 Pin<Box<dyn Future<Output = Result<Option<VmValue>, VmError>> + Send + 'a>>;
24
25pub fn host_call_ready(
27 result: Result<Option<VmValue>, VmError>,
28) -> HostCallDispatchFuture<'static> {
29 Box::pin(async move { result })
30}
31
32pub trait HostCallBridge: Send + Sync {
45 fn dispatch<'a>(
46 &'a self,
47 capability: &'a str,
48 operation: &'a str,
49 params: &'a crate::value::DictMap,
50 ) -> HostCallDispatchFuture<'a>;
51
52 fn list_tools(&self) -> Result<Option<VmValue>, VmError> {
53 Ok(None)
54 }
55
56 fn call_tool(&self, _name: &str, _args: &VmValue) -> Result<Option<VmValue>, VmError> {
57 Ok(None)
58 }
59}
60
61thread_local! {
62 pub(super) static HOST_CALL_BRIDGE: RefCell<Option<Arc<dyn HostCallBridge>>> =
63 const { RefCell::new(None) };
64}
65
66pub fn set_host_call_bridge(bridge: Arc<dyn HostCallBridge>) {
71 turn_cache::reset();
72 HOST_CALL_BRIDGE.with(|b| *b.borrow_mut() = Some(bridge));
73}
74
75pub fn install_host_call_bridge(bridge: Arc<dyn HostCallBridge>) -> HostCallBridgeGuard {
77 turn_cache::reset();
78 let previous = HOST_CALL_BRIDGE.with(|slot| slot.borrow_mut().replace(bridge));
79 HostCallBridgeGuard {
80 previous,
81 _not_send: PhantomData,
82 }
83}
84
85#[must_use = "dropping the guard restores the previous host-call bridge"]
86pub struct HostCallBridgeGuard {
87 previous: Option<Arc<dyn HostCallBridge>>,
88 _not_send: PhantomData<Rc<()>>,
91}
92
93impl Drop for HostCallBridgeGuard {
94 fn drop(&mut self) {
95 turn_cache::reset();
96 let previous = self.previous.take();
97 HOST_CALL_BRIDGE.with(|slot| *slot.borrow_mut() = previous);
98 }
99}
100
101pub fn clear_host_call_bridge() {
103 turn_cache::reset();
104 HOST_CALL_BRIDGE.with(|b| *b.borrow_mut() = None);
105}
106
107pub async fn dispatch_host_call_bridge(
117 capability: &str,
118 operation: &str,
119 params: &crate::value::DictMap,
120) -> Option<Result<VmValue, VmError>> {
121 let bridge = HOST_CALL_BRIDGE.with(|b| b.borrow().clone())?;
122 match bridge.dispatch(capability, operation, params).await {
123 Ok(Some(value)) => Some(Ok(value)),
124 Ok(None) => None,
125 Err(error) => Some(Err(error)),
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132 use crate::value::DictMap;
133
134 struct NamedBridge(&'static str);
135
136 impl HostCallBridge for NamedBridge {
137 fn dispatch<'a>(
138 &'a self,
139 _capability: &'a str,
140 _operation: &'a str,
141 _params: &'a DictMap,
142 ) -> HostCallDispatchFuture<'a> {
143 host_call_ready(Ok(Some(VmValue::String(self.0.into()))))
144 }
145 }
146
147 async fn active_name() -> Option<String> {
148 dispatch_host_call_bridge("test", "name", &DictMap::new())
149 .await?
150 .ok()
151 .and_then(|value| match value {
152 VmValue::String(value) => Some(value.to_string()),
153 _ => None,
154 })
155 }
156
157 #[tokio::test(flavor = "current_thread")]
158 async fn lexical_bridge_install_restores_nested_state() {
159 clear_host_call_bridge();
160 let outer = install_host_call_bridge(Arc::new(NamedBridge("outer")));
161 assert_eq!(active_name().await.as_deref(), Some("outer"));
162 {
163 let _inner = install_host_call_bridge(Arc::new(NamedBridge("inner")));
164 assert_eq!(active_name().await.as_deref(), Some("inner"));
165 }
166 assert_eq!(active_name().await.as_deref(), Some("outer"));
167 drop(outer);
168 assert_eq!(active_name().await, None);
169 }
170}