use agtrace_types::{ExecuteArgs, FileEditArgs, FileReadArgs, FileWriteArgs, SearchArgs};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct ClaudeBashArgs {
pub command: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub dangerouslyDisableSandbox: Option<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run_in_background: Option<bool>,
}
impl ClaudeBashArgs {
pub fn to_execute_args(&self) -> ExecuteArgs {
let mut extra = json!({});
if let Some(sandbox) = self.dangerouslyDisableSandbox {
extra["dangerouslyDisableSandbox"] = json!(sandbox);
}
if let Some(bg) = self.run_in_background {
extra["run_in_background"] = json!(bg);
}
ExecuteArgs {
command: Some(self.command.clone()),
description: self.description.clone(),
timeout: self.timeout,
extra,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeReadArgs {
pub file_path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub offset: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<u64>,
}
impl ClaudeReadArgs {
pub fn to_file_read_args(&self) -> FileReadArgs {
let mut extra = json!({});
if let Some(offset) = self.offset {
extra["offset"] = json!(offset);
}
if let Some(limit) = self.limit {
extra["limit"] = json!(limit);
}
FileReadArgs {
file_path: Some(self.file_path.clone()),
path: None,
pattern: None,
extra,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeGlobArgs {
pub pattern: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
}
impl ClaudeGlobArgs {
pub fn to_file_read_args(&self) -> FileReadArgs {
FileReadArgs {
file_path: None,
path: self.path.clone(),
pattern: Some(self.pattern.clone()),
extra: json!({}),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeEditArgs {
pub file_path: String,
pub old_string: String,
pub new_string: String,
#[serde(default)]
pub replace_all: bool,
}
impl ClaudeEditArgs {
pub fn to_file_edit_args(&self) -> FileEditArgs {
FileEditArgs {
file_path: self.file_path.clone(),
old_string: self.old_string.clone(),
new_string: self.new_string.clone(),
replace_all: self.replace_all,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeWriteArgs {
pub file_path: String,
pub content: String,
}
impl ClaudeWriteArgs {
pub fn to_file_write_args(&self) -> FileWriteArgs {
FileWriteArgs {
file_path: self.file_path.clone(),
content: self.content.clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeGrepArgs {
pub pattern: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub glob: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#type: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output_mode: Option<String>,
#[serde(rename = "-i", default, skip_serializing_if = "Option::is_none")]
pub case_insensitive: Option<bool>,
#[serde(rename = "-n", default, skip_serializing_if = "Option::is_none")]
pub line_numbers: Option<bool>,
#[serde(rename = "-A", default, skip_serializing_if = "Option::is_none")]
pub after_context: Option<u64>,
#[serde(rename = "-B", default, skip_serializing_if = "Option::is_none")]
pub before_context: Option<u64>,
#[serde(rename = "-C", default, skip_serializing_if = "Option::is_none")]
pub context: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub head_limit: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multiline: Option<bool>,
}
impl ClaudeGrepArgs {
pub fn to_search_args(&self) -> SearchArgs {
let mut extra = json!({});
if let Some(glob) = &self.glob {
extra["glob"] = json!(glob);
}
if let Some(t) = &self.r#type {
extra["type"] = json!(t);
}
if let Some(mode) = &self.output_mode {
extra["output_mode"] = json!(mode);
}
if let Some(ci) = self.case_insensitive {
extra["-i"] = json!(ci);
}
if let Some(ln) = self.line_numbers {
extra["-n"] = json!(ln);
}
if let Some(a) = self.after_context {
extra["-A"] = json!(a);
}
if let Some(b) = self.before_context {
extra["-B"] = json!(b);
}
if let Some(c) = self.context {
extra["-C"] = json!(c);
}
if let Some(hl) = self.head_limit {
extra["head_limit"] = json!(hl);
}
if let Some(ml) = self.multiline {
extra["multiline"] = json!(ml);
}
SearchArgs {
pattern: Some(self.pattern.clone()),
query: None,
input: None,
path: self.path.clone(),
extra,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeTodoWriteArgs {
pub todos: Vec<ClaudeTodoItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct ClaudeTodoItem {
pub content: String,
pub activeForm: String,
pub status: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bash_args_conversion() {
let args = ClaudeBashArgs {
command: "ls -la".to_string(),
description: Some("List files".to_string()),
timeout: Some(60000),
dangerouslyDisableSandbox: Some(false),
run_in_background: None,
};
let domain_args = args.to_execute_args();
assert_eq!(domain_args.command, Some("ls -la".to_string()));
assert_eq!(domain_args.description, Some("List files".to_string()));
assert_eq!(domain_args.timeout, Some(60000));
assert_eq!(
domain_args.extra.get("dangerouslyDisableSandbox"),
Some(&json!(false))
);
}
#[test]
fn test_read_args_conversion() {
let args = ClaudeReadArgs {
file_path: "src/main.rs".to_string(),
offset: Some(100),
limit: Some(500),
};
let domain_args = args.to_file_read_args();
assert_eq!(domain_args.file_path, Some("src/main.rs".to_string()));
assert_eq!(domain_args.extra.get("offset"), Some(&json!(100)));
assert_eq!(domain_args.extra.get("limit"), Some(&json!(500)));
}
#[test]
fn test_glob_args_conversion() {
let args = ClaudeGlobArgs {
pattern: "**/*.rs".to_string(),
path: Some("src/".to_string()),
};
let domain_args = args.to_file_read_args();
assert_eq!(domain_args.pattern, Some("**/*.rs".to_string()));
assert_eq!(domain_args.path, Some("src/".to_string()));
}
#[test]
fn test_edit_args_conversion() {
let args = ClaudeEditArgs {
file_path: "src/lib.rs".to_string(),
old_string: "old".to_string(),
new_string: "new".to_string(),
replace_all: true,
};
let domain_args = args.to_file_edit_args();
assert_eq!(domain_args.file_path, "src/lib.rs");
assert_eq!(domain_args.old_string, "old");
assert_eq!(domain_args.new_string, "new");
assert!(domain_args.replace_all);
}
#[test]
fn test_write_args_conversion() {
let args = ClaudeWriteArgs {
file_path: "test.txt".to_string(),
content: "hello world".to_string(),
};
let domain_args = args.to_file_write_args();
assert_eq!(domain_args.file_path, "test.txt");
assert_eq!(domain_args.content, "hello world");
}
#[test]
fn test_grep_args_conversion() {
let args = ClaudeGrepArgs {
pattern: "fn main".to_string(),
path: Some("src/".to_string()),
glob: Some("*.rs".to_string()),
r#type: None,
output_mode: Some("content".to_string()),
case_insensitive: Some(true),
line_numbers: Some(true),
after_context: Some(3),
before_context: None,
context: None,
head_limit: Some(100),
multiline: None,
};
let domain_args = args.to_search_args();
assert_eq!(domain_args.pattern, Some("fn main".to_string()));
assert_eq!(domain_args.path, Some("src/".to_string()));
assert_eq!(domain_args.extra.get("glob"), Some(&json!("*.rs")));
assert_eq!(
domain_args.extra.get("output_mode"),
Some(&json!("content"))
);
assert_eq!(domain_args.extra.get("-i"), Some(&json!(true)));
assert_eq!(domain_args.extra.get("-A"), Some(&json!(3)));
}
#[test]
fn test_todo_write_args_parsing() {
let json_value = json!({
"todos": [
{
"content": "Create tools.rs",
"activeForm": "Creating tools.rs",
"status": "in_progress"
},
{
"content": "Add tests",
"activeForm": "Adding tests",
"status": "pending"
}
]
});
let args: ClaudeTodoWriteArgs = serde_json::from_value(json_value).unwrap();
assert_eq!(args.todos.len(), 2);
assert_eq!(args.todos[0].content, "Create tools.rs");
assert_eq!(args.todos[0].activeForm, "Creating tools.rs");
assert_eq!(args.todos[0].status, "in_progress");
}
}