bote 0.90.0

MCP core service — JSON-RPC 2.0 protocol, tool registry, audit integration, and TypeScript bridge
Documentation
//! Event publishing — broadcast tool call and registration events.
//!
//! The [`EventSink`] trait defines the interface. Enable the `events` feature
//! for the [`MajraEvents`] implementation backed by majra's pub/sub engine.

/// Event topic for tool call completion.
pub const TOPIC_TOOL_COMPLETED: &str = "bote/tool/completed";
/// Event topic for tool call failure.
pub const TOPIC_TOOL_FAILED: &str = "bote/tool/failed";
/// Event topic for tool registration.
pub const TOPIC_TOOL_REGISTERED: &str = "bote/tool/registered";
/// Event topic for tool announcements (cross-node discovery).
pub const TOPIC_TOOL_ANNOUNCE: &str = "bote/tool/announce";
/// Event topic for discovered tools from other nodes.
pub const TOPIC_TOOL_DISCOVERED: &str = "bote/tool/discovered";
/// Event topic for tool deprecation warnings.
pub const TOPIC_TOOL_DEPRECATED: &str = "bote/tool/deprecated";
/// Event topic for tool deregistration.
pub const TOPIC_TOOL_DEREGISTERED: &str = "bote/tool/deregistered";
/// Event topic for sandbox creation.
#[cfg(feature = "sandbox")]
pub const TOPIC_SANDBOX_CREATED: &str = "bote/sandbox/created";
/// Event topic for sandbox destruction.
#[cfg(feature = "sandbox")]
pub const TOPIC_SANDBOX_DESTROYED: &str = "bote/sandbox/destroyed";
/// Event topic for sandbox errors.
#[cfg(feature = "sandbox")]
pub const TOPIC_SANDBOX_ERROR: &str = "bote/sandbox/error";

/// Trait for event publishing backends.
pub trait EventSink: Send + Sync {
    /// Publish an event to a topic.
    fn publish(&self, topic: &str, payload: serde_json::Value);
}

/// No-op event sink (used when event publishing is disabled).
impl EventSink for () {
    fn publish(&self, _topic: &str, _payload: serde_json::Value) {}
}

// --- majra integration (feature = "events") ---

#[cfg(feature = "events")]
mod majra_impl {
    use super::*;
    use majra::pubsub::PubSub;

    /// Event sink backed by majra's pub/sub engine.
    pub struct MajraEvents {
        pubsub: PubSub,
    }

    impl MajraEvents {
        #[must_use]
        pub fn new() -> Self {
            Self {
                pubsub: PubSub::new(),
            }
        }

        /// Create from an existing PubSub instance.
        #[must_use]
        pub fn with_pubsub(pubsub: PubSub) -> Self {
            Self { pubsub }
        }

        /// Access the underlying PubSub (e.g. for subscribing).
        #[must_use]
        pub fn pubsub(&self) -> &PubSub {
            &self.pubsub
        }
    }

    impl Default for MajraEvents {
        fn default() -> Self {
            Self::new()
        }
    }

    impl EventSink for MajraEvents {
        fn publish(&self, topic: &str, payload: serde_json::Value) {
            self.pubsub.publish(topic, payload);
        }
    }
}

#[cfg(feature = "events")]
pub use majra_impl::MajraEvents;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn noop_sink_compiles() {
        let sink: &dyn EventSink = &();
        sink.publish("test/topic", serde_json::json!({"hello": "world"}));
    }
}

#[cfg(all(test, feature = "events"))]
mod events_tests {
    use super::*;
    use majra::pubsub::PubSub;

    #[test]
    fn majra_events_publishes() {
        let pubsub = PubSub::new();
        let mut rx = pubsub.subscribe("bote/tool/#");
        let events = MajraEvents::with_pubsub(pubsub);

        events.publish(
            "bote/tool/completed",
            serde_json::json!({"tool_name": "echo", "duration_ms": 10}),
        );

        let msg = rx.try_recv().unwrap();
        assert_eq!(msg.topic, "bote/tool/completed");
        assert_eq!(msg.payload["tool_name"], "echo");
    }

    #[test]
    fn majra_events_multiple_topics() {
        let pubsub = PubSub::new();
        let mut rx = pubsub.subscribe("bote/#");
        let events = MajraEvents::with_pubsub(pubsub);

        events.publish("bote/tool/called", serde_json::json!({"tool_name": "a"}));
        events.publish("bote/tool/completed", serde_json::json!({"tool_name": "a"}));
        events.publish(
            "bote/tool/registered",
            serde_json::json!({"tool_name": "b"}),
        );

        let m1 = rx.try_recv().unwrap();
        let m2 = rx.try_recv().unwrap();
        let m3 = rx.try_recv().unwrap();
        assert_eq!(m1.topic, "bote/tool/called");
        assert_eq!(m2.topic, "bote/tool/completed");
        assert_eq!(m3.topic, "bote/tool/registered");
    }
}