swarm_engine_core/events/
action.rs1use std::time::Duration;
7
8use crate::types::{GroupId, LoraConfig, TaskId, WorkerId};
9
10#[derive(Debug, Clone)]
15pub struct ActionEvent {
16 pub id: ActionEventId,
18 pub task_id: TaskId,
20 pub group_id: Option<GroupId>,
22 pub tick: u64,
24 pub worker_id: WorkerId,
26 pub action: String,
28 pub target: Option<String>,
30 pub result: ActionEventResult,
32 pub duration: Duration,
34 pub context: ActionContext,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub struct ActionEventId(pub u64);
41
42impl ActionEventId {
43 pub fn new(tick: u64, worker_id: WorkerId, sequence: u32) -> Self {
45 let id = (tick << 24) | ((worker_id.0 as u64) << 8) | (sequence as u64 & 0xFF);
47 Self(id)
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct ActionEventResult {
54 pub success: bool,
56 pub output: Option<String>,
58 pub error: Option<String>,
60 pub discoveries: u32,
62 pub kpi_contribution: Option<f64>,
67}
68
69impl ActionEventResult {
70 pub fn success() -> Self {
71 Self {
72 success: true,
73 output: None,
74 error: None,
75 discoveries: 0,
76 kpi_contribution: None,
77 }
78 }
79
80 pub fn success_with_output(output: impl Into<String>) -> Self {
81 Self {
82 success: true,
83 output: Some(output.into()),
84 error: None,
85 discoveries: 0,
86 kpi_contribution: None,
87 }
88 }
89
90 pub fn failure(error: impl Into<String>) -> Self {
91 Self {
92 success: false,
93 output: None,
94 error: Some(error.into()),
95 discoveries: 0,
96 kpi_contribution: None,
97 }
98 }
99
100 pub fn with_discoveries(mut self, count: u32) -> Self {
101 self.discoveries = count;
102 self
103 }
104
105 pub fn with_kpi(mut self, contribution: f64) -> Self {
112 self.kpi_contribution = Some(contribution);
113 self
114 }
115}
116
117#[derive(Debug, Clone, Default)]
122pub struct ActionContext {
123 pub selection_logic: Option<String>,
125 pub exploration_node_id: Option<u64>,
127 pub from_guidance: bool,
129 pub previous_action: Option<String>,
131 pub lora: Option<LoraConfig>,
133 pub metadata: std::collections::HashMap<String, String>,
135}
136
137impl ActionContext {
138 pub fn new() -> Self {
139 Self::default()
140 }
141
142 pub fn with_selection_logic(mut self, logic: impl Into<String>) -> Self {
143 self.selection_logic = Some(logic.into());
144 self
145 }
146
147 pub fn with_exploration_node(mut self, node_id: u64) -> Self {
148 self.exploration_node_id = Some(node_id);
149 self
150 }
151
152 pub fn with_guidance(mut self) -> Self {
153 self.from_guidance = true;
154 self
155 }
156
157 pub fn with_previous_action(mut self, action: impl Into<String>) -> Self {
158 self.previous_action = Some(action.into());
159 self
160 }
161
162 pub fn with_lora(mut self, lora: LoraConfig) -> Self {
163 self.lora = Some(lora);
164 self
165 }
166
167 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
168 self.metadata.insert(key.into(), value.into());
169 self
170 }
171}
172
173pub struct ActionEventBuilder {
175 task_id: TaskId,
176 group_id: Option<GroupId>,
177 tick: u64,
178 worker_id: WorkerId,
179 sequence: u32,
180 action: String,
181 target: Option<String>,
182 result: ActionEventResult,
183 duration: Duration,
184 context: ActionContext,
185}
186
187impl ActionEventBuilder {
188 pub fn new(tick: u64, worker_id: WorkerId, action: impl Into<String>) -> Self {
189 Self {
190 task_id: TaskId::new(),
191 group_id: None,
192 tick,
193 worker_id,
194 sequence: 0,
195 action: action.into(),
196 target: None,
197 result: ActionEventResult::success(),
198 duration: Duration::ZERO,
199 context: ActionContext::default(),
200 }
201 }
202
203 pub fn task_id(mut self, task_id: TaskId) -> Self {
205 self.task_id = task_id;
206 self
207 }
208
209 pub fn group_id(mut self, group_id: GroupId) -> Self {
211 self.group_id = Some(group_id);
212 self
213 }
214
215 pub fn group_id_opt(mut self, group_id: Option<GroupId>) -> Self {
217 self.group_id = group_id;
218 self
219 }
220
221 pub fn sequence(mut self, seq: u32) -> Self {
222 self.sequence = seq;
223 self
224 }
225
226 pub fn target(mut self, target: impl Into<String>) -> Self {
227 self.target = Some(target.into());
228 self
229 }
230
231 pub fn result(mut self, result: ActionEventResult) -> Self {
232 self.result = result;
233 self
234 }
235
236 pub fn duration(mut self, duration: Duration) -> Self {
237 self.duration = duration;
238 self
239 }
240
241 pub fn context(mut self, context: ActionContext) -> Self {
242 self.context = context;
243 self
244 }
245
246 pub fn build(self) -> ActionEvent {
247 ActionEvent {
248 id: ActionEventId::new(self.tick, self.worker_id, self.sequence),
249 task_id: self.task_id,
250 group_id: self.group_id,
251 tick: self.tick,
252 worker_id: self.worker_id,
253 action: self.action,
254 target: self.target,
255 result: self.result,
256 duration: self.duration,
257 context: self.context,
258 }
259 }
260}
261
262#[cfg(test)]
263mod tests {
264 use super::*;
265
266 #[test]
267 fn test_action_event_id_uniqueness() {
268 let id1 = ActionEventId::new(100, WorkerId(0), 0);
269 let id2 = ActionEventId::new(100, WorkerId(0), 1);
270 let id3 = ActionEventId::new(100, WorkerId(1), 0);
271 let id4 = ActionEventId::new(101, WorkerId(0), 0);
272
273 assert_ne!(id1, id2);
274 assert_ne!(id1, id3);
275 assert_ne!(id1, id4);
276 }
277
278 #[test]
279 fn test_action_event_builder() {
280 let event = ActionEventBuilder::new(10, WorkerId(1), "CheckStatus")
281 .target("user-service")
282 .result(ActionEventResult::success_with_output("Service is running"))
283 .duration(Duration::from_millis(50))
284 .context(
285 ActionContext::new()
286 .with_selection_logic("UCB1")
287 .with_guidance(),
288 )
289 .build();
290
291 assert_eq!(event.tick, 10);
292 assert_eq!(event.worker_id, WorkerId(1));
293 assert_eq!(event.action, "CheckStatus");
294 assert_eq!(event.target, Some("user-service".to_string()));
295 assert!(event.result.success);
296 assert_eq!(event.duration, Duration::from_millis(50));
297 assert_eq!(event.context.selection_logic, Some("UCB1".to_string()));
298 assert!(event.context.from_guidance);
299 }
300}