claude_code_acp/types/
tool.rs

1//! Tool-related types for ACP notifications
2
3use serde::{Deserialize, Serialize};
4
5/// Tool kind for categorizing tools in UI
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ToolKind {
9    /// File read operations
10    Read,
11    /// File edit/write operations
12    Edit,
13    /// Command execution
14    Execute,
15    /// Search operations (grep, glob)
16    Search,
17    /// Network fetch operations
18    Fetch,
19    /// Thinking/planning operations
20    Think,
21    /// Mode switching operations
22    SwitchMode,
23    /// Other/unknown tool types
24    #[default]
25    Other,
26}
27
28/// Location information for tool calls
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ToolCallLocation {
31    /// File path or location identifier
32    pub path: String,
33
34    /// Optional line number
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub line: Option<u32>,
37
38    /// Optional column number
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub column: Option<u32>,
41}
42
43impl ToolCallLocation {
44    /// Create a new location with just a path
45    pub fn new(path: impl Into<String>) -> Self {
46        Self {
47            path: path.into(),
48            line: None,
49            column: None,
50        }
51    }
52
53    /// Create a location with path and line number
54    pub fn with_line(path: impl Into<String>, line: u32) -> Self {
55        Self {
56            path: path.into(),
57            line: Some(line),
58            column: None,
59        }
60    }
61}
62
63/// Tool information for display in UI
64#[derive(Debug, Clone, Default, Serialize, Deserialize)]
65pub struct ToolInfo {
66    /// Human-readable title for the tool call
67    pub title: String,
68
69    /// Tool kind/category
70    pub kind: ToolKind,
71
72    /// Content to display (e.g., command output, file content preview)
73    #[serde(default, skip_serializing_if = "Vec::is_empty")]
74    pub content: Vec<ToolInfoContent>,
75
76    /// Locations affected by this tool call
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub locations: Option<Vec<ToolCallLocation>>,
79}
80
81/// Content type for tool info display
82#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum ToolInfoContent {
85    /// Text content
86    Text { text: String },
87    /// Diff content
88    Diff { diff: String },
89    /// Terminal output
90    Terminal { output: String },
91}
92
93impl ToolInfo {
94    /// Create a new tool info
95    pub fn new(title: impl Into<String>, kind: ToolKind) -> Self {
96        Self {
97            title: title.into(),
98            kind,
99            content: Vec::new(),
100            locations: None,
101        }
102    }
103
104    /// Add a location
105    pub fn with_location(mut self, path: impl Into<String>) -> Self {
106        self.locations
107            .get_or_insert_with(Vec::new)
108            .push(ToolCallLocation::new(path));
109        self
110    }
111
112    /// Add text content
113    pub fn with_text(mut self, text: impl Into<String>) -> Self {
114        self.content
115            .push(ToolInfoContent::Text { text: text.into() });
116        self
117    }
118}
119
120/// Type of tool use (for caching purposes)
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
122pub enum ToolUseType {
123    /// Standard tool use
124    #[default]
125    ToolUse,
126    /// Server-side tool use
127    ServerToolUse,
128    /// MCP tool use
129    McpToolUse,
130}
131
132/// Cached tool use entry
133///
134/// Used to correlate tool_use blocks with their tool_result blocks.
135#[derive(Debug, Clone)]
136pub struct ToolUseEntry {
137    /// Type of tool use
138    pub tool_type: ToolUseType,
139
140    /// Tool use ID
141    pub id: String,
142
143    /// Tool name
144    pub name: String,
145
146    /// Tool input parameters
147    pub input: serde_json::Value,
148}
149
150impl ToolUseEntry {
151    /// Create a new tool use entry
152    pub fn new(id: String, name: String, input: serde_json::Value) -> Self {
153        Self {
154            tool_type: ToolUseType::ToolUse,
155            id,
156            name,
157            input,
158        }
159    }
160
161    /// Create with a specific tool type
162    pub fn with_type(mut self, tool_type: ToolUseType) -> Self {
163        self.tool_type = tool_type;
164        self
165    }
166}
167
168#[cfg(test)]
169mod tests {
170    use super::*;
171    use serde_json::json;
172
173    #[test]
174    fn test_tool_kind_serialization() {
175        assert_eq!(serde_json::to_string(&ToolKind::Read).unwrap(), "\"read\"");
176        assert_eq!(
177            serde_json::to_string(&ToolKind::Execute).unwrap(),
178            "\"execute\""
179        );
180    }
181
182    #[test]
183    fn test_tool_info_builder() {
184        let info = ToolInfo::new("Read file.txt", ToolKind::Read)
185            .with_location("/path/to/file.txt")
186            .with_text("File content preview...");
187
188        assert_eq!(info.title, "Read file.txt");
189        assert_eq!(info.kind, ToolKind::Read);
190        assert_eq!(info.locations.as_ref().unwrap().len(), 1);
191        assert_eq!(info.content.len(), 1);
192    }
193
194    #[test]
195    fn test_tool_use_entry() {
196        let entry = ToolUseEntry::new(
197            "tool_123".to_string(),
198            "Read".to_string(),
199            json!({"file_path": "/test.txt"}),
200        );
201
202        assert_eq!(entry.id, "tool_123");
203        assert_eq!(entry.name, "Read");
204        assert_eq!(entry.tool_type, ToolUseType::ToolUse);
205    }
206
207    #[test]
208    fn test_tool_call_location() {
209        let loc = ToolCallLocation::with_line("/path/to/file.rs", 42);
210        assert_eq!(loc.path, "/path/to/file.rs");
211        assert_eq!(loc.line, Some(42));
212        assert!(loc.column.is_none());
213    }
214}