1use serde::{Deserialize, Serialize};
20use std::time::Duration;
21use vex_core::Hash;
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct ToolResult {
45 pub output: serde_json::Value,
47
48 pub hash: Hash,
51
52 #[serde(with = "duration_serde")]
54 pub execution_time: Duration,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub tokens_used: Option<u32>,
59
60 pub timestamp: String,
62
63 pub tool_name: String,
65}
66
67impl ToolResult {
68 pub fn new(
88 tool_name: &str,
89 args: &serde_json::Value,
90 output: serde_json::Value,
91 execution_time: Duration,
92 ) -> Self {
93 let timestamp = chrono::Utc::now().to_rfc3339();
94
95 let hash_input = serde_json::json!({
98 "args": args,
99 "output": &output,
100 "timestamp": ×tamp,
101 "tool": tool_name,
102 });
103
104 let hash = Hash::digest(&serde_json::to_vec(&hash_input).unwrap_or_default());
106
107 Self {
108 output,
109 hash,
110 execution_time,
111 tokens_used: None,
112 timestamp,
113 tool_name: tool_name.to_string(),
114 }
115 }
116
117 pub fn with_tokens(mut self, tokens: u32) -> Self {
119 self.tokens_used = Some(tokens);
120 self
121 }
122
123 pub fn verify(&self, args: &serde_json::Value) -> bool {
130 let hash_input = serde_json::json!({
131 "args": args,
132 "output": &self.output,
133 "timestamp": &self.timestamp,
134 "tool": &self.tool_name,
135 });
136
137 let expected = Hash::digest(&serde_json::to_vec(&hash_input).unwrap_or_default());
138
139 self.hash == expected
140 }
141
142 pub fn execution_ms(&self) -> u128 {
144 self.execution_time.as_millis()
145 }
146}
147
148mod duration_serde {
150 use serde::{Deserialize, Deserializer, Serialize, Serializer};
151 use std::time::Duration;
152
153 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
154 where
155 S: Serializer,
156 {
157 duration.as_millis().serialize(serializer)
159 }
160
161 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
162 where
163 D: Deserializer<'de>,
164 {
165 let millis = u64::deserialize(deserializer)?;
166 Ok(Duration::from_millis(millis))
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use serde_json::json;
174
175 #[test]
176 fn test_result_creation() {
177 let args = json!({"expression": "1+1"});
178 let result = ToolResult::new(
179 "calculator",
180 &args,
181 json!({"result": 2}),
182 Duration::from_millis(10),
183 );
184
185 assert_eq!(result.tool_name, "calculator");
186 assert_eq!(result.output["result"], 2);
187 assert!(!result.hash.to_string().is_empty());
188 assert!(result.tokens_used.is_none());
189 }
190
191 #[test]
192 fn test_hash_verification() {
193 let args = json!({"expression": "2+2"});
194 let result = ToolResult::new(
195 "calculator",
196 &args,
197 json!({"result": 4}),
198 Duration::from_millis(5),
199 );
200
201 assert!(result.verify(&args));
203
204 let different_args = json!({"expression": "3+3"});
206 assert!(!result.verify(&different_args));
207 }
208
209 #[test]
210 fn test_with_tokens() {
211 let args = json!({});
212 let result = ToolResult::new(
213 "llm_tool",
214 &args,
215 json!({"text": "hello"}),
216 Duration::from_millis(100),
217 )
218 .with_tokens(150);
219
220 assert_eq!(result.tokens_used, Some(150));
221 }
222
223 #[test]
224 fn test_serialization() {
225 let args = json!({"x": 1});
226 let result = ToolResult::new("test", &args, json!({"y": 2}), Duration::from_millis(50));
227
228 let json = serde_json::to_string(&result).unwrap();
229 let deserialized: ToolResult = serde_json::from_str(&json).unwrap();
230
231 assert_eq!(deserialized.tool_name, result.tool_name);
232 assert_eq!(deserialized.output, result.output);
233 assert_eq!(deserialized.hash, result.hash);
234 }
235
236 #[test]
237 fn test_execution_ms() {
238 let args = json!({});
239 let result = ToolResult::new("test", &args, json!({}), Duration::from_millis(123));
240 assert_eq!(result.execution_ms(), 123);
241 }
242}