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