1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ToolKind {
9 Read,
11 Edit,
13 Execute,
15 Search,
17 Fetch,
19 Think,
21 SwitchMode,
23 #[default]
25 Other,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ToolCallLocation {
31 pub path: String,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub line: Option<u32>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub column: Option<u32>,
41}
42
43impl ToolCallLocation {
44 pub fn new(path: impl Into<String>) -> Self {
46 Self {
47 path: path.into(),
48 line: None,
49 column: None,
50 }
51 }
52
53 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#[derive(Debug, Clone, Default, Serialize, Deserialize)]
65pub struct ToolInfo {
66 pub title: String,
68
69 pub kind: ToolKind,
71
72 #[serde(default, skip_serializing_if = "Vec::is_empty")]
74 pub content: Vec<ToolInfoContent>,
75
76 #[serde(skip_serializing_if = "Option::is_none")]
78 pub locations: Option<Vec<ToolCallLocation>>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum ToolInfoContent {
85 Text { text: String },
87 Diff { diff: String },
89 Terminal { output: String },
91}
92
93impl ToolInfo {
94 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
122pub enum ToolUseType {
123 #[default]
125 ToolUse,
126 ServerToolUse,
128 McpToolUse,
130}
131
132#[derive(Debug, Clone)]
136pub struct ToolUseEntry {
137 pub tool_type: ToolUseType,
139
140 pub id: String,
142
143 pub name: String,
145
146 pub input: serde_json::Value,
148}
149
150impl ToolUseEntry {
151 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 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}