opi_coding_agent/
policy.rs1pub fn is_mutating_tool(name: &str) -> bool {
8 matches!(name, "write" | "edit" | "bash")
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RunMode {
14 Interactive,
15 NonInteractive,
16}
17
18pub const BUILTIN_TOOL_NAMES: &[&str] = &[
20 "read", "write", "edit", "bash", "grep", "find", "ls", "glob",
21];
22
23const CODING_DEFAULT_TOOLS: &[&str] = &["read", "write", "edit", "bash"];
24const READ_ONLY_DEFAULT_TOOLS: &[&str] = &["read", "grep", "find", "ls", "glob"];
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct ToolRuntimeConfig {
29 pub run_mode: RunMode,
30 pub active_tool_names: Vec<String>,
31}
32
33#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
34pub enum ToolPolicyError {
35 #[error("mutating tool '{tool}' requires --allow-mutating in non-interactive mode")]
36 MutatingToolRequiresOptIn { tool: String },
37}
38
39impl ToolRuntimeConfig {
40 pub fn resolve(
41 run_mode: RunMode,
42 allow_mutating: bool,
43 selection: ToolSelection,
44 ) -> Result<Self, ToolPolicyError> {
45 let active_tool_names = resolve_active_tool_names(run_mode, allow_mutating, &selection)?;
46 Ok(Self {
47 run_mode,
48 active_tool_names,
49 })
50 }
51}
52
53fn resolve_active_tool_names(
54 run_mode: RunMode,
55 allow_mutating: bool,
56 selection: &ToolSelection,
57) -> Result<Vec<String>, ToolPolicyError> {
58 match selection {
59 ToolSelection::Disabled | ToolSelection::NoBuiltin => Ok(Vec::new()),
60 ToolSelection::Allowlist(names) => {
61 if run_mode == RunMode::NonInteractive
62 && !allow_mutating
63 && let Some(tool) = names.iter().find(|name| is_mutating_tool(name))
64 {
65 return Err(ToolPolicyError::MutatingToolRequiresOptIn { tool: tool.clone() });
66 }
67 Ok(filter_tool_names(BUILTIN_TOOL_NAMES, selection))
68 }
69 ToolSelection::Default => {
70 let names = match (run_mode, allow_mutating) {
71 (RunMode::Interactive, _) | (RunMode::NonInteractive, true) => CODING_DEFAULT_TOOLS,
72 (RunMode::NonInteractive, false) => READ_ONLY_DEFAULT_TOOLS,
73 };
74 Ok(names.iter().map(|name| (*name).to_owned()).collect())
75 }
76 }
77}
78
79#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum ToolSelection {
86 Default,
88 Allowlist(Vec<String>),
90 Disabled,
92 NoBuiltin,
94}
95
96pub struct ToolFlags {
98 pub tools: Option<Vec<String>>,
100 pub no_tools: bool,
102 pub no_builtin_tools: bool,
104}
105
106pub fn resolve_tool_selection(flags: ToolFlags) -> ToolSelection {
110 if flags.no_tools {
111 ToolSelection::Disabled
112 } else if let Some(tools) = flags.tools {
113 ToolSelection::Allowlist(tools)
114 } else if flags.no_builtin_tools {
115 ToolSelection::NoBuiltin
116 } else {
117 ToolSelection::Default
118 }
119}
120
121pub fn filter_tool_names(all_names: &[&str], selection: &ToolSelection) -> Vec<String> {
126 match selection {
127 ToolSelection::Default => all_names.iter().map(|s| (*s).to_owned()).collect(),
128 ToolSelection::Disabled | ToolSelection::NoBuiltin => Vec::new(),
129 ToolSelection::Allowlist(names) => all_names
130 .iter()
131 .filter(|n| names.iter().any(|a| a == *n))
132 .map(|s| (*s).to_owned())
133 .collect(),
134 }
135}