Skip to main content

askit_std_agents/
data.rs

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// Get Value
27#[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// Set Value
105#[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// To Object
170#[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// To JSON
231#[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// From JSON
263#[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    // Split into the last key and the path before it
316    // keys = ["a", "b", "c"] -> path=["a", "b"], last_key="c"
317    let (last_key, path) = keys.split_last().unwrap();
318
319    let mut current = root;
320
321    // Traverse down to just before the target
322    for key in path {
323        // If current position is not an Object, forcibly overwrite it with an empty Object
324        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    // Set the value for the last key
336    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/// Zips multiple inputs into an object.
346///
347/// The number of inputs n and keys are specified via configuration.
348///
349/// If n=2, it takes two inputs: in1 and in2. Once all inputs are present,
350/// it emits them as { key1: in1, key2: in2 }.
351///
352/// If in2 arrives repeatedly before in1, the in2 values are queued; when in1 arrives,
353/// they’re paired in order from the head of the queue and emitted.
354///
355/// When the `use_ctx` config is true, inputs are matched by context key (including map frames)
356/// so that mapped items zip correctly even when they interleave.
357#[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    // Optimization: Pre-load and store key configuration (k1, k2...)
375    keys: Vec<String>,
376
377    // For simple mode: FIFO queues
378    queues: Vec<VecDeque<AgentValue>>,
379
380    // For use_ctx mode: Cache with TTL
381    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        // Dynamic generation of config definitions (ConfigSpecs)
420        let mut configs = AgentConfigs::new();
421        let mut config_specs = AgentConfigSpecs::default();
422
423        // Re-set required configurations
424        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            // Rebuild cache with new capacity and ttl
531            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        // Parse pin number
552        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        // Context Mode
565        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                // Zip keys and values, then collect
585                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        // Simple FIFO Mode
600        self.queues[idx].push_back(value);
601
602        if self.queues.iter().all(|q| !q.is_empty()) {
603            // Take from head and combine with keys to create Map
604            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        // Setup data: { "users": { "admin": { "name": "Alice" } } }
627        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        // Case 1: Successfully retrieve an existing value
638        let keys = vec!["users", "admin", "name"];
639        let result = get_nested_value(&root, &keys);
640        assert_eq!(result, Some(&AgentValue::string("Alice")));
641
642        // Case 2: Intermediate key does not exist (users -> guest)
643        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        // Case 3: Intermediate path is not an object (users -> admin -> name -> something)
648        // "name" is a string, so we cannot traverse deeper -> Should return None
649        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); // Filtered out by as_object()?
652
653        // Case 4: Empty keys (Should return the root object)
654        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    /// Verify if a deeply nested structure (a.b.c) can be auto-generated from an empty state.
660    #[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        // Verify: root["users"]["admin"]["name"] == "Alice"
669        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    /// Verify if a new key can be added without breaking existing structures.
681    #[test]
682    fn test_add_to_existing_structure() {
683        let mut root = AgentValue::object_default();
684        // Pre-create { "config": {} }
685        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        // Verify
694        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    /// Verify if an existing value can be overwritten.
700    #[test]
701    fn test_overwrite_existing_value() {
702        let mut root = AgentValue::object_default();
703        // Pre-create { "app": { "version": "v1" } }
704        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        // Execute overwrite
710        let keys = vec!["app", "version"];
711        let new_val = AgentValue::string("v2");
712        set_nested_value(&mut root, &keys, new_val);
713
714        // Verify
715        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    /// Verify if an intermediate path is not an Object, forcibly overwrite it with an empty Object.
721    /// Example: Try setting ["tags", "new_key"] against { "tags": "immutable_string" }
722    #[test]
723    fn test_overwrite_if_path_is_not_object() {
724        let mut root = AgentValue::object_default();
725        // "tags" is a string, not an object
726        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        // Ensure it returns without crashing
733        set_nested_value(&mut root, &keys, value);
734
735        // Verify that "tags" remains a string
736        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}