Skip to main content

agent_block_core/bus/
event.rs

1//! Event type flowing through the [`EventBus`](crate::bus::EventBus).
2//!
3//! Each [`Event`] carries a `kind` string used for handler dispatch, an `id`
4//! for correlation/logging, a `payload` (source-defined), a `meta` map
5//! (source-defined), and an optional `ack_tx` one-shot channel used to return
6//! the Lua handler's return value back to the source that produced the
7//! request (e.g. a mesh request/response round-trip).
8//!
9//! The `ack_tx` is `Option` because some sources are fire-and-forget
10//! (e.g. a future webhook broadcast) and do not need a response.
11
12use serde_json::Value;
13use tokio::sync::oneshot;
14
15use agent_block_types::error::BlockError;
16
17/// Result carried back to the originating source via [`Event::ack_tx`].
18pub type AckResult = Result<Value, BlockError>;
19
20/// Sender half of the ack channel. Carried inside [`Event`].
21pub type AckSender = oneshot::Sender<AckResult>;
22
23/// Receiver half of the ack channel. Held by whatever source produced the
24/// event and awaits the handler's return value.
25///
26/// Used by `Event::with_ack` callers — an adapter that pushes events in and
27/// waits for what the handler returned (webhook / WSS / timer). Kept exported
28/// for downstream consumers; no adapter in this crate uses it, because the
29/// mesh adapter in `host.rs` drives the ack loop directly.
30#[allow(dead_code)]
31pub type AckReceiver = oneshot::Receiver<AckResult>;
32
33/// A normalized event flowing through the bus.
34///
35/// Ownership: produced by a [`Source`](crate::bus::Source), moved through a
36/// bounded `mpsc::Sender<Event>` into the single dispatcher loop. The
37/// dispatcher consumes the `ack_tx` (via `Option::take`) to send the
38/// handler's return value back to the source.
39#[derive(Debug)]
40pub struct Event {
41    /// Dispatch key. Matched against `bus.on(kind, fn)` registrations.
42    pub kind: String,
43    /// Correlation id (source-assigned). Used in tracing/logging.
44    pub id: String,
45    /// Source-defined payload. Converted to Lua table at dispatch time.
46    pub payload: Value,
47    /// Source-defined metadata (e.g. mesh `from`, timestamps). Converted to
48    /// Lua table at dispatch time.
49    pub meta: Value,
50    /// Optional one-shot channel used to return the Lua handler's result.
51    /// `None` for fire-and-forget sources.
52    pub ack_tx: Option<AckSender>,
53}
54
55impl Event {
56    /// Construct a new event without an ack channel (fire-and-forget).
57    ///
58    /// Intended for an adapter that broadcasts without waiting (webhook
59    /// broadcast / timer). Not used by the mesh path, which needs the ack
60    /// round-trip.
61    #[allow(dead_code)]
62    pub fn fire_and_forget(kind: impl Into<String>, id: impl Into<String>, payload: Value) -> Self {
63        Self {
64            kind: kind.into(),
65            id: id.into(),
66            payload,
67            meta: Value::Null,
68            ack_tx: None,
69        }
70    }
71
72    /// Construct a new event paired with a fresh ack channel. Returns the
73    /// event (to be pushed to the bus) and the receiver half (to be awaited
74    /// by the source).
75    ///
76    /// Used by the dispatcher's in-crate tests, and by an adapter that wants
77    /// the ack without assembling the channel itself. The mesh adapter
78    /// constructs `Event` directly instead, to keep control over the `meta`
79    /// map and the ack sender's lifetime.
80    #[allow(dead_code)]
81    pub fn with_ack(
82        kind: impl Into<String>,
83        id: impl Into<String>,
84        payload: Value,
85        meta: Value,
86    ) -> (Self, AckReceiver) {
87        let (tx, rx) = oneshot::channel();
88        let evt = Self {
89            kind: kind.into(),
90            id: id.into(),
91            payload,
92            meta,
93            ack_tx: Some(tx),
94        };
95        (evt, rx)
96    }
97
98    /// Send `result` on `ack_tx` if it is still present. Logs a warning when
99    /// the receiver has been dropped (tracing-missing-on-err policy).
100    ///
101    /// Returns `Ok(())` when the ack was delivered or the event was
102    /// fire-and-forget. Returns `Err(BlockError::Bus)` only when the
103    /// receiver had been dropped — the caller can decide whether to treat
104    /// that as fatal.
105    pub fn deliver_ack(&mut self, result: AckResult) -> Result<(), BlockError> {
106        let Some(tx) = self.ack_tx.take() else {
107            return Ok(());
108        };
109        if let Err(dropped) = tx.send(result) {
110            tracing::warn!(
111                kind = %self.kind,
112                id = %self.id,
113                "ack receiver dropped; handler result discarded: {:?}",
114                dropped.as_ref().map(|_| "ok").unwrap_or_else(|e| match e {
115                    BlockError::Bus(_) => "bus-err",
116                    _ => "other-err",
117                })
118            );
119            return Err(BlockError::Bus(format!(
120                "ack receiver dropped (kind={}, id={})",
121                self.kind, self.id
122            )));
123        }
124        Ok(())
125    }
126}