tools: each module exports only tool() or tools(), nothing else

Every tool module now has a clean interface:
- read, write, edit, grep, glob, bash, vision: pub fn tool() -> Tool
- web: pub fn tools() -> [Tool; 2]
- memory: pub fn memory_tools() -> Vec<Tool>
- channels: pub fn tools() -> Vec<Tool>
- control: pub fn tools() -> Vec<Tool>

Definition and handler functions are private to each module.
mod.rs::tools() just chains the module exports.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
ProofOfConcept 2026-04-04 15:34:07 -04:00 committed by Kent Overstreet
parent fdb8c989f5
commit ed150df628
10 changed files with 65 additions and 36 deletions

View file

@ -33,7 +33,11 @@ struct Args {
timeout_secs: u64,
}
pub fn definition() -> ToolDef {
pub fn tool() -> super::Tool {
super::Tool { def: definition(), handler: |_a, v| Box::pin(async move { run_bash(&v).await }) }
}
fn definition() -> ToolDef {
ToolDef::new(
"bash",
"Execute a bash command and return its output. \
@ -55,7 +59,7 @@ pub fn definition() -> ToolDef {
)
}
pub async fn run_bash(args: &serde_json::Value) -> Result<String> {
async fn run_bash(args: &serde_json::Value) -> Result<String> {
let a: Args = serde_json::from_value(args.clone())
.context("invalid bash arguments")?;
let command = &a.command;

View file

@ -22,7 +22,11 @@ struct Args {
replace_all: bool,
}
pub fn definition() -> ToolDef {
pub fn tool() -> super::Tool {
super::Tool { def: definition(), handler: |_a, v| Box::pin(async move { edit_file(&v) }) }
}
fn definition() -> ToolDef {
ToolDef::new(
"edit_file",
"Perform exact string replacement in a file. The old_string must appear \
@ -53,7 +57,7 @@ pub fn definition() -> ToolDef {
)
}
pub fn edit_file(args: &serde_json::Value) -> Result<String> {
fn edit_file(args: &serde_json::Value) -> Result<String> {
let a: Args = serde_json::from_value(args.clone())
.context("invalid edit_file arguments")?;

View file

@ -20,7 +20,11 @@ struct Args {
fn default_path() -> String { ".".into() }
pub fn definition() -> ToolDef {
pub fn tool() -> super::Tool {
super::Tool { def: definition(), handler: |_a, v| Box::pin(async move { glob_search(&v) }) }
}
fn definition() -> ToolDef {
ToolDef::new(
"glob",
"Find files matching a glob pattern. Returns file paths sorted by \
@ -43,7 +47,7 @@ pub fn definition() -> ToolDef {
)
}
pub fn glob_search(args: &serde_json::Value) -> Result<String> {
fn glob_search(args: &serde_json::Value) -> Result<String> {
let a: Args = serde_json::from_value(args.clone())
.context("invalid glob arguments")?;

View file

@ -23,7 +23,11 @@ struct Args {
fn default_path() -> String { ".".into() }
pub fn definition() -> ToolDef {
pub fn tool() -> super::Tool {
super::Tool { def: definition(), handler: |_a, v| Box::pin(async move { grep(&v) }) }
}
fn definition() -> ToolDef {
ToolDef::new(
"grep",
"Search for a pattern in files. Returns matching file paths by default, \
@ -64,7 +68,7 @@ fn has_rg() -> bool {
*HAS_RG.get_or_init(|| Command::new("rg").arg("--version").output().is_ok())
}
pub fn grep(args: &serde_json::Value) -> Result<String> {
fn grep(args: &serde_json::Value) -> Result<String> {
let a: Args = serde_json::from_value(args.clone())
.context("invalid grep arguments")?;

View file

@ -265,7 +265,7 @@ fn query(args: &serde_json::Value) -> Result<String> {
.map_err(|e| anyhow::anyhow!("{}", e))
}
pub(super) fn output_def() -> ToolDef {
fn output_def() -> ToolDef {
ToolDef::new("output",
"Produce a named output value. Use this to pass structured results \
between steps subsequent prompts can see these in the conversation history.",
@ -275,7 +275,7 @@ pub(super) fn output_def() -> ToolDef {
},"required":["key","value"]}))
}
pub(super) fn output(args: &serde_json::Value) -> Result<String> {
fn output(args: &serde_json::Value) -> Result<String> {
let key = get_str(args, "key")?;
if key.starts_with("pid-") || key.contains('/') || key.contains("..") {
anyhow::bail!("invalid output key: {}", key);
@ -291,7 +291,7 @@ pub(super) fn output(args: &serde_json::Value) -> Result<String> {
// ── Journal tools ──────────────────────────────────────────────
pub(super) fn journal_tail_def() -> ToolDef {
fn journal_tail_def() -> ToolDef {
ToolDef::new("journal_tail",
"Read the last N journal entries (default 1).",
json!({"type":"object","properties":{
@ -299,7 +299,7 @@ pub(super) fn journal_tail_def() -> ToolDef {
}}))
}
pub(super) fn journal_tail(args: &serde_json::Value) -> Result<String> {
fn journal_tail(args: &serde_json::Value) -> Result<String> {
let count = args.get("count").and_then(|v| v.as_u64()).unwrap_or(1) as usize;
let store = load_store()?;
let mut entries: Vec<&crate::store::Node> = store.nodes.values()
@ -317,7 +317,7 @@ pub(super) fn journal_tail(args: &serde_json::Value) -> Result<String> {
}
}
pub(super) fn journal_new_def() -> ToolDef {
fn journal_new_def() -> ToolDef {
ToolDef::new("journal_new",
"Start a new journal entry.",
json!({"type":"object","properties":{
@ -327,7 +327,7 @@ pub(super) fn journal_new_def() -> ToolDef {
},"required":["name","title","body"]}))
}
pub(super) fn journal_new(args: &serde_json::Value) -> Result<String> {
fn journal_new(args: &serde_json::Value) -> Result<String> {
let name = get_str(args, "name")?;
let title = get_str(args, "title")?;
let body = get_str(args, "body")?;
@ -363,7 +363,7 @@ pub(super) fn journal_new(args: &serde_json::Value) -> Result<String> {
Ok(format!("New entry '{}' ({} words)", title, word_count))
}
pub(super) fn journal_update_def() -> ToolDef {
fn journal_update_def() -> ToolDef {
ToolDef::new("journal_update",
"Append text to the most recent journal entry (same thread continuing).",
json!({"type":"object","properties":{
@ -371,7 +371,7 @@ pub(super) fn journal_update_def() -> ToolDef {
},"required":["body"]}))
}
pub(super) fn journal_update(args: &serde_json::Value) -> Result<String> {
fn journal_update(args: &serde_json::Value) -> Result<String> {
let body = get_str(args, "body")?;
let mut store = load_store()?;
let latest_key = store.nodes.values()

View file

@ -203,17 +203,11 @@ pub async fn dispatch_shared(
/// Return all registered tools with definitions + handlers.
pub fn tools() -> Vec<Tool> {
let mut all = vec![
// File tools
Tool { def: read::definition(), handler: |_a, v| Box::pin(async move { read::read_file(&v) }) },
Tool { def: write::definition(), handler: |_a, v| Box::pin(async move { write::write_file(&v) }) },
Tool { def: edit::definition(), handler: |_a, v| Box::pin(async move { edit::edit_file(&v) }) },
Tool { def: grep::definition(), handler: |_a, v| Box::pin(async move { grep::grep(&v) }) },
Tool { def: glob::definition(), handler: |_a, v| Box::pin(async move { glob::glob_search(&v) }) },
Tool { def: bash::definition(), handler: |_a, v| Box::pin(async move { bash::run_bash(&v).await }) },
Tool { def: web::fetch_definition(), handler: |_a, v| Box::pin(async move { web::web_fetch(&v).await }) },
Tool { def: web::search_definition(), handler: |_a, v| Box::pin(async move { web::web_search(&v).await }) },
Tool { def: vision::definition(), handler: |_a, v| Box::pin(async move { vision::view_image_text(&v) }) },
read::tool(), write::tool(), edit::tool(),
grep::tool(), glob::tool(), bash::tool(),
vision::tool(),
];
all.extend(web::tools());
all.extend(memory::memory_tools());
all.extend(channels::tools());
all.extend(control::tools());

View file

@ -16,7 +16,11 @@ struct Args {
fn default_offset() -> usize { 1 }
pub fn definition() -> ToolDef {
pub fn tool() -> super::Tool {
super::Tool { def: definition(), handler: |_a, v| Box::pin(async move { read_file(&v) }) }
}
fn definition() -> ToolDef {
ToolDef::new(
"read_file",
"Read the contents of a file. Returns the file contents with line numbers.",
@ -41,7 +45,7 @@ pub fn definition() -> ToolDef {
)
}
pub fn read_file(args: &serde_json::Value) -> Result<String> {
fn read_file(args: &serde_json::Value) -> Result<String> {
let args: Args = serde_json::from_value(args.clone())
.context("invalid read_file arguments")?;

View file

@ -21,7 +21,7 @@ struct Args {
fn default_lines() -> usize { 50 }
pub(super) fn definition() -> ToolDef {
fn definition() -> ToolDef {
ToolDef::new(
"view_image",
"View an image file or capture a tmux pane screenshot. \
@ -48,8 +48,12 @@ pub(super) fn definition() -> ToolDef {
)
}
pub fn tool() -> super::Tool {
super::Tool { def: definition(), handler: |_a, v| Box::pin(async move { view_image_text(&v) }) }
}
/// Text-only version for the Tool registry.
pub fn view_image_text(args: &serde_json::Value) -> anyhow::Result<String> {
fn view_image_text(args: &serde_json::Value) -> anyhow::Result<String> {
let output = view_image(args)?;
Ok(output.text)
}

View file

@ -6,6 +6,13 @@ use serde_json::json;
use super::ToolDef;
pub fn tools() -> [super::Tool; 2] {
[
super::Tool { def: fetch_definition(), handler: |_a, v| Box::pin(async move { web_fetch(&v).await }) },
super::Tool { def: search_definition(), handler: |_a, v| Box::pin(async move { web_search(&v).await }) },
]
}
// ── Fetch ───────────────────────────────────────────────────────
#[derive(Deserialize)]
@ -13,7 +20,7 @@ struct FetchArgs {
url: String,
}
pub fn fetch_definition() -> ToolDef {
fn fetch_definition() -> ToolDef {
ToolDef::new(
"web_fetch",
"Fetch content from a URL and return it as text. \
@ -31,7 +38,7 @@ pub fn fetch_definition() -> ToolDef {
)
}
pub async fn web_fetch(args: &serde_json::Value) -> Result<String> {
async fn web_fetch(args: &serde_json::Value) -> Result<String> {
let a: FetchArgs = serde_json::from_value(args.clone())
.context("invalid web_fetch arguments")?;
@ -64,7 +71,7 @@ struct SearchArgs {
fn default_num_results() -> usize { 5 }
pub fn search_definition() -> ToolDef {
fn search_definition() -> ToolDef {
ToolDef::new(
"web_search",
"Search the web and return results. Use for finding \
@ -86,7 +93,7 @@ pub fn search_definition() -> ToolDef {
)
}
pub async fn web_search(args: &serde_json::Value) -> Result<String> {
async fn web_search(args: &serde_json::Value) -> Result<String> {
let a: SearchArgs = serde_json::from_value(args.clone())
.context("invalid web_search arguments")?;

View file

@ -13,7 +13,11 @@ struct Args {
content: String,
}
pub fn definition() -> ToolDef {
pub fn tool() -> super::Tool {
super::Tool { def: definition(), handler: |_a, v| Box::pin(async move { write_file(&v) }) }
}
fn definition() -> ToolDef {
ToolDef::new(
"write_file",
"Write content to a file. Creates the file if it doesn't exist, \
@ -35,7 +39,7 @@ pub fn definition() -> ToolDef {
)
}
pub fn write_file(args: &serde_json::Value) -> Result<String> {
fn write_file(args: &serde_json::Value) -> Result<String> {
let args: Args = serde_json::from_value(args.clone())
.context("invalid write_file arguments")?;