Skip to main content

lash_core/tool_provider/
host_events.rs

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