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;
6
7static CATEGORY: &str = "Std/String";
8
9static PIN_DATA: &str = "data";
10static PIN_STRING: &str = "string";
11static PIN_STRINGS: &str = "strings";
12
13static CONFIG_SEP: &str = "sep";
14static CONFIG_TEMPLATE: &str = "template";
15
16/// The `StringJoinAgent` is responsible for joining an array of strings into a single string
17/// using a specified separator. It processes input value, applies transformations to handle
18/// escape sequences (e.g., `\n`, `\t`), and outputs the resulting string.
19///
20/// # Configuration
21/// - `CONFIG_SEP`: Specifies the separator to use when joining strings. Defaults to an empty string.
22///
23/// # Input
24/// - Expects an array of strings as input value.
25///
26/// # Output
27/// - Produces a single joined string as output.
28///
29/// # Example
30/// Given the input `["Hello", "World"]` and `CONFIG_SEP` set to `" "`, the output will be `"Hello World"`.
31#[askit_agent(
32    title = "String Join",
33    category = CATEGORY,
34    inputs = [PIN_STRINGS],
35    outputs = [PIN_STRING],
36    string_config(name = CONFIG_SEP, default = "\\n")
37)]
38struct StringJoinAgent {
39    data: AgentData,
40}
41
42#[async_trait]
43impl AsAgent for StringJoinAgent {
44    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
45        Ok(Self {
46            data: AgentData::new(askit, id, spec),
47        })
48    }
49
50    async fn process(
51        &mut self,
52        ctx: AgentContext,
53        _pin: String,
54        value: AgentValue,
55    ) -> Result<(), AgentError> {
56        let config = self.configs()?;
57
58        let sep = config.get_string_or_default(CONFIG_SEP);
59
60        if value.is_array() {
61            let mut out = Vec::new();
62            for v in value
63                .as_array()
64                .ok_or_else(|| AgentError::InvalidArrayValue("Expected array".into()))?
65            {
66                out.push(v.as_str().unwrap_or_default());
67            }
68            let mut out = out.join(&sep);
69            out = out.replace("\\n", "\n");
70            out = out.replace("\\t", "\t");
71            out = out.replace("\\r", "\r");
72            out = out.replace("\\\\", "\\");
73            let out_value = AgentValue::string(out);
74            self.try_output(ctx, PIN_STRING, out_value)
75        } else {
76            self.try_output(ctx, PIN_STRING, value)
77        }
78    }
79}
80
81// Template String Agent
82#[askit_agent(
83    title = "Template String",
84    category = CATEGORY,
85    inputs = [PIN_DATA],
86    outputs = [PIN_STRING],
87    string_config(name = CONFIG_TEMPLATE, default = "{{value}}")
88)]
89struct TemplateStringAgent {
90    data: AgentData,
91}
92
93#[async_trait]
94impl AsAgent for TemplateStringAgent {
95    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
96        Ok(Self {
97            data: AgentData::new(askit, id, spec),
98        })
99    }
100
101    async fn process(
102        &mut self,
103        ctx: AgentContext,
104        _pin: String,
105        value: AgentValue,
106    ) -> Result<(), AgentError> {
107        let config = self.configs()?;
108
109        let template = config.get_string_or_default(CONFIG_TEMPLATE);
110        if template.is_empty() {
111            return Err(AgentError::InvalidConfig("template is not set".into()));
112        }
113
114        let reg = handlebars_new();
115
116        if value.is_array() {
117            let mut out_arr = Vec::new();
118            for v in value
119                .as_array()
120                .ok_or_else(|| AgentError::InvalidArrayValue("Expected array".into()))?
121            {
122                let rendered_string = reg.render_template(&template, v).map_err(|e| {
123                    AgentError::InvalidValue(format!("Failed to render template: {}", e))
124                })?;
125                out_arr.push(rendered_string.into());
126            }
127            self.try_output(ctx, PIN_STRING, AgentValue::array(out_arr))
128        } else {
129            let rendered_string = reg.render_template(&template, &value).map_err(|e| {
130                AgentError::InvalidValue(format!("Failed to render template: {}", e))
131            })?;
132            let out_value = AgentValue::string(rendered_string);
133            self.try_output(ctx, PIN_STRING, out_value)
134        }
135    }
136}
137
138// Template Text Agent
139#[askit_agent(
140    title = "Template Text",
141    category = CATEGORY,
142    inputs = [PIN_DATA],
143    outputs = [PIN_STRING],
144    text_config(name = CONFIG_TEMPLATE, default = "{{value}}")
145)]
146struct TemplateTextAgent {
147    data: AgentData,
148}
149
150#[async_trait]
151impl AsAgent for TemplateTextAgent {
152    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
153        Ok(Self {
154            data: AgentData::new(askit, id, spec),
155        })
156    }
157
158    async fn process(
159        &mut self,
160        ctx: AgentContext,
161        _pin: String,
162        value: AgentValue,
163    ) -> Result<(), AgentError> {
164        let config = self.configs()?;
165
166        let template = config.get_string_or_default(CONFIG_TEMPLATE);
167        if template.is_empty() {
168            return Err(AgentError::InvalidConfig("template is not set".into()));
169        }
170
171        let reg = handlebars_new();
172
173        if value.is_array() {
174            let mut out_arr = Vec::new();
175            for v in value
176                .as_array()
177                .ok_or_else(|| AgentError::InvalidArrayValue("Expected array".into()))?
178            {
179                let rendered_string = reg.render_template(&template, v).map_err(|e| {
180                    AgentError::InvalidValue(format!("Failed to render template: {}", e))
181                })?;
182                out_arr.push(rendered_string.into());
183            }
184            self.try_output(ctx, PIN_STRING, AgentValue::array(out_arr))
185        } else {
186            let rendered_string = reg.render_template(&template, &value).map_err(|e| {
187                AgentError::InvalidValue(format!("Failed to render template: {}", e))
188            })?;
189            let out_value = AgentValue::string(rendered_string);
190            self.try_output(ctx, PIN_STRING, out_value)
191        }
192    }
193}
194
195// Template Array Agent
196#[askit_agent(
197    title = "Template Array",
198    category = CATEGORY,
199    inputs = [PIN_DATA],
200    outputs = [PIN_STRING],
201    text_config(name = CONFIG_TEMPLATE, default = "{{value}}")
202)]
203struct TemplateArrayAgent {
204    data: AgentData,
205}
206
207#[async_trait]
208impl AsAgent for TemplateArrayAgent {
209    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
210        Ok(Self {
211            data: AgentData::new(askit, id, spec),
212        })
213    }
214
215    async fn process(
216        &mut self,
217        ctx: AgentContext,
218        _pin: String,
219        value: AgentValue,
220    ) -> Result<(), AgentError> {
221        let config = self.configs()?;
222
223        let template = config.get_string_or_default(CONFIG_TEMPLATE);
224        if template.is_empty() {
225            return Err(AgentError::InvalidConfig("template is not set".into()));
226        }
227
228        let reg = handlebars_new();
229
230        if value.is_array() {
231            let rendered_string = reg.render_template(&template, &value).map_err(|e| {
232                AgentError::InvalidValue(format!("Failed to render template: {}", e))
233            })?;
234            self.try_output(ctx, PIN_STRING, AgentValue::string(rendered_string))
235        } else {
236            let d = AgentValue::array(vec![value.clone()]);
237            let rendered_string = reg.render_template(&template, &d).map_err(|e| {
238                AgentError::InvalidValue(format!("Failed to render template: {}", e))
239            })?;
240            let out_value = AgentValue::string(rendered_string);
241            self.try_output(ctx, PIN_STRING, out_value)
242        }
243    }
244}
245
246fn handlebars_new<'a>() -> Handlebars<'a> {
247    let mut reg = Handlebars::new();
248    reg.register_escape_fn(handlebars::no_escape);
249    reg.register_helper("to_json", Box::new(to_json_helper));
250
251    #[cfg(feature = "yaml")]
252    reg.register_helper("to_yaml", Box::new(to_yaml_helper));
253
254    reg
255}
256
257fn to_json_helper(
258    h: &handlebars::Helper<'_>,
259    _: &handlebars::Handlebars<'_>,
260    _: &handlebars::Context,
261    _: &mut handlebars::RenderContext<'_, '_>,
262    out: &mut dyn handlebars::Output,
263) -> handlebars::HelperResult {
264    if let Some(value) = h.param(0) {
265        let json_str = serde_json::to_string_pretty(&value.value()).map_err(|e| {
266            handlebars::RenderErrorReason::Other(format!("Failed to serialize to JSON: {}", e))
267        })?;
268        out.write(&json_str)?;
269    }
270    Ok(())
271}
272
273#[cfg(feature = "yaml")]
274fn to_yaml_helper(
275    h: &handlebars::Helper<'_>,
276    _: &handlebars::Handlebars<'_>,
277    _: &handlebars::Context,
278    _: &mut handlebars::RenderContext<'_, '_>,
279    out: &mut dyn handlebars::Output,
280) -> handlebars::HelperResult {
281    if let Some(value) = h.param(0) {
282        let yaml_str = serde_yaml_ng::to_string(&value.value()).map_err(|e| {
283            handlebars::RenderErrorReason::Other(format!("Failed to serialize to YAML: {}", e))
284        })?;
285        out.write(&yaml_str)?;
286    }
287    Ok(())
288}