use crate::llm::ToolSpec;
use serde_json::Value;
use super::ToolContext;
use super::policy::{Approval, NetworkAccess};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ToolGate {
Always,
Interactive,
Network,
FilesWrite,
Shell,
}
#[derive(Clone, Copy)]
pub(super) struct ToolDef {
pub name: &'static str,
pub description: &'static str,
pub gate: ToolGate,
pub schema: fn() -> Value,
pub summary: fn(&Value) -> String,
pub output: fn(&Value) -> String,
}
pub(super) fn find_def(name: &str) -> Option<&'static ToolDef> {
TOOL_DEFS.iter().find(|def| def.name == name)
}
use super::preview;
const TOOL_DEFS: &[ToolDef] = &[
ToolDef {
name: "list",
description: "Find workspace paths with fff-style file discovery. Use first for discovery. Exact files/dirs and globs are honored; a non-existing non-glob `path` is treated as a fuzzy file query. Returns items, total count, and truncation state.",
gate: ToolGate::Always,
schema: super::schema::schema_list,
summary: preview::summary_list,
output: preview::preview_list,
},
ToolDef {
name: "read",
description: "Read one UTF-8 text file. Prefer narrow `offset`/`limit` slices over full-file reads.",
gate: ToolGate::Always,
schema: super::schema::schema_read,
summary: preview::summary_read,
output: preview::preview_read,
},
ToolDef {
name: "search",
description: "Search workspace text with fff grep over indexed files. `path` may be an exact file/dir or whitespace-separated exact paths. Respects gitignore/exclude and skips binary/oversized files. Auto mode uses literal for plain text and Rust regex for regex-looking patterns; use `mode=literal` for exact strings.",
gate: ToolGate::Always,
schema: super::schema::schema_search,
summary: preview::summary_search,
output: preview::preview_search,
},
ToolDef {
name: "sloc",
description: "Count source lines with tokei for repository sizing. `path` may be one path or whitespace-separated paths.",
gate: ToolGate::Always,
schema: super::schema::schema_sloc,
summary: preview::summary_sloc,
output: preview::preview_sloc,
},
ToolDef {
name: "todo",
description: "Manage the in-memory todo list. Available in read-only modes; persistence to TODO.md is opt-in and requires write approval.",
gate: ToolGate::Always,
schema: super::schema::schema_todo,
summary: preview::summary_todo,
output: preview::preview_todo,
},
ToolDef {
name: "ask",
description: "Ask the user in interactive runs. Reserve for genuine ambiguity or irreversible choices.",
gate: ToolGate::Interactive,
schema: super::schema::schema_ask,
summary: preview::summary_ask,
output: preview::preview_ask,
},
ToolDef {
name: "webfetch",
description: "Fetch a public web page and return its content as markdown, text, HTML, or XML. Minimal HTTP-only build; blocks localhost/private IPs.",
gate: ToolGate::Network,
schema: super::schema::schema_webfetch,
summary: preview::summary_webfetch,
output: preview::preview_webfetch,
},
ToolDef {
name: "replace",
description: "Replace text across fff-indexed workspace files under an exact file/dir. Default mode is Rust regex with captures; use `mode=literal` for exact text. Respects gitignore/exclude, skips unsafe/unreadable files, and reports diffs. Inspect/search before changing.",
gate: ToolGate::FilesWrite,
schema: super::schema::schema_replace,
summary: preview::summary_replace,
output: preview::preview_replace,
},
ToolDef {
name: "patch",
description: "Apply a unified/git diff to existing UTF-8 workspace files. Use for coordinated multi-file edits; inspect first and keep patches focused.",
gate: ToolGate::FilesWrite,
schema: super::schema::schema_patch,
summary: preview::summary_patch,
output: preview::preview_patch,
},
ToolDef {
name: "bash",
description: "Run a shell command in the workspace. Use only when file tools are insufficient or when you must run/check something.",
gate: ToolGate::Shell,
schema: super::schema::schema_bash,
summary: preview::summary_bash,
output: preview::preview_bash,
},
];
fn tool_enabled(ctx: &ToolContext, def: &ToolDef) -> bool {
match def.gate {
ToolGate::Always => true,
ToolGate::Interactive => ctx.interactive,
ToolGate::Network => ctx.policy.network == NetworkAccess::Enabled,
ToolGate::FilesWrite => ctx.policy.files_write() != Approval::Deny,
ToolGate::Shell => ctx.policy.shell != Approval::Deny,
}
}
pub(super) fn spec(def: &ToolDef) -> ToolSpec {
ToolSpec {
name: def.name.to_string(),
description: def.description.to_string(),
parameters: (def.schema)(),
cache: None,
}
}
pub(crate) fn tool_specs(ctx: &ToolContext) -> Vec<ToolSpec> {
enabled_tool_defs(ctx).into_iter().map(spec).collect()
}
pub(super) fn enabled_tool_defs(ctx: &ToolContext) -> Vec<&'static ToolDef> {
TOOL_DEFS
.iter()
.filter(|def| tool_enabled(ctx, def))
.collect()
}