use protocol::{Alert, Event, Method}; use session_store::LogEntry; use tokio::sync::broadcast; use crate::controller::WorkerHandle; /// Live channels and initial replay data for a Worker protocol session. /// /// This is intentionally transport-agnostic: Unix JSONL sockets and Runtime /// WebSocket transports should subscribe through this helper so they cannot /// drift on which Worker/log events make up the protocol stream. pub struct WorkerProtocolSessionStreams { pub snapshot_event: Event, pub alert_snapshot: Vec, pub log_entries: broadcast::Receiver, pub events: broadcast::Receiver, } pub fn subscribe_worker_protocol_session(handle: &WorkerHandle) -> WorkerProtocolSessionStreams { let (snapshot_event, log_entries) = handle.snapshot_event_with_entry_subscription(); let (alert_snapshot, events) = handle.alerter.subscribe_with_snapshot(); WorkerProtocolSessionStreams { snapshot_event, alert_snapshot, log_entries, events, } } pub fn live_log_entry_event(entry: LogEntry) -> Option { match entry { LogEntry::SegmentStart { .. } => { let value = serde_json::to_value(&entry).expect("LogEntry is Serialize"); Some(Event::SegmentRotated { entry: value }) } LogEntry::UserInput { segments, .. } => Some(Event::UserMessage { segments }), LogEntry::SystemItem { item, .. } => { let value = serde_json::to_value(&item).expect("SystemItem is Serialize"); Some(Event::SystemItem { item: value }) } LogEntry::Invoke { trigger, .. } => Some(Event::InvokeStart { kind: trigger }), other => { // `SegmentLogSink::is_live_relevant` keeps non-live-relevant // variants off the broadcast lane; reaching here means the two are // out of sync and we silently dropped a wire event. Log so a future // regression surfaces instead of vanishing. tracing::error!( entry_kind = ?std::mem::discriminant(&other), "session-log broadcast emitted a non-live-relevant entry; sink filter and protocol dispatch are out of sync" ); None } } } /// Dispatch a client Method that has same-connection response semantics. /// /// Methods returning `Some(Event)` are handled by the protocol session and must /// be written back only to the requesting transport. Other methods are sent to /// the Worker controller and their results appear through the normal protocol /// event/log streams. pub async fn dispatch_worker_protocol_method( handle: &WorkerHandle, method: Method, ) -> Option { match method { Method::ListCompletions { kind, prefix } => { let entries = handle.completion_entries(kind, &prefix).await; Some(Event::Completions { kind, entries }) } method => { let _ = handle.send(method).await; None } } } #[cfg(test)] mod tests { use super::*; #[test] fn user_input_log_entry_maps_to_user_message_event() { let segments = vec![protocol::Segment::text("hello from log")]; let event = live_log_entry_event(LogEntry::UserInput { ts: session_store::segment_log::now_millis(), extensions: vec![], segments: segments.clone(), }) .expect("UserInput must be live-relevant"); match event { Event::UserMessage { segments: echoed } => assert_eq!(echoed, segments), other => panic!("expected UserMessage, got {other:?}"), } } }