1pub use crate::session::SessionConfigPatch;
2use crate::support::*;
3pub use lash_core::{AcceptedInjectedTurnInput, PluginAction};
4
5#[derive(Clone)]
6pub struct HostEventsControl {
7 pub(crate) core: LashCore,
8}
9
10impl HostEventsControl {
11 pub async fn emit(
12 &self,
13 request: lash_core::HostEventOccurrenceRequest,
14 scoped_effect_controller: ScopedEffectController<'_>,
15 ) -> Result<lash_core::HostEventEmitReport> {
16 let store = self.core.env.host_event_store.as_ref().ok_or_else(|| {
17 EmbedError::Plugin(lash_core::PluginError::Session(
18 "host event store is unavailable in this runtime".to_string(),
19 ))
20 })?;
21 let process_work_poke = self.core.process_work_runner.poke().await;
22 let router = lash_core::HostEventRouter::new(
23 Arc::clone(store),
24 self.core.env.process_registry.clone(),
25 process_work_poke,
26 self.core.env.core.profile.host_profile_id.clone(),
27 );
28 router
29 .emit(request, scoped_effect_controller.controller())
30 .await
31 .map_err(Into::into)
32 }
33
34 pub async fn subscriptions(
35 &self,
36 filter: lash_core::TriggerSubscriptionFilter,
37 ) -> Result<Vec<lash_core::TriggerRegistration>> {
38 let store = self.core.env.host_event_store.as_ref().ok_or_else(|| {
39 EmbedError::Plugin(lash_core::PluginError::Session(
40 "host event store is unavailable in this runtime".to_string(),
41 ))
42 })?;
43 let records = store.list_subscriptions(filter).await?;
44 Ok(records
45 .iter()
46 .map(lash_core::TriggerRegistration::from)
47 .collect())
48 }
49}
50
51#[derive(Clone)]
52pub struct SessionControl {
53 pub(crate) runtime: RuntimeHandle,
54}
55
56impl SessionControl {
57 pub fn config(&self) -> ConfigControl {
58 ConfigControl {
59 control: self.clone(),
60 }
61 }
62
63 pub fn tools(&self) -> ToolsControl {
64 ToolsControl {
65 control: self.clone(),
66 }
67 }
68
69 pub fn commands(&self) -> SessionCommandsControl {
70 SessionCommandsControl {
71 control: self.clone(),
72 }
73 }
74
75 pub fn triggers(&self) -> TriggersControl {
76 TriggersControl {
77 control: self.clone(),
78 }
79 }
80
81 pub fn state(&self) -> StateControl {
82 StateControl {
83 control: self.clone(),
84 }
85 }
86
87 pub fn children(&self) -> ChildrenControl {
88 ChildrenControl {
89 control: self.clone(),
90 }
91 }
92
93 pub fn injection(&self) -> InjectionControl {
94 InjectionControl {
95 control: self.clone(),
96 }
97 }
98
99 pub fn mode(&self) -> ModeControl {
100 ModeControl {
101 control: self.clone(),
102 }
103 }
104
105 pub fn processes(&self) -> ProcessControl {
106 ProcessControl {
107 control: self.clone(),
108 }
109 }
110
111 async fn with_writer<F, T>(&self, f: F) -> T
116 where
117 F: AsyncFnOnce(&mut LashRuntime) -> T,
118 {
119 let writer = self.runtime.writer();
120 let mut runtime = writer.lock().await;
121 let value = f(&mut runtime).await;
122 self.runtime.publish_from(&runtime);
123 value
124 }
125
126 async fn update_config(&self, patch: SessionConfigPatch) -> Result<()> {
127 self.update_session_config(patch.provider, patch.model, patch.prompt)
128 .await?;
129 Ok(())
130 }
131
132 async fn update_session_config(
133 &self,
134 provider: Option<ProviderHandle>,
135 model: Option<lash_core::ModelSpec>,
136 prompt: Option<PromptLayer>,
137 ) -> Result<()> {
138 self.with_writer(async |runtime: &mut LashRuntime| {
139 runtime.update_session_config(provider, model, prompt).await;
140 })
141 .await;
142 Ok(())
143 }
144
145 async fn export_state(&self) -> lash_core::SessionSnapshot {
146 self.runtime.observe().read_view.to_snapshot()
147 }
148
149 async fn append_messages(&self, messages: Vec<PluginMessage>) -> Result<()> {
150 self.with_writer(async |runtime: &mut LashRuntime| {
151 runtime
152 .append_session_nodes(lash_core::AppendSessionNodesRequest {
153 nodes: messages
154 .into_iter()
155 .map(lash_core::SessionAppendNode::message)
156 .collect(),
157 requires_ancestor_node_id: None,
158 })
159 .await
160 .map(|_| ())
161 .map_err(Into::into)
162 })
163 .await
164 }
165
166 async fn append_plugin_body(
167 &self,
168 plugin_type: impl Into<String>,
169 body: serde_json::Value,
170 ) -> Result<()> {
171 self.with_writer(async |runtime: &mut LashRuntime| {
172 runtime
173 .append_session_nodes(lash_core::AppendSessionNodesRequest {
174 nodes: vec![lash_core::SessionAppendNode::plugin(plugin_type, body)],
175 requires_ancestor_node_id: None,
176 })
177 .await
178 .map(|_| ())
179 .map_err(Into::into)
180 })
181 .await
182 }
183
184 async fn set_persisted_state(&self, state: RuntimeSessionState) -> Result<()> {
185 self.with_writer(async |runtime: &mut LashRuntime| {
186 runtime.set_persisted_state(state).map_err(Into::into)
187 })
188 .await
189 }
190
191 async fn set_prompt_template(&self, template: PromptTemplate) -> Result<()> {
192 self.with_writer(async |runtime: &mut LashRuntime| {
193 runtime.set_prompt_template(template).await;
194 })
195 .await;
196 Ok(())
197 }
198
199 async fn clear_prompt_template(&self) -> Result<()> {
200 self.with_writer(async |runtime: &mut LashRuntime| {
201 runtime.clear_prompt_template().await;
202 })
203 .await;
204 Ok(())
205 }
206
207 async fn add_prompt_contribution(&self, contribution: PromptContribution) -> Result<()> {
208 self.with_writer(async |runtime: &mut LashRuntime| {
209 runtime.add_prompt_contribution(contribution).await;
210 })
211 .await;
212 Ok(())
213 }
214
215 async fn replace_prompt_slot(
216 &self,
217 slot: PromptSlot,
218 contributions: impl IntoIterator<Item = PromptContribution>,
219 ) -> Result<()> {
220 self.with_writer(async |runtime: &mut LashRuntime| {
221 runtime.replace_prompt_slot(slot, contributions).await;
222 })
223 .await;
224 Ok(())
225 }
226
227 async fn clear_prompt_slot(&self, slot: PromptSlot) -> Result<()> {
228 self.with_writer(async |runtime: &mut LashRuntime| {
229 runtime.clear_prompt_slot(slot).await;
230 })
231 .await;
232 Ok(())
233 }
234
235 async fn apply_protocol_session_extension(
236 &self,
237 extension: lash_core::ProtocolSessionExtensionHandle,
238 ) -> Result<()> {
239 self.with_writer(async |runtime: &mut LashRuntime| {
240 runtime
241 .apply_protocol_session_extension(extension)
242 .await
243 .map_err(Into::into)
244 })
245 .await
246 }
247
248 async fn branch_to_node(
249 &self,
250 target_leaf: Option<String>,
251 ) -> Result<lash_core::SessionSnapshot> {
252 self.with_writer(async |runtime: &mut LashRuntime| {
253 runtime
254 .branch_to_node(target_leaf)
255 .await
256 .map_err(Into::into)
257 })
258 .await
259 }
260
261 async fn await_background_work(&self) -> Result<()> {
262 self.with_writer(async |runtime: &mut LashRuntime| {
263 runtime.await_background_work().await.map_err(Into::into)
264 })
265 .await
266 }
267
268 async fn refresh_tool_surface(&self) -> Result<()> {
269 self.with_writer(async |runtime: &mut LashRuntime| {
270 runtime
271 .refresh_session_tool_surface()
272 .await
273 .map_err(Into::into)
274 })
275 .await
276 }
277
278 async fn submit_session_command(
279 &self,
280 command: lash_core::SessionCommand,
281 idempotency_key: impl Into<String>,
282 ) -> Result<lash_core::SessionCommandReceipt> {
283 let idempotency_key = idempotency_key.into();
284 self.with_writer(async |runtime: &mut LashRuntime| {
285 runtime
286 .submit_session_command(command, idempotency_key)
287 .await
288 .map_err(Into::into)
289 })
290 .await
291 }
292
293 async fn list_lashlang_trigger_registrations(
294 &self,
295 ) -> Result<Vec<lash_core::TriggerRegistration>> {
296 self.with_writer(async |runtime: &mut LashRuntime| {
297 runtime
298 .list_lashlang_trigger_registrations()
299 .await
300 .map_err(Into::into)
301 })
302 .await
303 }
304
305 async fn lashlang_trigger_registrations_by_source_type(
306 &self,
307 source_type: impl Into<lash_core::TriggerSourceType>,
308 ) -> Result<Vec<lash_core::TriggerRegistration>> {
309 self.with_writer(async |runtime: &mut LashRuntime| {
310 runtime
311 .lashlang_trigger_registrations_by_source_type(source_type)
312 .await
313 .map_err(Into::into)
314 })
315 .await
316 }
317
318 async fn invoke_plugin_action(
319 &self,
320 name: &str,
321 args: serde_json::Value,
322 ) -> Result<ToolResult> {
323 let session_id = self.runtime.observe().session_id().to_string();
324 let writer = self.runtime.writer();
325 writer
326 .lock()
327 .await
328 .invoke_plugin_action(name, args, Some(session_id))
329 .await
330 .map_err(Into::into)
331 }
332
333 async fn call_plugin_action<Op: lash_core::PluginAction>(
334 &self,
335 args: Op::Args,
336 ) -> Result<Op::Output> {
337 let result = self
338 .invoke_plugin_action(
339 Op::NAME,
340 serde_json::to_value(args).map_err(|err| {
341 EmbedError::Plugin(lash_core::PluginError::Invoke(format!(
342 "invalid {} args: {err}",
343 Op::NAME
344 )))
345 })?,
346 )
347 .await?;
348 if !result.is_success() {
349 return Err(EmbedError::Plugin(lash_core::PluginError::Invoke(format!(
350 "{} failed: {}",
351 Op::NAME,
352 result.value_for_projection()
353 ))));
354 }
355 serde_json::from_value(result.into_output().value_for_projection()).map_err(|err| {
356 EmbedError::Plugin(lash_core::PluginError::Invoke(format!(
357 "invalid {} output: {err}",
358 Op::NAME
359 )))
360 })
361 }
362
363 async fn compact_context(
364 &self,
365 instructions: Option<String>,
366 scoped_effect_controller: ScopedEffectController<'_>,
367 ) -> Result<bool> {
368 self.with_writer(async |runtime: &mut LashRuntime| {
369 runtime
370 .compact_context(instructions, scoped_effect_controller)
371 .await
372 .map_err(Into::into)
373 })
374 .await
375 }
376
377 async fn persist_current_state(&self) -> Result<RuntimeSessionState> {
378 self.with_writer(async |runtime: &mut LashRuntime| {
379 runtime.await_background_work().await?;
380 Ok(runtime.export_persisted_state())
381 })
382 .await
383 }
384
385 async fn list_process_handles(&self) -> Result<Vec<lash_core::ProcessHandleSummary>> {
386 Ok(self.runtime.observe().list_process_handles().await)
387 }
388
389 async fn list_all_process_handles(&self) -> Result<Vec<lash_core::ProcessHandleSummary>> {
390 Ok(self.runtime.observe().list_all_process_handles().await)
391 }
392
393 async fn start_process(
394 &self,
395 request: lash_core::ProcessStartRequest,
396 scoped_effect_controller: ScopedEffectController<'_>,
397 ) -> Result<lash_core::ProcessHandleSummary> {
398 let writer = self.runtime.writer();
399 let runtime = writer.lock().await;
400 let session_id = runtime.session_id().to_string();
401 let processes = runtime.process_service()?;
402 let scope = lash_core::ProcessOpScope::new(scoped_effect_controller);
403 let summary = processes
404 .start_from_request(&session_id, request, scope)
405 .await
406 .map_err(EmbedError::Plugin)?;
407 self.runtime.record_process_changed(
408 SessionProcessEventKind::Started,
409 vec![summary.process_id.clone()],
410 );
411 Ok(summary)
412 }
413
414 async fn session_state_service(&self) -> Result<Arc<dyn SessionStateService>> {
415 self.runtime
416 .writer()
417 .lock()
418 .await
419 .session_state_service()
420 .map_err(Into::into)
421 }
422
423 async fn cancel_process(
424 &self,
425 process_id: &str,
426 scoped_effect_controller: ScopedEffectController<'_>,
427 ) -> Result<lash_core::ProcessCancelSummary> {
428 let writer = self.runtime.writer();
429 let runtime = writer.lock().await;
430 let session_id = runtime.session_id().to_string();
431 let processes = runtime.process_service()?;
432 let cancel_ability = runtime.process_cancel_ability();
433 let scope = lash_core::ProcessOpScope::new(scoped_effect_controller);
434 let summary = cancel_ability
435 .cancel_summary(
436 processes.as_ref(),
437 lash_core::ProcessCancelRequest::new(
438 &session_id,
439 process_id,
440 scope,
441 lash_core::ProcessCancelSource::HostApi,
442 )
443 .with_reason("requested by host API"),
444 )
445 .await
446 .map_err(EmbedError::Plugin)?;
447 self.runtime.record_process_changed(
448 SessionProcessEventKind::Cancelled,
449 vec![summary.process_id.clone()],
450 );
451 Ok(summary)
452 }
453
454 async fn cancel_visible_processes(
455 &self,
456 scoped_effect_controller: ScopedEffectController<'_>,
457 ) -> Result<Vec<lash_core::ProcessCancelSummary>> {
458 let writer = self.runtime.writer();
459 let runtime = writer.lock().await;
460 let session_id = runtime.session_id().to_string();
461 let processes = runtime.process_service()?;
462 let cancel_ability = runtime.process_cancel_ability();
463 let scope = lash_core::ProcessOpScope::new(scoped_effect_controller);
464 let summaries = cancel_ability
465 .cancel_all_visible(
466 processes.as_ref(),
467 lash_core::ProcessCancelAllRequest::new(
468 &session_id,
469 scope,
470 lash_core::ProcessCancelSource::HostApi,
471 )
472 .with_reason("requested by host API"),
473 )
474 .await
475 .map_err(EmbedError::Plugin)?;
476 self.runtime.record_process_changed(
477 SessionProcessEventKind::Cancelled,
478 summaries
479 .iter()
480 .map(|summary| summary.process_id.clone())
481 .collect(),
482 );
483 Ok(summaries)
484 }
485
486 async fn snapshot_execution_state(&self) -> Result<Option<Vec<u8>>> {
487 self.with_writer(async |runtime: &mut LashRuntime| {
488 runtime.snapshot_execution_state().await.map_err(Into::into)
489 })
490 .await
491 }
492
493 async fn restore_execution_state(&self, bytes: &[u8]) -> Result<()> {
494 self.with_writer(async |runtime: &mut LashRuntime| {
495 runtime
496 .restore_execution_state(bytes)
497 .await
498 .map_err(Into::into)
499 })
500 .await
501 }
502
503 async fn tool_state(&self) -> Result<ToolState> {
504 self.runtime.observe().tool_state.clone().ok_or_else(|| {
505 EmbedError::Session(SessionError::Protocol(
506 "runtime session not available".to_string(),
507 ))
508 })
509 }
510
511 async fn apply_tool_state(&self, state: ToolState) -> Result<u64> {
512 self.with_writer(async |runtime: &mut LashRuntime| {
513 runtime
514 .apply_tool_state(state)
515 .await
516 .map_err(EmbedError::from)
517 })
518 .await
519 }
520
521 async fn restore_tool_state(&self, state: ToolState) -> Result<ToolRestoreReport> {
522 self.with_writer(async |runtime: &mut LashRuntime| {
523 runtime
524 .restore_tool_state(state)
525 .await
526 .map_err(EmbedError::from)
527 })
528 .await
529 }
530
531 async fn set_tool_availability(
532 &self,
533 name: &str,
534 availability: ToolAvailability,
535 ) -> Result<u64> {
536 self.set_tool_availability_many(&[(name, availability)])
537 .await
538 }
539
540 async fn set_tool_availability_many<N: AsRef<str>>(
541 &self,
542 updates: &[(N, ToolAvailability)],
543 ) -> Result<u64> {
544 let mut state = self.tool_state().await?;
545 for (name, availability) in updates {
546 state
547 .set_availability(name.as_ref(), Some(*availability))
548 .map_err(|err| EmbedError::Session(SessionError::Protocol(err.to_string())))?;
549 }
550 self.apply_tool_state(state).await
551 }
552
553 async fn clear_tool_availability_override(&self, name: &str) -> Result<u64> {
554 let mut state = self.tool_state().await?;
555 state
556 .set_availability(name, None)
557 .map_err(|err| EmbedError::Session(SessionError::Protocol(err.to_string())))?;
558 self.apply_tool_state(state).await
559 }
560
561 async fn active_tool_manifests(&self) -> Result<Vec<ToolManifest>> {
562 Ok(self.tool_state().await?.tool_manifests())
563 }
564
565 async fn add_tool_provider(&self, provider: Arc<dyn ToolProvider>) -> Result<ToolSourceHandle> {
566 let tool_registry = self.tool_registry().await?;
567 let handle = tool_registry
568 .add_tool_provider(provider)
569 .map_err(|err| EmbedError::Session(SessionError::Protocol(err.to_string())))?;
570 self.refresh_tool_surface().await?;
571 Ok(handle)
572 }
573
574 async fn remove_tool_source(&self, handle: &ToolSourceHandle) -> Result<u64> {
575 let tool_registry = self.tool_registry().await?;
576 let generation = tool_registry
577 .remove_source(handle)
578 .map_err(|err| EmbedError::Session(SessionError::Protocol(err.to_string())))?;
579 self.refresh_tool_surface().await?;
580 Ok(generation)
581 }
582
583 async fn create_child_session(&self, request: SessionCreateRequest) -> Result<SessionHandle> {
584 let writer = self.runtime.writer();
585 let runtime = writer.lock().await;
586 let lifecycle = runtime.session_lifecycle_service()?;
587 lifecycle.create_session(request).await.map_err(Into::into)
588 }
589
590 async fn close_child_session(&self, session_id: &str) -> Result<()> {
591 let writer = self.runtime.writer();
592 let runtime = writer.lock().await;
593 let lifecycle = runtime.session_lifecycle_service()?;
594 lifecycle
595 .close_session(session_id)
596 .await
597 .map_err(Into::into)
598 }
599
600 async fn activate_managed_session(&self, session_id: &str) -> Result<()> {
601 self.with_writer(async |runtime: &mut LashRuntime| {
602 runtime
603 .activate_managed_session(session_id)
604 .await
605 .map_err(Into::into)
606 })
607 .await
608 }
609
610 async fn inject_turn_input(&self, id: Option<String>, message: PluginMessage) -> Result<()> {
611 self.inject_turn_inputs(vec![lash_core::InjectedTurnInput { id, message }])
612 .await
613 }
614
615 async fn inject_turn_inputs(&self, messages: Vec<lash_core::InjectedTurnInput>) -> Result<()> {
616 for input in messages {
617 let source_key = input.id.map(|id| format!("injection:{id}"));
618 let turn_input = turn_input_from_plugin_message(input.message);
619 self.runtime
620 .enqueue_turn_input(
621 turn_input,
622 lash_core::DeliveryPolicy::EarliestSafeBoundary,
623 lash_core::SlotPolicy::Join,
624 source_key,
625 )
626 .await
627 .map(|_| ())
628 .map_err(EmbedError::Runtime)?;
629 }
630 Ok(())
631 }
632
633 async fn tool_registry(&self) -> Result<Arc<lash_core::ToolRegistry>> {
634 self.runtime
635 .writer()
636 .lock()
637 .await
638 .plugin_session()
639 .map(|session| session.tool_registry())
640 .ok_or_else(|| {
641 EmbedError::Session(SessionError::Protocol(
642 "tool registry is unavailable in this runtime session".to_string(),
643 ))
644 })
645 }
646}
647
648fn turn_input_from_plugin_message(message: PluginMessage) -> TurnInput {
649 let mut input = TurnInput::empty();
650 if !message.content.is_empty() {
651 input.items.push(InputItem::Text {
652 text: message.content,
653 });
654 }
655 for (index, bytes) in message.images.into_iter().enumerate() {
656 let id = format!("injected-image-{index}");
657 input.items.push(InputItem::ImageRef { id: id.clone() });
658 input.image_blobs.insert(id, bytes);
659 }
660 input
661}
662
663#[derive(Clone)]
664pub struct ConfigControl {
665 control: SessionControl,
666}
667
668impl ConfigControl {
669 pub async fn update(&self, patch: SessionConfigPatch) -> Result<()> {
670 self.control.update_config(patch).await
671 }
672
673 pub async fn update_session_config(
674 &self,
675 provider: Option<ProviderHandle>,
676 model: Option<lash_core::ModelSpec>,
677 prompt: Option<PromptLayer>,
678 ) -> Result<()> {
679 self.control
680 .update_session_config(provider, model, prompt)
681 .await
682 }
683
684 pub async fn set_prompt_template(&self, template: PromptTemplate) -> Result<()> {
685 self.control.set_prompt_template(template).await
686 }
687
688 pub async fn clear_prompt_template(&self) -> Result<()> {
689 self.control.clear_prompt_template().await
690 }
691
692 pub async fn add_prompt_contribution(&self, contribution: PromptContribution) -> Result<()> {
693 self.control.add_prompt_contribution(contribution).await
694 }
695
696 pub async fn replace_prompt_slot(
697 &self,
698 slot: PromptSlot,
699 contributions: impl IntoIterator<Item = PromptContribution>,
700 ) -> Result<()> {
701 self.control.replace_prompt_slot(slot, contributions).await
702 }
703
704 pub async fn clear_prompt_slot(&self, slot: PromptSlot) -> Result<()> {
705 self.control.clear_prompt_slot(slot).await
706 }
707}
708
709#[derive(Clone)]
710pub struct ToolsControl {
711 control: SessionControl,
712}
713
714impl ToolsControl {
715 pub(crate) fn new(control: SessionControl) -> Self {
716 Self { control }
717 }
718}
719
720impl ToolsControl {
721 pub async fn state(&self) -> Result<ToolState> {
722 self.control.tool_state().await
723 }
724
725 pub fn advanced(&self) -> AdvancedToolsControl {
726 AdvancedToolsControl {
727 control: self.control.clone(),
728 }
729 }
730
731 pub async fn set_availability(
732 &self,
733 name: impl AsRef<str>,
734 availability: ToolAvailability,
735 ) -> Result<u64> {
736 self.control
737 .set_tool_availability(name.as_ref(), availability)
738 .await
739 }
740
741 pub async fn set_availability_many<N: AsRef<str>>(
742 &self,
743 updates: &[(N, ToolAvailability)],
744 ) -> Result<u64> {
745 self.control.set_tool_availability_many(updates).await
746 }
747
748 pub async fn clear_availability_override(&self, name: impl AsRef<str>) -> Result<u64> {
749 self.control
750 .clear_tool_availability_override(name.as_ref())
751 .await
752 }
753
754 pub async fn active_manifests(&self) -> Result<Vec<ToolManifest>> {
755 self.control.active_tool_manifests().await
756 }
757
758 pub async fn add_provider(&self, provider: Arc<dyn ToolProvider>) -> Result<ToolSourceHandle> {
759 self.control.add_tool_provider(provider).await
760 }
761
762 pub async fn remove_source(&self, handle: &ToolSourceHandle) -> Result<u64> {
763 self.control.remove_tool_source(handle).await
764 }
765}
766
767#[derive(Clone)]
768pub struct AdvancedToolsControl {
769 control: SessionControl,
770}
771
772impl AdvancedToolsControl {
773 pub async fn apply_state(&self, state: ToolState) -> Result<u64> {
779 self.control.apply_tool_state(state).await
780 }
781
782 pub async fn restore_state(&self, state: ToolState) -> Result<ToolRestoreReport> {
795 self.control.restore_tool_state(state).await
796 }
797}
798
799#[derive(Clone)]
800pub struct SessionCommandsControl {
801 control: SessionControl,
802}
803
804impl SessionCommandsControl {
805 pub async fn refresh_tool_surface(
810 &self,
811 reason: impl Into<String>,
812 idempotency_key: impl Into<String>,
813 ) -> Result<lash_core::SessionCommandReceipt> {
814 self.control
815 .submit_session_command(
816 lash_core::SessionCommand::RefreshToolSurface {
817 reason: reason.into(),
818 },
819 idempotency_key,
820 )
821 .await
822 }
823
824 pub async fn reset(
825 &self,
826 reason: impl Into<String>,
827 idempotency_key: impl Into<String>,
828 ) -> Result<lash_core::SessionCommandReceipt> {
829 self.control
830 .submit_session_command(
831 lash_core::SessionCommand::ResetSession {
832 reason: reason.into(),
833 },
834 idempotency_key,
835 )
836 .await
837 }
838}
839
840#[derive(Clone)]
842pub struct TriggersControl {
843 control: SessionControl,
844}
845
846impl TriggersControl {
847 pub async fn list_all(&self) -> Result<Vec<lash_core::TriggerRegistration>> {
853 self.control.list_lashlang_trigger_registrations().await
854 }
855
856 pub async fn by_source_type(
861 &self,
862 source_type: impl Into<lash_core::TriggerSourceType>,
863 ) -> Result<Vec<lash_core::TriggerRegistration>> {
864 self.control
865 .lashlang_trigger_registrations_by_source_type(source_type)
866 .await
867 }
868}
869
870#[derive(Clone)]
871pub struct ProcessControl {
872 control: SessionControl,
873}
874
875impl ProcessControl {
876 pub(crate) fn new(control: SessionControl) -> Self {
877 Self { control }
878 }
879
880 pub async fn start(
881 &self,
882 request: lash_core::ProcessStartRequest,
883 scoped_effect_controller: ScopedEffectController<'_>,
884 ) -> Result<lash_core::ProcessHandleSummary> {
885 self.control
886 .start_process(request, scoped_effect_controller)
887 .await
888 }
889
890 pub async fn list(&self) -> Result<Vec<lash_core::ProcessHandleSummary>> {
891 self.control.list_process_handles().await
892 }
893
894 pub async fn list_all(&self) -> Result<Vec<lash_core::ProcessHandleSummary>> {
895 self.control.list_all_process_handles().await
896 }
897
898 pub async fn await_all(&self) -> Result<()> {
899 self.control.await_background_work().await
900 }
901
902 pub async fn cancel(
903 &self,
904 process_id: &str,
905 scoped_effect_controller: ScopedEffectController<'_>,
906 ) -> Result<lash_core::ProcessCancelSummary> {
907 self.control
908 .cancel_process(process_id, scoped_effect_controller)
909 .await
910 }
911
912 pub async fn cancel_all(
913 &self,
914 scoped_effect_controller: ScopedEffectController<'_>,
915 ) -> Result<Vec<lash_core::ProcessCancelSummary>> {
916 self.control
917 .cancel_visible_processes(scoped_effect_controller)
918 .await
919 }
920}
921
922#[derive(Clone)]
923pub struct StateControl {
924 control: SessionControl,
925}
926
927impl StateControl {
928 pub async fn export(&self) -> lash_core::SessionSnapshot {
929 self.control.export_state().await
930 }
931
932 pub async fn append_messages(&self, messages: Vec<PluginMessage>) -> Result<()> {
933 self.control.append_messages(messages).await
934 }
935
936 pub async fn append_plugin_body(
937 &self,
938 plugin_type: impl Into<String>,
939 body: serde_json::Value,
940 ) -> Result<()> {
941 self.control.append_plugin_body(plugin_type, body).await
942 }
943
944 pub async fn set_persisted(&self, state: RuntimeSessionState) -> Result<()> {
945 self.control.set_persisted_state(state).await
946 }
947
948 pub async fn branch_to_node(
949 &self,
950 target_leaf: Option<String>,
951 ) -> Result<lash_core::SessionSnapshot> {
952 self.control.branch_to_node(target_leaf).await
953 }
954
955 pub async fn persist_current(&self) -> Result<RuntimeSessionState> {
956 self.control.persist_current_state().await
957 }
958
959 pub async fn session_state_service(&self) -> Result<Arc<dyn SessionStateService>> {
960 self.control.session_state_service().await
961 }
962
963 pub async fn snapshot_execution(&self) -> Result<Option<Vec<u8>>> {
964 self.control.snapshot_execution_state().await
965 }
966
967 pub async fn restore_execution(&self, bytes: &[u8]) -> Result<()> {
968 self.control.restore_execution_state(bytes).await
969 }
970
971 pub async fn compact_context(
972 &self,
973 instructions: Option<String>,
974 scoped_effect_controller: ScopedEffectController<'_>,
975 ) -> Result<bool> {
976 self.control
977 .compact_context(instructions, scoped_effect_controller)
978 .await
979 }
980}
981
982#[derive(Clone)]
983pub struct PluginActions {
984 pub(crate) control: SessionControl,
985}
986
987impl PluginActions {
988 pub async fn call<Op: lash_core::PluginAction>(&self, args: Op::Args) -> Result<Op::Output> {
989 self.control.call_plugin_action::<Op>(args).await
990 }
991}
992
993#[derive(Clone)]
994pub struct ChildrenControl {
995 control: SessionControl,
996}
997
998impl ChildrenControl {
999 pub async fn create_session(&self, request: SessionCreateRequest) -> Result<SessionHandle> {
1000 self.control.create_child_session(request).await
1001 }
1002
1003 pub async fn close_session(&self, session_id: &str) -> Result<()> {
1004 self.control.close_child_session(session_id).await
1005 }
1006
1007 pub async fn activate_managed_session(&self, session_id: &str) -> Result<()> {
1008 self.control.activate_managed_session(session_id).await
1009 }
1010}
1011
1012#[derive(Clone)]
1013pub struct InjectionControl {
1014 control: SessionControl,
1015}
1016
1017impl InjectionControl {
1018 pub async fn inject_turn_input(
1019 &self,
1020 id: Option<String>,
1021 message: PluginMessage,
1022 ) -> Result<()> {
1023 self.control.inject_turn_input(id, message).await
1024 }
1025
1026 pub async fn inject_turn_inputs(
1027 &self,
1028 messages: Vec<lash_core::InjectedTurnInput>,
1029 ) -> Result<()> {
1030 self.control.inject_turn_inputs(messages).await
1031 }
1032}
1033
1034#[derive(Clone)]
1035pub struct ModeControl {
1036 control: SessionControl,
1037}
1038
1039impl ModeControl {
1040 pub async fn apply_session_extension(
1041 &self,
1042 extension: lash_core::ProtocolSessionExtensionHandle,
1043 ) -> Result<()> {
1044 self.control
1045 .apply_protocol_session_extension(extension)
1046 .await
1047 }
1048}