Skip to main content

lash_core/tool_provider/
host_events.rs

1use std::sync::Arc;
2
3use crate::{HostEventEmitReport, PluginError};
4
5use super::ToolContext;
6
7#[derive(Clone)]
8pub struct ToolHostEventControl<'run> {
9    pub(super) context: ToolContext<'run>,
10}
11
12impl<'run> ToolHostEventControl<'run> {
13    pub async fn emit(
14        &self,
15        resource_type: impl AsRef<str>,
16        alias: impl AsRef<str>,
17        event: impl AsRef<str>,
18        payload: serde_json::Value,
19    ) -> Result<HostEventEmitReport, PluginError> {
20        let Some(dispatch) = self.context.runtime_dispatch.clone() else {
21            return Err(PluginError::Session(
22                "host event emission is unavailable outside runtime tool execution".to_string(),
23            ));
24        };
25        let resource_type = resource_type.as_ref();
26        let alias = alias.as_ref();
27        let event = event.as_ref();
28        crate::session::triggers::validate_host_event(
29            dispatch.plugins.as_ref(),
30            resource_type,
31            alias,
32            event,
33            &payload,
34        )?;
35        let source_type = crate::host_event_source_type(alias, event);
36        let event_scope = tool_event_scope_id(&self.context, &source_type, &payload)?;
37        let scoped_effect_controller = crate::ScopedEffectController::borrowed(
38            dispatch.effect_controller.controller(),
39            crate::EffectScope::host_event(&self.context.session_id, event_scope),
40        )
41        .map_err(|err| PluginError::Session(err.to_string()))?;
42        let activation = dispatch
43            .plugins
44            .trigger_activation_service(Arc::clone(&dispatch.processes), scoped_effect_controller);
45        let started_process_ids = activation
46            .activate_source_type(
47                &source_type,
48                payload.clone(),
49                self.context.parent_invocation.clone(),
50            )
51            .await?;
52        dispatch
53            .host_event_outcomes
54            .enqueue(crate::tool_dispatch::ToolHostEventEffectOutcome {
55                resource_type: resource_type.to_string(),
56                alias: alias.to_string(),
57                event: event.to_string(),
58                source_type,
59                payload: payload.clone(),
60                started_process_ids: started_process_ids.clone(),
61            })
62            .map_err(PluginError::Session)?;
63        Ok(HostEventEmitReport {
64            started_process_ids,
65        })
66    }
67}
68
69fn tool_event_scope_id(
70    context: &ToolContext<'_>,
71    source_type: &str,
72    payload: &serde_json::Value,
73) -> Result<String, PluginError> {
74    if let Some(tool_call_id) = context.tool_call_id() {
75        return Ok(format!("tool:{tool_call_id}:{source_type}"));
76    }
77    let digest = crate::stable_hash::stable_json_sha256_hex(&(source_type, payload))
78        .map_err(|err| PluginError::Session(format!("hash tool host event: {err}")))?;
79    Ok(format!("tool:{source_type}:sha256:{digest}"))
80}