Skip to main content

lash_core/runtime/
host.rs

1use lash_trace::{TraceContext, TraceLevel, TraceSink};
2use std::sync::Arc;
3
4use super::process::{
5    InMemoryProcessExecutionEnvStore, ProcessEngineRegistry, ProcessExecutionEnvStore,
6    ProcessRegistry,
7};
8use super::{
9    EffectHost, InlineEffectHost, ProcessWorkPoke, QueuedWorkPoke, SessionStoreFactory,
10    TerminationPolicy,
11};
12
13/// Required host configuration for all runtimes.
14#[derive(Clone)]
15pub struct RuntimeHostConfig {
16    pub durability: RuntimeDurabilityConfig,
17    pub process_engines: ProcessEngineRegistry,
18    pub providers: RuntimeProviderConfig,
19    pub prompt: RuntimePromptConfig,
20    pub control: RuntimeControlConfig,
21    pub tracing: RuntimeTracingConfig,
22}
23
24#[derive(Clone)]
25pub struct RuntimeDurabilityConfig {
26    pub attachment_store: Arc<dyn crate::AttachmentStore>,
27    pub process_env_store: Arc<dyn ProcessExecutionEnvStore>,
28}
29
30#[derive(Clone)]
31pub struct RuntimeProviderConfig {
32    pub provider_resolver: Arc<dyn crate::RuntimeProviderResolver>,
33}
34
35#[derive(Clone)]
36pub struct RuntimePromptConfig {
37    pub prompt: crate::PromptLayer,
38}
39
40#[derive(Clone)]
41pub struct RuntimeControlConfig {
42    pub effect_host: Arc<dyn EffectHost>,
43    pub process_cancel_ability: Arc<dyn crate::ProcessCancelAbility>,
44    pub termination: TerminationPolicy,
45}
46
47#[derive(Clone)]
48pub struct RuntimeTracingConfig {
49    pub trace_sink: Option<Arc<dyn TraceSink>>,
50    pub trace_level: TraceLevel,
51    pub trace_context: TraceContext,
52}
53
54impl RuntimeHostConfig {
55    /// Construct a config with the three host-owned dependencies named
56    /// explicitly.
57    ///
58    /// There is intentionally no `Default`. The effect host and stores decide
59    /// a runtime's durability, so hosts must choose them rather than silently
60    /// inheriting in-memory implementations. Use [`RuntimeHostConfig::in_memory`]
61    /// to opt into the in-process / in-memory versions by name.
62    pub fn new(
63        effect_host: Arc<dyn EffectHost>,
64        attachment_store: Arc<dyn crate::AttachmentStore>,
65        process_env_store: Arc<dyn ProcessExecutionEnvStore>,
66    ) -> Self {
67        Self {
68            durability: RuntimeDurabilityConfig {
69                attachment_store,
70                process_env_store,
71            },
72            process_engines: ProcessEngineRegistry::new(),
73            providers: RuntimeProviderConfig {
74                provider_resolver: Arc::new(crate::EmptyProviderResolver),
75            },
76            prompt: RuntimePromptConfig {
77                prompt: crate::PromptLayer::new(),
78            },
79            control: RuntimeControlConfig {
80                termination: TerminationPolicy::default(),
81                effect_host,
82                process_cancel_ability: Arc::new(crate::DefaultProcessCancelAbility),
83            },
84            tracing: RuntimeTracingConfig {
85                trace_sink: None,
86                trace_level: TraceLevel::Standard,
87                trace_context: TraceContext::default(),
88            },
89        }
90    }
91
92    /// Explicit in-process / in-memory configuration: an
93    /// [`InlineEffectHost`] and in-memory stores.
94    ///
95    /// Convenient for tests and local experiments; not durable. Named so the
96    /// choice is never silent.
97    pub fn in_memory() -> Self {
98        Self::new(
99            Arc::new(InlineEffectHost::default()),
100            Arc::new(crate::InMemoryAttachmentStore::new()),
101            Arc::new(InMemoryProcessExecutionEnvStore::new()),
102        )
103    }
104
105    pub fn with_process_env_store(
106        mut self,
107        process_env_store: Arc<dyn ProcessExecutionEnvStore>,
108    ) -> Self {
109        self.durability.process_env_store = process_env_store;
110        self
111    }
112
113    pub fn with_process_cancel_ability(
114        mut self,
115        process_cancel_ability: Arc<dyn crate::ProcessCancelAbility>,
116    ) -> Self {
117        self.control.process_cancel_ability = process_cancel_ability;
118        self
119    }
120
121    pub fn with_process_engine(mut self, engine: Arc<dyn crate::ProcessEngine>) -> Self {
122        self.process_engines = self.process_engines.with_engine(engine);
123        self
124    }
125}
126
127/// Base host shape for embedded runtimes.
128#[derive(Clone)]
129pub struct EmbeddedRuntimeHost {
130    pub core: RuntimeHostConfig,
131    pub session_store_factory: Option<Arc<dyn SessionStoreFactory>>,
132    pub trigger_store: Option<Arc<dyn crate::TriggerStore>>,
133}
134
135impl EmbeddedRuntimeHost {
136    pub fn new(core: RuntimeHostConfig) -> Self {
137        Self {
138            core,
139            session_store_factory: None,
140            trigger_store: Some(Arc::new(crate::InMemoryTriggerStore::default())),
141        }
142    }
143
144    pub fn with_session_store_factory(
145        mut self,
146        session_store_factory: Arc<dyn SessionStoreFactory>,
147    ) -> Self {
148        self.session_store_factory = Some(session_store_factory);
149        self
150    }
151
152    pub fn with_trigger_store(mut self, store: Arc<dyn crate::TriggerStore>) -> Self {
153        self.trigger_store = Some(store);
154        self
155    }
156}
157
158/// Host shape for runtimes that support background plugin work.
159#[derive(Clone)]
160pub struct ProcessRuntimeHost {
161    pub embedded: EmbeddedRuntimeHost,
162    pub process_registry: Arc<dyn ProcessRegistry>,
163}
164
165impl ProcessRuntimeHost {
166    pub fn new(embedded: EmbeddedRuntimeHost, process_registry: Arc<dyn ProcessRegistry>) -> Self {
167        Self {
168            embedded,
169            process_registry,
170        }
171    }
172}
173
174#[derive(Clone)]
175pub(crate) struct RuntimeHost {
176    pub core: RuntimeHostConfig,
177    pub session_store_factory: Option<Arc<dyn SessionStoreFactory>>,
178    pub trigger_store: Option<Arc<dyn crate::TriggerStore>>,
179    pub process_registry: Option<Arc<dyn ProcessRegistry>>,
180    /// Wakes the host's [`ProcessWorkRunner`](super::ProcessWorkRunner) so a
181    /// successful process start is consumed promptly. Absent when no work runner
182    /// is wired (e.g. a registry-less host); poking is then a no-op.
183    pub process_work_poke: Option<ProcessWorkPoke>,
184    /// Wakes the host's [`QueuedWorkRunner`](super::QueuedWorkRunner) so queued
185    /// turn work drains promptly after queue ingress.
186    pub queued_work_poke: Option<QueuedWorkPoke>,
187}
188
189impl RuntimeHost {
190    pub(crate) fn resolve_session_policy(
191        &self,
192        session_id: &str,
193        policy: crate::SessionPolicy,
194    ) -> Result<crate::RuntimeSessionPolicy, crate::SessionError> {
195        let provider_id = policy.recorded_provider_id();
196        let binding = self
197            .core
198            .providers
199            .provider_resolver
200            .resolve_provider_binding(provider_id)
201            .map_err(|err| match err {
202                crate::ProviderResolutionError::MissingProviderId => {
203                    crate::SessionError::ProviderUnconfigured {
204                        session_id: session_id.to_string(),
205                    }
206                }
207                crate::ProviderResolutionError::UnknownProvider { provider_id } => {
208                    crate::SessionError::ProviderUnavailable {
209                        provider_id,
210                        session_id: session_id.to_string(),
211                    }
212                }
213                crate::ProviderResolutionError::ProviderIdMismatch { expected, actual } => {
214                    crate::SessionError::ProviderMismatch {
215                        expected,
216                        actual,
217                        session_id: session_id.to_string(),
218                    }
219                }
220            })?;
221        Ok(crate::RuntimeSessionPolicy::new(policy, binding))
222    }
223}
224
225impl From<EmbeddedRuntimeHost> for RuntimeHost {
226    fn from(value: EmbeddedRuntimeHost) -> Self {
227        Self {
228            core: value.core,
229            session_store_factory: value.session_store_factory,
230            trigger_store: value.trigger_store,
231            process_registry: None,
232            process_work_poke: None,
233            queued_work_poke: None,
234        }
235    }
236}
237
238impl From<ProcessRuntimeHost> for RuntimeHost {
239    fn from(value: ProcessRuntimeHost) -> Self {
240        Self {
241            core: value.embedded.core,
242            session_store_factory: value.embedded.session_store_factory,
243            trigger_store: value.embedded.trigger_store,
244            process_registry: Some(value.process_registry),
245            process_work_poke: None,
246            queued_work_poke: None,
247        }
248    }
249}