askit_std_agents/
string.rs

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