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) process_observer: Option<ProcessWorkObserver>,
18 pub(crate) process_work_runner: Arc<ProcessWorkRunnerSlot>,
23}
24
25pub(crate) enum ProcessWorkRunnerSetup {
28 None,
30 LazyDefault {
37 config: Box<DurableProcessWorkerConfig>,
38 },
39 External { driver: ProcessWorkDriver },
42}
43
44#[derive(Clone, Default)]
45pub(crate) enum ProcessWorkSource {
46 #[default]
47 None,
48 Inline {
49 registry: Arc<dyn ProcessRegistry>,
50 },
51 External(ProcessWorkDriver),
52}
53
54impl ProcessWorkSource {
55 fn process_registry(&self) -> Option<Arc<dyn ProcessRegistry>> {
56 match self {
57 Self::None => None,
58 Self::Inline { registry } => Some(Arc::clone(registry)),
59 Self::External(driver) => Some(driver.process_registry()),
60 }
61 }
62
63 fn has_registry(&self) -> bool {
64 !matches!(self, Self::None)
65 }
66}
67
68pub(crate) struct ProcessWorkRunnerSlot {
75 setup: ProcessWorkRunnerSetup,
76 poke: tokio::sync::OnceCell<Option<ProcessWorkPoke>>,
77}
78
79impl ProcessWorkRunnerSlot {
80 fn new(setup: ProcessWorkRunnerSetup) -> Self {
81 Self {
82 setup,
83 poke: tokio::sync::OnceCell::new(),
84 }
85 }
86
87 pub(crate) async fn poke(&self) -> Option<ProcessWorkPoke> {
90 self.poke
91 .get_or_init(|| async {
92 match &self.setup {
93 ProcessWorkRunnerSetup::None => None,
94 ProcessWorkRunnerSetup::External { driver } => Some(driver.poke_handle()),
95 ProcessWorkRunnerSetup::LazyDefault { config } => {
96 let worker = DurableProcessWorker::new((**config).clone());
97 Some(ProcessWorkRunner::inline(worker).spawn())
98 }
99 }
100 })
101 .await
102 .clone()
103 }
104}
105
106#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
107pub struct SessionDeleteReport {
108 pub session_id: String,
109 pub process: Option<lash_core::ProcessSessionDeleteReport>,
110}
111
112impl LashCore {
113 pub fn builder() -> LashCoreBuilder {
114 LashCoreBuilder::default()
115 }
116
117 pub fn standard() -> LashCoreBuilder {
123 Self::builder()
124 .install_mode(ModePreset::standard())
125 .default_mode(ModeId::standard())
126 .plugins(default_runtime_stack())
127 }
128
129 pub fn rlm() -> LashCoreBuilder {
135 Self::builder()
136 .install_mode(ModePreset::rlm())
137 .default_mode(ModeId::rlm())
138 .plugins(default_runtime_stack())
139 }
140
141 pub fn session(&self, session_id: impl Into<String>) -> SessionBuilder {
142 SessionBuilder {
143 core: self.clone(),
144 session_id: session_id.into(),
145 spec: SessionSpec::inherit(),
146 mode: None,
147 parent_session_id: None,
148 store: None,
149 provider: None,
150 active_plugins: Vec::new(),
151 plugin_factories: Vec::new(),
152 rlm_final_answer_format: None,
153 }
154 }
155
156 pub fn host_events(&self) -> crate::control::HostEventsControl {
157 crate::control::HostEventsControl { core: self.clone() }
158 }
159
160 pub async fn delete_session(&self, session_id: impl AsRef<str>) -> Result<SessionDeleteReport> {
161 let session_id = session_id.as_ref().to_string();
162 let effect_host = Arc::clone(&self.env.core.control.effect_host);
163 let scoped_effect_controller = effect_host
164 .scoped(lash_core::EffectScope::session_delete(&session_id))
165 .map_err(EmbedError::from)?;
166 self.delete_session_with_effect_scope(session_id, scoped_effect_controller)
167 .await
168 }
169
170 pub async fn delete_session_with_effect_scope(
171 &self,
172 session_id: impl AsRef<str>,
173 scoped_effect_controller: ScopedEffectController<'_>,
174 ) -> Result<SessionDeleteReport> {
175 let session_id = session_id.as_ref().to_string();
176 let Some(store_factory) = self.store_factory.as_ref() else {
177 return Err(EmbedError::MissingSessionStoreFactory);
178 };
179 let process = if let Some(process_registry) = self.env.process_registry.as_ref() {
180 let invocation = RuntimeInvocation::effect(
181 RuntimeScope::new(session_id.clone()),
182 format!("process:delete-session:{session_id}"),
183 RuntimeEffectKind::Process,
184 format!("{session_id}:delete-session"),
185 );
186 let outcome = scoped_effect_controller
187 .controller()
188 .execute_effect(
189 RuntimeEffectEnvelope::new(
190 invocation,
191 RuntimeEffectCommand::Process {
192 command: ProcessCommand::DeleteSession {
193 session_id: session_id.clone(),
194 },
195 },
196 ),
197 RuntimeEffectLocalExecutor::process_control(Arc::clone(process_registry)),
198 )
199 .await
200 .map_err(|err| EmbedError::SessionDeleteProcess {
201 session_id: session_id.clone(),
202 message: err.to_string(),
203 })?;
204 match outcome {
205 RuntimeEffectOutcome::Process {
206 result: ProcessEffectOutcome::DeleteSession { report },
207 } => Some(report),
208 other => {
209 return Err(EmbedError::SessionDeleteProcess {
210 session_id,
211 message: format!(
212 "process delete returned the wrong outcome: {}",
213 other.kind().as_str()
214 ),
215 });
216 }
217 }
218 } else {
219 None
220 };
221 store_factory
222 .delete_session(&session_id)
223 .await
224 .map_err(|message| EmbedError::StoreFactory {
225 session_id: session_id.clone(),
226 message,
227 })?;
228 Ok(SessionDeleteReport {
229 session_id,
230 process,
231 })
232 }
233
234 pub fn installed_modes(&self) -> impl Iterator<Item = &ModeId> {
235 self.modes.keys()
236 }
237
238 pub fn process_observer(&self) -> Option<&ProcessWorkObserver> {
239 self.process_observer.as_ref()
240 }
241
242 pub fn process_registry(&self) -> Option<Arc<dyn ProcessRegistry>> {
243 self.env.process_registry.as_ref().cloned()
244 }
245
246 pub fn durable_process_worker_config(&self) -> Result<DurableProcessWorkerConfig> {
247 self.durable_process_worker_config_with_plugins(std::iter::empty::<Arc<dyn PluginFactory>>())
248 }
249
250 pub fn durable_process_worker_config_with_plugins(
251 &self,
252 extra_plugin_factories: impl IntoIterator<Item = Arc<dyn PluginFactory>>,
253 ) -> Result<DurableProcessWorkerConfig> {
254 let Some(process_registry) = self.process_registry() else {
255 return Err(EmbedError::MissingProcessRegistry);
256 };
257 let Some(store_factory) = self.store_factory.as_ref() else {
258 return Err(EmbedError::MissingProcessWorkerStoreFactory);
259 };
260 let plugin_host = build_plugin_host_for_mode(
261 &self.modes,
262 &self.default_mode,
263 self.plugin_factories.as_ref(),
264 extra_plugin_factories.into_iter().collect(),
265 true,
266 )?;
267 Ok(DurableProcessWorkerConfig::new(
268 Arc::new(plugin_host),
269 self.env.core.clone(),
270 Arc::clone(store_factory),
271 process_registry,
272 )
273 .with_session_policy(self.policy.clone())
274 .with_residency(self.env.residency))
275 }
276}
277
278fn default_runtime_stack() -> PluginStack {
279 lash_plugin_tool_output_budget::tool_output_budget_stack()
280}
281
282#[derive(Default)]
283pub struct LashCoreBuilder {
284 pub(crate) modes: BTreeMap<ModeId, ModePreset>,
285 pub(crate) default_mode: Option<ModeId>,
286 session_spec: SessionSpec,
287 provider: Option<ProviderHandle>,
288 pub(crate) store_factory: Option<Arc<dyn SessionStoreFactory>>,
289 child_store_factory: Option<Arc<dyn SessionStoreFactory>>,
290 effect_host: Option<Arc<dyn EffectHost>>,
294 attachment_store: Option<Arc<dyn AttachmentStore>>,
295 lashlang_artifact_store: Option<Arc<dyn lash_core::LashlangArtifactStore>>,
296 host_event_store: Option<Arc<dyn lash_core::HostEventStore>>,
297 prompt: Option<PromptLayer>,
299 trace_sink: Option<Arc<dyn lash_trace::TraceSink>>,
300 lashlang_execution_sink: Option<Arc<dyn lash_trace::TraceSink>>,
301 trace_level: Option<lash_trace::TraceLevel>,
302 trace_context: Option<lash_trace::TraceContext>,
303 termination: Option<TerminationPolicy>,
304 runtime_host_config: Option<RuntimeHostConfig>,
306 tool_providers: Vec<Arc<dyn ToolProvider>>,
307 plugin_stack: PluginStack,
308 plugin_host: Option<PluginHost>,
309 residency: Option<Residency>,
310 process_work_source: ProcessWorkSource,
313 queued_work_poke: Option<QueuedWorkPoke>,
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 fn resolve_runtime_host_config(&mut self) -> Result<RuntimeHostConfig> {
476 if let Some(base) = self.runtime_host_config.take() {
477 return Ok(self.apply_core_overrides(base));
478 }
479 let effect_host = self
480 .effect_host
481 .take()
482 .ok_or(EmbedError::MissingEffectHost)?;
483 let lashlang_artifact_store = self
484 .lashlang_artifact_store
485 .take()
486 .ok_or(EmbedError::MissingLashlangArtifactStore)?;
487 let attachment_store = self
488 .attachment_store
489 .take()
490 .ok_or(EmbedError::MissingAttachmentStore)?;
491 let core = RuntimeHostConfig::new(effect_host, lashlang_artifact_store, attachment_store);
492 Ok(self.apply_core_overrides(core))
493 }
494
495 fn apply_core_overrides(&mut self, mut core: RuntimeHostConfig) -> RuntimeHostConfig {
497 if let Some(effect_host) = self.effect_host.take() {
498 core.control.effect_host = effect_host;
499 }
500 if let Some(attachment_store) = self.attachment_store.take() {
501 core.durability.attachment_store = attachment_store;
502 }
503 if let Some(artifact_store) = self.lashlang_artifact_store.take() {
504 core.durability.lashlang_artifact_store = artifact_store;
505 }
506 if let Some(prompt) = self.prompt.take() {
507 core.prompt.prompt = prompt;
508 }
509 if let Some(trace_sink) = self.trace_sink.take() {
510 core.tracing.trace_sink = Some(trace_sink);
511 }
512 if let Some(lashlang_execution_sink) = self.lashlang_execution_sink.take() {
513 core.tracing.lashlang_execution_sink = Some(lashlang_execution_sink);
514 }
515 if let Some(trace_level) = self.trace_level.take() {
516 core.tracing.trace_level = trace_level;
517 }
518 if let Some(trace_context) = self.trace_context.take() {
519 core.tracing.trace_context = trace_context;
520 }
521 if let Some(termination) = self.termination.take() {
522 core.control.termination = termination;
523 }
524 core
525 }
526
527 fn ensure_store_peer_coherence(&self) -> Result<()> {
540 let session_store_tier = self
546 .child_store_factory
547 .as_ref()
548 .or(self.store_factory.as_ref())
549 .map(|factory| factory.durability_tier());
550 let attachment_tier = self
551 .attachment_store
552 .as_ref()
553 .map(|store| store.persistence().durability_tier());
554 let artifact_tier = self
555 .lashlang_artifact_store
556 .as_ref()
557 .map(|store| store.durability_tier());
558 let host_event_store_tier = self
559 .host_event_store
560 .as_ref()
561 .map(|store| store.durability_tier());
562
563 if session_store_tier == Some(DurabilityTier::Durable) {
564 if attachment_tier == Some(DurabilityTier::Inline) {
565 return Err(EmbedError::DurableStorePeerRequired {
566 facet: "attachment store",
567 });
568 }
569 if artifact_tier == Some(DurabilityTier::Inline) {
570 return Err(EmbedError::DurableStorePeerRequired {
571 facet: "artifact store",
572 });
573 }
574 }
575
576 if let Some(process_registry) = self.process_work_source.process_registry().as_ref() {
577 if process_registry.durability_tier() == DurabilityTier::Durable {
578 if session_store_tier != Some(DurabilityTier::Durable) {
579 return Err(EmbedError::DurableProcessRegistryRequiresStoreFactory);
580 }
581 if host_event_store_tier != Some(DurabilityTier::Durable) {
582 return Err(EmbedError::DurableStorePeerRequired {
583 facet: "host event store",
584 });
585 }
586 }
587 }
588
589 if host_event_store_tier == Some(DurabilityTier::Durable) {
590 if session_store_tier != Some(DurabilityTier::Durable) {
591 return Err(EmbedError::DurableStorePeerRequired {
592 facet: "session store factory",
593 });
594 }
595 if let Some(process_registry) = self.process_work_source.process_registry().as_ref()
596 && process_registry.durability_tier() == DurabilityTier::Inline
597 {
598 return Err(EmbedError::DurableStorePeerRequired {
599 facet: "process registry",
600 });
601 }
602 }
603
604 Ok(())
605 }
606
607 pub fn build(mut self) -> Result<LashCore> {
608 if self.modes.is_empty() {
609 return Err(EmbedError::NoModesInstalled);
610 }
611 self.ensure_store_peer_coherence()?;
612 let default_mode = self
613 .default_mode
614 .clone()
615 .ok_or(EmbedError::NoModesInstalled)?;
616 if !self.modes.contains_key(&default_mode) {
617 return Err(EmbedError::DefaultModeNotInstalled { mode: default_mode });
618 }
619 let provider_id = self
620 .session_spec
621 .provider_id
622 .clone()
623 .or_else(|| {
624 self.provider
625 .as_ref()
626 .map(|provider| provider.kind().to_string())
627 })
628 .unwrap_or_default();
629 let model = self
630 .session_spec
631 .model
632 .clone()
633 .ok_or(EmbedError::MissingModelSpec)?;
634
635 let base_policy = SessionPolicy {
636 provider_id,
637 model,
638 max_turns: self.session_spec.max_turns.flatten(),
639 ..SessionPolicy::default()
640 };
641 let policy = self.session_spec.resolve_against(&base_policy);
642
643 let mut core = self.resolve_runtime_host_config()?;
644 if let Some(provider) = self.provider.clone() {
645 core.providers.provider_resolver =
646 Arc::new(lash_core::SingleProviderResolver::new(provider));
647 }
648
649 let plugin_factories = if let Some(plugin_host) = self.plugin_host {
650 plugin_host.factories().to_vec()
651 } else {
652 let mut factories = Vec::new();
653 if !self.tool_providers.is_empty() {
654 let spec = self
655 .tool_providers
656 .into_iter()
657 .fold(PluginSpec::new(), PluginSpec::with_tool_provider);
658 factories.push(Arc::new(StaticPluginFactory::new("embed_tools", spec))
659 as Arc<dyn PluginFactory>);
660 }
661 factories.extend(self.plugin_stack.into_factories());
662 factories
663 };
664 let default_plugin_host = build_plugin_host_for_mode(
665 &self.modes,
666 &default_mode,
667 &plugin_factories,
668 Vec::new(),
669 self.process_work_source.has_registry(),
670 )?;
671
672 let process_registry = self.process_work_source.process_registry();
673
674 let process_work_runner = Self::resolve_process_work_runner(
682 &self.process_work_source,
683 &default_plugin_host,
684 &core,
685 self.child_store_factory
688 .as_ref()
689 .or(self.store_factory.as_ref()),
690 &policy,
691 self.residency.unwrap_or_default(),
692 self.host_event_store.as_ref(),
693 )?;
694
695 let mut env_builder = RuntimeEnvironment::builder()
696 .with_plugin_host(Arc::new(default_plugin_host))
697 .with_runtime_host_config(core);
698 if let Some(process_registry) = process_registry.as_ref() {
699 env_builder = env_builder.with_process_registry(Arc::clone(process_registry));
700 }
701 if let Some(residency) = self.residency {
702 env_builder = env_builder.with_residency(residency);
703 }
704 if let Some(child_store_factory) = self
705 .child_store_factory
706 .as_ref()
707 .or(self.store_factory.as_ref())
708 {
709 env_builder = env_builder.with_session_store_factory(Arc::clone(child_store_factory));
710 }
711 if let Some(host_event_store) = self.host_event_store.as_ref() {
712 env_builder = env_builder.with_host_event_store(Arc::clone(host_event_store));
713 }
714 if let Some(queued_work_poke) = self.queued_work_poke.clone() {
715 env_builder = env_builder.with_queued_work_poke(queued_work_poke);
716 }
717
718 Ok(LashCore {
719 env: env_builder.build(),
720 policy,
721 modes: Arc::new(self.modes),
722 default_mode,
723 store_factory: self.store_factory,
724 plugin_factories: Arc::new(plugin_factories),
725 provider: self.provider,
726 process_observer: process_registry.map(ProcessWorkObserver::new),
727 process_work_runner: Arc::new(ProcessWorkRunnerSlot::new(process_work_runner)),
728 })
729 }
730
731 fn resolve_process_work_runner(
739 process_work_source: &ProcessWorkSource,
740 worker_plugin_host: &PluginHost,
741 core: &RuntimeHostConfig,
742 store_factory: Option<&Arc<dyn SessionStoreFactory>>,
743 policy: &SessionPolicy,
744 residency: lash_core::Residency,
745 host_event_store: Option<&Arc<dyn lash_core::HostEventStore>>,
746 ) -> Result<ProcessWorkRunnerSetup> {
747 let process_registry = match process_work_source {
748 ProcessWorkSource::None => return Ok(ProcessWorkRunnerSetup::None),
749 ProcessWorkSource::External(driver) => {
750 return Ok(ProcessWorkRunnerSetup::External {
751 driver: driver.clone(),
752 });
753 }
754 ProcessWorkSource::Inline { registry } => Arc::clone(registry),
755 };
756 let Some(store_factory) = store_factory else {
760 return Err(EmbedError::ProcessRegistryRequiresStoreFactory);
761 };
762 let config = Box::new(
770 DurableProcessWorkerConfig::new(
771 Arc::new(worker_plugin_host.clone()),
772 core.clone(),
773 Arc::clone(store_factory),
774 process_registry,
775 )
776 .with_session_policy(policy.clone())
777 .with_host_event_store(
778 host_event_store
779 .cloned()
780 .unwrap_or_else(|| Arc::new(lash_core::InMemoryHostEventStore::default())),
781 )
782 .with_residency(residency),
783 );
784 Ok(ProcessWorkRunnerSetup::LazyDefault { config })
785 }
786
787 pub fn advanced(self) -> AdvancedLashCoreBuilder {
788 AdvancedLashCoreBuilder { builder: self }
789 }
790
791 pub fn process_registry(mut self, process_registry: Arc<dyn ProcessRegistry>) -> Self {
792 self.process_work_source = ProcessWorkSource::Inline {
793 registry: process_registry,
794 };
795 self
796 }
797
798 pub fn host_event_store(mut self, store: Arc<dyn lash_core::HostEventStore>) -> Self {
799 self.host_event_store = Some(store);
800 self
801 }
802
803 pub fn process_work_driver(mut self, driver: ProcessWorkDriver) -> Self {
810 self.process_work_source = ProcessWorkSource::External(driver);
811 self
812 }
813
814 pub fn with_queued_work_runner(mut self, poke: QueuedWorkPoke) -> Self {
815 self.queued_work_poke = Some(poke);
816 self
817 }
818}
819
820pub(crate) fn build_plugin_host_for_mode(
821 modes: &BTreeMap<ModeId, ModePreset>,
822 mode: &ModeId,
823 common_factories: &[Arc<dyn PluginFactory>],
824 extra_factories: Vec<Arc<dyn PluginFactory>>,
825 process_lifecycle: bool,
826) -> Result<PluginHost> {
827 let preset = modes
828 .get(mode)
829 .ok_or_else(|| EmbedError::ModeNotInstalled { mode: mode.clone() })?;
830 let mut factories = Vec::with_capacity(1 + common_factories.len() + extra_factories.len());
831 factories.push(Arc::clone(&preset.factory));
832 factories.extend(common_factories.iter().cloned());
833 factories.extend(extra_factories);
834 let mut plugin_host = PluginHost::new(factories);
835 if process_lifecycle {
836 let abilities = plugin_host
837 .lashlang_abilities()
838 .with_processes()
839 .with_sleep()
840 .with_process_signals();
841 plugin_host = plugin_host.with_lashlang_abilities(abilities);
842 }
843 Ok(plugin_host)
844}
845
846impl PromptLayerSink for LashCoreBuilder {
847 fn prompt_layer_mut(&mut self) -> &mut PromptLayer {
848 self.prompt.get_or_insert_with(PromptLayer::new)
849 }
850}
851
852pub struct AdvancedLashCoreBuilder {
853 builder: LashCoreBuilder,
854}
855
856impl AdvancedLashCoreBuilder {
857 pub fn runtime_host_config(mut self, core: lash_core::RuntimeHostConfig) -> Self {
858 self.builder.runtime_host_config = Some(core);
859 self
860 }
861
862 pub fn plugin_host(mut self, plugin_host: PluginHost) -> Self {
863 self.builder.plugin_host = Some(plugin_host);
864 self
865 }
866
867 pub fn build(self) -> Result<LashCore> {
868 self.builder.build()
869 }
870}