askit_std_agents/
string.rs

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/// Check if the input is a string.
22#[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/// Check if the input string is empty.
55#[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/// The `StringJoinAgent` is responsible for joining an array of strings into a single string
93/// using a specified separator. It processes input value, applies transformations to handle
94/// escape sequences (e.g., `\n`, `\t`), and outputs the resulting string.
95///
96/// # Configuration
97/// - `CONFIG_SEP`: Specifies the separator to use when joining strings. Defaults to an empty string.
98///
99/// # Input
100/// - Expects an array of strings as input value.
101///
102/// # Output
103/// - Produces a single joined string as output.
104///
105/// # Example
106/// Given the input `["Hello", "World"]` and `CONFIG_SEP` set to `" "`, the output will be `"Hello World"`.
107#[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// Template String Agent
230#[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// Template Text Agent
289#[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// Template Array Agent
348#[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}