Skip to content

Commit 562e1aa

Browse files
committed
WIP tools
1 parent f3cb5a8 commit 562e1aa

File tree

8 files changed

+182
-88
lines changed

8 files changed

+182
-88
lines changed

crates/agent/src/agent/agent_config/definitions.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl Default for AgentConfigV2025_08_22 {
175175
use_legacy_mcp_json: false,
176176

177177
resources: Default::default(),
178-
allowed_tools: HashSet::from([BuiltInToolName::FileRead.to_string()]),
178+
allowed_tools: HashSet::from([BuiltInToolName::FsRead.to_string()]),
179179
}
180180
}
181181
}

crates/agent/src/agent/agent_loop/types.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,9 @@ pub struct ImageBlock {
266266
pub source: ImageSource,
267267
}
268268

269-
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::EnumString, strum::Display)]
269+
#[derive(
270+
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, strum::EnumString, strum::Display, strum::EnumIter,
271+
)]
270272
#[serde(rename_all = "lowercase")]
271273
#[strum(serialize_all = "lowercase")]
272274
pub enum ImageFormat {

crates/agent/src/agent/tools/execute_cmd.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,17 @@ const EXECUTE_CMD_SCHEMA: &str = r#"
6464
"#;
6565

6666
impl BuiltInToolTrait for ExecuteCmd {
67-
const DESCRIPTION: &str = EXECUTE_CMD_TOOL_DESCRIPTION;
68-
const INPUT_SCHEMA: &str = EXECUTE_CMD_SCHEMA;
69-
const NAME: BuiltInToolName = BuiltInToolName::ExecuteCmd;
67+
fn name() -> BuiltInToolName {
68+
BuiltInToolName::ExecuteCmd
69+
}
70+
71+
fn description() -> std::borrow::Cow<'static, str> {
72+
EXECUTE_CMD_TOOL_DESCRIPTION.into()
73+
}
74+
75+
fn input_schema() -> std::borrow::Cow<'static, str> {
76+
EXECUTE_CMD_SCHEMA.into()
77+
}
7078
}
7179

7280
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]

crates/agent/src/agent/tools/file_read.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ use crate::agent::util::path::canonicalize_path;
2828

2929
const MAX_READ_SIZE: u32 = 250 * 1024;
3030

31-
const FILE_READ_TOOL_DESCRIPTION: &str = r#"
31+
const FS_READ_TOOL_DESCRIPTION: &str = r#"
3232
A tool for viewing file contents.
3333
3434
WHEN TO USE THIS TOOL:
@@ -43,7 +43,6 @@ HOW TO USE:
4343
- Do not use this for directories, use the ls tool instead
4444
4545
FEATURES:
46-
- Displays file contents with line numbers for easy reference
4746
- Can read from any position in a file using the offset parameter
4847
- Handles large files by limiting the number of lines read
4948
@@ -54,21 +53,29 @@ LIMITATIONS:
5453

5554
// TODO - migrate from JsonSchema, it's not very configurable and prone to breaking changes in the
5655
// generated structure.
57-
const FILE_READ_SCHEMA: &str = "";
56+
const FS_READ_SCHEMA: &str = "";
5857

59-
impl BuiltInToolTrait for FileRead {
60-
const DESCRIPTION: &str = FILE_READ_TOOL_DESCRIPTION;
61-
const INPUT_SCHEMA: &str = FILE_READ_SCHEMA;
62-
const NAME: BuiltInToolName = BuiltInToolName::FileRead;
58+
impl BuiltInToolTrait for FsRead {
59+
fn name() -> BuiltInToolName {
60+
BuiltInToolName::FsRead
61+
}
62+
63+
fn description() -> std::borrow::Cow<'static, str> {
64+
FS_READ_TOOL_DESCRIPTION.into()
65+
}
66+
67+
fn input_schema() -> std::borrow::Cow<'static, str> {
68+
FS_READ_SCHEMA.into()
69+
}
6370
}
6471

