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