agtrace_providers/codex/
tools.rs1use agtrace_types::{ExecuteArgs, FileReadArgs};
6use serde::{Deserialize, Serialize};
7use serde_json::json;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct ApplyPatchArgs {
39 pub raw: String,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct ParsedPatch {
48 pub operation: PatchOperation,
50 pub file_path: String,
52 pub raw_patch: String,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub enum PatchOperation {
59 Add,
61 Update,
63}
64
65impl ApplyPatchArgs {
66 pub fn parse(&self) -> Result<ParsedPatch, String> {
73 let raw = &self.raw;
74
75 for line in raw.lines() {
77 if let Some(path) = line.strip_prefix("*** Add File: ") {
78 return Ok(ParsedPatch {
79 operation: PatchOperation::Add,
80 file_path: path.trim().to_string(),
81 raw_patch: raw.clone(),
82 });
83 }
84 if let Some(path) = line.strip_prefix("*** Update File: ") {
85 return Ok(ParsedPatch {
86 operation: PatchOperation::Update,
87 file_path: path.trim().to_string(),
88 raw_patch: raw.clone(),
89 });
90 }
91 }
92
93 Err("Failed to parse patch: no file path header found".to_string())
94 }
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ShellArgs {
111 pub command: Vec<String>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
115 pub timeout_ms: Option<u64>,
116 #[serde(default, skip_serializing_if = "Option::is_none")]
118 pub workdir: Option<String>,
119}
120
121impl ShellArgs {
122 pub fn to_execute_args(&self) -> ExecuteArgs {
128 let command_str = self.command.join(" ");
129
130 let mut extra = json!({});
131 if let Some(workdir) = &self.workdir {
132 extra["workdir"] = json!(workdir);
133 }
134
135 ExecuteArgs {
136 command: Some(command_str),
137 description: None,
138 timeout: self.timeout_ms,
139 extra,
140 }
141 }
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct ReadMcpResourceArgs {
157 pub server: String,
159 pub uri: String,
161}
162
163impl ReadMcpResourceArgs {
164 pub fn to_file_read_args(&self) -> FileReadArgs {
169 let mut extra = json!({});
170 extra["server"] = json!(&self.server);
171
172 FileReadArgs {
173 file_path: Some(self.uri.clone()),
174 path: None,
175 pattern: None,
176 extra,
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_parse_add_file_patch() {
187 let args = ApplyPatchArgs {
188 raw: r#"*** Begin Patch
189*** Add File: docs/test.md
190+# Test Document
191+
192+This is a test.
193*** End Patch"#
194 .to_string(),
195 };
196
197 let parsed = args.parse().unwrap();
198 assert_eq!(parsed.operation, PatchOperation::Add);
199 assert_eq!(parsed.file_path, "docs/test.md");
200 }
201
202 #[test]
203 fn test_parse_update_file_patch() {
204 let args = ApplyPatchArgs {
205 raw: r#"*** Begin Patch
206*** Update File: src/lib.rs
207@@
208 fn example() {
209- old_code()
210+ new_code()
211 }
212*** End Patch"#
213 .to_string(),
214 };
215
216 let parsed = args.parse().unwrap();
217 assert_eq!(parsed.operation, PatchOperation::Update);
218 assert_eq!(parsed.file_path, "src/lib.rs");
219 }
220
221 #[test]
222 fn test_parse_invalid_patch() {
223 let args = ApplyPatchArgs {
224 raw: "*** Begin Patch\nno header\n*** End Patch".to_string(),
225 };
226
227 assert!(args.parse().is_err());
228 }
229
230 #[test]
231 fn test_shell_args_to_execute_args() {
232 let shell_args = ShellArgs {
233 command: vec!["bash".to_string(), "-lc".to_string(), "ls".to_string()],
234 timeout_ms: Some(10000),
235 workdir: Some("/path/to/dir".to_string()),
236 };
237
238 let execute_args = shell_args.to_execute_args();
239 assert_eq!(execute_args.command, Some("bash -lc ls".to_string()));
240 assert_eq!(execute_args.timeout, Some(10000));
241 assert_eq!(
242 execute_args.extra.get("workdir"),
243 Some(&json!("/path/to/dir"))
244 );
245 }
246
247 #[test]
248 fn test_shell_args_without_optional_fields() {
249 let shell_args = ShellArgs {
250 command: vec!["echo".to_string(), "hello".to_string()],
251 timeout_ms: None,
252 workdir: None,
253 };
254
255 let execute_args = shell_args.to_execute_args();
256 assert_eq!(execute_args.command, Some("echo hello".to_string()));
257 assert_eq!(execute_args.timeout, None);
258 assert_eq!(execute_args.extra, json!({}));
259 }
260
261 #[test]
262 fn test_read_mcp_resource_args_to_file_read_args() {
263 let mcp_args = ReadMcpResourceArgs {
264 server: "local".to_string(),
265 uri: "/foo-bar-hoge-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/AGENTS.md".to_string(),
266 };
267
268 let file_read_args = mcp_args.to_file_read_args();
269 assert_eq!(
270 file_read_args.file_path,
271 Some("/foo-bar-hoge-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/AGENTS.md".to_string())
272 );
273 assert_eq!(file_read_args.path, None);
274 assert_eq!(file_read_args.pattern, None);
275 assert_eq!(file_read_args.extra.get("server"), Some(&json!("local")));
276 }
277}