6572
/// A tool for reading files
6673
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
67-
pub struct FileRead {
68-
pub ops: Vec<FileReadOp>,
74+
pub struct FsRead {
75+
pub ops: Vec<FsReadOp>,
6976
}
7077

71-
impl FileRead {
78+
impl FsRead {
7279
pub fn tool_schema() -> serde_json::Value {
7380
let schema = schema_for!(Self);
7481
serde_json::to_value(schema).expect("creating tool schema should not fail")
@@ -124,7 +131,7 @@ impl FileRead {
124131
}
125132

126133
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
127-
pub struct FileReadOp {
134+
pub struct FsReadOp {
128135
/// Path to the file
129136
pub path: String,
130137
/// Number of lines to read
@@ -133,7 +140,7 @@ pub struct FileReadOp {
133140
pub offset: Option<u32>,
134141
}
135142

136-
impl FileReadOp {
143+
impl FsReadOp {
137144
async fn execute(&self) -> Result<ToolExecutionOutputItem, ToolExecutionError> {
138145
let path = PathBuf::from(canonicalize_path(&self.path).map_err(|e| ToolExecutionError::Custom(e.to_string()))?);
139146

@@ -182,7 +189,7 @@ mod tests {
182189

183190
#[test]
184191
fn test_file_read_tool_schema() {
185-
let schema = FileRead::tool_schema();
192+
let schema = FsRead::tool_schema();
186193
println!("{}", serde_json::to_string_pretty(&schema).unwrap());
187194
}
188195
}

crates/agent/src/agent/tools/file_write.rs

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ use std::path::{
33
PathBuf,
44
};
55

6-
use schemars::JsonSchema;
76
use serde::{
87
Deserialize,
98
Serialize,
109
};
10+
use syntect::util::LinesWithEndings;
1111

1212
use super::{
1313
BuiltInToolName,
@@ -17,8 +17,8 @@ use super::{
1717
};
1818
use crate::agent::util::path::canonicalize_path;
1919

20-
const FILE_WRITE_TOOL_DESCRIPTION: &str = r#"
21-
A tool for creating and editing files.
20+
const FS_WRITE_TOOL_DESCRIPTION: &str = r#"
21+
A tool for creating and editing text files.
2222
2323
WHEN TO USE THIS TOOL:
2424
- Use when you need to create a new file, or modify an existing file
@@ -33,9 +33,10 @@ HOW TO USE:
3333
3434
TIPS:
3535
- Read the file first before making modifications to ensure you have the most up-to-date version of the file.
36+
- To append content to the end of a file, use `insert` with no `insert_line`
3637
"#;
3738

38-
const FILE_WRITE_SCHEMA: &str = r#"
39+
const FS_WRITE_SCHEMA: &str = r#"
3940
{
4041
"type": "object",
4142
"properties": {
@@ -53,7 +54,7 @@ const FILE_WRITE_SCHEMA: &str = r#"
5354
"type": "string"
5455
},
5556
"insert_line": {
56-
"description": "Required parameter of `insert` command. The `content` will be inserted AFTER the line `insert_line` of `path`.",
57+
"description": "Optional parameter of `insert` command. Line is 0-indexed. `content` will be inserted at the provided line. If not provided, content will be inserted at the end of the file on a new line, inserting a newline at the end of the file if it is missing.",
5758
"type": "integer"
5859
},
5960
"new_str": {
@@ -76,27 +77,38 @@ const FILE_WRITE_SCHEMA: &str = r#"
7677
}
7778
"#;
7879

79-
impl BuiltInToolTrait for FileWrite {
80-
const DESCRIPTION: &str = FILE_WRITE_TOOL_DESCRIPTION;
81-
const INPUT_SCHEMA: &str = FILE_WRITE_SCHEMA;
82-
const NAME: BuiltInToolName = BuiltInToolName::FileWrite;
80+
#[cfg(unix)]
81+
const NEWLINE: &str = "\n";
82+
83+
impl BuiltInToolTrait for FsWrite {
84+
fn name() -> BuiltInToolName {
85+
BuiltInToolName::FsWrite
86+
}
87+
88+
fn description() -> std::borrow::Cow<'static, str> {
89+
FS_WRITE_TOOL_DESCRIPTION.into()
90+
}
91+
92+
fn input_schema() -> std::borrow::Cow<'static, str> {
93+
FS_WRITE_SCHEMA.into()
94+
}
8395
}
8496

8597
#[derive(Debug, Clone, Serialize, Deserialize)]
8698
#[serde(rename_all = "camelCase")]
8799
#[serde(tag = "command")]
88-
pub enum FileWrite {
100+
pub enum FsWrite {
89101
Create(FileCreate),
90102
StrReplace(StrReplace),
91103
Insert(Insert),
92104
}
93105

94-
impl FileWrite {
106+
impl FsWrite {
95107
pub fn path(&self) -> &str {
96108
match self {
97-
FileWrite::Create(v) => &v.path,
98-
FileWrite::StrReplace(v) => &v.path,
99-
FileWrite::Insert(v) => &v.path,
109+
FsWrite::Create(v) => &v.path,
110+
FsWrite::StrReplace(v) => &v.path,
111+
FsWrite::Insert(v) => &v.path,
100112
}
101113
}
102114

@@ -114,15 +126,15 @@ impl FileWrite {
114126
}
115127

116128
match &self {
117-
FileWrite::Create(_) => (),
118-
FileWrite::StrReplace(_) => {
129+
FsWrite::Create(_) => (),
130+
FsWrite::StrReplace(_) => {
119131
if !self.canonical_path()?.exists() {
120132
errors.push(
121133
"The provided path must exist in order to replace or insert contents into it".to_string(),
122134
);
123135
}
124136
},
125-
FileWrite::Insert(v) => {
137+
FsWrite::Insert(v) => {
126138
if v.content.is_empty() {
127139
errors.push("Content to insert must not be empty".to_string());
128140
}
@@ -136,35 +148,27 @@ impl FileWrite {
136148
}
137149
}
138150

139-
pub async fn make_context(&self) -> eyre::Result<FileWriteContext> {
151+
pub async fn make_context(&self) -> eyre::Result<FsWriteContext> {
140152
// TODO - return file diff context
141-
Ok(FileWriteContext {
153+
Ok(FsWriteContext {
142154
path: self.path().to_string(),
143155
})
144156
}
145157

146-
pub async fn execute(&self, _state: Option<&mut FileWriteState>) -> ToolExecutionResult {
158+
pub async fn execute(&self, _state: Option<&mut FsWriteState>) -> ToolExecutionResult {
147159
let path = self.canonical_path().map_err(ToolExecutionError::Custom)?;
148160

149161
match &self {
150-
FileWrite::Create(v) => v.execute(path).await?,
151-
FileWrite::StrReplace(v) => v.execute(path).await?,
152-
FileWrite::Insert(v) => v.execute(path).await?,
162+
FsWrite::Create(v) => v.execute(path).await?,
163+
FsWrite::StrReplace(v) => v.execute(path).await?,
164+
FsWrite::Insert(v) => v.execute(path).await?,
153165
}
154166

155167
Ok(Default::default())
156168
}
157169
}
158170

159-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
160-
#[serde(rename_all = "camelCase")]
161-
pub enum FileWriteOp {
162-
Create(FileCreate),
163-
StrReplace(StrReplace),
164-
Insert(Insert),
165-
}
166-
167-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
171+
#[derive(Debug, Clone, Serialize, Deserialize)]
168172
pub struct FileCreate {
169173
path: String,
170174
content: String,
@@ -190,7 +194,7 @@ impl FileCreate {
190194
}
191195
}
192196

193-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
197+
#[derive(Debug, Clone, Serialize, Deserialize)]
194198
#[serde(rename_all = "camelCase")]
195199
pub struct StrReplace {
196200
path: String,
@@ -238,7 +242,7 @@ impl StrReplace {
238242
}
239243
}
240244

241-
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
245+
#[derive(Debug, Clone, Serialize, Deserialize)]
242246
#[serde(rename_all = "camelCase")]
243247
pub struct Insert {
244248
path: String,
@@ -247,20 +251,53 @@ pub struct Insert {
247251
}
248252

249253
impl Insert {
250-
async fn execute(&self, _path: impl AsRef<Path>) -> Result<(), ToolExecutionError> {
251-
panic!("unimplemented")
254+
async fn execute(&self, path: impl AsRef<Path>) -> Result<(), ToolExecutionError> {
255+
let path = path.as_ref();
256+
257+
let mut file = tokio::fs::read_to_string(path)
258+
.await
259+
.map_err(|e| ToolExecutionError::io(format!("failed to read {}", path.to_string_lossy()), e))?;
260+
261+
let line_count = file.lines().count() as u32;
262+
263+
if let Some(insert_line) = self.insert_line {
264+
let insert_line = insert_line.clamp(0, line_count);
265+
266+
// Get the index to insert at.
267+
let mut i = 0;
268+
for line in LinesWithEndings::from(&file).take(insert_line as usize) {
269+
i += line.len();
270+
}
271+
272+
let mut content = self.content.clone();
273+
if !content.ends_with(NEWLINE) {
274+
content.push_str(NEWLINE);
275+
}
276+
file.insert_str(i, &content);
277+
} else {
278+
if !file.ends_with(NEWLINE) {
279+
file.push_str(NEWLINE);
280+
}
281+
file.push_str(&self.content);
282+
}
283+
284+
tokio::fs::write(path, file)
285+
.await
286+
.map_err(|e| ToolExecutionError::io(format!("failed to write to {}", path.to_string_lossy()), e))?;
287+
288+
Ok(())
252289
}
253290
}
254291

255292
#[derive(Debug, Clone, Serialize, Deserialize)]
256293
#[serde(rename_all = "camelCase")]
257-
pub struct FileWriteContext {
294+
pub struct FsWriteContext {
258295
path: String,
259296
}
260297

261298
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
262299
#[serde(rename_all = "camelCase")]
263-
pub struct FileWriteState {
300+
pub struct FsWriteState {
264301
pub line_tracker: FileLineTracker,
265302
}
266303

0 commit comments

Comments
 (0)