Skip to main content

vtcode_core/tools/tool_intent/
actions.rs

1use serde_json::Value;
2
3use crate::config::constants::tools;
4
5/// Determine the action for file_operation tool based on args.
6/// Returns the action string or a default if inference is possible.
7pub fn file_operation_action(args: &Value) -> Option<&str> {
8    args.get("action").and_then(|v| v.as_str()).or_else(|| {
9        let has_read_path = args.get("path").is_some()
10            || args.get("file_path").is_some()
11            || args.get("filepath").is_some()
12            || args.get("target_path").is_some()
13            || args.get("file").is_some()
14            || args.get("p").is_some();
15
16        if args.get("old_str").is_some() {
17            Some("edit")
18        } else if args.get("content").is_some() {
19            Some("write")
20        } else if args.get("destination").is_some() {
21            Some("move")
22        } else if has_read_path {
23            Some("read")
24        } else {
25            None
26        }
27    })
28}
29
30/// Determine the action for command_session tool based on args.
31/// Returns the action string or None if no inference is possible.
32pub fn command_session_action(args: &Value) -> Option<&str> {
33    args.get("action").and_then(|v| v.as_str()).or_else(|| {
34        // Check for standard command fields
35        if args.get("command").is_some()
36            || args.get("cmd").is_some()
37            || args.get("raw_command").is_some()
38            || crate::tools::command_args::has_indexed_command_parts(args)
39        {
40            Some("run")
41        } else if args.get("code").is_some() {
42            Some("code")
43        } else if args.get("input").is_some() || args.get("chars").is_some() || args.get("text").is_some() {
44            Some("write")
45        } else if args.get("spool_path").is_some()
46            || args.get("query").is_some()
47            || args.get("head_lines").is_some()
48            || args.get("tail_lines").is_some()
49            || args.get("max_matches").is_some()
50            || args.get("literal").is_some()
51        {
52            Some("inspect")
53        } else if args.get("session_id").is_some() || args.get("s").is_some() {
54            Some("poll")
55        } else {
56            None
57        }
58    })
59}
60
61fn action_matches(action: Option<&str>, expected: &str) -> bool {
62    action.is_some_and(|candidate| candidate.eq_ignore_ascii_case(expected))
63}
64
65pub fn action_matches_any(action: Option<&str>, expected: &[&str]) -> bool {
66    action.is_some_and(|candidate| {
67        expected
68            .iter()
69            .any(|expected_action| candidate.eq_ignore_ascii_case(expected_action))
70    })
71}
72
73pub fn file_operation_action_is(args: &Value, expected: &str) -> bool {
74    action_matches(file_operation_action(args), expected)
75}
76
77pub fn file_operation_action_in(args: &Value, expected: &[&str]) -> bool {
78    action_matches_any(file_operation_action(args), expected)
79}
80
81pub fn command_session_action_is(args: &Value, expected: &str) -> bool {
82    action_matches(command_session_action(args), expected)
83}
84
85pub fn command_session_action_in(args: &Value, expected: &[&str]) -> bool {
86    action_matches_any(command_session_action(args), expected)
87}
88
89/// Return the explicit action requested through the consolidated MCP tool.
90pub fn mcp_action(args: &Value) -> Option<&str> {
91    args.get("action").and_then(Value::as_str)
92}
93
94pub fn mcp_action_is(args: &Value, expected: &str) -> bool {
95    action_matches(mcp_action(args), expected)
96}
97
98/// Return the policy identity for actions whose risk differs from their
99/// otherwise low-risk parent tool.
100pub fn action_qualified_policy_name(tool_name: &str, args: Option<&Value>) -> Option<&'static str> {
101    if tool_name == tools::MCP_CONNECT_SERVER
102        || (tool_name == tools::MCP && args.is_some_and(|args| mcp_action_is(args, "connect")))
103    {
104        return Some("mcp:connect");
105    }
106
107    if tool_name == tools::MCP_DISCONNECT_SERVER
108        || (tool_name == tools::MCP && args.is_some_and(|args| mcp_action_is(args, "disconnect")))
109    {
110        return Some("mcp:disconnect");
111    }
112
113    None
114}