test_tools.rs

  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}