1use std::time::Duration;
2use std::{collections::VecDeque, vec};
3
4use agent_stream_kit::{
5 ASKit, AgentConfigSpec, AgentConfigSpecs, AgentConfigs, AgentContext, AgentData, AgentError,
6 AgentOutput, AgentSpec, AgentValue, AsAgent, askit_agent, async_trait,
7};
8use im::{HashMap, Vector};
9use mini_moka::sync::Cache;
10
11const CATEGORY: &str = "Std/Data";
12
13const PIN_IN1: &str = "in1";
14const PIN_IN2: &str = "in2";
15const PIN_JSON: &str = "json";
16const PIN_OBJECT: &str = "object";
17const PIN_VALUE: &str = "value";
18
19const CONFIG_KEY: &str = "key";
20const CONFIG_VALUE: &str = "value";
21const CONFIG_N: &str = "n";
22const CONFIG_USE_CTX: &str = "use_ctx";
23const CONFIG_TTL_SECONDS: &str = "ttl_sec";
24const CONFIG_CAPACITY: &str = "capacity";
25
26#[askit_agent(
28 title = "Get Value",
29 category = CATEGORY,
30 inputs = [PIN_VALUE],
31 outputs = [PIN_VALUE],
32 string_config(name = CONFIG_KEY)
33)]
34struct GetValueAgent {
35 data: AgentData,
36 target_keys: Vec<String>,
37}
38
39impl GetValueAgent {
40 fn update_spec(spec: &mut AgentSpec) -> Result<Vec<String>, AgentError> {
41 let key_str = spec
42 .configs
43 .as_ref()
44 .map(|cfg| cfg.get_string_or_default(CONFIG_KEY))
45 .unwrap_or_default();
46 if key_str.is_empty() {
47 return Ok(Vec::new());
48 }
49 let target_keys = key_str.split('.').map(|s| s.to_string()).collect();
50 Ok(target_keys)
51 }
52}
53
54#[async_trait]
55impl AsAgent for GetValueAgent {
56 fn new(askit: ASKit, id: String, mut spec: AgentSpec) -> Result<Self, AgentError> {
57 let target_keys = Self::update_spec(&mut spec)?;
58 Ok(Self {
59 data: AgentData::new(askit, id, spec),
60 target_keys,
61 })
62 }
63
64 fn configs_changed(&mut self) -> Result<(), AgentError> {
65 let target_keys = Self::update_spec(&mut self.data.spec)?;
66 self.target_keys = target_keys;
67 Ok(())
68 }
69
70 async fn process(
71 &mut self,
72 ctx: AgentContext,
73 _pin: String,
74 value: AgentValue,
75 ) -> Result<(), AgentError> {
76 if self.target_keys.is_empty() {
77 return Ok(());
78 }
79
80 let output_value = match value {
81 AgentValue::Array(arr) => {
82 let extracted: Vector<AgentValue> = arr
83 .iter()
84 .map(|item| {
85 get_nested_value(item, &self.target_keys)
86 .cloned()
87 .unwrap_or(AgentValue::Unit)
88 })
89 .collect();
90 AgentValue::Array(extracted)
91 }
92
93 AgentValue::Object(_) => get_nested_value(&value, &self.target_keys)
94 .cloned()
95 .unwrap_or(AgentValue::Unit),
96
97 _ => AgentValue::Unit,
98 };
99
100 self.output(ctx, PIN_VALUE, output_value).await
101 }
102}
103
104#[askit_agent(
106 title = "Set Value",
107 category = CATEGORY,
108 inputs = [PIN_VALUE],
109 outputs = [PIN_VALUE],
110 string_config(name = CONFIG_KEY),
111 object_config(name = CONFIG_VALUE),
112)]
113struct SetValueAgent {
114 data: AgentData,
115 target_keys: Vec<String>,
116 target_value: AgentValue,
117}
118
119impl SetValueAgent {
120 fn update_spec(spec: &mut AgentSpec) -> Result<(Vec<String>, AgentValue), AgentError> {
121 let key_str = spec
122 .configs
123 .as_ref()
124 .map(|cfg| cfg.get_string_or_default(CONFIG_KEY))
125 .unwrap_or_default();
126 let target_keys = key_str.split('.').map(|s| s.to_string()).collect();
127 let target_value = spec
128 .configs
129 .as_ref()
130 .map(|cfg| cfg.get(CONFIG_VALUE).cloned().unwrap_or(AgentValue::Unit))
131 .unwrap_or(AgentValue::Unit);
132 Ok((target_keys, target_value))
133 }
134}
135
136#[async_trait]
137impl AsAgent for SetValueAgent {
138 fn new(askit: ASKit, id: String, mut spec: AgentSpec) -> Result<Self, AgentError> {
139 let (target_keys, target_value) = Self::update_spec(&mut spec)?;
140 Ok(Self {
141 data: AgentData::new(askit, id, spec),
142 target_keys,
143 target_value,
144 })
145 }
146
147 fn configs_changed(&mut self) -> Result<(), AgentError> {
148 let (target_keys, target_value) = Self::update_spec(&mut self.data.spec)?;
149 self.target_keys = target_keys;
150 self.target_value = target_value;
151 Ok(())
152 }
153
154 async fn process(
155 &mut self,
156 ctx: AgentContext,
157 _pin: String,
158 mut value: AgentValue,
159 ) -> Result<(), AgentError> {
160 if self.target_keys.is_empty() {
161 return Ok(());
162 }
163
164 set_nested_value(&mut value, &self.target_keys, self.target_value.clone());
165 self.output(ctx, PIN_VALUE, value).await
166 }
167}
168
169#[askit_agent(
171 title = "To Object",
172 category = CATEGORY,
173 inputs = [PIN_VALUE],
174 outputs = [PIN_VALUE],
175 string_config(name = CONFIG_KEY)
176)]
177struct ToObjectAgent {
178 data: AgentData,
179 target_keys: Vec<String>,
180}
181
182impl ToObjectAgent {
183 fn update_spec(spec: &mut AgentSpec) -> Result<Vec<String>, AgentError> {
184 let key_str = spec
185 .configs
186 .as_ref()
187 .map(|cfg| cfg.get_string_or_default(CONFIG_KEY))
188 .unwrap_or_default();
189 if key_str.is_empty() {
190 return Ok(Vec::new());
191 }
192 let target_keys = key_str.split('.').map(|s| s.to_string()).collect();
193 Ok(target_keys)
194 }
195}
196
197#[async_trait]
198impl AsAgent for ToObjectAgent {
199 fn new(askit: ASKit, id: String, mut spec: AgentSpec) -> Result<Self, AgentError> {
200 let target_keys = Self::update_spec(&mut spec)?;
201 Ok(Self {
202 data: AgentData::new(askit, id, spec),
203 target_keys,
204 })
205 }
206
207 fn configs_changed(&mut self) -> Result<(), AgentError> {
208 let target_keys = Self::update_spec(&mut self.data.spec)?;
209 self.target_keys = target_keys;
210 Ok(())
211 }
212
213 async fn process(
214 &mut self,
215 ctx: AgentContext,
216 _pin: String,
217 value: AgentValue,
218 ) -> Result<(), AgentError> {
219 if self.target_keys.is_empty() {
220 return Ok(());
221 }
222
223 let mut new_value = AgentValue::object_default();
224 set_nested_value(&mut new_value, &self.target_keys, value);
225
226 self.output(ctx, PIN_VALUE, new_value).await
227 }
228}
229
230#[askit_agent(
232 title = "To JSON",
233 category = CATEGORY,
234 inputs = [PIN_VALUE],
235 outputs = [PIN_JSON]
236)]
237struct ToJsonAgent {
238 data: AgentData,
239}
240
241#[async_trait]
242impl AsAgent for ToJsonAgent {
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 json = serde_json::to_string_pretty(&value)
256 .map_err(|e| AgentError::InvalidValue(e.to_string()))?;
257 self.output(ctx, PIN_JSON, AgentValue::string(json)).await?;
258 Ok(())
259 }
260}
261
262#[askit_agent(
264 title = "From JSON",
265 category = CATEGORY,
266 inputs = [PIN_JSON],
267 outputs = [PIN_VALUE]
268)]
269struct FromJsonAgent {
270 data: AgentData,
271}
272
273#[async_trait]
274impl AsAgent for FromJsonAgent {
275 fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
276 Ok(Self {
277 data: AgentData::new(askit, id, spec),
278 })
279 }
280
281 async fn process(
282 &mut self,
283 ctx: AgentContext,
284 _pin: String,
285 value: AgentValue,
286 ) -> Result<(), AgentError> {
287 let s = value
288 .as_str()
289 .ok_or_else(|| AgentError::InvalidValue("not a string".to_string()))?;
290 let json_value: serde_json::Value =
291 serde_json::from_str(s).map_err(|e| AgentError::InvalidValue(e.to_string()))?;
292 let value = AgentValue::from_json(json_value)?;
293 self.output(ctx, PIN_VALUE, value).await?;
294 Ok(())
295 }
296}
297
298fn get_nested_value<'a, K: AsRef<str>>(
299 value: &'a AgentValue,
300 keys: &[K],
301) -> Option<&'a AgentValue> {
302 let mut current_value = value;
303 for key in keys {
304 let obj = current_value.as_object()?;
305 current_value = obj.get(key.as_ref())?;
306 }
307 Some(current_value)
308}
309
310fn set_nested_value<K: AsRef<str>>(root: &mut AgentValue, keys: &[K], new_value: AgentValue) {
311 if keys.is_empty() {
312 return;
313 }
314
315 let (last_key, path) = keys.split_last().unwrap();
318
319 let mut current = root;
320
321 for key in path {
323 if !current.is_object() {
325 *current = AgentValue::object_default();
326 }
327
328 let obj = current.as_object_mut().unwrap();
329
330 current = obj
331 .entry(key.as_ref().to_string())
332 .or_insert_with(AgentValue::object_default);
333 }
334
335 if !current.is_object() {
337 *current = AgentValue::object_default();
338 }
339
340 if let Some(obj) = current.as_object_mut() {
341 obj.insert(last_key.as_ref().to_string(), new_value);
342 }
343}
344
345#[askit_agent(
358 title = "ZipToObject",
359 category = CATEGORY,
360 inputs = [PIN_IN1, PIN_IN2],
361 outputs = [PIN_OBJECT],
362 integer_config(name = CONFIG_N, default = 2),
363 boolean_config(name = CONFIG_USE_CTX),
364 integer_config(name = CONFIG_TTL_SECONDS, default = 60),
365 integer_config(name = CONFIG_CAPACITY, default = 1000),
366)]
367struct ZipToObjectAgent {
368 data: AgentData,
369 n: usize,
370 use_ctx: bool,
371 ttl_seconds: u64,
372 capacity: usize,
373
374 keys: Vec<String>,
376
377 queues: Vec<VecDeque<AgentValue>>,
379
380 ctx_buffers: Cache<String, PendingZip>,
382}
383
384#[derive(Clone)]
385struct PendingZip {
386 values: Vec<Option<AgentValue>>,
387 count: usize,
388}
389
390impl ZipToObjectAgent {
391 fn update_spec(
392 spec: &mut AgentSpec,
393 ) -> Result<(usize, bool, u64, u64, Vec<String>), AgentError> {
394 let n = spec
395 .configs
396 .as_ref()
397 .map(|cfg| cfg.get_integer_or(CONFIG_N, 2))
398 .unwrap_or(2) as usize;
399 let n = if n < 1 { 1 } else { n };
400
401 let use_ctx = spec
402 .configs
403 .as_ref()
404 .map(|cfg| cfg.get_bool_or_default(CONFIG_USE_CTX))
405 .unwrap_or(false);
406
407 let ttl_sec = spec
408 .configs
409 .as_ref()
410 .map(|c| c.get_integer_or("ttl_seconds", 60))
411 .unwrap_or(60) as u64;
412
413 let capacity = spec
414 .configs
415 .as_ref()
416 .map(|c| c.get_integer_or("capacity", 1000))
417 .unwrap_or(1000) as u64;
418
419 let mut configs = AgentConfigs::new();
421 let mut config_specs = AgentConfigSpecs::default();
422
423 configs.set(CONFIG_N.to_string(), AgentValue::integer(n as i64));
425 let Some(n_spec) = spec
426 .config_specs
427 .as_ref()
428 .and_then(|cs| cs.get(CONFIG_N))
429 .cloned()
430 else {
431 return Err(AgentError::InvalidConfig("config n must be present".into()));
432 };
433 config_specs.insert(CONFIG_N.to_string(), n_spec);
434
435 let Some(use_ctx_spec) = spec
436 .config_specs
437 .as_ref()
438 .and_then(|cs| cs.get(CONFIG_USE_CTX))
439 .cloned()
440 else {
441 return Err(AgentError::InvalidConfig(
442 "config use_ctx must be present".into(),
443 ));
444 };
445 config_specs.insert(CONFIG_USE_CTX.to_string(), use_ctx_spec);
446
447 let mut keys = Vec::with_capacity(n);
448 for i in 1..=n {
449 let key_name = format!("k{}", i);
450 let default_key = format!("in{}", i);
451 let v = spec
452 .configs
453 .as_ref()
454 .map(|cfg| cfg.get_string_or(&key_name, &default_key))
455 .unwrap_or(default_key);
456
457 keys.push(v.clone());
458
459 configs.set(key_name.clone(), AgentValue::string(v));
460 config_specs.insert(
461 key_name,
462 AgentConfigSpec {
463 value: AgentValue::string_default(),
464 type_: Some("string".to_string()),
465 ..Default::default()
466 },
467 );
468 }
469
470 spec.configs = Some(configs);
471 spec.config_specs = Some(config_specs);
472
473 spec.inputs = Some((1..=n).map(|i| format!("in{}", i)).collect());
474
475 Ok((n as usize, use_ctx, ttl_sec, capacity, keys))
476 }
477
478 fn reset_state(&mut self) {
479 self.queues = vec![VecDeque::new(); self.n];
480 self.ctx_buffers.invalidate_all();
481 }
482}
483
484#[async_trait]
485impl AsAgent for ZipToObjectAgent {
486 fn new(askit: ASKit, id: String, mut spec: AgentSpec) -> Result<Self, AgentError> {
487 let (n, use_ctx, ttl_sec, capacity, keys) = Self::update_spec(&mut spec)?;
488 let cache = Cache::builder()
489 .max_capacity(capacity)
490 .time_to_live(Duration::from_secs(ttl_sec))
491 .build();
492 let data = AgentData::new(askit, id, spec);
493 Ok(Self {
494 data,
495 n,
496 use_ctx,
497 ttl_seconds: ttl_sec,
498 capacity: capacity as usize,
499 keys,
500 queues: vec![VecDeque::new(); n],
501 ctx_buffers: cache,
502 })
503 }
504
505 fn configs_changed(&mut self) -> Result<(), AgentError> {
506 let (n, use_ctx, ttl_sec, capacity, keys) = Self::update_spec(&mut self.data.spec)?;
507 let mut changed = false;
508 if n != self.n {
509 self.n = n;
510 changed = true;
511 }
512 if use_ctx != self.use_ctx {
513 self.use_ctx = use_ctx;
514 changed = true;
515 }
516 if ttl_sec != self.ttl_seconds {
517 self.ttl_seconds = ttl_sec;
518 changed = true;
519 }
520 if capacity != self.capacity as u64 {
521 self.capacity = capacity as usize;
522 changed = true;
523 }
524 if keys != self.keys {
525 self.keys = keys;
526 changed = true;
527 }
528 if changed {
529 self.reset_state();
530 self.ctx_buffers = Cache::builder()
532 .max_capacity(capacity)
533 .time_to_live(Duration::from_secs(ttl_sec))
534 .build();
535 self.emit_agent_spec_updated();
536 }
537 Ok(())
538 }
539
540 async fn stop(&mut self) -> Result<(), AgentError> {
541 self.reset_state();
542 Ok(())
543 }
544
545 async fn process(
546 &mut self,
547 ctx: AgentContext,
548 pin: String,
549 value: AgentValue,
550 ) -> Result<(), AgentError> {
551 let Some(idx) = pin
553 .strip_prefix("in")
554 .and_then(|s| s.parse::<usize>().ok())
555 .filter(|&i| i >= 1 && i <= self.n)
556 .map(|i| i - 1)
557 else {
558 return Err(AgentError::InvalidValue(format!(
559 "Invalid input pin: {}",
560 pin
561 )));
562 };
563
564 if self.use_ctx {
566 let ctx_key = ctx.ctx_key()?;
567
568 let mut entry = self
569 .ctx_buffers
570 .get(&ctx_key)
571 .unwrap_or_else(|| PendingZip {
572 values: vec![None; self.n],
573 count: 0,
574 });
575
576 if entry.values[idx].is_none() {
577 entry.count += 1;
578 }
579 entry.values[idx] = Some(value);
580
581 if entry.count == self.n {
582 self.ctx_buffers.invalidate(&ctx_key);
583
584 let map: HashMap<String, AgentValue> = self
586 .keys
587 .iter()
588 .zip(entry.values.into_iter().map(|v| v.unwrap()))
589 .map(|(k, v)| (k.clone(), v))
590 .collect();
591
592 return self.output(ctx, PIN_OBJECT, AgentValue::Object(map)).await;
593 } else {
594 self.ctx_buffers.insert(ctx_key, entry);
595 }
596 return Ok(());
597 }
598
599 self.queues[idx].push_back(value);
601
602 if self.queues.iter().all(|q| !q.is_empty()) {
603 let map: HashMap<String, AgentValue> = self
605 .keys
606 .iter()
607 .zip(self.queues.iter_mut())
608 .map(|(k, q)| (k.clone(), q.pop_front().unwrap()))
609 .collect();
610
611 self.output(ctx, PIN_OBJECT, AgentValue::Object(map)).await
612 } else {
613 Ok(())
614 }
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use im::hashmap;
621
622 use super::*;
623
624 #[test]
625 fn test_get_nested_value() {
626 let mut root = AgentValue::object_default();
628 let mut users = AgentValue::object_default();
629 let mut admin = AgentValue::object_default();
630
631 admin
632 .set("name".to_string(), AgentValue::string("Alice"))
633 .unwrap();
634 users.set("admin".to_string(), admin).unwrap();
635 root.set("users".to_string(), users).unwrap();
636
637 let keys = vec!["users", "admin", "name"];
639 let result = get_nested_value(&root, &keys);
640 assert_eq!(result, Some(&AgentValue::string("Alice")));
641
642 let keys_missing = vec!["users", "guest", "name"];
644 let result_missing = get_nested_value(&root, &keys_missing);
645 assert_eq!(result_missing, None);
646
647 let keys_not_obj = vec!["users", "admin", "name", "length"];
650 let result_not_obj = get_nested_value(&root, &keys_not_obj);
651 assert_eq!(result_not_obj, None); let keys_empty: Vec<&str> = vec![];
655 let result_root = get_nested_value(&root, &keys_empty);
656 assert_eq!(result_root, Some(&root));
657 }
658
659 #[test]
661 fn test_create_deeply_nested_structure() {
662 let mut root = AgentValue::object_default();
663 let keys = vec!["users", "admin", "name"];
664 let value = AgentValue::string("Alice");
665
666 set_nested_value(&mut root, &keys, value);
667
668 if let Some(users) = root.get_mut("users") {
670 if let Some(admin) = users.get_mut("admin") {
671 if let Some(name) = admin.get_mut("name") {
672 assert_eq!(*name, AgentValue::string("Alice"));
673 return;
674 }
675 }
676 }
677 panic!("Nested structure was not created correctly: {:?}", root);
678 }
679
680 #[test]
682 fn test_add_to_existing_structure() {
683 let mut root = AgentValue::object_default();
684 root.set("config".to_string(), AgentValue::object_default())
686 .unwrap();
687
688 let keys = vec!["config", "timeout"];
689 let value = AgentValue::string("30s");
690
691 set_nested_value(&mut root, &keys, value);
692
693 let config = root.get_mut("config").unwrap();
695 let timeout = config.get_mut("timeout").unwrap();
696 assert_eq!(*timeout, AgentValue::string("30s"));
697 }
698
699 #[test]
701 fn test_overwrite_existing_value() {
702 let mut root = AgentValue::object_default();
703 let mut app = AgentValue::object_default();
705 app.set("version".to_string(), AgentValue::string("v1"))
706 .unwrap();
707 root.set("app".to_string(), app).unwrap();
708
709 let keys = vec!["app", "version"];
711 let new_val = AgentValue::string("v2");
712 set_nested_value(&mut root, &keys, new_val);
713
714 let app = root.get_mut("app").unwrap();
716 let version = app.get_mut("version").unwrap();
717 assert_eq!(*version, AgentValue::string("v2"));
718 }
719
720 #[test]
723 fn test_overwrite_if_path_is_not_object() {
724 let mut root = AgentValue::object_default();
725 root.set("tags".to_string(), AgentValue::string("some_string"))
727 .unwrap();
728
729 let keys = vec!["tags", "new_key"];
730 let value = AgentValue::string("value");
731
732 set_nested_value(&mut root, &keys, value);
734
735 let tags = root.get_mut("tags").unwrap();
737 assert_eq!(
738 *tags,
739 AgentValue::object(hashmap! {
740 "new_key".to_string() => AgentValue::string("value")
741 })
742 );
743 }
744}