git_internal/internal/object/
tool.rs1use std::fmt;
38
39use serde::{Deserialize, Serialize};
40use uuid::Uuid;
41
42use crate::{
43 errors::GitError,
44 hash::ObjectHash,
45 internal::object::{
46 ObjectTrait,
47 types::{ActorRef, ArtifactRef, Header, ObjectType},
48 },
49};
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53#[serde(rename_all = "snake_case")]
54pub enum ToolStatus {
55 Ok,
57 Error,
59}
60
61impl ToolStatus {
62 pub fn as_str(&self) -> &'static str {
63 match self {
64 ToolStatus::Ok => "ok",
65 ToolStatus::Error => "error",
66 }
67 }
68}
69
70impl fmt::Display for ToolStatus {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 write!(f, "{}", self.as_str())
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct IoFootprint {
83 #[serde(default, skip_serializing_if = "Vec::is_empty")]
86 pub paths_read: Vec<String>,
87 #[serde(default, skip_serializing_if = "Vec::is_empty")]
90 pub paths_written: Vec<String>,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct ToolInvocation {
100 #[serde(flatten)]
102 header: Header,
103 run_id: Uuid,
110 tool_name: String,
116 #[serde(default, skip_serializing_if = "Option::is_none")]
121 io_footprint: Option<IoFootprint>,
122 #[serde(default)]
129 args: serde_json::Value,
130 status: ToolStatus,
136 #[serde(default, skip_serializing_if = "Option::is_none")]
143 result_summary: Option<String>,
144 #[serde(default, skip_serializing_if = "Vec::is_empty")]
150 artifacts: Vec<ArtifactRef>,
151}
152
153impl ToolInvocation {
154 pub fn new(
155 created_by: ActorRef,
156 run_id: Uuid,
157 tool_name: impl Into<String>,
158 ) -> Result<Self, String> {
159 Ok(Self {
160 header: Header::new(ObjectType::ToolInvocation, created_by)?,
161 run_id,
162 tool_name: tool_name.into(),
163 io_footprint: None,
164 args: serde_json::Value::Null,
165 status: ToolStatus::Ok,
166 result_summary: None,
167 artifacts: Vec::new(),
168 })
169 }
170
171 pub fn header(&self) -> &Header {
172 &self.header
173 }
174
175 pub fn run_id(&self) -> Uuid {
176 self.run_id
177 }
178
179 pub fn tool_name(&self) -> &str {
180 &self.tool_name
181 }
182
183 pub fn io_footprint(&self) -> Option<&IoFootprint> {
184 self.io_footprint.as_ref()
185 }
186
187 pub fn args(&self) -> &serde_json::Value {
188 &self.args
189 }
190
191 pub fn status(&self) -> &ToolStatus {
192 &self.status
193 }
194
195 pub fn result_summary(&self) -> Option<&str> {
196 self.result_summary.as_deref()
197 }
198
199 pub fn artifacts(&self) -> &[ArtifactRef] {
200 &self.artifacts
201 }
202
203 pub fn set_io_footprint(&mut self, io_footprint: Option<IoFootprint>) {
204 self.io_footprint = io_footprint;
205 }
206
207 pub fn set_args(&mut self, args: serde_json::Value) {
208 self.args = args;
209 }
210
211 pub fn set_status(&mut self, status: ToolStatus) {
212 self.status = status;
213 }
214
215 pub fn set_result_summary(&mut self, result_summary: Option<String>) {
216 self.result_summary = result_summary;
217 }
218
219 pub fn add_artifact(&mut self, artifact: ArtifactRef) {
220 self.artifacts.push(artifact);
221 }
222}
223
224impl fmt::Display for ToolInvocation {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 write!(f, "ToolInvocation: {}", self.header.object_id())
227 }
228}
229
230impl ObjectTrait for ToolInvocation {
231 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
232 where
233 Self: Sized,
234 {
235 serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
236 }
237
238 fn get_type(&self) -> ObjectType {
239 ObjectType::ToolInvocation
240 }
241
242 fn get_size(&self) -> usize {
243 match serde_json::to_vec(self) {
244 Ok(v) => v.len(),
245 Err(e) => {
246 tracing::warn!("failed to compute ToolInvocation size: {}", e);
247 0
248 }
249 }
250 }
251
252 fn to_data(&self) -> Result<Vec<u8>, GitError> {
253 serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_tool_invocation_io_footprint() {
263 let actor = ActorRef::human("jackie").expect("actor");
264 let run_id = Uuid::from_u128(0x1);
265
266 let mut tool_inv =
267 ToolInvocation::new(actor, run_id, "read_file").expect("tool_invocation");
268
269 let footprint = IoFootprint {
270 paths_read: vec!["src/main.rs".to_string()],
271 paths_written: vec![],
272 };
273
274 tool_inv.set_io_footprint(Some(footprint));
275
276 assert_eq!(tool_inv.tool_name(), "read_file");
277 assert!(tool_inv.io_footprint().is_some());
278 assert_eq!(
279 tool_inv.io_footprint().unwrap().paths_read[0],
280 "src/main.rs"
281 );
282 }
283
284 #[test]
285 fn test_tool_invocation_fields() {
286 let actor = ActorRef::human("jackie").expect("actor");
287 let run_id = Uuid::from_u128(0x1);
288
289 let mut tool_inv =
290 ToolInvocation::new(actor, run_id, "apply_patch").expect("tool_invocation");
291 tool_inv.set_status(ToolStatus::Error);
292 tool_inv.set_args(serde_json::json!({"path": "src/lib.rs"}));
293 tool_inv.set_result_summary(Some("failed".to_string()));
294 tool_inv.add_artifact(ArtifactRef::new("local", "artifact-key").expect("artifact"));
295
296 assert_eq!(tool_inv.status(), &ToolStatus::Error);
297 assert_eq!(tool_inv.artifacts().len(), 1);
298 assert_eq!(tool_inv.args()["path"], "src/lib.rs");
299 }
300}