use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use thiserror::Error;
const JSONRPC: &str = "2.0";
#[derive(Debug, Clone, Error)]
#[non_exhaustive]
pub enum AgentEventError {
#[error("Missing required field: {0}")]
MissingField(String),
#[error("Invalid type for field: {0}")]
InvalidFieldType(String),
#[error("Unknown method: {0}")]
UnknownMethod(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCall {
pub id: String,
pub call_type: String,
pub function: FunctionCall,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FunctionCall {
pub name: String,
pub arguments: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ToolResult {
Ok(String),
Err(String),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JsonRpcNotification {
pub jsonrpc: String,
pub method: String,
pub params: Map<String, Value>,
}
impl JsonRpcNotification {
pub fn builder() -> Self {
Self {
jsonrpc: JSONRPC.into(),
method: String::new(),
params: Map::new(),
}
}
pub fn method(mut self, method: String) -> Self {
self.method = method;
self
}
pub fn add_param(mut self, key: String, value: Value) -> Self {
self.params.insert(key, value);
self
}
}
pub trait AgentEvent: Debug + Send + Sync {
fn to_jsonrpc(&self) -> JsonRpcNotification;
fn session_id(&self) -> String;
}
#[cfg(test)]
mod tests {
use serde_json::{Error, json};
use super::*;
#[test]
fn test_value_from_toolcall() {
let tc = ToolCall {
call_type: "function".into(),
id: "1".into(),
function: FunctionCall {
name: "tool".into(),
arguments: "{}".into(),
},
};
let val = serde_json::to_value(&tc).expect("Should be able to convert to Value");
if let Some(v) = val.as_object() {
assert_eq!(v.get("call_type"), Some(Value::from("function")).as_ref());
assert_eq!(v.get("id"), Some(Value::from("1")).as_ref());
assert!(v.get("function").is_some_and(|o| o.is_object()));
}
}
#[test]
fn test_toolcall_from_value_ok() {
let value = json!({
"call_type": "function",
"id": "1",
"function": {
"name": "tool",
"arguments": "{}"
}
});
let tc: ToolCall = serde_json::from_value(value)
.expect("Value should be correctly converted to tool call");
assert_eq!(tc.call_type, "function".to_string());
assert_eq!(tc.id, "1".to_string());
assert_eq!(tc.function.name, "tool".to_string());
assert_eq!(tc.function.arguments, "{}".to_string());
}
#[test]
fn test_toolcall_from_value_err() {
let value = json!({
"call_typ": "function",
"id": "1",
"func": {
"name": "tool",
"arguments": "{}"
}
});
let result: Result<ToolCall, Error> = serde_json::from_value(value);
assert!(result.is_err());
}
#[test]
fn test_value_from_tool_result() {
let trs = ToolResult::Ok("success!".to_string());
let trf = ToolResult::Err("error!".to_string());
let value_s = serde_json::to_value(trs).expect("Should be able to convert to value");
let value_f = serde_json::to_value(trf).expect("Should be able to convert to value");
assert_eq!(
value_s,
json!({
"Ok": "success!",
})
);
assert_eq!(
value_f,
json!({
"Err": "error!",
})
);
}
#[test]
fn test_jsonrpc_notification_builder() {
let jsonrpc = JsonRpcNotification::builder();
assert_eq!(jsonrpc.jsonrpc, JSONRPC.to_string());
assert_eq!(jsonrpc.method, String::new());
assert_eq!(jsonrpc.params, Map::<String, Value>::new());
let j = jsonrpc
.method("test".into())
.add_param("test".into(), Value::from("string"))
.add_param("number".into(), Value::from(1));
assert_eq!(j.method, "test".to_string());
assert_eq!(j.params.len(), 2);
assert!(j.params.get("test").is_some_and(|v| v.is_string()));
assert!(j.params.get("number").is_some_and(|v| v.is_number()));
}
}