//! Neutral tool events. Connector adapters translate to wire types. use crate::tool::ToolKind; use bytes::Bytes; use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::mpsc; /// Opaque permission-request id, allocated by the dispatcher. #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct PermissionRequestId(Arc); impl PermissionRequestId { /// Construct a new permission-request id from any string-like value. pub fn new(value: impl Into>) -> Self { Self(value.into()) } /// Borrow the inner id as a string slice. pub fn as_str(&self) -> &str { &self.0 } } /// Where a tool is operating (file path + optional line). #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ToolLocation { pub path: String, pub line: Option, } /// Result content shape. Mirrors what most providers accept as a tool result. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ToolResultContent { Text { text: Arc }, Json { value: serde_json::Value }, Image { mime: Arc, #[serde(with = "serde_bytes_arc")] data: Bytes }, Parts { parts: Vec }, } impl ToolResultContent { pub fn text(s: impl Into>) -> Self { Self::Text { text: s.into() } } } /// Events emitted by a running tool. Transport-agnostic. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] pub enum ToolEvent { Started { title: Arc, kind: ToolKind, location: Option }, TitleUpdate { title: Arc, location: Option }, PartialOutput { content: ToolResultContent }, Status { message: Arc }, PermissionRequested { request_id: PermissionRequestId, summary: Arc }, Completed, Failed, } /// Sink a tool emits events into. Cheap to clone. #[derive(Clone, Debug)] pub struct ToolEventSink { tx: mpsc::UnboundedSender, } impl ToolEventSink { pub fn new() -> (Self, mpsc::UnboundedReceiver) { let (tx, rx) = mpsc::unbounded_channel(); (Self { tx }, rx) } /// Best-effort emit. Drops the event if the receiver is gone. pub fn emit(&self, event: ToolEvent) { let _ = self.tx.send(event); } } mod serde_bytes_arc { use bytes::Bytes; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub fn serialize(b: &Bytes, s: S) -> Result { serde_bytes::Bytes::new(b.as_ref()).serialize(s) } pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result { let v: Vec = serde_bytes::ByteBuf::deserialize(d)?.into_vec(); Ok(Bytes::from(v)) } } #[cfg(test)] mod tests { use super::*; #[tokio::test] async fn sink_round_trips_event() { let (sink, mut rx) = ToolEventSink::new(); sink.emit(ToolEvent::Status { message: "hi".into() }); let got = rx.recv().await.unwrap(); match got { ToolEvent::Status { message } => assert_eq!(&*message, "hi"), _ => panic!("wrong variant"), } } #[test] fn result_content_text_helper() { match ToolResultContent::text("hello") { ToolResultContent::Text { text } => assert_eq!(&*text, "hello"), _ => panic!(), } } #[test] fn tool_event_serde_round_trip() { let ev = ToolEvent::PartialOutput { content: ToolResultContent::text("x") }; let json = serde_json::to_string(&ev).unwrap(); let _back: ToolEvent = serde_json::from_str(&json).unwrap(); } #[test] fn permission_request_id_constructor_and_accessor() { let id = PermissionRequestId::new("abc"); assert_eq!(id.as_str(), "abc"); } #[test] fn permission_request_id_serde_is_transparent_string() { let id = PermissionRequestId::new("foo"); let json = serde_json::to_string(&id).unwrap(); assert_eq!(json, "\"foo\""); let back: PermissionRequestId = serde_json::from_str(&json).unwrap(); assert_eq!(back, id); } }