askit_std_agents/
sequence.rs

1use agent_stream_kit::{
2    ASKit, AgentContext, AgentData, AgentError, AgentOutput, AgentSpec, AgentValue, AsAgent,
3    askit_agent, async_trait,
4};
5
6static CATEGORY: &str = "Std/Sequence";
7
8static PIN_IN: &str = "in";
9static PIN_IN1: &str = "in1";
10static PIN_IN2: &str = "in2";
11static PIN_OUT1: &str = "out1";
12static PIN_OUT2: &str = "out2";
13
14/// Receives an input and emits it sequentially to n outputs.
15#[askit_agent(
16    title = "Sequence",
17    category = CATEGORY,
18    inputs = [PIN_IN],
19    outputs = [PIN_OUT1, PIN_OUT2],
20    integer_config(name = "n", default = 2),
21)]
22struct SequenceAgent {
23    data: AgentData,
24    n: usize,
25}
26
27#[async_trait]
28impl AsAgent for SequenceAgent {
29    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
30        let n = spec
31            .configs
32            .as_ref()
33            .map(|cfg| cfg.get_integer_or("n", 2))
34            .unwrap_or(2) as usize;
35        let mut spec = spec;
36        spec.outputs = Some((1..=n).map(|i| format!("out{}", i)).collect());
37        let data = AgentData::new(askit, id, spec);
38        Ok(Self { data, n })
39    }
40
41    fn configs_changed(&mut self) -> Result<(), AgentError> {
42        let cfg_n = self
43            .data
44            .spec
45            .configs
46            .as_ref()
47            .map(|cfg| cfg.get_integer_or("n", 2))
48            .unwrap_or(2) as usize;
49        if cfg_n != self.n {
50            self.n = cfg_n;
51            self.data.spec.outputs = Some((1..=self.n).map(|i| format!("out{}", i)).collect());
52            self.emit_agent_spec_updated();
53        }
54        Ok(())
55    }
56
57    async fn process(
58        &mut self,
59        ctx: AgentContext,
60        _pin: String,
61        value: AgentValue,
62    ) -> Result<(), AgentError> {
63        for i in 0..self.n {
64            let out_pin = format!("out{}", i + 1);
65            self.try_output(ctx.clone(), out_pin, value.clone())?;
66        }
67        Ok(())
68    }
69}
70
71/// Receives inputs in any order and, once all are present, emits them sequentially.
72#[askit_agent(
73    title = "Sync",
74    category = CATEGORY,
75    inputs = [PIN_IN1, PIN_IN2],
76    outputs = [PIN_OUT1, PIN_OUT2],
77    integer_config(name = "n", default = 2),
78)]
79struct SyncAgent {
80    data: AgentData,
81    n: usize,
82    input_values: Vec<Option<AgentValue>>,
83    current_id: usize,
84}
85
86#[async_trait]
87impl AsAgent for SyncAgent {
88    fn new(askit: ASKit, id: String, spec: AgentSpec) -> Result<Self, AgentError> {
89        let n = spec
90            .configs
91            .as_ref()
92            .map(|cfg| cfg.get_integer_or("n", 2))
93            .unwrap_or(2) as usize;
94        let mut spec = spec;
95        spec.inputs = Some((1..=n).map(|i| format!("in{}", i)).collect());
96        spec.outputs = Some((1..=n).map(|i| format!("out{}", i)).collect());
97        let data = AgentData::new(askit, id, spec);
98        Ok(Self {
99            data,
100            n,
101            input_values: vec![None; n],
102            current_id: 0,
103        })
104    }
105
106    fn configs_changed(&mut self) -> Result<(), AgentError> {
107        let cfg_n = self
108            .data
109            .spec
110            .configs
111            .as_ref()
112            .map(|cfg| cfg.get_integer_or("n", 2))
113            .unwrap_or(2) as usize;
114        if cfg_n < 1 {
115            return Err(AgentError::InvalidConfig("n must be at least 1".into()));
116        }
117        if cfg_n != self.n {
118            self.n = cfg_n;
119            self.data.spec.inputs = Some((1..=self.n).map(|i| format!("in{}", i)).collect());
120            self.data.spec.outputs = Some((1..=self.n).map(|i| format!("out{}", i)).collect());
121            self.input_values = vec![None; self.n];
122            self.current_id = 0;
123            self.emit_agent_spec_updated();
124        }
125        Ok(())
126    }
127
128    async fn process(
129        &mut self,
130        ctx: AgentContext,
131        pin: String,
132        value: AgentValue,
133    ) -> Result<(), AgentError> {
134        // Reset input values if context ID changes
135        let ctx_id = ctx.id();
136        if ctx_id != self.current_id {
137            self.current_id = ctx_id;
138            self.input_values = vec![None; self.n];
139        }
140
141        // Store the input value
142        let Some(i) = pin
143            .strip_prefix("in")
144            .and_then(|s| s.parse::<usize>().ok())
145            .and_then(|idx| {
146                if idx >= 1 && idx <= self.n {
147                    Some(idx - 1)
148                } else {
149                    None
150                }
151            })
152        else {
153            return Err(AgentError::InvalidValue(format!(
154                "Invalid input pin: {}",
155                pin
156            )));
157        };
158
159        self.input_values[i] = Some(value);
160
161        // Check if some input is still missing
162        if self.input_values.iter().any(|v| v.is_none()) {
163            return Ok(());
164        }
165
166        // All inputs are present, output in order
167        for i in 0..self.n {
168            let out_value = self.input_values[i].take().unwrap();
169            self.try_output(
170                ctx.clone(),
171                self.data.spec.outputs.as_ref().unwrap()[i].clone(),
172                out_value,
173            )?;
174        }
175
176        Ok(())
177    }
178}