steer_tools/tools/
replace.rs1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::path::Path;
4use steer_macros::tool;
5use tokio::fs;
6
7use crate::result::{EditResult, ReplaceResult};
8use crate::tools::{LS_TOOL_NAME, VIEW_TOOL_NAME};
9use crate::{ExecutionContext, ToolError};
10
11#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
12pub struct ReplaceParams {
13 pub file_path: String,
15 pub content: String,
17}
18
19tool! {
20 ReplaceTool {
21 params: ReplaceParams,
22 output: ReplaceResult,
23 variant: Edit,
24 description: format!(r#"Writes a file to the local filesystem.
25
26Before using this tool:
27
281. Use the {} tool to understand the file's contents and context
29
302. Directory Verification (only applicable when creating new files):
31 - Use the {} tool to verify the parent directory exists and is the correct location"#, VIEW_TOOL_NAME, LS_TOOL_NAME),
32 name: "write_file",
33 require_approval: true
34 }
35
36 async fn run(
37 _tool: &ReplaceTool,
38 params: ReplaceParams,
39 context: &ExecutionContext,
40 ) -> Result<ReplaceResult, ToolError> {
41 if !params.file_path.starts_with('/') && !params.file_path.starts_with("\\") {
43 return Err(ToolError::invalid_params(
44 REPLACE_TOOL_NAME,
45 "file_path must be an absolute path".to_string(),
46 ));
47 }
48
49 let abs_path = if Path::new(¶ms.file_path).is_absolute() {
51 params.file_path.clone()
52 } else {
53 context
54 .working_directory
55 .join(¶ms.file_path)
56 .to_string_lossy()
57 .to_string()
58 };
59
60 let path = Path::new(&abs_path);
61
62 if context.is_cancelled() {
63 return Err(ToolError::Cancelled(REPLACE_TOOL_NAME.to_string()));
64 }
65
66 if let Some(parent) = path.parent() {
68 if !parent.exists() {
69 fs::create_dir_all(parent).await.map_err(|e| {
70 ToolError::io(
71 REPLACE_TOOL_NAME,
72 format!("Failed to create parent directory: {e}"),
73 )
74 })?;
75 }
76 }
77
78 let file_existed = path.exists();
80
81 fs::write(path, ¶ms.content).await.map_err(|e| {
83 ToolError::io(
84 REPLACE_TOOL_NAME,
85 format!("Failed to write file {abs_path}: {e}"),
86 )
87 })?;
88
89 Ok(ReplaceResult(EditResult {
90 file_path: abs_path,
91 changes_made: 1,
92 file_created: !file_existed,
93 old_content: None,
94 new_content: Some(params.content),
95 }))
96 }
97}