Introduce ToolContext struct to simplify ToolExecutor type

Bundle the execution parameters (env, cancel) into a single
ToolContext struct, reducing the ToolExecutor closure signature
from 3 parameters to 2. This makes the type alias simpler and
means future parameters won't change the signature.

Key changes:
- Add ToolContext { env, cancel } in tool_registry.rs
- Update ToolExecutor type alias: Fn(Value, ToolContext) -> ...
- Update all tool factories in tools.rs, v4a_patch.rs,
  subagent.rs, mcp_integration.rs, and skills.rs
- Update all call sites in session.rs and tool_execution.rs
- Update test helpers and test call sites

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bryan Helmkamp 2026-02-27 23:46:23 -05:00
parent b391af7968
commit ee831c2db0
8 changed files with 81 additions and 88 deletions

View file

@ -21,7 +21,7 @@ pub fn make_mcp_tools(manager: Arc<McpConnectionManager>) -> Vec<RegisteredTool>
description: info.description.clone(),
parameters: info.input_schema.clone(),
},
executor: Arc::new(move |args, _env, _cancel| {
executor: Arc::new(move |args, _ctx| {
let mgr = Arc::clone(&mgr);
let name = name.clone();
let timeout = tool_timeout;
@ -85,13 +85,13 @@ mod tests {
use crate::execution_env::ExecutionEnvironment;
use crate::test_support::MockExecutionEnvironment;
use crate::tool_registry::ToolContext;
use tokio_util::sync::CancellationToken;
let env: Arc<dyn ExecutionEnvironment> = Arc::new(MockExecutionEnvironment::default());
let result = (tool.executor)(
serde_json::json!({"message": "test message"}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
assert_eq!(result.unwrap(), "test message");

View file

@ -9,8 +9,6 @@ use crate::profiles::EnvContext;
use crate::project_docs::discover_project_docs;
use crate::provider_profile::ProviderProfile;
use crate::skills::{default_skill_dirs, discover_skills, expand_skill, make_use_skill_tool, Skill};
use crate::tool_registry::ToolRegistry;
use crate::truncation::truncate_tool_output;
use crate::types::{AgentEvent, SessionState, Turn};
use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
@ -19,7 +17,7 @@ use futures::StreamExt;
use llm::client::Client;
use llm::error::{ProviderErrorKind, SdkError};
use llm::generate::StreamAccumulator;
use llm::types::{Message, Request, StreamEvent, ToolChoice, ToolResult};
use llm::types::{Message, Request, StreamEvent, ToolChoice};
use tokio_util::sync::CancellationToken;
pub struct Session {
@ -980,7 +978,7 @@ mod tests {
description: "Sets abort flag".into(),
parameters: serde_json::json!({"type": "object"}),
},
executor: Arc::new(move |_args, _env, _cancel| {
executor: Arc::new(move |_args, _ctx| {
let token = cancel_token_for_tool.clone();
Box::pin(async move {
token.cancel();
@ -1247,7 +1245,7 @@ mod tests {
"required": ["text"]
}),
},
executor: Arc::new(|_args, _env, _cancel| {
executor: Arc::new(|_args, _ctx| {
Box::pin(async move { Ok("should not reach".to_string()) })
}),
});
@ -1288,7 +1286,7 @@ mod tests {
"required": ["text"]
}),
},
executor: Arc::new(|_args, _env, _cancel| {
executor: Arc::new(|_args, _ctx| {
Box::pin(async move { Ok("tool executed".to_string()) })
}),
});
@ -1771,7 +1769,7 @@ mod tests {
description: "Read a file".into(),
parameters: serde_json::json!({"type": "object", "properties": {"file_path": {"type": "string"}}}),
},
executor: Arc::new(|_args, _env, _cancel| {
executor: Arc::new(|_args, _ctx| {
Box::pin(async move { Ok("file contents".to_string()) })
}),
};

View file

@ -170,7 +170,7 @@ pub fn make_use_skill_tool(skills: Arc<Vec<Skill>>) -> RegisteredTool {
"required": ["skill_name"]
}),
},
executor: Arc::new(move |args, _env, _cancel| {
executor: Arc::new(move |args, _ctx| {
let skills = skills.clone();
Box::pin(async move {
let name = required_str(&args, "skill_name")?;
@ -544,7 +544,8 @@ name: trimmed
let env: Arc<dyn crate::execution_env::ExecutionEnvironment> =
Arc::new(MockExecutionEnvironment::default());
let args = serde_json::json!({"skill_name": "commit"});
let result = (tool.executor)(args, env, tokio_util::sync::CancellationToken::new()).await;
let ctx = crate::tool_registry::ToolContext { env, cancel: tokio_util::sync::CancellationToken::new() };
let result = (tool.executor)(args, ctx).await;
assert_eq!(
result.unwrap(),
"Review changes and commit.\n\n{{user_input}}"
@ -559,7 +560,8 @@ name: trimmed
let env: Arc<dyn crate::execution_env::ExecutionEnvironment> =
Arc::new(MockExecutionEnvironment::default());
let args = serde_json::json!({"skill_name": "nonexistent"});
let result = (tool.executor)(args, env, tokio_util::sync::CancellationToken::new()).await;
let ctx = crate::tool_registry::ToolContext { env, cancel: tokio_util::sync::CancellationToken::new() };
let result = (tool.executor)(args, ctx).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Unknown skill"));
}
@ -572,7 +574,8 @@ name: trimmed
let env: Arc<dyn crate::execution_env::ExecutionEnvironment> =
Arc::new(MockExecutionEnvironment::default());
let args = serde_json::json!({});
let result = (tool.executor)(args, env, tokio_util::sync::CancellationToken::new()).await;
let ctx = crate::tool_registry::ToolContext { env, cancel: tokio_util::sync::CancellationToken::new() };
let result = (tool.executor)(args, ctx).await;
assert!(result.is_err());
assert!(result.unwrap_err().contains("Missing required parameter"));
}

View file

@ -254,7 +254,7 @@ pub fn make_spawn_agent_tool(
"required": ["task"]
}),
},
executor: Arc::new(move |args, _env, _cancel| {
executor: Arc::new(move |args, _ctx| {
let manager = manager.clone();
let session_factory = session_factory.clone();
Box::pin(async move {
@ -300,7 +300,7 @@ pub fn make_send_input_tool(
"required": ["agent_id", "message"]
}),
},
executor: Arc::new(move |args, _env, _cancel| {
executor: Arc::new(move |args, _ctx| {
let manager = manager.clone();
Box::pin(async move {
let agent_id = required_str(&args, "agent_id")?;
@ -333,7 +333,7 @@ pub fn make_wait_tool(
"required": ["agent_id"]
}),
},
executor: Arc::new(move |args, _env, _cancel| {
executor: Arc::new(move |args, _ctx| {
let manager = manager.clone();
Box::pin(async move {
let agent_id = required_str(&args, "agent_id")?;
@ -368,7 +368,7 @@ pub fn make_close_agent_tool(
"required": ["agent_id"]
}),
},
executor: Arc::new(move |args, _env, _cancel| {
executor: Arc::new(move |args, _ctx| {
let manager = manager.clone();
Box::pin(async move {
let agent_id = required_str(&args, "agent_id")?;

View file

@ -574,7 +574,7 @@ pub fn make_echo_tool() -> crate::tool_registry::RegisteredTool {
description: "Echoes the input".into(),
parameters: serde_json::json!({"type": "object", "properties": {"text": {"type": "string"}}}),
},
executor: Arc::new(|args, _env, _cancel| {
executor: Arc::new(|args, _ctx| {
Box::pin(async move {
let text = args
.get("text")
@ -594,7 +594,7 @@ pub fn make_error_tool() -> crate::tool_registry::RegisteredTool {
description: "Always fails".into(),
parameters: serde_json::json!({"type": "object"}),
},
executor: Arc::new(|_args, _env, _cancel| {
executor: Arc::new(|_args, _ctx| {
Box::pin(async move { Err("tool execution failed".to_string()) })
}),
}

View file

@ -6,16 +6,21 @@ use std::sync::Arc;
use llm::types::ToolDefinition;
use tokio_util::sync::CancellationToken;
pub struct ToolContext {
pub env: Arc<dyn ExecutionEnvironment>,
pub cancel: CancellationToken,
}
pub type ToolExecutor = Arc<
dyn Fn(
serde_json::Value,
Arc<dyn ExecutionEnvironment>,
CancellationToken,
ToolContext,
) -> Pin<Box<dyn Future<Output = Result<String, String>> + Send>>
+ Send
+ Sync,
>;
#[derive(Clone)]
pub struct RegisteredTool {
pub definition: ToolDefinition,
pub executor: ToolExecutor,
@ -74,7 +79,7 @@ mod tests {
description: format!("Tool {name}"),
parameters: serde_json::json!({"type": "object"}),
},
executor: Arc::new(|_args, _env, _cancel| Box::pin(async { Ok("ok".into()) })),
executor: Arc::new(|_args, _ctx| Box::pin(async { Ok("ok".into()) })),
}
}
@ -118,7 +123,7 @@ mod tests {
description: "version 1".into(),
parameters: serde_json::json!({}),
},
executor: Arc::new(|_args, _env, _cancel| Box::pin(async { Ok("v1".into()) })),
executor: Arc::new(|_args, _ctx| Box::pin(async { Ok("v1".into()) })),
});
registry.register(RegisteredTool {
definition: ToolDefinition {
@ -126,7 +131,7 @@ mod tests {
description: "version 2".into(),
parameters: serde_json::json!({}),
},
executor: Arc::new(|_args, _env, _cancel| Box::pin(async { Ok("v2".into()) })),
executor: Arc::new(|_args, _ctx| Box::pin(async { Ok("v2".into()) })),
});
let tool = registry.get("tool_a").unwrap();
@ -167,9 +172,11 @@ mod tests {
use crate::execution_env::ExecutionEnvironment;
use crate::test_support::MockExecutionEnvironment;
use super::ToolContext;
let env: Arc<dyn ExecutionEnvironment> = Arc::new(MockExecutionEnvironment::default());
let result = (tool.executor)(serde_json::json!({}), env, CancellationToken::new()).await;
let ctx = ToolContext { env, cancel: CancellationToken::new() };
let result = (tool.executor)(serde_json::json!({}), ctx).await;
assert_eq!(result.unwrap(), "ok");
}

View file

@ -108,12 +108,12 @@ pub fn make_write_file_tool() -> RegisteredTool {
"required": ["file_path", "content"]
}),
},
executor: Arc::new(|args, env, _cancel| {
executor: Arc::new(|args, ctx| {
Box::pin(async move {
let file_path = required_str(&args, "file_path")?;
let content = required_str(&args, "content")?;
env.write_file(file_path, content).await?;
ctx.env.write_file(file_path, content).await?;
Ok(format!("Successfully wrote to {file_path}"))
})
}),
@ -137,7 +137,7 @@ pub fn make_edit_file_tool() -> RegisteredTool {
"required": ["file_path", "old_string", "new_string"]
}),
},
executor: Arc::new(|args, env, _cancel| {
executor: Arc::new(|args, ctx| {
Box::pin(async move {
let file_path = required_str(&args, "file_path")?;
let old_string = required_str(&args, "old_string")?;
@ -147,7 +147,7 @@ pub fn make_edit_file_tool() -> RegisteredTool {
.and_then(serde_json::Value::as_bool)
.unwrap_or(false);
let numbered_content = env.read_file(file_path, None, None).await?;
let numbered_content = ctx.env.read_file(file_path, None, None).await?;
// Strip line numbers: each line looks like " 1 | content" or " 10 | content"
let raw_lines: Vec<&str> = numbered_content
@ -175,7 +175,7 @@ pub fn make_edit_file_tool() -> RegisteredTool {
raw_content.replacen(old_string, new_string, 1)
};
env.write_file(file_path, &new_content).await?;
ctx.env.write_file(file_path, &new_content).await?;
Ok(format!("Successfully edited {file_path}"))
})
}),
@ -205,7 +205,7 @@ pub fn make_shell_tool_with_config(config: &SessionConfig) -> RegisteredTool {
"required": ["command"]
}),
},
executor: Arc::new(move |args, env, cancel| {
executor: Arc::new(move |args, ctx| {
Box::pin(async move {
let command = required_str(&args, "command")?;
let timeout_ms = args
@ -214,8 +214,8 @@ pub fn make_shell_tool_with_config(config: &SessionConfig) -> RegisteredTool {
.unwrap_or(default_timeout)
.min(max_timeout);
let result = env
.exec_command(command, timeout_ms, None, None, Some(cancel))
let result = ctx.env
.exec_command(command, timeout_ms, None, None, Some(ctx.cancel))
.await?;
let mut output = String::new();
@ -251,7 +251,7 @@ pub fn make_grep_tool() -> RegisteredTool {
"required": ["pattern"]
}),
},
executor: Arc::new(|args, env, _cancel| {
executor: Arc::new(|args, ctx| {
Box::pin(async move {
let pattern = required_str(&args, "pattern")?;
let path = args
@ -274,7 +274,7 @@ pub fn make_grep_tool() -> RegisteredTool {
.map(|v| v as usize),
};
let results = env.grep(pattern, path, &options).await?;
let results = ctx.env.grep(pattern, path, &options).await?;
Ok(results.join("\n"))
})
}),
@ -296,14 +296,14 @@ pub fn make_glob_tool() -> RegisteredTool {
"required": ["pattern"]
}),
},
executor: Arc::new(|args, env, _cancel| {
executor: Arc::new(|args, ctx| {
Box::pin(async move {
let pattern = required_str(&args, "pattern")?;
let path = args
.get("path")
.and_then(serde_json::Value::as_str);
let results = env.glob(pattern, path).await?;
let results = ctx.env.glob(pattern, path).await?;
Ok(results.join("\n"))
})
}),
@ -328,7 +328,7 @@ pub(crate) fn make_read_many_files_tool() -> RegisteredTool {
"required": ["paths"]
}),
},
executor: Arc::new(|args, env, _cancel| {
executor: Arc::new(|args, ctx| {
Box::pin(async move {
let paths = args["paths"]
.as_array()
@ -339,7 +339,7 @@ pub(crate) fn make_read_many_files_tool() -> RegisteredTool {
let path = path_val
.as_str()
.ok_or_else(|| "each path must be a string".to_string())?;
match env.read_file(path, None, None).await {
match ctx.env.read_file(path, None, None).await {
Ok(content) => {
let _ = write!(output, "=== {path} ===\n{content}\n\n");
}
@ -369,7 +369,7 @@ pub(crate) fn make_list_dir_tool() -> RegisteredTool {
"required": ["path"]
}),
},
executor: Arc::new(|args, env, _cancel| {
executor: Arc::new(|args, ctx| {
Box::pin(async move {
let path = required_str(&args, "path")?;
let depth = args
@ -377,7 +377,7 @@ pub(crate) fn make_list_dir_tool() -> RegisteredTool {
.and_then(serde_json::Value::as_u64)
.map(|v| v as usize);
let entries = env.list_directory(path, depth).await?;
let entries = ctx.env.list_directory(path, depth).await?;
let lines: Vec<String> = entries
.iter()
.map(|e| {
@ -434,7 +434,7 @@ fn make_web_search_tool_with_api_key(api_key: Option<String>) -> RegisteredTool
"required": ["query"]
}),
},
executor: Arc::new(move |args, _env, _cancel| {
executor: Arc::new(move |args, _ctx| {
let client = client.clone();
let api_key = api_key.clone();
Box::pin(async move {
@ -488,7 +488,7 @@ pub(crate) fn make_web_fetch_tool(summarizer: Option<WebFetchSummarizer>) -> Reg
"required": ["url"]
}),
},
executor: Arc::new(move |args, env, cancel| {
executor: Arc::new(move |args, ctx| {
let summarizer = summarizer.clone();
Box::pin(async move {
let url = required_str(&args, "url")?;
@ -509,8 +509,8 @@ pub(crate) fn make_web_fetch_tool(summarizer: Option<WebFetchSummarizer>) -> Reg
"curl -sL --max-time {timeout_secs} -H 'User-Agent: attractor-agent/0.1' {escaped_url}"
);
let result = env
.exec_command(&command, timeout_ms, None, None, Some(cancel))
let result = ctx.env
.exec_command(&command, timeout_ms, None, None, Some(ctx.cancel))
.await?;
if result.exit_code != 0 {
@ -566,6 +566,7 @@ mod tests {
use super::*;
use crate::execution_env::*;
use crate::test_support::MockExecutionEnvironment;
use crate::tool_registry::ToolContext;
use std::collections::HashMap;
use tokio_util::sync::CancellationToken;
@ -579,7 +580,7 @@ mod tests {
apply_read_offset_limit: true,
..Default::default()
});
let result = (tool.executor)(serde_json::json!({"file_path": "/test.txt"}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({"file_path": "/test.txt"}), ToolContext { env, cancel: CancellationToken::new() }).await;
assert_eq!(result.unwrap(), " 1 | hello\n 2 | world");
}
@ -598,8 +599,7 @@ mod tests {
});
let result = (tool.executor)(
serde_json::json!({"file_path": "/test.txt", "offset": 2, "limit": 2}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
assert_eq!(result.unwrap(), " 2 | line2\n 3 | line3");
@ -612,8 +612,7 @@ mod tests {
let env_clone: Arc<dyn ExecutionEnvironment> = env.clone();
let result = (tool.executor)(
serde_json::json!({"file_path": "/out.txt", "content": "hello"}),
env_clone,
CancellationToken::new(),
ToolContext { env: env_clone, cancel: CancellationToken::new() },
)
.await;
assert_eq!(result.unwrap(), "Successfully wrote to /out.txt");
@ -639,8 +638,7 @@ mod tests {
"old_string": "hello",
"new_string": "goodbye"
}),
env_clone,
CancellationToken::new(),
ToolContext { env: env_clone, cancel: CancellationToken::new() },
)
.await;
assert_eq!(result.unwrap(), "Successfully edited /f.txt");
@ -664,8 +662,7 @@ mod tests {
"old_string": "missing",
"new_string": "replacement"
}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
assert_eq!(result.unwrap_err(), "old_string not found in file");
@ -686,8 +683,7 @@ mod tests {
"old_string": "aa",
"new_string": "cc"
}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
let err = result.unwrap_err();
@ -712,8 +708,7 @@ mod tests {
"new_string": "cc",
"replace_all": true
}),
env_clone,
CancellationToken::new(),
ToolContext { env: env_clone, cancel: CancellationToken::new() },
)
.await;
assert_eq!(result.unwrap(), "Successfully edited /f.txt");
@ -735,7 +730,7 @@ mod tests {
},
..Default::default()
});
let result = (tool.executor)(serde_json::json!({"command": "echo hello"}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({"command": "echo hello"}), ToolContext { env, cancel: CancellationToken::new() }).await;
let output = result.unwrap();
assert!(output.contains("Exit code: 0"));
assert!(output.contains("hello"));
@ -748,8 +743,7 @@ mod tests {
let env_clone: Arc<dyn ExecutionEnvironment> = env.clone();
let _result = (tool.executor)(
serde_json::json!({"command": "sleep 1", "timeout_ms": 5000}),
env_clone,
CancellationToken::new(),
ToolContext { env: env_clone, cancel: CancellationToken::new() },
)
.await;
assert_eq!(*env.captured_timeout.lock().unwrap(), Some(5000));
@ -768,7 +762,7 @@ mod tests {
},
..Default::default()
});
let result = (tool.executor)(serde_json::json!({"command": "false"}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({"command": "false"}), ToolContext { env, cancel: CancellationToken::new() }).await;
let output = result.unwrap();
assert!(output.contains("Exit code: 1"));
assert!(output.contains("error"));
@ -787,7 +781,7 @@ mod tests {
},
..Default::default()
});
let result = (tool.executor)(serde_json::json!({"command": "sleep 100"}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({"command": "sleep 100"}), ToolContext { env, cancel: CancellationToken::new() }).await;
let output = result.unwrap();
assert!(output.starts_with("Command timed out.\n"));
}
@ -799,7 +793,7 @@ mod tests {
grep_results: vec!["src/main.rs:10:fn main()".into(), "src/lib.rs:5:pub fn".into()],
..Default::default()
});
let result = (tool.executor)(serde_json::json!({"pattern": "fn"}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({"pattern": "fn"}), ToolContext { env, cancel: CancellationToken::new() }).await;
let output = result.unwrap();
assert!(output.contains("src/main.rs:10:fn main()"));
assert!(output.contains("src/lib.rs:5:pub fn"));
@ -812,7 +806,7 @@ mod tests {
glob_results: vec!["src/main.rs".into(), "src/lib.rs".into()],
..Default::default()
});
let result = (tool.executor)(serde_json::json!({"pattern": "src/**/*.rs"}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({"pattern": "src/**/*.rs"}), ToolContext { env, cancel: CancellationToken::new() }).await;
let output = result.unwrap();
assert!(output.contains("src/main.rs"));
assert!(output.contains("src/lib.rs"));
@ -822,7 +816,7 @@ mod tests {
async fn web_search_missing_api_key_returns_error() {
let tool = make_web_search_tool_with_api_key(None);
let env: Arc<dyn ExecutionEnvironment> = Arc::new(MockExecutionEnvironment::default());
let result = (tool.executor)(serde_json::json!({"query": "test"}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({"query": "test"}), ToolContext { env, cancel: CancellationToken::new() }).await;
let err = result.unwrap_err();
assert!(err.contains("BRAVE_SEARCH_API_KEY"), "error should mention BRAVE_SEARCH_API_KEY, got: {err}");
}
@ -831,7 +825,7 @@ mod tests {
async fn web_search_missing_query_returns_error() {
let tool = make_web_search_tool_with_api_key(Some("fake-key".into()));
let env: Arc<dyn ExecutionEnvironment> = Arc::new(MockExecutionEnvironment::default());
let result = (tool.executor)(serde_json::json!({}), env, CancellationToken::new()).await;
let result = (tool.executor)(serde_json::json!({}), ToolContext { env, cancel: CancellationToken::new() }).await;
let err = result.unwrap_err();
assert!(err.contains("query"), "error should mention missing query, got: {err}");
}
@ -875,8 +869,7 @@ mod tests {
let env_clone: Arc<dyn ExecutionEnvironment> = env.clone();
let result = (tool.executor)(
serde_json::json!({"url": "https://example.com"}),
env_clone,
CancellationToken::new(),
ToolContext { env: env_clone, cancel: CancellationToken::new() },
)
.await;
let output = result.unwrap();
@ -894,8 +887,7 @@ mod tests {
let env: Arc<dyn ExecutionEnvironment> = Arc::new(MockExecutionEnvironment::default());
let result = (tool.executor)(
serde_json::json!({"url": "ftp://example.com/file"}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
let err = result.unwrap_err();
@ -909,8 +901,7 @@ mod tests {
let env_clone: Arc<dyn ExecutionEnvironment> = env.clone();
let _result = (tool.executor)(
serde_json::json!({"url": "https://example.com", "timeout_ms": 15000}),
env_clone,
CancellationToken::new(),
ToolContext { env: env_clone, cancel: CancellationToken::new() },
)
.await;
assert_eq!(*env.captured_timeout.lock().unwrap(), Some(15000));
@ -925,8 +916,7 @@ mod tests {
let env_clone: Arc<dyn ExecutionEnvironment> = env.clone();
let _result = (tool.executor)(
serde_json::json!({"url": "https://example.com", "timeout_ms": 120000}),
env_clone,
CancellationToken::new(),
ToolContext { env: env_clone, cancel: CancellationToken::new() },
)
.await;
assert_eq!(*env.captured_timeout.lock().unwrap(), Some(60000));
@ -950,8 +940,7 @@ mod tests {
});
let result = (tool.executor)(
serde_json::json!({"url": "https://example.com"}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
let output = result.unwrap();
@ -974,8 +963,7 @@ mod tests {
});
let result = (tool.executor)(
serde_json::json!({"url": "https://nonexistent.example.com"}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
let err = result.unwrap_err();
@ -1009,8 +997,7 @@ mod tests {
});
let result = (tool.executor)(
serde_json::json!({"url": "https://example.com", "prompt": "What is Rust?"}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
let output = result.unwrap();
@ -1032,8 +1019,7 @@ mod tests {
});
let result = (tool.executor)(
serde_json::json!({"url": "https://example.com", "prompt": "What is Rust?"}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
let output = result.unwrap();
@ -1074,8 +1060,7 @@ mod tests {
let env: Arc<dyn ExecutionEnvironment> = Arc::new(MockExecutionEnvironment::default());
let result = (tool.executor)(
serde_json::json!({"query": "rust programming language"}),
env,
CancellationToken::new(),
ToolContext { env, cancel: CancellationToken::new() },
)
.await;
let output = result.expect("web search should succeed with valid API key");

View file

@ -227,7 +227,7 @@ pub fn make_apply_patch_tool() -> RegisteredTool {
"required": ["patch"]
}),
},
executor: Arc::new(|args, env, _cancel| {
executor: Arc::new(|args, ctx| {
Box::pin(async move {
let patch_text = args
.get("patch")
@ -235,7 +235,7 @@ pub fn make_apply_patch_tool() -> RegisteredTool {
.ok_or_else(|| "Missing required parameter: patch".to_string())?;
let ops = parse_v4a_patch(patch_text)?;
apply_patch_operations(&ops, env.as_ref()).await
apply_patch_operations(&ops, ctx.env.as_ref()).await
})
}),
}