Skip to main content

lash_core/runtime/
host.rs

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