1use std::sync::Arc;
8
9use schemars::JsonSchema;
10use serde::Deserialize;
11use serde_json::Value;
12use sgr_agent_core::agent_tool::{Tool, ToolError, ToolOutput, parse_args};
13use sgr_agent_core::context::AgentContext;
14use sgr_agent_core::schema::json_schema_for;
15
16use crate::backend::FileBackend;
17use crate::helpers::backend_err;
18
19pub struct WriteTool<B: FileBackend>(pub Arc<B>);
20
21#[derive(Deserialize, JsonSchema)]
22struct WriteArgs {
23 path: String,
25 content: String,
27 #[serde(default)]
29 start_line: i32,
30 #[serde(default)]
32 end_line: i32,
33}
34
35fn maybe_repair_json(path: &str, content: &str) -> String {
38 if !path.ends_with(".json") || path.contains("README") {
39 return content.to_string();
40 }
41 match serde_json::from_str::<serde_json::Value>(content) {
42 Ok(_) => content.to_string(),
43 Err(_) => {
44 let opts = llm_json::RepairOptions::default();
45 match llm_json::repair_json(content, &opts) {
46 Ok(fixed) => {
47 eprintln!(" sgr-tools: auto-fixed JSON via llm_json in {}", path);
48 fixed
49 }
50 Err(_) => content.to_string(),
51 }
52 }
53 }
54}
55
56#[async_trait::async_trait]
57impl<B: FileBackend> Tool for WriteTool<B> {
58 fn name(&self) -> &str {
59 "write"
60 }
61 fn description(&self) -> &str {
62 "Write content to a file. Without start_line/end_line: overwrites entire file. \
63 With start_line and end_line: replaces only those lines (like sed). \
64 Example: start_line=5, end_line=7 replaces lines 5-7 with content. \
65 Use read with number=true first to see line numbers."
66 }
67 fn parameters_schema(&self) -> Value {
68 json_schema_for::<WriteArgs>()
69 }
70 async fn execute(&self, args: Value, _ctx: &mut AgentContext) -> Result<ToolOutput, ToolError> {
71 let a: WriteArgs = parse_args(&args)?;
72 let content = maybe_repair_json(&a.path, &a.content);
73
74 self.0
75 .write(&a.path, &content, a.start_line, a.end_line)
76 .await
77 .map_err(backend_err)?;
78
79 let msg = if a.start_line > 0 && a.end_line > 0 {
80 format!(
81 "Replaced lines {}-{} in {}",
82 a.start_line, a.end_line, a.path
83 )
84 } else if a.start_line > 0 {
85 format!("Replaced from line {} in {}", a.start_line, a.path)
86 } else {
87 format!("Written to {}", a.path)
88 };
89 Ok(ToolOutput::text(msg))
90 }
91}