pub fn is_mutating_tool(name: &str) -> bool {
matches!(name, "write" | "edit" | "bash")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RunMode {
Interactive,
NonInteractive,
}
pub const BUILTIN_TOOL_NAMES: &[&str] = &[
"read", "write", "edit", "bash", "grep", "find", "ls", "glob",
];
const CODING_DEFAULT_TOOLS: &[&str] = &["read", "write", "edit", "bash"];
const READ_ONLY_DEFAULT_TOOLS: &[&str] = &["read", "grep", "find", "ls", "glob"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ToolRuntimeConfig {
pub run_mode: RunMode,
pub active_tool_names: Vec<String>,
}
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
pub enum ToolPolicyError {
#[error("mutating tool '{tool}' requires --allow-mutating in non-interactive mode")]
MutatingToolRequiresOptIn { tool: String },
}
impl ToolRuntimeConfig {
pub fn resolve(
run_mode: RunMode,
allow_mutating: bool,
selection: ToolSelection,
) -> Result<Self, ToolPolicyError> {
let active_tool_names = resolve_active_tool_names(run_mode, allow_mutating, &selection)?;
Ok(Self {
run_mode,
active_tool_names,
})
}
}
fn resolve_active_tool_names(
run_mode: RunMode,
allow_mutating: bool,
selection: &ToolSelection,
) -> Result<Vec<String>, ToolPolicyError> {
match selection {
ToolSelection::Disabled | ToolSelection::NoBuiltin => Ok(Vec::new()),
ToolSelection::Allowlist(names) => {
if run_mode == RunMode::NonInteractive
&& !allow_mutating
&& let Some(tool) = names.iter().find(|name| is_mutating_tool(name))
{
return Err(ToolPolicyError::MutatingToolRequiresOptIn { tool: tool.clone() });
}
Ok(filter_tool_names(BUILTIN_TOOL_NAMES, selection))
}
ToolSelection::Default => {
let names = match (run_mode, allow_mutating) {
(RunMode::Interactive, _) | (RunMode::NonInteractive, true) => CODING_DEFAULT_TOOLS,
(RunMode::NonInteractive, false) => READ_ONLY_DEFAULT_TOOLS,
};
Ok(names.iter().map(|name| (*name).to_owned()).collect())
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ToolSelection {
Default,
Allowlist(Vec<String>),
Disabled,
NoBuiltin,
}
pub struct ToolFlags {
pub tools: Option<Vec<String>>,
pub no_tools: bool,
pub no_builtin_tools: bool,
}
pub fn resolve_tool_selection(flags: ToolFlags) -> ToolSelection {
if flags.no_tools {
ToolSelection::Disabled
} else if let Some(tools) = flags.tools {
ToolSelection::Allowlist(tools)
} else if flags.no_builtin_tools {
ToolSelection::NoBuiltin
} else {
ToolSelection::Default
}
}
pub fn filter_tool_names(all_names: &[&str], selection: &ToolSelection) -> Vec<String> {
match selection {
ToolSelection::Default => all_names.iter().map(|s| (*s).to_owned()).collect(),
ToolSelection::Disabled | ToolSelection::NoBuiltin => Vec::new(),
ToolSelection::Allowlist(names) => all_names
.iter()
.filter(|n| names.iter().any(|a| a == *n))
.map(|s| (*s).to_owned())
.collect(),
}
}