askit_std_agents/
file.rs

1use std::fs;
2use std::path::Path;
3
4use agent_stream_kit::{
5    ASKit, AgentContext, AgentData, AgentError, AgentOutput, AgentSpec, AgentValue, AsAgent,
6    askit_agent, async_trait,
7};
8use glob::glob;
9
10static CATEGORY: &str = "Std/File";
11
12static PIN_PATH: &str = "path";
13static PIN_FILES: &str = "files";
14static PIN_TEXT: &str = "text";
15static PIN_DATA: &str = "data";
16
17// Glob Agent
18#[askit_agent(
19    title = "Glob",
20    category = CATEGORY,
21    inputs = [PIN_PATH],
22    outputs = [PIN_FILES]
23)]
24struct GlobAgent {
25    data: AgentData,
26}
27
28#[async_trait]
29impl AsAgent for GlobAgent {
30    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
31        Ok(Self {
32            data: AgentData::new(askit, id, spec),
33        })
34    }
35
36    async fn process(
37        &mut self,
38        ctx: AgentContext,
39        _pin: String,
40        value: AgentValue,
41    ) -> Result<(), AgentError> {
42        let pat = value
43            .as_str()
44            .ok_or_else(|| AgentError::InvalidValue("not a string".to_string()))?;
45
46        let mut files = Vec::new();
47
48        for entry in glob(pat).map_err(|e| {
49            AgentError::InvalidValue(format!("Failed to read glob pattern {}: {}", pat, e))
50        })? {
51            match entry {
52                Ok(path) => {
53                    files.push(path.to_string_lossy().to_string().into());
54                }
55                Err(e) => {
56                    return Err(AgentError::InvalidValue(format!(
57                        "Failed to read glob entry: {}",
58                        e
59                    )));
60                }
61            }
62        }
63
64        let out_value = AgentValue::array(files);
65        self.try_output(ctx, PIN_FILES, out_value)
66    }
67}
68
69// List Files Agent
70#[askit_agent(
71    title = "List Files",
72    category = CATEGORY,
73    inputs = [PIN_PATH],
74    outputs = [PIN_FILES]
75)]
76struct ListFilesAgent {
77    data: AgentData,
78}
79
80#[async_trait]
81impl AsAgent for ListFilesAgent {
82    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
83        Ok(Self {
84            data: AgentData::new(askit, id, spec),
85        })
86    }
87
88    async fn process(
89        &mut self,
90        ctx: AgentContext,
91        _pin: String,
92        value: AgentValue,
93    ) -> Result<(), AgentError> {
94        let path = value
95            .as_str()
96            .ok_or_else(|| AgentError::InvalidValue("path is not a string".to_string()))?;
97        let path = Path::new(path);
98
99        if !path.exists() {
100            return Err(AgentError::InvalidValue(format!(
101                "Path does not exist: {}",
102                path.display()
103            )));
104        }
105
106        if !path.is_dir() {
107            return Err(AgentError::InvalidValue(format!(
108                "Path is not a directory: {}",
109                path.display()
110            )));
111        }
112
113        let mut files = Vec::new();
114        let entries = fs::read_dir(path).map_err(|e| {
115            AgentError::InvalidValue(format!(
116                "Failed to read directory {}: {}",
117                path.display(),
118                e
119            ))
120        })?;
121
122        for entry in entries {
123            let entry = entry.map_err(|e| {
124                AgentError::InvalidValue(format!("Failed to read directory entry: {}", e))
125            })?;
126            let file_name = entry.file_name().to_string_lossy().to_string();
127            files.push(file_name.into());
128        }
129
130        let out_value = AgentValue::array(files);
131        self.try_output(ctx, PIN_FILES, out_value)
132    }
133}
134
135// Read Text File Agent
136#[askit_agent(
137    title = "Read Text File",
138    category = CATEGORY,
139    inputs = [PIN_PATH],
140    outputs = [PIN_TEXT]
141)]
142struct ReadTextFileAgent {
143    data: AgentData,
144}
145
146#[async_trait]
147impl AsAgent for ReadTextFileAgent {
148    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
149        Ok(Self {
150            data: AgentData::new(askit, id, spec),
151        })
152    }
153
154    async fn process(
155        &mut self,
156        ctx: AgentContext,
157        _pin: String,
158        value: AgentValue,
159    ) -> Result<(), AgentError> {
160        let path = value
161            .as_str()
162            .ok_or_else(|| AgentError::InvalidValue("path is not a string".into()))?;
163        let path = Path::new(path);
164
165        if !path.exists() {
166            return Err(AgentError::InvalidValue(format!(
167                "Path does not exist: {}",
168                path.display()
169            )));
170        }
171
172        if !path.is_file() {
173            return Err(AgentError::InvalidValue(format!(
174                "Path is not a file: {}",
175                path.display()
176            )));
177        }
178
179        let content = fs::read_to_string(path).map_err(|e| {
180            AgentError::InvalidValue(format!("Failed to read file {}: {}", path.display(), e))
181        })?;
182        let out_value = AgentValue::string(content);
183        self.try_output(ctx, PIN_TEXT, out_value)
184    }
185}
186
187// Write Text File Agent
188#[askit_agent(
189    title = "Write Text File",
190    category = CATEGORY,
191    inputs = [PIN_DATA],
192    outputs = [PIN_DATA]
193)]
194struct WriteTextFileAgent {
195    data: AgentData,
196}
197
198#[async_trait]
199impl AsAgent for WriteTextFileAgent {
200    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
201        Ok(Self {
202            data: AgentData::new(askit, id, spec),
203        })
204    }
205
206    async fn process(
207        &mut self,
208        ctx: AgentContext,
209        _pin: String,
210        value: AgentValue,
211    ) -> Result<(), AgentError> {
212        let input = value
213            .as_object()
214            .ok_or_else(|| AgentError::InvalidValue("Input is not an object".into()))?;
215
216        let path = input
217            .get("path")
218            .ok_or_else(|| AgentError::InvalidValue("Missing 'path' in input".into()))?
219            .as_str()
220            .ok_or_else(|| AgentError::InvalidValue("'path' is not a string".into()))?;
221
222        let text = input
223            .get("text")
224            .ok_or_else(|| AgentError::InvalidValue("Missing 'text' in input".into()))?
225            .as_str()
226            .ok_or_else(|| AgentError::InvalidValue("'text' is not a string".into()))?;
227
228        let path = Path::new(path);
229
230        // Ensure parent directories exist
231        if let Some(parent) = path.parent() {
232            if !parent.exists() {
233                fs::create_dir_all(parent).map_err(|e| {
234                    AgentError::InvalidValue(format!("Failed to create parent directories: {}", e))
235                })?
236            }
237        }
238
239        fs::write(path, text).map_err(|e| {
240            AgentError::InvalidValue(format!("Failed to write file {}: {}", path.display(), e))
241        })?;
242
243        self.try_output(ctx, PIN_DATA, value)
244    }
245}