1#![allow(deprecated)] use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::HashMap;
11use std::sync::Arc;
12
13use crate::stream::StreamEvent;
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
33pub enum GraphEvent {
34 RunStart {
36 run_id: String,
37 #[deprecated(
40 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
41 )]
42 trace_id: String,
43 #[serde(skip_serializing_if = "Option::is_none", default)]
44 trace_ctx: Option<stack_ids::TraceCtx>,
45 graph_name: Option<String>,
46 },
47 RunEnd {
49 run_id: String,
50 #[deprecated(
53 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
54 )]
55 trace_id: String,
56 #[serde(skip_serializing_if = "Option::is_none", default)]
57 trace_ctx: Option<stack_ids::TraceCtx>,
58 },
59 NodeStart {
61 run_id: String,
62 #[deprecated(
65 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
66 )]
67 trace_id: String,
68 #[serde(skip_serializing_if = "Option::is_none", default)]
69 trace_ctx: Option<stack_ids::TraceCtx>,
70 node_id: String,
71 #[deprecated(
74 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
75 )]
76 attempt: u32,
77 #[serde(skip_serializing_if = "Option::is_none", default)]
78 attempt_id: Option<stack_ids::AttemptId>,
79 #[serde(skip_serializing_if = "Option::is_none", default)]
80 trial_id: Option<stack_ids::TrialId>,
81 },
82 NodeEnd {
84 run_id: String,
85 #[deprecated(
88 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
89 )]
90 trace_id: String,
91 #[serde(skip_serializing_if = "Option::is_none", default)]
92 trace_ctx: Option<stack_ids::TraceCtx>,
93 node_id: String,
94 outcome: NodeOutcomeKind,
95 #[serde(skip_serializing_if = "Option::is_none", default)]
96 attempt_id: Option<stack_ids::AttemptId>,
97 #[serde(skip_serializing_if = "Option::is_none", default)]
98 trial_id: Option<stack_ids::TrialId>,
99 },
100 Token {
102 run_id: String,
103 #[deprecated(
106 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
107 )]
108 trace_id: String,
109 #[serde(skip_serializing_if = "Option::is_none", default)]
110 trace_ctx: Option<stack_ids::TraceCtx>,
111 node_id: String,
112 token: String,
113 },
114 CheckpointWritten {
116 run_id: String,
117 #[deprecated(
120 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
121 )]
122 trace_id: String,
123 #[serde(skip_serializing_if = "Option::is_none", default)]
124 trace_ctx: Option<stack_ids::TraceCtx>,
125 #[serde(alias = "attempt_id")]
127 checkpoint_attempt_id: String,
128 },
129 InterruptRaised {
131 run_id: String,
132 #[deprecated(
135 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
136 )]
137 trace_id: String,
138 #[serde(skip_serializing_if = "Option::is_none", default)]
139 trace_ctx: Option<stack_ids::TraceCtx>,
140 node_id: String,
141 kind: String,
142 payload: Value,
143 },
144 StateUpdate {
146 run_id: String,
147 #[deprecated(
150 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
151 )]
152 trace_id: String,
153 #[serde(skip_serializing_if = "Option::is_none", default)]
154 trace_ctx: Option<stack_ids::TraceCtx>,
155 node_id: String,
156 updates: HashMap<String, Value>,
157 },
158 SuperstepStart {
160 run_id: String,
161 #[deprecated(
164 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
165 )]
166 trace_id: String,
167 #[serde(skip_serializing_if = "Option::is_none", default)]
168 trace_ctx: Option<stack_ids::TraceCtx>,
169 step: usize,
170 nodes: Vec<String>,
171 },
172 SuperstepEnd {
174 run_id: String,
175 #[deprecated(
178 note = "Use trace_ctx/attempt_id/trial_id instead. Will be removed when all consumers migrate."
179 )]
180 trace_id: String,
181 #[serde(skip_serializing_if = "Option::is_none", default)]
182 trace_ctx: Option<stack_ids::TraceCtx>,
183 step: usize,
184 },
185 ParallelCancellation {
187 run_id: String,
188 #[deprecated(note = "Use trace_ctx instead.")]
189 trace_id: String,
190 #[serde(skip_serializing_if = "Option::is_none", default)]
191 trace_ctx: Option<stack_ids::TraceCtx>,
192 external_effects_may_have_escaped: bool,
194 },
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199pub enum NodeOutcomeKind {
200 Success,
201 Failed,
202 Interrupted,
203}
204
205pub trait EventSink: Send + Sync {
210 fn emit(&self, event: GraphEvent);
212}
213
214pub struct NoopEventSink;
216
217impl EventSink for NoopEventSink {
218 fn emit(&self, _event: GraphEvent) {}
219}
220
221pub struct ChannelEventSink {
226 sender: tokio::sync::mpsc::Sender<StreamEvent>,
227}
228
229impl ChannelEventSink {
230 pub fn new(sender: tokio::sync::mpsc::Sender<StreamEvent>) -> Self {
231 Self { sender }
232 }
233}
234
235impl EventSink for ChannelEventSink {
236 fn emit(&self, event: GraphEvent) {
237 let stream_event = match event {
238 GraphEvent::RunStart { graph_name, .. } => StreamEvent::GraphStart { graph_name },
239 GraphEvent::RunEnd { .. } => StreamEvent::GraphEnd { graph_name: None },
240 GraphEvent::NodeStart { node_id, .. } => StreamEvent::NodeStart { node: node_id },
241 GraphEvent::NodeEnd { node_id, .. } => StreamEvent::NodeEnd { node: node_id },
242 GraphEvent::Token {
243 run_id,
244 node_id,
245 token,
246 ..
247 } => StreamEvent::Custom(serde_json::json!({
248 "type": "token",
249 "run_id": run_id,
250 "node": node_id,
251 "token": token,
252 })),
253 GraphEvent::InterruptRaised {
254 node_id, payload, ..
255 } => StreamEvent::Interrupt {
256 node: node_id,
257 value: Some(payload),
258 },
259 GraphEvent::StateUpdate {
260 node_id, updates, ..
261 } => StreamEvent::StateUpdate {
262 node: node_id,
263 updates,
264 },
265 GraphEvent::SuperstepStart { step, nodes, .. } => {
266 StreamEvent::SuperstepStart { step, nodes }
267 }
268 GraphEvent::SuperstepEnd { step, .. } => StreamEvent::SuperstepEnd { step },
269 GraphEvent::ParallelCancellation { .. } => return,
270 GraphEvent::CheckpointWritten { .. } => return, };
272 let _ = self.sender.try_send(stream_event);
274 }
275}
276
277pub struct CallbackEventSink<F: Fn(GraphEvent) + Send + Sync> {
279 callback: F,
280}
281
282impl<F: Fn(GraphEvent) + Send + Sync> CallbackEventSink<F> {
283 pub fn new(callback: F) -> Self {
284 Self { callback }
285 }
286}
287
288impl<F: Fn(GraphEvent) + Send + Sync> EventSink for CallbackEventSink<F> {
289 fn emit(&self, event: GraphEvent) {
290 (self.callback)(event);
291 }
292}
293
294pub struct CompositeEventSink {
296 sinks: Vec<Arc<dyn EventSink>>,
297}
298
299impl CompositeEventSink {
300 pub fn new(sinks: Vec<Arc<dyn EventSink>>) -> Self {
301 Self { sinks }
302 }
303}
304
305impl EventSink for CompositeEventSink {
306 fn emit(&self, event: GraphEvent) {
307 for sink in &self.sinks {
308 sink.emit(event.clone());
309 }
310 }
311}