| 226 | } |
| 227 | |
| 228 | async fn execute(&self, args: &serde_json::Value, ctx: &ToolContext) -> Result<ToolOutput> { |
| 229 | let file_path = match args.get("file_path").and_then(|v| v.as_str()) { |
| 230 | Some(p) => p, |
| 231 | None => return Ok(ToolOutput::error("file_path parameter is required")), |
| 232 | }; |
| 233 | |
| 234 | let diff = match args.get("diff").and_then(|v| v.as_str()) { |
| 235 | Some(d) => d, |
| 236 | None => return Ok(ToolOutput::error("diff parameter is required")), |
| 237 | }; |
| 238 | |
| 239 | let workspace_path = match ctx.resolve_workspace_path(file_path) { |
| 240 | Ok(path) => path, |
| 241 | Err(e) => return Ok(ToolOutput::error(format!("Failed to resolve path: {}", e))), |
| 242 | }; |
| 243 | let display_path = ctx.workspace_services.display_path(&workspace_path); |
| 244 | |
| 245 | let (content, version) = match ctx.workspace_services.read_for_edit(&workspace_path).await { |
| 246 | Ok(pair) => pair, |
| 247 | Err(e) => { |
| 248 | return Ok(ToolOutput::error(format!( |
| 249 | "Failed to read file {}: {}", |
| 250 | display_path, e |
| 251 | ))) |
| 252 | } |
| 253 | }; |
| 254 | |
| 255 | let hunks = match parse_hunks(diff) { |
| 256 | Ok(h) => h, |
| 257 | Err(e) => return Ok(ToolOutput::error(format!("Failed to parse diff: {}", e))), |
| 258 | }; |
| 259 | |
| 260 | let new_content = match apply_hunks(&content, &hunks) { |
| 261 | Ok(c) => c, |
| 262 | Err(e) => return Ok(ToolOutput::error(format!("Failed to apply patch: {}", e))), |
| 263 | }; |
| 264 | |
| 265 | // Preserve trailing newline if original had one |
| 266 | let final_content = if content.ends_with('\n') && !new_content.ends_with('\n') { |
| 267 | format!("{}\n", new_content) |
| 268 | } else { |
| 269 | new_content |
| 270 | }; |
| 271 | |
| 272 | match ctx |
| 273 | .workspace_services |
| 274 | .write_for_edit(&workspace_path, &final_content, version.as_deref()) |
| 275 | .await |
| 276 | { |
| 277 | Ok(_) => Ok(ToolOutput::success(format!( |
| 278 | "Applied {} hunk(s) to {}", |
| 279 | hunks.len(), |
| 280 | display_path |
| 281 | ))), |
| 282 | Err(e) => { |
| 283 | let typed = crate::tools::ToolErrorKind::from_workspace_error(&e); |
| 284 | let out = if matches!(e, WorkspaceError::VersionConflict(_)) { |
| 285 | ToolOutput::error(format!( |