1use std::sync::Arc;
2
3use crate::plugin::{PluginFactory, PluginHost, PluginSession};
4use crate::{
5 EffectHost, EmbeddedRuntimeHost, LashRuntime, PluginStack, ProcessRegistry, Residency,
6 RuntimeHostConfig, RuntimePersistence, RuntimeSessionState, SessionError, SessionPolicy,
7 SessionStoreFactory, TerminationPolicy,
8};
9
10enum PluginSource {
11 Host(PluginHost),
12 Session(Arc<PluginSession>),
13}
14
15pub struct EmbeddedRuntimeBuilder {
16 session_id: Option<String>,
17 policy: Option<SessionPolicy>,
18 plugin_options: crate::PluginOptions,
19 initial_state: Option<RuntimeSessionState>,
20 plugin_source: PluginSource,
21 core: RuntimeHostConfig,
22 session_store_factory: Option<Arc<dyn SessionStoreFactory>>,
23 trigger_store: Option<Arc<dyn crate::TriggerStore>>,
24 store: Option<Arc<dyn RuntimePersistence>>,
25 process_registry: Option<Arc<dyn ProcessRegistry>>,
26 residency: Residency,
27}
28
29impl Default for EmbeddedRuntimeBuilder {
30 fn default() -> Self {
31 Self {
32 session_id: None,
33 policy: None,
34 plugin_options: crate::PluginOptions::default(),
35 initial_state: None,
36 plugin_source: PluginSource::Host(PluginHost::empty()),
37 core: RuntimeHostConfig::in_memory(),
41 session_store_factory: None,
42 trigger_store: Some(Arc::new(crate::InMemoryTriggerStore::default())),
43 store: None,
44 process_registry: None,
45 residency: Residency::default(),
46 }
47 }
48}
49
50impl EmbeddedRuntimeBuilder {
51 pub fn new() -> Self {
52 Self::default()
53 }
54
55 pub fn session_id(&self) -> Option<&str> {
56 self.session_id.as_deref()
57 }
58
59 pub fn policy(&self) -> Option<&SessionPolicy> {
60 self.policy.as_ref()
61 }
62
63 pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
64 self.session_id = Some(session_id.into());
65 self
66 }
67
68 pub fn with_policy(mut self, policy: SessionPolicy) -> Self {
69 self.policy = Some(policy);
70 self
71 }
72
73 pub fn with_plugin_options(mut self, plugin_options: crate::PluginOptions) -> Self {
74 self.plugin_options = plugin_options;
75 self
76 }
77
78 pub fn with_initial_state(mut self, state: RuntimeSessionState) -> Self {
79 self.initial_state = Some(state);
80 self
81 }
82
83 pub fn with_plugin_host(mut self, plugin_host: PluginHost) -> Self {
84 self.plugin_source = PluginSource::Host(plugin_host);
85 self
86 }
87
88 pub fn with_plugin_session(mut self, plugin_session: Arc<PluginSession>) -> Self {
89 self.plugin_source = PluginSource::Session(plugin_session);
90 self
91 }
92
93 pub fn with_plugin_factories(mut self, factories: Vec<Arc<dyn PluginFactory>>) -> Self {
94 let host = PluginHost::new(factories);
95 self.plugin_source = PluginSource::Host(host);
96 self
97 }
98
99 pub fn with_plugin_stack(self, stack: PluginStack) -> Self {
100 self.with_plugin_factories(stack.into_factories())
101 }
102
103 pub fn with_runtime_host(mut self, core: RuntimeHostConfig) -> Self {
104 self.core = core;
105 self
106 }
107
108 pub fn with_attachment_store(
109 mut self,
110 attachment_store: Arc<dyn crate::AttachmentStore>,
111 ) -> Self {
112 self.core.durability.attachment_store = attachment_store;
113 self
114 }
115
116 pub fn with_prompt_template(mut self, prompt_template: crate::PromptTemplate) -> Self {
117 self.core.prompt.prompt.template = Some(prompt_template);
118 self
119 }
120
121 pub fn with_prompt_contribution(mut self, contribution: crate::PromptContribution) -> Self {
122 self.core.prompt.prompt.add_contribution(contribution);
123 self
124 }
125
126 pub fn with_replaced_prompt_slot(
127 mut self,
128 slot: crate::PromptSlot,
129 contributions: impl IntoIterator<Item = crate::PromptContribution>,
130 ) -> Self {
131 self.core.prompt.prompt.replace_slot(slot, contributions);
132 self
133 }
134
135 pub fn with_cleared_prompt_slot(mut self, slot: crate::PromptSlot) -> Self {
136 self.core.prompt.prompt.clear_slot(slot);
137 self
138 }
139
140 pub fn with_prompt_layer(mut self, prompt: crate::PromptLayer) -> Self {
141 self.core.prompt.prompt = prompt;
142 self
143 }
144
145 pub fn with_trace_sink(mut self, sink: Option<Arc<dyn lash_trace::TraceSink>>) -> Self {
146 self.core.tracing.trace_sink = sink;
147 self
148 }
149
150 pub fn with_trace_level(mut self, level: lash_trace::TraceLevel) -> Self {
151 self.core.tracing.trace_level = level;
152 self
153 }
154
155 pub fn with_trace_context(mut self, context: lash_trace::TraceContext) -> Self {
156 self.core.tracing.trace_context = context;
157 self
158 }
159
160 pub fn with_termination(mut self, termination: TerminationPolicy) -> Self {
161 self.core.control.termination = termination;
162 self
163 }
164
165 pub fn with_effect_host(mut self, effect_host: Arc<dyn EffectHost>) -> Self {
166 self.core.control.effect_host = effect_host;
167 self
168 }
169
170 pub fn with_provider_resolver(
171 mut self,
172 provider_resolver: Arc<dyn crate::RuntimeProviderResolver>,
173 ) -> Self {
174 self.core.providers.provider_resolver = provider_resolver;
175 self
176 }
177
178 pub fn with_session_store_factory(
179 mut self,
180 session_store_factory: Arc<dyn SessionStoreFactory>,
181 ) -> Self {
182 self.session_store_factory = Some(session_store_factory);
183 self
184 }
185
186 pub fn with_trigger_store(mut self, store: Arc<dyn crate::TriggerStore>) -> Self {
187 self.trigger_store = Some(store);
188 self
189 }
190
191 pub fn with_store(mut self, store: Arc<dyn RuntimePersistence>) -> Self {
192 self.store = Some(store);
193 self
194 }
195
196 pub fn with_process_registry(mut self, process_registry: Arc<dyn ProcessRegistry>) -> Self {
197 self.process_registry = Some(process_registry);
198 self
199 }
200
201 pub fn with_residency(mut self, residency: Residency) -> Self {
208 self.residency = residency;
209 self
210 }
211
212 fn resolve_state_from_defaults(&self) -> RuntimeSessionState {
213 let mut state = self.initial_state.clone().unwrap_or_default();
214 if let Some(session_id) = &self.session_id {
215 state.session_id = session_id.clone();
216 }
217 if let Some(policy) = &self.policy {
218 state.policy = policy.clone();
219 }
220 state
221 }
222
223 async fn resolve_state(&self) -> Result<RuntimeSessionState, SessionError> {
224 if let Some(state) = &self.initial_state {
225 return Ok({
226 let mut state = state.clone();
227 if let Some(session_id) = &self.session_id {
228 state.session_id = session_id.clone();
229 }
230 if let Some(policy) = &self.policy {
231 let recorded_provider_id = state.policy.recorded_provider_id().to_string();
232 state.policy.provider_id = recorded_provider_id;
233 state.policy.session_id = policy.session_id.clone();
234 if state.policy.model.id.trim().is_empty() {
235 state.policy.model = policy.model.clone();
236 }
237 }
238 state
239 });
240 }
241 if let Some(store) = &self.store {
242 if let Some(mut state) = crate::store::load_persisted_session_state(store.as_ref())
243 .await
244 .map_err(|err| SessionError::Protocol(format!("failed to load store: {err}")))?
245 {
246 if let Some(session_id) = &self.session_id
247 && &state.session_id != session_id
248 {
249 return Err(SessionError::Protocol(format!(
250 "store is bound to session `{}` but builder requested `{session_id}`",
251 state.session_id
252 )));
253 }
254 if let Some(policy) = &self.policy {
255 let recorded_provider_id = state.policy.recorded_provider_id().to_string();
256 state.policy.provider_id = recorded_provider_id;
257 state.policy.session_id = policy.session_id.clone();
258 if state.policy.model.id.trim().is_empty() {
259 state.policy.model = policy.model.clone();
260 }
261 }
262 return Ok(state);
263 }
264 let mut state = self.resolve_state_from_defaults();
265 if let Some(policy) = &self.policy {
266 state.policy = policy.clone();
267 }
268 return Ok(state);
269 }
270 Ok(self.resolve_state_from_defaults())
271 }
272
273 fn resolve_plugins(
274 &self,
275 state: &RuntimeSessionState,
276 ) -> Result<Arc<PluginSession>, SessionError> {
277 match &self.plugin_source {
278 PluginSource::Session(session) => Ok(Arc::clone(session)),
279 PluginSource::Host(host) => host
280 .clone()
281 .isolated_registry()
282 .build_session_with_parent(
283 state.session_id.clone(),
284 None,
285 None,
286 crate::plugin::SessionAuthorityContext {
287 plugin_options: self.plugin_options.clone(),
288 ..crate::plugin::SessionAuthorityContext::default()
289 },
290 )
291 .map_err(|err| SessionError::Protocol(err.to_string())),
292 }
293 }
294
295 pub async fn build(self) -> Result<LashRuntime, SessionError> {
296 let state = self.resolve_state().await?;
297 let plugins = self.resolve_plugins(&state)?;
298 let embedded_host = EmbeddedRuntimeHost::new(self.core)
299 .with_session_store_factory_option(self.session_store_factory.clone())
300 .with_trigger_store_option(self.trigger_store.clone());
301 LashRuntime::assemble_runtime(
304 state.policy.clone(),
305 embedded_host,
306 plugins,
307 self.store,
308 self.process_registry,
309 state,
310 self.residency,
311 )
312 .await
313 }
314
315 pub async fn build_ephemeral(mut self) -> Result<LashRuntime, SessionError> {
316 self.store = None;
317 self.build().await
318 }
319
320 pub async fn build_persistent(
321 mut self,
322 store: Arc<dyn RuntimePersistence>,
323 ) -> Result<LashRuntime, SessionError> {
324 self.store = Some(store);
325 self.build().await
326 }
327
328 pub async fn build_background_persistent(
329 mut self,
330 store: Arc<dyn RuntimePersistence>,
331 process_registry: Arc<dyn ProcessRegistry>,
332 ) -> Result<LashRuntime, SessionError> {
333 self.store = Some(store);
334 self = self.with_process_registry(process_registry);
335 self.build().await
336 }
337}
338
339impl LashRuntime {
340 pub fn builder() -> EmbeddedRuntimeBuilder {
341 EmbeddedRuntimeBuilder::new()
342 }
343}
344
345trait EmbeddedRuntimeHostExt {
346 fn with_session_store_factory_option(
347 self,
348 session_store_factory: Option<Arc<dyn SessionStoreFactory>>,
349 ) -> Self;
350
351 fn with_trigger_store_option(self, trigger_store: Option<Arc<dyn crate::TriggerStore>>)
352 -> Self;
353}
354
355impl EmbeddedRuntimeHostExt for EmbeddedRuntimeHost {
356 fn with_session_store_factory_option(
357 mut self,
358 session_store_factory: Option<Arc<dyn SessionStoreFactory>>,
359 ) -> Self {
360 self.session_store_factory = session_store_factory;
361 self
362 }
363
364 fn with_trigger_store_option(
365 mut self,
366 trigger_store: Option<Arc<dyn crate::TriggerStore>>,
367 ) -> Self {
368 self.trigger_store = trigger_store;
369 self
370 }
371}