use soma_studio_core::ChatStreamEvent;
#[derive(Debug, Clone)]
pub struct ChatEventEnvelope {
pub session_id: String,
pub conversation_id: String,
pub payload: String,
}
pub fn chat_stream_event_envelope(
session_id: &str,
conversation_id: &str,
event: &ChatStreamEvent,
) -> ChatEventEnvelope {
ChatEventEnvelope {
session_id: session_id.to_string(),
conversation_id: conversation_id.to_string(),
payload: chat_stream_event_payload(event),
}
}
pub fn chat_event_matches_subscription(
event: &ChatEventEnvelope,
session_id: &str,
conversation_filter: Option<&str>,
) -> bool {
if event.session_id != session_id {
return false;
}
if let Some(filter) = conversation_filter {
return event.conversation_id == filter;
}
true
}
pub fn chat_stream_event_payload(event: &ChatStreamEvent) -> String {
serde_json::to_string(event).expect("chat stream event should serialize")
}
#[cfg(test)]
mod tests {
use super::{
ChatEventEnvelope, chat_event_matches_subscription, chat_stream_event_envelope,
chat_stream_event_payload,
};
use soma_studio_core::ChatStreamEvent;
fn sample_event(session_id: &str, conversation_id: &str) -> ChatEventEnvelope {
ChatEventEnvelope {
session_id: session_id.to_string(),
conversation_id: conversation_id.to_string(),
payload: "{\"type\":\"chat-start\"}".to_string(),
}
}
#[test]
fn chat_event_filter_allows_same_session_without_conversation_filter() {
let event = sample_event("session-a", "conversation-1");
assert!(chat_event_matches_subscription(&event, "session-a", None));
}
#[test]
fn chat_event_filter_rejects_other_session() {
let event = sample_event("session-a", "conversation-1");
assert!(!chat_event_matches_subscription(&event, "session-b", None));
}
#[test]
fn chat_event_filter_rejects_other_conversation_when_filtered() {
let event = sample_event("session-a", "conversation-1");
assert!(chat_event_matches_subscription(
&event,
"session-a",
Some("conversation-1"),
));
assert!(!chat_event_matches_subscription(
&event,
"session-a",
Some("conversation-2"),
));
}
#[test]
fn chat_stream_event_payload_uses_wire_type_tags() {
let payload = chat_stream_event_payload(&ChatStreamEvent::ChatDelta {
conversation_id: "conversation-1".to_string(),
message_id: "message-1".to_string(),
selected_provider: Some("lmstudio".to_string()),
selected_model_id: Some("model-1".to_string()),
delta: "hello".to_string(),
});
let value: serde_json::Value = serde_json::from_str(&payload).expect("json payload");
assert_eq!(value["type"], "chat-delta");
assert_eq!(value["conversation_id"], "conversation-1");
assert_eq!(value["message_id"], "message-1");
assert_eq!(value["delta"], "hello");
}
#[test]
fn chat_stream_event_envelope_carries_subscription_keys() {
let event = ChatStreamEvent::Heartbeat {
message: "ready".to_string(),
};
let envelope = chat_stream_event_envelope("session-a", "conversation-1", &event);
assert_eq!(envelope.session_id, "session-a");
assert_eq!(envelope.conversation_id, "conversation-1");
assert!(envelope.payload.contains("\"type\":\"heartbeat\""));
}
}