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#[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#[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#[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#[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 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}