1use agent_stream_kit::{
2 ASKit, Agent, AgentContext, AgentData, AgentError, AgentOutput, AgentSpec, AgentValue, AsAgent,
3 askit_agent, async_trait,
4};
5use handlebars::Handlebars;
6use serde_json::json;
7
8static CATEGORY: &str = "Std/String";
9
10static PIN_STRING: &str = "string";
11static PIN_STRINGS: &str = "strings";
12static PIN_VALUE: &str = "value";
13static PIN_T: &str = "t";
14static PIN_F: &str = "f";
15
16static CONFIG_LEN: &str = "len";
17static CONFIG_OVERLAP: &str = "overlap";
18static CONFIG_SEP: &str = "sep";
19static CONFIG_TEMPLATE: &str = "template";
20
21#[askit_agent(
23 title = "IsString",
24 category = CATEGORY,
25 inputs = [PIN_VALUE],
26 outputs = [PIN_T, PIN_F],
27)]
28struct IsStringAgent {
29 data: AgentData,
30}
31
32#[async_trait]
33impl AsAgent for IsStringAgent {
34 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
35 Ok(Self {
36 data: AgentData::new(askit, id, spec),
37 })
38 }
39
40 async fn process(
41 &mut self,
42 ctx: AgentContext,
43 _pin: String,
44 value: AgentValue,
45 ) -> Result<(), AgentError> {
46 if value.is_string() {
47 self.try_output(ctx, PIN_T, value)
48 } else {
49 self.try_output(ctx, PIN_F, value)
50 }
51 }
52}
53
54#[askit_agent(
56 title = "IsEmptyString",
57 category = CATEGORY,
58 inputs = [PIN_STRING],
59 outputs = [PIN_T, PIN_F],
60)]
61struct IsEmptyStringAgent {
62 data: AgentData,
63}
64
65#[async_trait]
66impl AsAgent for IsEmptyStringAgent {
67 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
68 Ok(Self {
69 data: AgentData::new(askit, id, spec),
70 })
71 }
72
73 async fn process(
74 &mut self,
75 ctx: AgentContext,
76 _pin: String,
77 value: AgentValue,
78 ) -> Result<(), AgentError> {
79 let is_empty = if let Some(s) = value.as_str() {
80 s.is_empty()
81 } else {
82 false
83 };
84 if is_empty {
85 self.try_output(ctx, PIN_T, value)
86 } else {
87 self.try_output(ctx, PIN_F, value)
88 }
89 }
90}
91
92#[askit_agent(
108 title = "String Join",
109 category = CATEGORY,
110 inputs = [PIN_STRINGS],
111 outputs = [PIN_STRING],
112 string_config(name = CONFIG_SEP, default = "\\n")
113)]
114struct StringJoinAgent {
115 data: AgentData,
116}
117
118#[async_trait]
119impl AsAgent for StringJoinAgent {
120 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
121 Ok(Self {
122 data: AgentData::new(askit, id, spec),
123 })
124 }
125
126 async fn process(
127 &mut self,
128 ctx: AgentContext,
129 _pin: String,
130 value: AgentValue,
131 ) -> Result<(), AgentError> {
132 let config = self.configs()?;
133
134 let sep = config.get_string_or_default(CONFIG_SEP);
135
136 if value.is_array() {
137 let mut out = Vec::new();
138 for v in value
139 .as_array()
140 .ok_or_else(|| AgentError::InvalidArrayValue("Expected array".into()))?
141 {
142 out.push(v.as_str().unwrap_or_default());
143 }
144 let mut out = out.join(&sep);
145 out = out.replace("\\n", "\n");
146 out = out.replace("\\t", "\t");
147 out = out.replace("\\r", "\r");
148 out = out.replace("\\\\", "\\");
149 let out_value = AgentValue::string(out);
150 self.try_output(ctx, PIN_STRING, out_value)
151 } else {
152 self.try_output(ctx, PIN_STRING, value)
153 }
154 }
155}
156
157#[askit_agent(
158 title = "String Length Split",
159 category = CATEGORY,
160 inputs = [PIN_STRING],
161 outputs = [PIN_STRINGS],
162 integer_config(name = CONFIG_LEN, default = 65536),
163 integer_config(name = CONFIG_OVERLAP, default = 1024),
164)]
165struct StringLengthSplitAgent {
166 data: AgentData,
167}
168
169#[async_trait]
170impl AsAgent for StringLengthSplitAgent {
171 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
172 Ok(Self {
173 data: AgentData::new(askit, id, spec),
174 })
175 }
176
177 async fn process(
178 &mut self,
179 ctx: AgentContext,
180 _pin: String,
181 value: AgentValue,
182 ) -> Result<(), AgentError> {
183 let config = self.configs()?;
184
185 let n = config.get_integer_or_default(CONFIG_LEN) as usize;
186 if n <= 0 {
187 return Err(AgentError::InvalidConfig("n must be greater than 0".into()));
188 }
189
190 let overlap = config.get_integer_or_default(CONFIG_OVERLAP) as usize;
191 if overlap >= n {
192 return Err(AgentError::InvalidConfig(
193 "overlap must be less than n".into(),
194 ));
195 }
196
197 let s = value
198 .as_str()
199 .ok_or_else(|| AgentError::InvalidValue("Input value must be a string".into()))?;
200
201 let mut out = Vec::new();
202 let mut start = 0;
203 let len = s.len();
204 while start < len {
205 let mut end = usize::min(start + n, len);
206 while !s.is_char_boundary(end) {
207 end -= 1;
208 }
209 if end <= start {
210 end = start + s[start..].chars().next().map(|c| c.len_utf8()).unwrap_or(1);
211 }
212
213 out.push(AgentValue::string(s[start..end].to_string()));
214
215 if end == len {
216 break;
217 }
218
219 let mut next_start = end.saturating_sub(overlap);
220 while next_start < len && !s.is_char_boundary(next_start) {
221 next_start += 1;
222 }
223 start = next_start;
224 }
225 self.try_output(ctx, PIN_STRINGS, AgentValue::array(out))
226 }
227}
228
229#[askit_agent(
231 title = "Template String",
232 category = CATEGORY,
233 inputs = [PIN_VALUE],
234 outputs = [PIN_STRING],
235 string_config(name = CONFIG_TEMPLATE, default = "{{value}}")
236)]
237struct TemplateStringAgent {
238 data: AgentData,
239}
240
241#[async_trait]
242impl AsAgent for TemplateStringAgent {
243 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
244 Ok(Self {
245 data: AgentData::new(askit, id, spec),
246 })
247 }
248
249 async fn process(
250 &mut self,
251 ctx: AgentContext,
252 _pin: String,
253 value: AgentValue,
254 ) -> Result<(), AgentError> {
255 let config = self.configs()?;
256
257 let template = config.get_string_or_default(CONFIG_TEMPLATE);
258 if template.is_empty() {
259 return Err(AgentError::InvalidConfig("template is not set".into()));
260 }
261
262 let reg = handlebars_new();
263
264 if value.is_array() {
265 let mut out_arr = Vec::new();
266 for v in value
267 .as_array()
268 .ok_or_else(|| AgentError::InvalidArrayValue("Expected array".into()))?
269 {
270 let data = json!({"value": v});
271 let rendered_string = reg.render_template(&template, &data).map_err(|e| {
272 AgentError::InvalidValue(format!("Failed to render template: {}", e))
273 })?;
274 out_arr.push(rendered_string.into());
275 }
276 self.try_output(ctx, PIN_STRING, AgentValue::array(out_arr))
277 } else {
278 let data = json!({"value": value});
279 let rendered_string = reg.render_template(&template, &data).map_err(|e| {
280 AgentError::InvalidValue(format!("Failed to render template: {}", e))
281 })?;
282 let out_value = AgentValue::string(rendered_string);
283 self.try_output(ctx, PIN_STRING, out_value)
284 }
285 }
286}
287
288#[askit_agent(
290 title = "Template Text",
291 category = CATEGORY,
292 inputs = [PIN_VALUE],
293 outputs = [PIN_STRING],
294 text_config(name = CONFIG_TEMPLATE, default = "{{value}}")
295)]
296struct TemplateTextAgent {
297 data: AgentData,
298}
299
300#[async_trait]
301impl AsAgent for TemplateTextAgent {
302 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
303 Ok(Self {
304 data: AgentData::new(askit, id, spec),
305 })
306 }
307
308 async fn process(
309 &mut self,
310 ctx: AgentContext,
311 _pin: String,
312 value: AgentValue,
313 ) -> Result<(), AgentError> {
314 let config = self.configs()?;
315
316 let template = config.get_string_or_default(CONFIG_TEMPLATE);
317 if template.is_empty() {
318 return Err(AgentError::InvalidConfig("template is not set".into()));
319 }
320
321 let reg = handlebars_new();
322
323 if value.is_array() {
324 let mut out_arr = Vec::new();
325 for v in value
326 .as_array()
327 .ok_or_else(|| AgentError::InvalidArrayValue("Expected array".into()))?
328 {
329 let data = json!({"value": v});
330 let rendered_string = reg.render_template(&template, &data).map_err(|e| {
331 AgentError::InvalidValue(format!("Failed to render template: {}", e))
332 })?;
333 out_arr.push(rendered_string.into());
334 }
335 self.try_output(ctx, PIN_STRING, AgentValue::array(out_arr))
336 } else {
337 let data = json!({"value": value});
338 let rendered_string = reg.render_template(&template, &data).map_err(|e| {
339 AgentError::InvalidValue(format!("Failed to render template: {}", e))
340 })?;
341 let out_value = AgentValue::string(rendered_string);
342 self.try_output(ctx, PIN_STRING, out_value)
343 }
344 }
345}
346
347#[askit_agent(
349 title = "Template Array",
350 category = CATEGORY,
351 inputs = [PIN_VALUE],
352 outputs = [PIN_STRING],
353 text_config(name = CONFIG_TEMPLATE, default = "{{value}}")
354)]
355struct TemplateArrayAgent {
356 data: AgentData,
357}
358
359#[async_trait]
360impl AsAgent for TemplateArrayAgent {
361 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
362 Ok(Self {
363 data: AgentData::new(askit, id, spec),
364 })
365 }
366
367 async fn process(
368 &mut self,
369 ctx: AgentContext,
370 _pin: String,
371 value: AgentValue,
372 ) -> Result<(), AgentError> {
373 let config = self.configs()?;
374
375 let template = config.get_string_or_default(CONFIG_TEMPLATE);
376 if template.is_empty() {
377 return Err(AgentError::InvalidConfig("template is not set".into()));
378 }
379
380 let reg = handlebars_new();
381
382 if value.is_array() {
383 let rendered_string = reg.render_template(&template, &value).map_err(|e| {
384 AgentError::InvalidValue(format!("Failed to render template: {}", e))
385 })?;
386 self.try_output(ctx, PIN_STRING, AgentValue::string(rendered_string))
387 } else {
388 let d = AgentValue::array(vec![value.clone()]);
389 let rendered_string = reg.render_template(&template, &d).map_err(|e| {
390 AgentError::InvalidValue(format!("Failed to render template: {}", e))
391 })?;
392 let out_value = AgentValue::string(rendered_string);
393 self.try_output(ctx, PIN_STRING, out_value)
394 }
395 }
396}
397
398fn handlebars_new<'a>() -> Handlebars<'a> {
399 let mut reg = Handlebars::new();
400 reg.register_escape_fn(handlebars::no_escape);
401 reg.register_helper("to_json", Box::new(to_json_helper));
402
403 #[cfg(feature = "yaml")]
404 reg.register_helper("to_yaml", Box::new(to_yaml_helper));
405
406 reg
407}
408
409fn to_json_helper(
410 h: &handlebars::Helper<'_>,
411 _: &handlebars::Handlebars<'_>,
412 _: &handlebars::Context,
413 _: &mut handlebars::RenderContext<'_, '_>,
414 out: &mut dyn handlebars::Output,
415) -> handlebars::HelperResult {
416 if let Some(value) = h.param(0) {
417 let json_str = serde_json::to_string_pretty(&value.value()).map_err(|e| {
418 handlebars::RenderErrorReason::Other(format!("Failed to serialize to JSON: {}", e))
419 })?;
420 out.write(&json_str)?;
421 }
422 Ok(())
423}
424
425#[cfg(feature = "yaml")]
426fn to_yaml_helper(
427 h: &handlebars::Helper<'_>,
428 _: &handlebars::Handlebars<'_>,
429 _: &handlebars::Context,
430 _: &mut handlebars::RenderContext<'_, '_>,
431 out: &mut dyn handlebars::Output,
432) -> handlebars::HelperResult {
433 if let Some(value) = h.param(0) {
434 let yaml_str = serde_yaml_ng::to_string(&value.value()).map_err(|e| {
435 handlebars::RenderErrorReason::Other(format!("Failed to serialize to YAML: {}", e))
436 })?;
437 out.write(&yaml_str)?;
438 }
439 Ok(())
440}