1use super::*;
2use agent_settings::AgentSettings;
3use anyhow::Result;
4use gpui::{App, SharedString, Task};
5use std::future;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8/// A tool that echoes its input
9#[derive(JsonSchema, Serialize, Deserialize)]
10pub struct EchoToolInput {
11 /// The text to echo.
12 pub text: String,
13}
14
15pub struct EchoTool;
16
17impl AgentTool for EchoTool {
18 type Input = EchoToolInput;
19 type Output = String;
20
21 fn name() -> &'static str {
22 "echo"
23 }
24
25 fn kind() -> acp::ToolKind {
26 acp::ToolKind::Other
27 }
28
29 fn initial_title(
30 &self,
31 _input: Result<Self::Input, serde_json::Value>,
32 _cx: &mut App,
33 ) -> SharedString {
34 "Echo".into()
35 }
36
37 fn run(
38 self: Arc<Self>,
39 input: Self::Input,
40 _event_stream: ToolCallEventStream,
41 _cx: &mut App,
42 ) -> Task<Result<String>> {
43 Task::ready(Ok(input.text))
44 }
45}
46
47/// A tool that waits for a specified delay
48#[derive(JsonSchema, Serialize, Deserialize)]
49pub struct DelayToolInput {
50 /// The delay in milliseconds.
51 ms: u64,
52}
53
54pub struct DelayTool;
55
56impl AgentTool for DelayTool {
57 type Input = DelayToolInput;
58 type Output = String;
59
60 fn name() -> &'static str {
61 "delay"
62 }
63
64 fn initial_title(
65 &self,
66 input: Result<Self::Input, serde_json::Value>,
67 _cx: &mut App,
68 ) -> SharedString {
69 if let Ok(input) = input {
70 format!("Delay {}ms", input.ms).into()
71 } else {
72 "Delay".into()
73 }
74 }
75
76 fn kind() -> acp::ToolKind {
77 acp::ToolKind::Other
78 }
79
80 fn run(
81 self: Arc<Self>,
82 input: Self::Input,
83 _event_stream: ToolCallEventStream,
84 cx: &mut App,
85 ) -> Task<Result<String>>
86 where
87 Self: Sized,
88 {
89 let executor = cx.background_executor().clone();
90 cx.foreground_executor().spawn(async move {
91 executor.timer(Duration::from_millis(input.ms)).await;
92 Ok("Ding".to_string())
93 })
94 }
95}
96
97#[derive(JsonSchema, Serialize, Deserialize)]
98pub struct ToolRequiringPermissionInput {}
99
100pub struct ToolRequiringPermission;
101
102impl AgentTool for ToolRequiringPermission {
103 type Input = ToolRequiringPermissionInput;
104 type Output = String;
105
106 fn name() -> &'static str {
107 "tool_requiring_permission"
108 }
109
110 fn kind() -> acp::ToolKind {
111 acp::ToolKind::Other
112 }
113
114 fn initial_title(
115 &self,
116 _input: Result<Self::Input, serde_json::Value>,
117 _cx: &mut App,
118 ) -> SharedString {
119 "This tool requires permission".into()
120 }
121
122 fn run(
123 self: Arc<Self>,
124 _input: Self::Input,
125 event_stream: ToolCallEventStream,
126 cx: &mut App,
127 ) -> Task<Result<String>> {
128 let settings = AgentSettings::get_global(cx);
129 let decision = decide_permission_from_settings(Self::name(), "", settings);
130
131 let authorize = match decision {
132 ToolPermissionDecision::Allow => None,
133 ToolPermissionDecision::Deny(reason) => {
134 return Task::ready(Err(anyhow::anyhow!("{}", reason)));
135 }
136 ToolPermissionDecision::Confirm => {
137 let context = crate::ToolPermissionContext {
138 tool_name: "tool_requiring_permission".to_string(),
139 input_value: String::new(),
140 };
141 Some(event_stream.authorize("Authorize?", context, cx))
142 }
143 };
144
145 cx.foreground_executor().spawn(async move {
146 if let Some(authorize) = authorize {
147 authorize.await?;
148 }
149 Ok("Allowed".to_string())
150 })
151 }
152}
153
154#[derive(JsonSchema, Serialize, Deserialize)]
155pub struct InfiniteToolInput {}
156
157pub struct InfiniteTool;
158
159impl AgentTool for InfiniteTool {
160 type Input = InfiniteToolInput;
161 type Output = String;
162
163 fn name() -> &'static str {
164 "infinite"
165 }
166
167 fn kind() -> acp::ToolKind {
168 acp::ToolKind::Other
169 }
170
171 fn initial_title(
172 &self,
173 _input: Result<Self::Input, serde_json::Value>,
174 _cx: &mut App,
175 ) -> SharedString {
176 "Infinite Tool".into()
177 }
178
179 fn run(
180 self: Arc<Self>,
181 _input: Self::Input,
182 _event_stream: ToolCallEventStream,
183 cx: &mut App,
184 ) -> Task<Result<String>> {
185 cx.foreground_executor().spawn(async move {
186 future::pending::<()>().await;
187 unreachable!()
188 })
189 }
190}
191
192/// A tool that loops forever but properly handles cancellation via `select!`,
193/// similar to how edit_file_tool handles cancellation.
194#[derive(JsonSchema, Serialize, Deserialize)]
195pub struct CancellationAwareToolInput {}
196
197pub struct CancellationAwareTool {
198 pub was_cancelled: Arc<AtomicBool>,
199}
200
201impl CancellationAwareTool {
202 pub fn new() -> (Self, Arc<AtomicBool>) {
203 let was_cancelled = Arc::new(AtomicBool::new(false));
204 (
205 Self {
206 was_cancelled: was_cancelled.clone(),
207 },
208 was_cancelled,
209 )
210 }
211}
212
213impl AgentTool for CancellationAwareTool {
214 type Input = CancellationAwareToolInput;
215 type Output = String;
216
217 fn name() -> &'static str {
218 "cancellation_aware"
219 }
220
221 fn kind() -> acp::ToolKind {
222 acp::ToolKind::Other
223 }
224
225 fn initial_title(
226 &self,
227 _input: Result<Self::Input, serde_json::Value>,
228 _cx: &mut App,
229 ) -> SharedString {
230 "Cancellation Aware Tool".into()
231 }
232
233 fn run(
234 self: Arc<Self>,
235 _input: Self::Input,
236 event_stream: ToolCallEventStream,
237 cx: &mut App,
238 ) -> Task<Result<String>> {
239 cx.foreground_executor().spawn(async move {
240 // Wait for cancellation - this tool does nothing but wait to be cancelled
241 event_stream.cancelled_by_user().await;
242 self.was_cancelled.store(true, Ordering::SeqCst);
243 anyhow::bail!("Tool cancelled by user");
244 })
245 }
246}
247
248/// A tool that takes an object with map from letters to random words starting with that letter.
249/// All fiealds are required! Pass a word for every letter!
250#[derive(JsonSchema, Serialize, Deserialize)]
251pub struct WordListInput {
252 /// Provide a random word that starts with A.
253 a: Option<String>,
254 /// Provide a random word that starts with B.
255 b: Option<String>,
256 /// Provide a random word that starts with C.
257 c: Option<String>,
258 /// Provide a random word that starts with D.
259 d: Option<String>,
260 /// Provide a random word that starts with E.
261 e: Option<String>,
262 /// Provide a random word that starts with F.
263 f: Option<String>,
264 /// Provide a random word that starts with G.
265 g: Option<String>,
266}
267
268pub struct WordListTool;
269
270impl AgentTool for WordListTool {
271 type Input = WordListInput;
272 type Output = String;
273
274 fn name() -> &'static str {
275 "word_list"
276 }
277
278 fn kind() -> acp::ToolKind {
279 acp::ToolKind::Other
280 }
281
282 fn initial_title(
283 &self,
284 _input: Result<Self::Input, serde_json::Value>,
285 _cx: &mut App,
286 ) -> SharedString {
287 "List of random words".into()
288 }
289
290 fn run(
291 self: Arc<Self>,
292 _input: Self::Input,
293 _event_stream: ToolCallEventStream,
294 _cx: &mut App,
295 ) -> Task<Result<String>> {
296 Task::ready(Ok("ok".to_string()))
297 }
298}