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