use serde::{Deserialize, Serialize};
use std::time::Duration;
use vex_core::Hash;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
pub output: serde_json::Value,
pub hash: Hash,
#[serde(with = "duration_serde")]
pub execution_time: Duration,
#[serde(skip_serializing_if = "Option::is_none")]
pub tokens_used: Option<u32>,
pub timestamp: String,
pub tool_name: String,
}
impl ToolResult {
pub fn new(
tool_name: &str,
args: &serde_json::Value,
output: serde_json::Value,
execution_time: Duration,
) -> Self {
let timestamp = chrono::Utc::now().to_rfc3339();
let hash_input = serde_json::json!({
"args": args,
"output": &output,
"timestamp": ×tamp,
"tool": tool_name,
});
let hash = Hash::digest(&serde_json::to_vec(&hash_input).unwrap_or_default());
Self {
output,
hash,
execution_time,
tokens_used: None,
timestamp,
tool_name: tool_name.to_string(),
}
}
pub fn with_tokens(mut self, tokens: u32) -> Self {
self.tokens_used = Some(tokens);
self
}
pub fn verify(&self, args: &serde_json::Value) -> bool {
let hash_input = serde_json::json!({
"args": args,
"output": &self.output,
"timestamp": &self.timestamp,
"tool": &self.tool_name,
});
let expected = Hash::digest(&serde_json::to_vec(&hash_input).unwrap_or_default());
self.hash == expected
}
pub fn execution_ms(&self) -> u128 {
self.execution_time.as_millis()
}
}
mod duration_serde {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::time::Duration;
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
duration.as_millis().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
where
D: Deserializer<'de>,
{
let millis = u64::deserialize(deserializer)?;
Ok(Duration::from_millis(millis))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_result_creation() {
let args = json!({"expression": "1+1"});
let result = ToolResult::new(
"calculator",
&args,
json!({"result": 2}),
Duration::from_millis(10),
);
assert_eq!(result.tool_name, "calculator");
assert_eq!(result.output["result"], 2);
assert!(!result.hash.to_string().is_empty());
assert!(result.tokens_used.is_none());
}
#[test]
fn test_hash_verification() {
let args = json!({"expression": "2+2"});
let result = ToolResult::new(
"calculator",
&args,
json!({"result": 4}),
Duration::from_millis(5),
);
assert!(result.verify(&args));
let different_args = json!({"expression": "3+3"});
assert!(!result.verify(&different_args));
}
#[test]
fn test_with_tokens() {
let args = json!({});
let result = ToolResult::new(
"llm_tool",
&args,
json!({"text": "hello"}),
Duration::from_millis(100),
)
.with_tokens(150);
assert_eq!(result.tokens_used, Some(150));
}
#[test]
fn test_serialization() {
let args = json!({"x": 1});
let result = ToolResult::new("test", &args, json!({"y": 2}), Duration::from_millis(50));
let json = serde_json::to_string(&result).unwrap();
let deserialized: ToolResult = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.tool_name, result.tool_name);
assert_eq!(deserialized.output, result.output);
assert_eq!(deserialized.hash, result.hash);
}
#[test]
fn test_execution_ms() {
let args = json!({});
let result = ToolResult::new("test", &args, json!({}), Duration::from_millis(123));
assert_eq!(result.execution_ms(), 123);
}
}