Skip to main content

microagents_events/
types.rs

1use std::fmt::Debug;
2
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5use thiserror::Error;
6
7const JSONRPC: &str = "2.0";
8
9/// Errors that can occur when parsing a [`JsonRpcNotification`] into an [`AgentEventAny`].
10#[derive(Debug, Clone, Error)]
11#[non_exhaustive]
12pub enum AgentEventError {
13    /// A required field was missing from the JSON-RPC params.
14    #[error("Missing required field: {0}")]
15    MissingField(String),
16    /// A field had an unexpected type.
17    #[error("Invalid type for field: {0}")]
18    InvalidFieldType(String),
19    /// The JSON-RPC method name is not recognized.
20    #[error("Unknown method: {0}")]
21    UnknownMethod(String),
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ToolCall {
26    pub id: String,
27    pub call_type: String,
28    pub function: FunctionCall,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct FunctionCall {
33    pub name: String,
34    pub arguments: String,
35}
36
37/// Result of a tool execution, either success with output or failure with an error message.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[non_exhaustive]
40pub enum ToolResult {
41    /// Tool executed successfully.
42    Ok(String),
43    /// Tool execution failed.
44    Err(String),
45}
46
47/// A JSON-RPC 2.0 notification message.
48#[derive(Debug, Serialize, Deserialize, Clone)]
49pub struct JsonRpcNotification {
50    pub jsonrpc: String,
51    pub method: String,
52    pub params: Map<String, Value>,
53}
54
55impl JsonRpcNotification {
56    pub fn builder() -> Self {
57        Self {
58            jsonrpc: JSONRPC.into(),
59            method: String::new(),
60            params: Map::new(),
61        }
62    }
63
64    pub fn method(mut self, method: String) -> Self {
65        self.method = method;
66        self
67    }
68
69    pub fn add_param(mut self, key: String, value: Value) -> Self {
70        self.params.insert(key, value);
71        self
72    }
73}
74
75/// Trait for events that can be converted to JSON-RPC notifications and carry a session ID.
76pub trait AgentEvent: Debug + Send + Sync {
77    /// Convert this event into a [`JsonRpcNotification`].
78    fn to_jsonrpc(&self) -> JsonRpcNotification;
79    /// Return the session ID associated with this event.
80    fn session_id(&self) -> String;
81}
82
83#[cfg(test)]
84mod tests {
85
86    use serde_json::{Error, json};
87
88    use super::*;
89
90    #[test]
91    fn test_value_from_toolcall() {
92        let tc = ToolCall {
93            call_type: "function".into(),
94            id: "1".into(),
95            function: FunctionCall {
96                name: "tool".into(),
97                arguments: "{}".into(),
98            },
99        };
100        let val = serde_json::to_value(&tc).expect("Should be able to convert to Value");
101        if let Some(v) = val.as_object() {
102            assert_eq!(v.get("call_type"), Some(Value::from("function")).as_ref());
103            assert_eq!(v.get("id"), Some(Value::from("1")).as_ref());
104            assert!(v.get("function").is_some_and(|o| o.is_object()));
105        }
106    }
107
108    #[test]
109    fn test_toolcall_from_value_ok() {
110        let value = json!({
111            "call_type": "function",
112            "id": "1",
113            "function": {
114                "name": "tool",
115                "arguments": "{}"
116            }
117        });
118        let tc: ToolCall = serde_json::from_value(value)
119            .expect("Value should be correctly converted to tool call");
120        assert_eq!(tc.call_type, "function".to_string());
121        assert_eq!(tc.id, "1".to_string());
122        assert_eq!(tc.function.name, "tool".to_string());
123        assert_eq!(tc.function.arguments, "{}".to_string());
124    }
125
126    #[test]
127    fn test_toolcall_from_value_err() {
128        let value = json!({
129            "call_typ": "function",
130            "id": "1",
131            "func": {
132                "name": "tool",
133                "arguments": "{}"
134            }
135        });
136        let result: Result<ToolCall, Error> = serde_json::from_value(value);
137        assert!(result.is_err());
138    }
139
140    #[test]
141    fn test_value_from_tool_result() {
142        let trs = ToolResult::Ok("success!".to_string());
143        let trf = ToolResult::Err("error!".to_string());
144        let value_s = serde_json::to_value(trs).expect("Should be able to convert to value");
145        let value_f = serde_json::to_value(trf).expect("Should be able to convert to value");
146        assert_eq!(
147            value_s,
148            json!({
149                "Ok": "success!",
150            })
151        );
152        assert_eq!(
153            value_f,
154            json!({
155                "Err": "error!",
156            })
157        );
158    }
159
160    #[test]
161    fn test_jsonrpc_notification_builder() {
162        let jsonrpc = JsonRpcNotification::builder();
163        assert_eq!(jsonrpc.jsonrpc, JSONRPC.to_string());
164        assert_eq!(jsonrpc.method, String::new());
165        assert_eq!(jsonrpc.params, Map::<String, Value>::new());
166
167        let j = jsonrpc
168            .method("test".into())
169            .add_param("test".into(), Value::from("string"))
170            .add_param("number".into(), Value::from(1));
171
172        assert_eq!(j.method, "test".to_string());
173        assert_eq!(j.params.len(), 2);
174        assert!(j.params.get("test").is_some_and(|v| v.is_string()));
175        assert!(j.params.get("number").is_some_and(|v| v.is_number()));
176    }
177}