Skip to main content

elizaos_plugin_code/actions/
search_files.rs

1use async_trait::async_trait;
2use serde_json::Value;
3
4use crate::service::CoderService;
5use crate::{Action, ActionExample, ActionResult};
6
7pub struct SearchFilesAction;
8
9#[async_trait]
10impl Action for SearchFilesAction {
11    fn name(&self) -> &str {
12        "SEARCH_FILES"
13    }
14
15    fn similes(&self) -> Vec<&str> {
16        vec!["GREP", "RG", "FIND_IN_FILES", "SEARCH"]
17    }
18
19    fn description(&self) -> &str {
20        "Search for text across files under a directory."
21    }
22
23    async fn validate(&self, _message: &Value, _state: &Value) -> bool {
24        true
25    }
26
27    async fn handler(
28        &self,
29        message: &Value,
30        state: &Value,
31        service: Option<&mut CoderService>,
32    ) -> ActionResult {
33        let Some(svc) = service else {
34            return ActionResult {
35                success: false,
36                text: "Coder service is not available.".to_string(),
37                data: None,
38                error: Some("missing_service".to_string()),
39            };
40        };
41
42        let pattern = state
43            .get("pattern")
44            .and_then(|v| v.as_str())
45            .unwrap_or("")
46            .to_string();
47        let dirpath = state
48            .get("path")
49            .and_then(|v| v.as_str())
50            .unwrap_or(".")
51            .to_string();
52        let max_matches = state
53            .get("max_matches")
54            .and_then(|v| v.as_u64())
55            .unwrap_or(50) as usize;
56        if pattern.trim().is_empty() {
57            return ActionResult {
58                success: false,
59                text: "Missing pattern.".to_string(),
60                data: None,
61                error: Some("missing_pattern".to_string()),
62            };
63        }
64
65        let conv = message
66            .get("room_id")
67            .and_then(|v| v.as_str())
68            .or_else(|| message.get("agent_id").and_then(|v| v.as_str()))
69            .unwrap_or("default");
70
71        match svc
72            .search_files(conv, &pattern, &dirpath, max_matches)
73            .await
74        {
75            Ok(matches) => {
76                let text = if matches.is_empty() {
77                    format!("No matches for \"{}\".", pattern)
78                } else {
79                    matches
80                        .iter()
81                        .map(|(f, l, c)| format!("{}:L{}: {}", f, l, c))
82                        .collect::<Vec<String>>()
83                        .join("\n")
84                };
85                ActionResult {
86                    success: true,
87                    text,
88                    data: None,
89                    error: None,
90                }
91            }
92            Err(err) => ActionResult {
93                success: false,
94                text: err,
95                data: None,
96                error: Some("search_failed".to_string()),
97            },
98        }
99    }
100
101    fn examples(&self) -> Vec<ActionExample> {
102        vec![ActionExample {
103            user_message: "search files".to_string(),
104            agent_response: "Searching…".to_string(),
105        }]
106    }
107}