tool: add execution context

This commit is contained in:
2026-06-09 19:31:11 +09:00
parent b21fab82fc
commit d8aed7befe
39 changed files with 1212 additions and 259 deletions
+45 -11
View File
@@ -101,7 +101,11 @@ impl Drop for BashTool {
#[async_trait]
impl Tool for BashTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let params: BashParams = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid Bash input: {e}")))?;
let timeout_secs = params
@@ -394,7 +398,10 @@ mod tests {
assert_eq!(meta.name, "Bash");
let inp = serde_json::json!({ "command": "echo hello" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert_eq!(out.summary, "$ echo hello");
assert_eq!(out.content.as_deref().map(str::trim), Some("hello"));
}
@@ -407,7 +414,10 @@ mod tests {
let inp = serde_json::json!({
"command": "echo out; echo err 1>&2",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains("out"));
assert!(body.contains("err"));
@@ -419,7 +429,10 @@ mod tests {
let tool = make_tool(&h);
let inp = serde_json::json!({ "command": "exit 7" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("exit 7"), "summary: {}", out.summary);
assert!(
out.content.is_none(),
@@ -441,12 +454,16 @@ mod tests {
"command": format!("cd {}", sub.to_str().unwrap()),
})
.to_string(),
Default::default(),
)
.await
.unwrap();
let pwd_out = tool
.execute(&serde_json::json!({ "command": "pwd" }).to_string())
.execute(
&serde_json::json!({ "command": "pwd" }).to_string(),
Default::default(),
)
.await
.unwrap();
let body = pwd_out.content.unwrap();
@@ -467,7 +484,10 @@ mod tests {
"command": "sleep 30",
"timeout": 1,
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(
out.summary.contains("timed out"),
"summary: {}",
@@ -480,7 +500,10 @@ mod tests {
let h = setup();
let tool = make_tool(&h);
let err = tool.execute("not json").await.unwrap_err();
let err = tool
.execute("not json", Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -494,7 +517,10 @@ mod tests {
let inp = serde_json::json!({
"command": "for i in $(seq 1 200); do echo line $i; done",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.expect("expected content");
assert!(
@@ -523,7 +549,10 @@ mod tests {
let inp = serde_json::json!({
"command": "printf 'x%.0s' {1..20480}",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(
body.contains(spill_dir.to_str().unwrap()),
@@ -542,7 +571,10 @@ mod tests {
"command": "(sleep 0.05; echo bg) &",
"timeout": 5,
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(
!out.summary.contains("timed out"),
"summary: {}",
@@ -559,7 +591,9 @@ mod tests {
let inp = serde_json::json!({
"command": "for i in $(seq 1 200); do echo $i; done",
});
tool.execute(&inp.to_string()).await.unwrap();
tool.execute(&inp.to_string(), Default::default())
.await
.unwrap();
// The spill dir should now contain exactly one bash-*.log file.
let files_before: Vec<_> = std::fs::read_dir(&spill_dir)
+33 -8
View File
@@ -36,7 +36,11 @@ pub(crate) struct EditTool {
#[async_trait]
impl Tool for EditTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let params: EditParams = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid Edit input: {e}")))?;
@@ -169,7 +173,10 @@ mod tests {
let def = read_tool(fs.clone(), tracker.clone());
let (_, reader) = def();
let inp = serde_json::json!({ "file_path": file.to_str().unwrap() });
reader.execute(&inp.to_string()).await.unwrap();
reader
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
}
#[tokio::test]
@@ -188,7 +195,10 @@ mod tests {
"old_string": "foo bar",
"new_string": "foo baz",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("1 replacement"));
assert_eq!(
std::fs::read_to_string(&file).unwrap(),
@@ -212,7 +222,10 @@ mod tests {
"new_string": "y",
"replace_all": true,
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("3 replacements"));
assert_eq!(std::fs::read_to_string(&file).unwrap(), "y y y\n");
}
@@ -231,7 +244,10 @@ mod tests {
"old_string": "a",
"new_string": "b",
});
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -249,7 +265,10 @@ mod tests {
"old_string": "world",
"new_string": "x",
});
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -266,7 +285,10 @@ mod tests {
"old_string": "foo",
"new_string": "bar",
});
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -287,7 +309,10 @@ mod tests {
"old_string": "foo",
"new_string": "bar",
});
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("modified externally"), "{msg}");
}
+33 -8
View File
@@ -35,7 +35,11 @@ pub(crate) struct GlobTool {
#[async_trait]
impl Tool for GlobTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let params: GlobParams = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid Glob input: {e}")))?;
@@ -239,7 +243,10 @@ mod tests {
assert_eq!(meta.name, "Glob");
let inp = serde_json::json!({ "pattern": "**/*.rs" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("2 file(s)"));
let body = out.content.unwrap();
assert!(body.contains("a.rs"));
@@ -261,7 +268,10 @@ mod tests {
let def = glob_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "*.rs" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
let new_pos = body.find("new.rs").unwrap();
let old_pos = body.find("old.rs").unwrap();
@@ -274,7 +284,10 @@ mod tests {
let def = glob_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "**/*.nonexistent" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("No files"));
assert!(out.content.is_none());
}
@@ -285,7 +298,10 @@ mod tests {
let def = glob_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "[unterminated" });
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -317,7 +333,10 @@ mod tests {
let def = glob_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "**/*.rs" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap_or_default();
assert!(body.contains("visible.rs"));
assert!(
@@ -335,7 +354,10 @@ mod tests {
let def = glob_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "*.rs" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains(".hidden.rs"));
assert!(body.contains("visible.rs"));
@@ -358,7 +380,10 @@ mod tests {
"path": link.to_str().unwrap(),
"pattern": "**/*.rs",
});
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("Glob does not follow symlink directories"),
+65 -16
View File
@@ -82,7 +82,11 @@ pub(crate) struct GrepTool {
#[async_trait]
impl Tool for GrepTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let params: GrepParams = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid Grep input: {e}")))?;
@@ -563,7 +567,10 @@ mod tests {
let def = grep_tool(scoped);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "needle" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap_or_default();
assert!(body.contains("visible.txt"));
assert!(
@@ -583,7 +590,10 @@ mod tests {
assert_eq!(meta.name, "Grep");
let inp = serde_json::json!({ "pattern": "bravo" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("1 file"));
assert!(out.content.unwrap().contains("a.txt"));
}
@@ -599,7 +609,10 @@ mod tests {
"pattern": "two",
"output_mode": "content",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains(":2:two"));
}
@@ -616,7 +629,10 @@ mod tests {
"pattern": "x",
"output_mode": "count",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains("a.txt:3"));
assert!(body.contains("b.txt:1"));
@@ -635,7 +651,10 @@ mod tests {
"-i": true,
"output_mode": "content",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert!(out.content.unwrap().contains("HELLO"));
}
@@ -654,7 +673,10 @@ mod tests {
"output_mode": "content",
"-C": 1,
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
// should contain: line2 (before context), MATCH, line4 (after context)
assert!(body.contains("line2"));
@@ -677,7 +699,10 @@ mod tests {
"multiline": true,
"output_mode": "content",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains("foo"));
}
@@ -694,7 +719,10 @@ mod tests {
"pattern": "target",
"glob": "*.rs",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains("a.rs"));
assert!(!body.contains("b.txt"));
@@ -712,7 +740,10 @@ mod tests {
"pattern": "target",
"type": "rust",
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains("a.rs"));
assert!(!body.contains("b.py"));
@@ -731,7 +762,10 @@ mod tests {
"pattern": "x",
"head_limit": 2,
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert_eq!(body.lines().count(), 2);
assert!(out.summary.contains("truncated at 2"));
@@ -752,7 +786,10 @@ mod tests {
"offset": 3,
"head_limit": 10,
});
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
// We skipped 3, so only 2 should remain.
assert_eq!(body.lines().count(), 2);
@@ -769,7 +806,10 @@ mod tests {
let def = grep_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "needle" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
let body = out.content.unwrap();
assert!(body.contains("b.txt"));
assert!(!body.contains("a.bin"));
@@ -781,7 +821,10 @@ mod tests {
let def = grep_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "(" });
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -794,7 +837,10 @@ mod tests {
"pattern": "x",
"type": "nonexistent",
});
let err = tool.execute(&inp.to_string()).await.unwrap_err();
let err = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -805,7 +851,10 @@ mod tests {
let def = grep_tool(fs);
let (_, tool) = def();
let inp = serde_json::json!({ "pattern": "zzz" });
let out = tool.execute(&inp.to_string()).await.unwrap();
let out = tool
.execute(&inp.to_string(), Default::default())
.await
.unwrap();
assert_eq!(out.summary, "No files matched");
assert!(out.content.is_none());
}
+21 -5
View File
@@ -36,7 +36,11 @@ pub(crate) struct ReadTool {
#[async_trait]
impl Tool for ReadTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let params: ReadParams = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid Read input: {e}")))?;
let offset = params.offset.unwrap_or(0);
@@ -155,7 +159,10 @@ mod tests {
assert_eq!(meta.name, "Read");
let input = serde_json::json!({ "file_path": file.to_str().unwrap() });
let out = tool.execute(&input.to_string()).await.unwrap();
let out = tool
.execute(&input.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("Read 3 line(s)"));
let body = out.content.unwrap();
assert!(body.contains(" 1\talpha"));
@@ -178,7 +185,10 @@ mod tests {
"offset": 1,
"limit": 2,
});
let out = tool.execute(&input.to_string()).await.unwrap();
let out = tool
.execute(&input.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("[2..3] of 5"));
let body = out.content.unwrap();
assert!(body.contains(" 2\t2"));
@@ -193,7 +203,10 @@ mod tests {
let input = serde_json::json!({
"file_path": dir.path().join("nope.txt").to_str().unwrap()
});
let err = tool.execute(&input.to_string()).await.unwrap_err();
let err = tool
.execute(&input.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::ExecutionFailed(_)));
}
@@ -202,7 +215,10 @@ mod tests {
let (_dir, fs, tracker) = setup();
let def = read_tool(fs, tracker);
let (_, tool) = def();
let err = tool.execute("not json").await.unwrap_err();
let err = tool
.execute("not json", Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
}
+10 -2
View File
@@ -146,7 +146,11 @@ struct WebFetchTool {
#[async_trait]
impl Tool for WebSearchTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let input: WebSearchInput = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid WebSearch input: {e}")))?;
self.web.run_search(input).await
@@ -193,7 +197,11 @@ impl WebTools {
#[async_trait]
impl Tool for WebFetchTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let input: WebFetchInput = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid WebFetch input: {e}")))?;
self.web.run_fetch(input).await
+30 -7
View File
@@ -30,7 +30,11 @@ pub(crate) struct WriteTool {
#[async_trait]
impl Tool for WriteTool {
async fn execute(&self, input_json: &str) -> Result<ToolOutput, ToolError> {
async fn execute(
&self,
input_json: &str,
_ctx: llm_worker::tool::ToolExecutionContext,
) -> Result<ToolOutput, ToolError> {
let params: WriteParams = serde_json::from_str(input_json)
.map_err(|e| ToolError::InvalidArgument(format!("invalid Write input: {e}")))?;
@@ -118,7 +122,10 @@ mod tests {
"file_path": file.to_str().unwrap(),
"content": "hello\n",
});
let out = tool.execute(&input.to_string()).await.unwrap();
let out = tool
.execute(&input.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("Created"));
assert_eq!(std::fs::read_to_string(&file).unwrap(), "hello\n");
}
@@ -135,7 +142,10 @@ mod tests {
"file_path": file.to_str().unwrap(),
"content": "new",
});
let err = tool.execute(&input.to_string()).await.unwrap_err();
let err = tool
.execute(&input.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
@@ -148,7 +158,10 @@ mod tests {
let read_def = read_tool(fs.clone(), tracker.clone());
let (_, reader) = read_def();
let read_in = serde_json::json!({ "file_path": file.to_str().unwrap() });
reader.execute(&read_in.to_string()).await.unwrap();
reader
.execute(&read_in.to_string(), Default::default())
.await
.unwrap();
let write_def = write_tool(fs, tracker);
let (_, writer) = write_def();
@@ -156,7 +169,10 @@ mod tests {
"file_path": file.to_str().unwrap(),
"content": "new\n",
});
let out = writer.execute(&write_in.to_string()).await.unwrap();
let out = writer
.execute(&write_in.to_string(), Default::default())
.await
.unwrap();
assert!(out.summary.contains("Overwrote"));
assert_eq!(std::fs::read_to_string(&file).unwrap(), "new\n");
}
@@ -171,7 +187,10 @@ mod tests {
let read_def = read_tool(fs.clone(), tracker.clone());
let (_, reader) = read_def();
reader
.execute(&serde_json::json!({ "file_path": file.to_str().unwrap() }).to_string())
.execute(
&serde_json::json!({ "file_path": file.to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap();
@@ -187,6 +206,7 @@ mod tests {
"content": "new",
})
.to_string(),
Default::default(),
)
.await
.unwrap_err();
@@ -205,7 +225,10 @@ mod tests {
"file_path": outside.path().join("x.txt").to_str().unwrap(),
"content": "x",
});
let err = tool.execute(&input.to_string()).await.unwrap_err();
let err = tool
.execute(&input.to_string(), Default::default())
.await
.unwrap_err();
assert!(matches!(err, ToolError::InvalidArgument(_)));
}
}
+40 -10
View File
@@ -66,13 +66,17 @@ async fn unicode_path_and_content() {
"content": content,
})
.to_string(),
Default::default(),
)
.await
.unwrap();
let read = reg.get("Read");
let out = read
.execute(&json!({ "file_path": file.to_str().unwrap() }).to_string())
.execute(
&json!({ "file_path": file.to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap();
let body = out.content.unwrap();
@@ -98,7 +102,10 @@ async fn symlink_to_outside_scope_is_rejected_for_write() {
// target sits outside the scope.
let read = reg.get("Read");
let read_err = read
.execute(&json!({ "file_path": link.to_str().unwrap() }).to_string())
.execute(
&json!({ "file_path": link.to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap_err();
assert!(
@@ -119,6 +126,7 @@ async fn symlink_to_outside_scope_is_rejected_for_write() {
"content": "overwritten",
})
.to_string(),
Default::default(),
)
.await
.unwrap_err();
@@ -147,7 +155,10 @@ async fn broken_symlink_reports_target_and_repair_hint() {
let read = reg.get("Read");
let err = read
.execute(&json!({ "file_path": link.to_str().unwrap() }).to_string())
.execute(
&json!({ "file_path": link.to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap_err();
let msg = format!("{err}");
@@ -165,7 +176,10 @@ async fn empty_file_read_and_edit() {
let read = reg.get("Read");
let out = read
.execute(&json!({ "file_path": file.to_str().unwrap() }).to_string())
.execute(
&json!({ "file_path": file.to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap();
assert!(out.summary.contains("0 line"));
@@ -180,6 +194,7 @@ async fn empty_file_read_and_edit() {
"new_string": "bar",
})
.to_string(),
Default::default(),
)
.await
.unwrap_err();
@@ -196,7 +211,10 @@ async fn very_long_single_line() {
let read = reg.get("Read");
let out = read
.execute(&json!({ "file_path": file.to_str().unwrap() }).to_string())
.execute(
&json!({ "file_path": file.to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap();
// Should return exactly 1 line
@@ -208,7 +226,10 @@ async fn relative_path_is_rejected() {
let (_dir, _spill, reg) = setup();
let read = reg.get("Read");
let err = read
.execute(&json!({ "file_path": "relative.txt" }).to_string())
.execute(
&json!({ "file_path": "relative.txt" }).to_string(),
Default::default(),
)
.await
.unwrap_err();
assert!(format!("{err}").contains("absolute"));
@@ -219,7 +240,10 @@ async fn directory_target_is_rejected_for_read() {
let (dir, _spill, reg) = setup();
let read = reg.get("Read");
let err = read
.execute(&json!({ "file_path": dir.path().to_str().unwrap() }).to_string())
.execute(
&json!({ "file_path": dir.path().to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap_err();
assert!(format!("{err}").contains("directory"));
@@ -237,6 +261,7 @@ async fn deeply_nested_new_file_is_created() {
"content": "deep\n",
})
.to_string(),
Default::default(),
)
.await
.unwrap();
@@ -250,9 +275,12 @@ async fn replace_preserves_unicode() {
std::fs::write(&file, "🦀 rust 🦀\n").unwrap();
let read = reg.get("Read");
read.execute(&json!({ "file_path": file.to_str().unwrap() }).to_string())
.await
.unwrap();
read.execute(
&json!({ "file_path": file.to_str().unwrap() }).to_string(),
Default::default(),
)
.await
.unwrap();
let edit = reg.get("Edit");
edit.execute(
@@ -262,6 +290,7 @@ async fn replace_preserves_unicode() {
"new_string": "ラスト",
})
.to_string(),
Default::default(),
)
.await
.unwrap();
@@ -282,6 +311,7 @@ async fn grep_handles_unicode_pattern() {
"output_mode": "content",
})
.to_string(),
Default::default(),
)
.await
.unwrap();
+2 -2
View File
@@ -66,13 +66,13 @@ fn setup() -> (TempDir, TempDir, Registry) {
}
async fn call(tool: &Arc<dyn Tool>, input: serde_json::Value) -> llm_worker::tool::ToolOutput {
tool.execute(&input.to_string())
tool.execute(&input.to_string(), Default::default())
.await
.expect("tool execution failed")
}
async fn call_err(tool: &Arc<dyn Tool>, input: serde_json::Value) -> llm_worker::tool::ToolError {
tool.execute(&input.to_string())
tool.execute(&input.to_string(), Default::default())
.await
.expect_err("expected error")
}