1use crate::support::*;
2use lash_core::runtime::{
3 ProcessCommand, ProcessEffectOutcome, RuntimeEffectCommand, RuntimeEffectEnvelope,
4 RuntimeEffectKind, RuntimeEffectLocalExecutor, RuntimeEffectOutcome, RuntimeInvocation,
5 RuntimeScope,
6};
7
8#[derive(Clone)]
9pub struct LashCore {
10 pub(crate) env: RuntimeEnvironment,
11 pub(crate) policy: SessionPolicy,
12 pub(crate) modes: Arc<BTreeMap<ModeId, ModePreset>>,
13 pub(crate) default_mode: ModeId,
14 pub(crate) store_factory: Option<Arc<dyn SessionStoreFactory>>,
15 pub(crate) plugin_factories: Arc<Vec<Arc<dyn PluginFactory>>>,
16 pub(crate) provider: Option<ProviderHandle>,
17 pub(crate) live_replay_store: Arc<dyn LiveReplayStore>,
18 pub(crate) process_observer: Option<ProcessWorkObserver>,
19 pub(crate) process_work_runner: Arc<ProcessWorkRunnerSlot>,
24}
25
26pub(crate) enum ProcessWorkRunnerSetup {
29 None,
31 LazyDefault {
38 config: Box<DurableProcessWorkerConfig>,
39 },
40 External { driver: ProcessWorkDriver },
43}
44
45#[derive(Clone, Default)]
46pub(crate) enum ProcessWorkSource {
47 #[default]
48 None,
49 Inline {
50 registry: Arc<dyn ProcessRegistry>,
51 },
52 External(ProcessWorkDriver),
53}
54
55impl ProcessWorkSource {
56 fn process_registry(&self) -> Option<Arc<dyn ProcessRegistry>> {
57 match self {
58 Self::None => None,
59 Self::Inline { registry } => Some(Arc::clone(registry)),
60 Self::External(driver) => Some(driver.process_registry()),
61 }
62 }
63
64 fn has_registry(&self) -> bool {
65 !matches!(self, Self::None)
66 }
67}
68
69pub(crate) struct ProcessWorkRunnerSlot {
76 setup: ProcessWorkRunnerSetup,
77 poke: tokio::sync::OnceCell<Option<ProcessWorkPoke>>,
78}
79
80impl ProcessWorkRunnerSlot {
81 fn new(setup: ProcessWorkRunnerSetup) -> Self {
82 Self {
83 setup,
84 poke: tokio::sync::OnceCell::new(),
85 }
86 }
87
88 pub(crate) async fn poke(&self) -> Option<ProcessWorkPoke> {
91 self.poke
92 .get_or_init(|| async {
93 match &self.setup {
94 ProcessWorkRunnerSetup::None => None,
95 ProcessWorkRunnerSetup::External { driver } => Some(driver.poke_handle()),
96 ProcessWorkRunnerSetup::LazyDefault { config } => {
97 let worker = DurableProcessWorker::new((**config).clone());
98 Some(ProcessWorkRunner::inline(worker).spawn())
99 }
100 }
101 })
102 .await
103 .clone()
104 }
105}
106
107#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
108pub struct SessionDeleteReport {
109 pub session_id: String,
110 pub process: Option<lash_core::ProcessSessionDeleteReport>,
111}
112
113impl LashCore {
114 pub fn builder() -> LashCoreBuilder {
115 LashCoreBuilder::default()
116 }
117
118 pub fn standard() -> LashCoreBuilder {
124 Self::builder()
125 .install_mode(ModePreset::standard())
126 .default_mode(ModeId::standard())
127 .plugins(default_runtime_stack())
128 }
129
130 pub fn rlm() -> LashCoreBuilder {
136 Self::builder()
137 .install_mode(ModePreset::rlm())
138 .default_mode(ModeId::rlm())
139 .plugins(default_runtime_stack())
140 }
141
142 pub fn session(&self, session_id: impl Into<String>) -> SessionBuilder {
143 SessionBuilder {
144 core: self.clone(),
145 session_id: session_id.into(),
146 spec: SessionSpec::inherit(),
147 mode: None,
148 parent_session_id: None,
149 store: None,
150 provider: None,
151 active_plugins: Vec::new(),
152 plugin_factories: Vec::new(),
153 rlm_final_answer_format: None,
154 }
155 }
156
157 pub fn host_events(&self) -> crate::control::HostEventsControl {
158 crate::control::HostEventsControl { core: self.clone() }
159 }
160
161 pub fn effect_host(&self) -> Arc<dyn EffectHost> {
162 Arc::clone(&self.env.core.control.effect_host)
163 }
164
165 pub async fn delete_session(
166 &self,
167 session_id: impl AsRef<str>,
168 scoped_effect_controller: ScopedEffectController<'_>,
169 ) -> Result<SessionDeleteReport> {
170 let session_id = session_id.as_ref().to_string();
171 let Some(store_factory) = self.store_factory.as_ref() else {
172 return Err(EmbedError::MissingSessionStoreFactory);
173 };
174 let process = if let Some(process_registry) = self.env.process_registry.as_ref() {
175 let invocation = RuntimeInvocation::effect(
176 RuntimeScope::new(session_id.clone()),
177 format!("process:delete-session:{session_id}"),
178 RuntimeEffectKind::Process,
179 format!("{session_id}:delete-session"),
180 );
181 let outcome = scoped_effect_controller
182 .controller()
183 .execute_effect(
184 RuntimeEffectEnvelope::new(
185 invocation,
186 RuntimeEffectCommand::Process {
187 command: ProcessCommand::DeleteSession {
188 session_id: session_id.clone(),
189 },
190 },
191 ),
192 RuntimeEffectLocalExecutor::process_control(Arc::clone(process_registry)),
193 )
194 .await
195 .map_err(|err| EmbedError::SessionDeleteProcess {
196 session_id: session_id.clone(),
197 message: err.to_string(),
198 })?;
199 match outcome {
200 RuntimeEffectOutcome::Process {
201 result: ProcessEffectOutcome::DeleteSession { report },
202 } => Some(report),
203 other => {
204 return Err(EmbedError::SessionDeleteProcess {
205 session_id,
206 message: format!(
207 "process delete returned the wrong outcome: {}",
208 other.kind().as_str()
209 ),
210 });
211 }
212 }
213 } else {
214 None
215 };
216 store_factory
217 .delete_session(&session_id)
218 .await
219 .map_err(|message| EmbedError::StoreFactory {
220 session_id: session_id.clone(),
221 message,
222 })?;
223 Ok(SessionDeleteReport {
224 session_id,
225 process,
226 })
227 }
228
229 pub fn installed_modes(&self) -> impl Iterator<Item = &ModeId> {
230 self.modes.keys()
231 }
232
233 pub fn process_observer(&self) -> Option<&ProcessWorkObserver> {
234 self.process_observer.as_ref()
235 }
236
237 pub fn process_registry(&self) -> Option<Arc<dyn ProcessRegistry>> {
238 self.env.process_registry.as_ref().cloned()
239 }
240
241 pub fn durable_process_worker_config(&self) -> Result<DurableProcessWorkerConfig> {
242 self.durable_process_worker_config_with_plugins(std::iter::empty::<Arc<dyn PluginFactory>>())
243 }
244
245 pub fn durable_process_worker_config_with_plugins(
246 &self,
247 extra_plugin_factories: impl IntoIterator<Item = Arc<dyn PluginFactory>>,
248 ) -> Result<DurableProcessWorkerConfig> {
249 let Some(process_registry) = self.process_registry() else {
250 return Err(EmbedError::MissingProcessRegistry);
251 };
252 let Some(store_factory) = self.store_factory.as_ref() else {
253 return Err(EmbedError::MissingProcessWorkerStoreFactory);
254 };
255 let plugin_host = build_plugin_host_for_mode(
256 &self.modes,
257 &self.default_mode,
258 self.plugin_factories.as_ref(),
259 extra_plugin_factories.into_iter().collect(),
260 true,
261 )?;
262 let mut config = DurableProcessWorkerConfig::new(
263 Arc::new(plugin_host),
264 self.env.core.clone(),
265 Arc::clone(store_factory),
266 process_registry,
267 )
268 .with_session_policy(self.policy.clone())
269 .with_residency(self.env.residency);
270 if let Some(host_event_store) = self.env.host_event_store.as_ref() {
271 config = config.with_host_event_store(Arc::clone(host_event_store));
272 }
273 Ok(config)
274 }
275}
276
277fn default_runtime_stack() -> PluginStack {
278 lash_plugin_tool_output_budget::tool_output_budget_stack()
279}
280
281#[derive(Default)]
282pub struct LashCoreBuilder {
283 pub(crate) modes: BTreeMap<ModeId, ModePreset>,
284 pub(crate) default_mode: Option<ModeId>,
285 session_spec: SessionSpec,
286 provider: Option<ProviderHandle>,
287 pub(crate) store_factory: Option<Arc<dyn SessionStoreFactory>>,
288 child_store_factory: Option<Arc<dyn SessionStoreFactory>>,
289 effect_host: Option<Arc<dyn EffectHost>>,
293 attachment_store: Option<Arc<dyn AttachmentStore>>,
294 lashlang_artifact_store: Option<Arc<dyn lash_core::LashlangArtifactStore>>,
295 host_event_store: Option<Arc<dyn lash_core::HostEventStore>>,
296 prompt: Option<PromptLayer>,
298 trace_sink: Option<Arc<dyn lash_trace::TraceSink>>,
299 lashlang_execution_sink: Option<Arc<dyn lash_trace::TraceSink>>,
300 trace_level: Option<lash_trace::TraceLevel>,
301 trace_context: Option<lash_trace::TraceContext>,
302 termination: Option<TerminationPolicy>,
303 runtime_host_config: Option<RuntimeHostConfig>,
305 tool_providers: Vec<Arc<dyn ToolProvider>>,
306 plugin_stack: PluginStack,
307 plugin_host: Option<PluginHost>,
308 residency: Option<Residency>,
309 process_work_source: ProcessWorkSource,
312 queued_work_poke: Option<QueuedWorkPoke>,
313 live_replay_store: Option<Arc<dyn LiveReplayStore>>,
314}
315
316impl LashCoreBuilder {
317 pub fn install_mode(mut self, preset: ModePreset) -> Self {
318 let mode_id = preset.mode_id.clone();
319 if self.default_mode.is_none() {
320 self.default_mode = Some(mode_id.clone());
321 }
322 self.modes.insert(mode_id, preset);
323 self
324 }
325
326 pub fn default_mode(mut self, mode: ModeId) -> Self {
327 self.default_mode = Some(mode);
328 self
329 }
330
331 pub fn mode(mut self, mode: ModeId) -> Self {
332 self.default_mode = Some(mode);
333 self
334 }
335
336 pub fn provider(mut self, provider: ProviderHandle) -> Self {
337 self.session_spec = self.session_spec.provider_id(provider.kind());
338 self.provider = Some(provider);
339 self
340 }
341
342 pub fn model(mut self, model: lash_core::ModelSpec) -> Self {
343 self.session_spec = self.session_spec.model(model);
344 self
345 }
346
347 pub fn max_turns(mut self, max_turns: usize) -> Self {
348 self.session_spec = self.session_spec.max_turns(max_turns);
349 self
350 }
351
352 pub fn session_spec(mut self, spec: SessionSpec) -> Self {
353 self.session_spec = spec;
354 self
355 }
356
357 pub fn store_factory(mut self, store_factory: Arc<dyn SessionStoreFactory>) -> Self {
365 self.store_factory = Some(store_factory);
366 self
367 }
368
369 pub fn child_store_factory(mut self, store_factory: Arc<dyn SessionStoreFactory>) -> Self {
377 self.child_store_factory = Some(store_factory);
378 self
379 }
380
381 pub fn attachment_store(mut self, attachment_store: Arc<dyn AttachmentStore>) -> Self {
382 self.attachment_store = Some(attachment_store);
383 self
384 }
385
386 pub fn lashlang_artifact_store(
390 mut self,
391 artifact_store: Arc<dyn lash_core::LashlangArtifactStore>,
392 ) -> Self {
393 self.lashlang_artifact_store = Some(artifact_store);
394 self
395 }
396
397 pub fn effect_host(mut self, effect_host: Arc<dyn EffectHost>) -> Self {
402 self.effect_host = Some(effect_host);
403 self
404 }
405
406 pub fn tools(mut self, tools: Arc<dyn ToolProvider>) -> Self {
407 self.tool_providers.push(tools);
408 self
409 }
410
411 pub fn plugin(mut self, plugin: Arc<dyn PluginFactory>) -> Self {
412 self.plugin_stack.push(plugin);
413 self
414 }
415
416 pub fn plugins(mut self, stack: PluginStack) -> Self {
417 self.plugin_stack = stack;
418 self
419 }
420
421 pub fn configure_plugins(mut self, configure: impl FnOnce(&mut PluginStack)) -> Self {
422 configure(&mut self.plugin_stack);
423 self
424 }
425
426 pub fn trace_sink(mut self, trace_sink: Option<Arc<dyn lash_trace::TraceSink>>) -> Self {
427 self.trace_sink = trace_sink;
428 self
429 }
430
431 pub fn trace_jsonl_path(mut self, path: Option<std::path::PathBuf>) -> Self {
432 self.trace_sink = path.map(|path| {
433 Arc::new(lash_trace::JsonlTraceSink::new(path)) as Arc<dyn lash_trace::TraceSink>
434 });
435 self
436 }
437
438 pub fn lashlang_execution_sink(
439 mut self,
440 lashlang_execution_sink: Option<Arc<dyn lash_trace::TraceSink>>,
441 ) -> Self {
442 self.lashlang_execution_sink = lashlang_execution_sink;
443 self
444 }
445
446 pub fn lashlang_execution_jsonl_path(mut self, path: Option<std::path::PathBuf>) -> Self {
447 self.lashlang_execution_sink = path.map(|path| {
448 Arc::new(lash_trace::JsonlTraceSink::new(path)) as Arc<dyn lash_trace::TraceSink>
449 });
450 self
451 }
452
453 pub fn trace_level(mut self, trace_level: lash_trace::TraceLevel) -> Self {
454 self.trace_level = Some(trace_level);
455 self
456 }
457
458 pub fn trace_context(mut self, trace_context: lash_trace::TraceContext) -> Self {
459 self.trace_context = Some(trace_context);
460 self
461 }
462
463 pub fn termination(mut self, termination: TerminationPolicy) -> Self {
464 self.termination = Some(termination);
465 self
466 }
467
468 pub fn residency(mut self, residency: Residency) -> Self {
469 self.residency = Some(residency);
470 self
471 }
472
473 pub fn live_replay_store(mut self, live_replay_store: Arc<dyn LiveReplayStore>) -> Self {
477 self.live_replay_store = Some(live_replay_store);
478 self
479 }
480
481 fn resolve_runtime_host_config(&mut self) -> Result<RuntimeHostConfig> {
484 if let Some(base) = self.runtime_host_config.take() {
485 return Ok(self.apply_core_overrides(base));
486 }
487 let effect_host = self
488 .effect_host
489 .take()
490 .ok_or(EmbedError::MissingEffectHost)?;
491 let lashlang_artifact_store = self
492 .lashlang_artifact_store
493 .take()
494 .ok_or(EmbedError::MissingLashlangArtifactStore)?;
495 let attachment_store = self
496 .attachment_store
497 .take()
498 .ok_or(EmbedError::MissingAttachmentStore)?;
499 let core = RuntimeHostConfig::new(effect_host, lashlang_artifact_store, attachment_store);
500 Ok(self.apply_core_overrides(core))
501 }
502
503 fn apply_core_overrides(&mut self, mut core: RuntimeHostConfig) -> RuntimeHostConfig {
505 if let Some(effect_host) = self.effect_host.take() {
506 core.control.effect_host = effect_host;
507 }
508 if let Some(attachment_store) = self.attachment_store.take() {
509 core.durability.attachment_store = attachment_store;
510 }
511 if let Some(artifact_store) = self.lashlang_artifact_store.take() {
512 core.durability.lashlang_artifact_store = artifact_store;
513 }
514 if let Some(prompt) = self.prompt.take() {
515 core.prompt.prompt = prompt;
516 }
517 if let Some(trace_sink) = self.trace_sink.take() {
518 core.tracing.trace_sink = Some(trace_sink);
519 }
520 if let Some(lashlang_execution_sink) = self.lashlang_execution_sink.take() {
521 core.tracing.lashlang_execution_sink = Some(lashlang_execution_sink);
522 }
523 if let Some(trace_level) = self.trace_level.take() {
524 core.tracing.trace_level = trace_level;
525 }
526 if let Some(trace_context) = self.trace_context.take() {
527 core.tracing.trace_context = trace_context;
528 }
529 if let Some(termination) = self.termination.take() {
530 core.control.termination = termination;
531 }
532 core
533 }
534
535 fn ensure_store_peer_coherence(&self) -> Result<()> {
548 let session_store_tier = self
554 .child_store_factory
555 .as_ref()
556 .or(self.store_factory.as_ref())
557 .map(|factory| factory.durability_tier());
558 let attachment_tier = self
559 .attachment_store
560 .as_ref()
561 .map(|store| store.persistence().durability_tier());
562 let artifact_tier = self
563 .lashlang_artifact_store
564 .as_ref()
565 .map(|store| store.durability_tier());
566 let host_event_store_tier = self
567 .host_event_store
568 .as_ref()
569 .map(|store| store.durability_tier());
570
571 if session_store_tier == Some(DurabilityTier::Durable) {
572 if attachment_tier == Some(DurabilityTier::Inline) {
573 return Err(EmbedError::DurableStorePeerRequired {
574 facet: "attachment store",
575 });
576 }
577 if artifact_tier == Some(DurabilityTier::Inline) {
578 return Err(EmbedError::DurableStorePeerRequired {
579 facet: "artifact store",
580 });
581 }
582 }
583
584 if let Some(process_registry) = self.process_work_source.process_registry().as_ref() {
585 if process_registry.durability_tier() == DurabilityTier::Durable {
586 if session_store_tier != Some(DurabilityTier::Durable) {
587 return Err(EmbedError::DurableProcessRegistryRequiresStoreFactory);
588 }
589 if host_event_store_tier != Some(DurabilityTier::Durable) {
590 return Err(EmbedError::DurableStorePeerRequired {
591 facet: "host event store",
592 });
593 }
594 }
595 }
596
597 if host_event_store_tier == Some(DurabilityTier::Durable) {
598 if session_store_tier != Some(DurabilityTier::Durable) {
599 return Err(EmbedError::DurableStorePeerRequired {
600 facet: "session store factory",
601 });
602 }
603 if let Some(process_registry) = self.process_work_source.process_registry().as_ref()
604 && process_registry.durability_tier() == DurabilityTier::Inline
605 {
606 return Err(EmbedError::DurableStorePeerRequired {
607 facet: "process registry",
608 });
609 }
610 }
611
612 Ok(())
613 }
614
615 pub fn build(mut self) -> Result<LashCore> {
616 if self.modes.is_empty() {
617 return Err(EmbedError::NoModesInstalled);
618 }
619 self.ensure_store_peer_coherence()?;
620 let default_mode = self
621 .default_mode
622 .clone()
623 .ok_or(EmbedError::NoModesInstalled)?;
624 if !self.modes.contains_key(&default_mode) {
625 return Err(EmbedError::DefaultModeNotInstalled { mode: default_mode });
626 }
627 let provider_id = self
628 .session_spec
629 .provider_id
630 .clone()
631 .or_else(|| {
632 self.provider
633 .as_ref()
634 .map(|provider| provider.kind().to_string())
635 })
636 .unwrap_or_default();
637 let model = self
638 .session_spec
639 .model
640 .clone()
641 .ok_or(EmbedError::MissingModelSpec)?;
642
643 let base_policy = SessionPolicy {
644 provider_id,
645 model,
646 max_turns: self.session_spec.max_turns.flatten(),
647 ..SessionPolicy::default()
648 };
649 let policy = self.session_spec.resolve_against(&base_policy);
650
651 let mut core = self.resolve_runtime_host_config()?;
652 if let Some(provider) = self.provider.clone() {
653 core.providers.provider_resolver =
654 Arc::new(lash_core::SingleProviderResolver::new(provider));
655 }
656
657 let plugin_factories = if let Some(plugin_host) = self.plugin_host {
658 plugin_host.factories().to_vec()
659 } else {
660 let mut factories = Vec::new();
661 if !self.tool_providers.is_empty() {
662 let spec = self
663 .tool_providers
664 .into_iter()
665 .fold(PluginSpec::new(), PluginSpec::with_tool_provider);
666 factories.push(Arc::new(StaticPluginFactory::new("embed_tools", spec))
667 as Arc<dyn PluginFactory>);
668 }
669 factories.extend(self.plugin_stack.into_factories());
670 factories
671 };
672 let default_plugin_host = build_plugin_host_for_mode(
673 &self.modes,
674 &default_mode,
675 &plugin_factories,
676 Vec::new(),
677 self.process_work_source.has_registry(),
678 )?;
679
680 let process_registry = self.process_work_source.process_registry();
681
682 let process_work_runner = Self::resolve_process_work_runner(
690 &self.process_work_source,
691 &default_plugin_host,
692 &core,
693 self.child_store_factory
696 .as_ref()
697 .or(self.store_factory.as_ref()),
698 &policy,
699 self.residency.unwrap_or_default(),
700 self.host_event_store.as_ref(),
701 )?;
702
703 let mut env_builder = RuntimeEnvironment::builder()
704 .with_plugin_host(Arc::new(default_plugin_host))
705 .with_runtime_host_config(core);
706 if let Some(process_registry) = process_registry.as_ref() {
707 env_builder = env_builder.with_process_registry(Arc::clone(process_registry));
708 }
709 if let Some(residency) = self.residency {
710 env_builder = env_builder.with_residency(residency);
711 }
712 if let Some(child_store_factory) = self
713 .child_store_factory
714 .as_ref()
715 .or(self.store_factory.as_ref())
716 {
717 env_builder = env_builder.with_session_store_factory(Arc::clone(child_store_factory));
718 }
719 if let Some(host_event_store) = self.host_event_store.as_ref() {
720 env_builder = env_builder.with_host_event_store(Arc::clone(host_event_store));
721 }
722 if let Some(queued_work_poke) = self.queued_work_poke.clone() {
723 env_builder = env_builder.with_queued_work_poke(queued_work_poke);
724 }
725
726 let live_replay_store = self
727 .live_replay_store
728 .take()
729 .unwrap_or_else(|| Arc::new(InMemoryLiveReplayStore::default()));
730
731 Ok(LashCore {
732 env: env_builder.build(),
733 policy,
734 modes: Arc::new(self.modes),
735 default_mode,
736 store_factory: self.store_factory,
737 plugin_factories: Arc::new(plugin_factories),
738 provider: self.provider,
739 live_replay_store,
740 process_observer: process_registry.map(ProcessWorkObserver::new),
741 process_work_runner: Arc::new(ProcessWorkRunnerSlot::new(process_work_runner)),
742 })
743 }
744
745 fn resolve_process_work_runner(
753 process_work_source: &ProcessWorkSource,
754 worker_plugin_host: &PluginHost,
755 core: &RuntimeHostConfig,
756 store_factory: Option<&Arc<dyn SessionStoreFactory>>,
757 policy: &SessionPolicy,
758 residency: lash_core::Residency,
759 host_event_store: Option<&Arc<dyn lash_core::HostEventStore>>,
760 ) -> Result<ProcessWorkRunnerSetup> {
761 let process_registry = match process_work_source {
762 ProcessWorkSource::None => return Ok(ProcessWorkRunnerSetup::None),
763 ProcessWorkSource::External(driver) => {
764 return Ok(ProcessWorkRunnerSetup::External {
765 driver: driver.clone(),
766 });
767 }
768 ProcessWorkSource::Inline { registry } => Arc::clone(registry),
769 };
770 let Some(store_factory) = store_factory else {
774 return Err(EmbedError::ProcessRegistryRequiresStoreFactory);
775 };
776 let config = Box::new(
784 DurableProcessWorkerConfig::new(
785 Arc::new(worker_plugin_host.clone()),
786 core.clone(),
787 Arc::clone(store_factory),
788 process_registry,
789 )
790 .with_session_policy(policy.clone())
791 .with_host_event_store(
792 host_event_store
793 .cloned()
794 .unwrap_or_else(|| Arc::new(lash_core::InMemoryHostEventStore::default())),
795 )
796 .with_residency(residency),
797 );
798 Ok(ProcessWorkRunnerSetup::LazyDefault { config })
799 }
800
801 pub fn advanced(self) -> AdvancedLashCoreBuilder {
802 AdvancedLashCoreBuilder { builder: self }
803 }
804
805 pub fn process_registry(mut self, process_registry: Arc<dyn ProcessRegistry>) -> Self {
806 self.process_work_source = ProcessWorkSource::Inline {
807 registry: process_registry,
808 };
809 self
810 }
811
812 pub fn host_event_store(mut self, store: Arc<dyn lash_core::HostEventStore>) -> Self {
813 self.host_event_store = Some(store);
814 self
815 }
816
817 pub fn process_work_driver(mut self, driver: ProcessWorkDriver) -> Self {
824 self.process_work_source = ProcessWorkSource::External(driver);
825 self
826 }
827
828 pub fn with_queued_work_runner(mut self, poke: QueuedWorkPoke) -> Self {
829 self.queued_work_poke = Some(poke);
830 self
831 }
832}
833
834pub(crate) fn build_plugin_host_for_mode(
835 modes: &BTreeMap<ModeId, ModePreset>,
836 mode: &ModeId,
837 common_factories: &[Arc<dyn PluginFactory>],
838 extra_factories: Vec<Arc<dyn PluginFactory>>,
839 process_lifecycle: bool,
840) -> Result<PluginHost> {
841 let preset = modes
842 .get(mode)
843 .ok_or_else(|| EmbedError::ModeNotInstalled { mode: mode.clone() })?;
844 let mut factories = Vec::with_capacity(1 + common_factories.len() + extra_factories.len());
845 factories.push(Arc::clone(&preset.factory));
846 factories.extend(common_factories.iter().cloned());
847 factories.extend(extra_factories);
848 let mut plugin_host = PluginHost::new(factories);
849 if process_lifecycle {
850 let abilities = plugin_host
851 .lashlang_abilities()
852 .with_processes()
853 .with_sleep()
854 .with_process_signals();
855 plugin_host = plugin_host.with_lashlang_abilities(abilities);
856 }
857 Ok(plugin_host)
858}
859
860impl PromptLayerSink for LashCoreBuilder {
861 fn prompt_layer_mut(&mut self) -> &mut PromptLayer {
862 self.prompt.get_or_insert_with(PromptLayer::new)
863 }
864}
865
866pub struct AdvancedLashCoreBuilder {
867 builder: LashCoreBuilder,
868}
869
870impl AdvancedLashCoreBuilder {
871 pub fn runtime_host_config(mut self, core: lash_core::RuntimeHostConfig) -> Self {
872 self.builder.runtime_host_config = Some(core);
873 self
874 }
875
876 pub fn plugin_host(mut self, plugin_host: PluginHost) -> Self {
877 self.builder.plugin_host = Some(plugin_host);
878 self
879 }
880
881 pub fn build(self) -> Result<LashCore> {
882 self.builder.build()
883 }
884}