git_internal/internal/object/
tool.rs1use std::fmt;
20
21use serde::{Deserialize, Serialize};
22use uuid::Uuid;
23
24use crate::{
25 errors::GitError,
26 hash::ObjectHash,
27 internal::object::{
28 ObjectTrait,
29 types::{ActorRef, ArtifactRef, Header, ObjectType},
30 },
31};
32
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
35#[serde(rename_all = "snake_case")]
36pub enum ToolStatus {
37 Ok,
39 Error,
41}
42
43impl ToolStatus {
44 pub fn as_str(&self) -> &'static str {
45 match self {
46 ToolStatus::Ok => "ok",
47 ToolStatus::Error => "error",
48 }
49 }
50}
51
52impl fmt::Display for ToolStatus {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 write!(f, "{}", self.as_str())
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct IoFootprint {
62 #[serde(default)]
63 pub paths_read: Vec<String>,
64 #[serde(default)]
65 pub paths_written: Vec<String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct ToolInvocation {
72 #[serde(flatten)]
73 header: Header,
74 run_id: Uuid,
75 tool_name: String,
76 io_footprint: Option<IoFootprint>,
77 #[serde(default)]
78 args: serde_json::Value,
79 status: ToolStatus,
80 result_summary: Option<String>,
81 #[serde(default)]
82 artifacts: Vec<ArtifactRef>,
83}
84
85impl ToolInvocation {
86 pub fn new(
87 repo_id: Uuid,
88 created_by: ActorRef,
89 run_id: Uuid,
90 tool_name: impl Into<String>,
91 ) -> Result<Self, String> {
92 Ok(Self {
93 header: Header::new(ObjectType::ToolInvocation, repo_id, created_by)?,
94 run_id,
95 tool_name: tool_name.into(),
96 io_footprint: None,
97 args: serde_json::Value::Null,
98 status: ToolStatus::Ok,
99 result_summary: None,
100 artifacts: Vec::new(),
101 })
102 }
103
104 pub fn header(&self) -> &Header {
105 &self.header
106 }
107
108 pub fn run_id(&self) -> Uuid {
109 self.run_id
110 }
111
112 pub fn tool_name(&self) -> &str {
113 &self.tool_name
114 }
115
116 pub fn io_footprint(&self) -> Option<&IoFootprint> {
117 self.io_footprint.as_ref()
118 }
119
120 pub fn args(&self) -> &serde_json::Value {
121 &self.args
122 }
123
124 pub fn status(&self) -> &ToolStatus {
125 &self.status
126 }
127
128 pub fn result_summary(&self) -> Option<&str> {
129 self.result_summary.as_deref()
130 }
131
132 pub fn artifacts(&self) -> &[ArtifactRef] {
133 &self.artifacts
134 }
135
136 pub fn set_io_footprint(&mut self, io_footprint: Option<IoFootprint>) {
137 self.io_footprint = io_footprint;
138 }
139
140 pub fn set_args(&mut self, args: serde_json::Value) {
141 self.args = args;
142 }
143
144 pub fn set_status(&mut self, status: ToolStatus) {
145 self.status = status;
146 }
147
148 pub fn set_result_summary(&mut self, result_summary: Option<String>) {
149 self.result_summary = result_summary;
150 }
151
152 pub fn add_artifact(&mut self, artifact: ArtifactRef) {
153 self.artifacts.push(artifact);
154 }
155}
156
157impl fmt::Display for ToolInvocation {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 write!(f, "ToolInvocation: {}", self.header.object_id())
160 }
161}
162
163impl ObjectTrait for ToolInvocation {
164 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
165 where
166 Self: Sized,
167 {
168 serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
169 }
170
171 fn get_type(&self) -> ObjectType {
172 ObjectType::ToolInvocation
173 }
174
175 fn get_size(&self) -> usize {
176 serde_json::to_vec(self).map(|v| v.len()).unwrap_or(0)
177 }
178
179 fn to_data(&self) -> Result<Vec<u8>, GitError> {
180 serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn test_tool_invocation_io_footprint() {
190 let repo_id = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
191 let actor = ActorRef::human("jackie").expect("actor");
192 let run_id = Uuid::from_u128(0x1);
193
194 let mut tool_inv =
195 ToolInvocation::new(repo_id, actor, run_id, "read_file").expect("tool_invocation");
196
197 let footprint = IoFootprint {
198 paths_read: vec!["src/main.rs".to_string()],
199 paths_written: vec![],
200 };
201
202 tool_inv.set_io_footprint(Some(footprint));
203
204 assert_eq!(tool_inv.tool_name(), "read_file");
205 assert!(tool_inv.io_footprint().is_some());
206 assert_eq!(
207 tool_inv.io_footprint().unwrap().paths_read[0],
208 "src/main.rs"
209 );
210 }
211
212 #[test]
213 fn test_tool_invocation_fields() {
214 let repo_id = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
215 let actor = ActorRef::human("jackie").expect("actor");
216 let run_id = Uuid::from_u128(0x1);
217
218 let mut tool_inv =
219 ToolInvocation::new(repo_id, actor, run_id, "apply_patch").expect("tool_invocation");
220 tool_inv.set_status(ToolStatus::Error);
221 tool_inv.set_args(serde_json::json!({"path": "src/lib.rs"}));
222 tool_inv.set_result_summary(Some("failed".to_string()));
223 tool_inv.add_artifact(ArtifactRef::new("local", "artifact-key").expect("artifact"));
224
225 assert_eq!(tool_inv.status(), &ToolStatus::Error);
226 assert_eq!(tool_inv.artifacts().len(), 1);
227 assert_eq!(tool_inv.args()["path"], "src/lib.rs");
228 }
229}