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