1use std::collections::VecDeque;
2use std::time::Duration;
3
4use agent_stream_kit::{
5 ASKit, AgentContext, AgentData, AgentError, AgentOutput, AgentSpec, AgentValue, AsAgent,
6 askit_agent, async_trait,
7};
8use mini_moka::sync::Cache;
9
10const CONFIG_TTL_SEC: &str = "ttl_sec";
11const CONFIG_CAPACITY: &str = "capacity";
12
13const CATEGORY: &str = "Std/Sequence";
14
15const PIN_IN: &str = "in";
16const PIN_IN1: &str = "in1";
17const PIN_IN2: &str = "in2";
18const PIN_OUT1: &str = "out1";
19const PIN_OUT2: &str = "out2";
20
21const CONFIG_N: &str = "n";
22const CONFIG_USE_CTX: &str = "use_ctx";
23
24#[askit_agent(
26 title = "Sequence",
27 category = CATEGORY,
28 inputs = [PIN_IN],
29 outputs = [PIN_OUT1, PIN_OUT2],
30 integer_config(name = CONFIG_N, default = 2),
31)]
32struct SequenceAgent {
33 data: AgentData,
34 n: usize,
35}
36
37impl SequenceAgent {
38 fn update_spec(spec: &mut AgentSpec) -> Result<usize, AgentError> {
39 let mut n = spec
40 .configs
41 .as_ref()
42 .map(|cfg| cfg.get_integer_or(CONFIG_N, 2))
43 .unwrap_or(2) as usize;
44 if n < 1 {
45 n = 1;
46 }
47
48 spec.outputs = Some((1..=n).map(|i| format!("out{}", i)).collect());
49
50 Ok(n)
51 }
52}
53
54#[async_trait]
55impl AsAgent for SequenceAgent {
56 fn new(askit: ASKit, id: String, mut spec: AgentSpec) -> Result<Self, AgentError> {
57 let n = Self::update_spec(&mut spec)?;
58 let data = AgentData::new(askit, id, spec);
59 Ok(Self { data, n })
60 }
61
62 fn configs_changed(&mut self) -> Result<(), AgentError> {
63 let n = Self::update_spec(&mut self.data.spec)?;
64 let mut changed = false;
65 if n != self.n {
66 self.n = n;
67 changed = true;
68 }
69 if changed {
70 self.emit_agent_spec_updated();
71 }
72 Ok(())
73 }
74
75 async fn process(
76 &mut self,
77 ctx: AgentContext,
78 _pin: String,
79 value: AgentValue,
80 ) -> Result<(), AgentError> {
81 for i in 0..self.n {
82 let out_pin = format!("out{}", i + 1);
83 self.output(ctx.clone(), out_pin, value.clone()).await?;
84 }
85 Ok(())
86 }
87}
88
89#[askit_agent(
91 title = "Sync",
92 category = CATEGORY,
93 inputs = [PIN_IN1, PIN_IN2],
94 outputs = [PIN_OUT1, PIN_OUT2],
95 integer_config(name = CONFIG_N, default = 2),
96 boolean_config(name = CONFIG_USE_CTX),
97 integer_config(name = CONFIG_TTL_SEC, default = 60),
98 integer_config(name = CONFIG_CAPACITY, default = 1000),
99)]
100struct SyncAgent {
101 data: AgentData,
102 n: usize,
103 use_ctx: bool,
104 ttl_sec: u64,
105 capacity: u64,
106
107 output_pins: Vec<String>,
109
110 queues: Vec<VecDeque<AgentValue>>,
112
113 ctx_buffers: Cache<String, PendingSync>,
115}
116
117#[derive(Clone)]
118struct PendingSync {
119 values: Vec<Option<AgentValue>>,
120 count: usize,
121}
122
123impl SyncAgent {
124 fn update_spec(spec: &mut AgentSpec) -> Result<(usize, bool, u64, u64, Vec<String>), AgentError> {
125 let n = spec.configs.as_ref()
126 .map(|cfg| cfg.get_integer_or(CONFIG_N, 2))
127 .unwrap_or(2) as usize;
128 let n = if n < 1 { 1 } else { n };
129
130 let use_ctx = spec
131 .configs
132 .as_ref()
133 .map(|cfg| cfg.get_bool_or_default(CONFIG_USE_CTX))
134 .unwrap_or(false);
135
136 let ttl_sec = spec
137 .configs
138 .as_ref()
139 .map(|c| c.get_integer_or(CONFIG_TTL_SEC, 60))
140 .unwrap_or(60) as u64;
141
142 let capacity = spec
143 .configs
144 .as_ref()
145 .map(|c| c.get_integer_or(CONFIG_CAPACITY, 1000))
146 .unwrap_or(1000) as u64;
147
148 spec.inputs = Some((1..=n).map(|i| format!("in{}", i)).collect());
149
150 let output_pins: Vec<String> = (1..=n).map(|i| format!("out{}", i)).collect();
151 spec.outputs = Some(output_pins.clone());
152
153 Ok((n, use_ctx, ttl_sec, capacity, output_pins))
154 }
155
156 fn reset_state(&mut self) {
157 self.queues = vec![VecDeque::new(); self.n];
158 self.ctx_buffers.invalidate_all();
159 }
160}
161
162#[async_trait]
163impl AsAgent for SyncAgent {
164 fn new(askit: ASKit, id: String, mut spec: AgentSpec) -> Result<Self, AgentError> {
165 let (n, use_ctx, ttl_sec, capacity, output_pins) = Self::update_spec(&mut spec)?;
166
167 let cache = Cache::builder()
168 .max_capacity(capacity)
169 .time_to_live(Duration::from_secs(ttl_sec))
170 .build();
171
172 let data = AgentData::new(askit, id, spec);
173 Ok(Self {
174 data,
175 n,
176 use_ctx,
177 ttl_sec,
178 capacity,
179 output_pins,
180 queues: vec![VecDeque::new(); n],
181 ctx_buffers: cache,
182 })
183 }
184
185 fn configs_changed(&mut self) -> Result<(), AgentError> {
186 let (n, use_ctx, ttl_sec, capacity, output_pins) = Self::update_spec(&mut self.data.spec)?;
187 let mut changed = false;
188 if n != self.n {
189 self.n = n;
190 changed = true;
191 }
192 if use_ctx != self.use_ctx {
193 self.use_ctx = use_ctx;
194 changed = true;
195 }
196 if ttl_sec != self.ttl_sec {
197 self.ttl_sec = ttl_sec;
198 changed = true;
199 }
200 if capacity != self.capacity {
201 self.capacity = capacity;
202 changed = true;
203 }
204 if changed {
205 self.reset_state();
206 self.output_pins = output_pins;
207 self.ctx_buffers = Cache::builder()
208 .max_capacity(capacity)
209 .time_to_live(Duration::from_secs(ttl_sec))
210 .build();
211 self.emit_agent_spec_updated();
212 }
213 Ok(())
214 }
215
216 async fn stop(&mut self) -> Result<(), AgentError> {
217 self.reset_state();
219 Ok(())
220 }
221
222 async fn process(
223 &mut self,
224 ctx: AgentContext,
225 pin: String,
226 value: AgentValue,
227 ) -> Result<(), AgentError> {
228 let Some(idx) = pin
230 .strip_prefix("in")
231 .and_then(|s| s.parse::<usize>().ok())
232 .filter(|&i| i >= 1 && i <= self.n)
233 .map(|i| i - 1)
234 else {
235 return Err(AgentError::InvalidValue(format!("Invalid input pin: {}", pin)));
236 };
237
238 if self.use_ctx {
240 let ctx_key = ctx.ctx_key()?;
241
242 let mut entry = self.ctx_buffers.get(&ctx_key).unwrap_or_else(|| PendingSync {
244 values: vec![None; self.n],
245 count: 0,
246 });
247
248 if entry.values[idx].is_none() {
249 entry.count += 1;
250 }
251 entry.values[idx] = Some(value);
252
253 if entry.count == self.n {
254 self.ctx_buffers.invalidate(&ctx_key);
256
257 for (i, val_opt) in entry.values.into_iter().enumerate() {
259 if let Some(val) = val_opt {
260 self.output(ctx.clone(), &self.output_pins[i], val).await?;
261 }
262 }
263 }
264 return Ok(());
265 }
266
267 self.queues[idx].push_back(value);
269
270 if self.queues.iter().all(|q| !q.is_empty()) {
272 let ready_values: Vec<AgentValue> = self.queues
273 .iter_mut()
274 .map(|q| q.pop_front().unwrap())
275 .collect();
276
277 for (i, val) in ready_values.into_iter().enumerate() {
278 self.output(ctx.clone(), &self.output_pins[i], val).await?;
279 }
280 }
281
282 Ok(())
283 }
284}