use crate::types::error::APIError;
use crate::types::message::Message;
use crate::types::message::Part;
use crate::types::permission::PermissionReply;
use crate::types::permission::PermissionRequest;
use crate::types::session::RevertInfo;
use crate::types::session::Session;
use crate::types::session::SessionSummary;
use crate::types::session::SessionTime;
use crate::types::session::ShareInfo;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalEvent {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub directory: Option<String>,
#[serde(default)]
pub project: Option<String>,
#[serde(default)]
pub workspace: Option<String>,
pub payload: GlobalEventPayload,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GlobalEventPayload {
Sync(Box<GlobalSyncEventPayload>),
Event(Box<Event>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalSyncEventPayload {
#[serde(rename = "type")]
pub kind: GlobalSyncPayloadKind,
#[serde(rename = "syncEvent")]
pub sync_event: SyncEvent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum GlobalSyncPayloadKind {
#[serde(rename = "sync")]
Sync,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SyncEvent {
#[serde(rename = "message.updated.1")]
MessageUpdated {
id: String,
seq: u64,
#[serde(rename = "aggregateID")]
aggregate_id: String,
data: Box<SyncMessageUpdatedData>,
},
#[serde(rename = "message.removed.1")]
MessageRemoved {
id: String,
seq: u64,
#[serde(rename = "aggregateID")]
aggregate_id: String,
data: Box<SyncMessageRemovedData>,
},
#[serde(rename = "message.part.updated.1")]
MessagePartUpdated {
id: String,
seq: u64,
#[serde(rename = "aggregateID")]
aggregate_id: String,
data: Box<SyncMessagePartUpdatedData>,
},
#[serde(rename = "message.part.removed.1")]
MessagePartRemoved {
id: String,
seq: u64,
#[serde(rename = "aggregateID")]
aggregate_id: String,
data: Box<SyncMessagePartRemovedData>,
},
#[serde(rename = "session.created.1")]
SessionCreated {
id: String,
seq: u64,
#[serde(rename = "aggregateID")]
aggregate_id: String,
data: Box<SyncSessionData>,
},
#[serde(rename = "session.updated.1")]
SessionUpdated {
id: String,
seq: u64,
#[serde(rename = "aggregateID")]
aggregate_id: String,
data: Box<SyncSessionUpdatedData>,
},
#[serde(rename = "session.deleted.1")]
SessionDeleted {
id: String,
seq: u64,
#[serde(rename = "aggregateID")]
aggregate_id: String,
data: Box<SyncSessionData>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncMessageUpdatedData {
#[serde(rename = "sessionID")]
pub session_id: String,
pub info: Message,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncMessageRemovedData {
#[serde(rename = "sessionID")]
pub session_id: String,
#[serde(rename = "messageID")]
pub message_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncMessagePartUpdatedData {
#[serde(rename = "sessionID")]
pub session_id: String,
pub part: Part,
pub time: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncMessagePartRemovedData {
#[serde(rename = "sessionID")]
pub session_id: String,
#[serde(rename = "messageID")]
pub message_id: String,
#[serde(rename = "partID")]
pub part_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncSessionData {
#[serde(rename = "sessionID")]
pub session_id: String,
pub info: Session,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SyncSessionUpdatedData {
#[serde(rename = "sessionID")]
pub session_id: String,
pub info: SyncSessionPatch,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SyncSessionPatch {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub slug: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub directory: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parent_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<SessionSummary>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub share: Option<ShareInfo>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub time: Option<SessionTime>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub permission: Option<crate::types::permission::Ruleset>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub revert: Option<RevertInfo>,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
#[serde(tag = "type")]
pub enum Event {
#[serde(rename = "server.connected")]
ServerConnected {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "server.heartbeat")]
ServerHeartbeat {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "server.instance.disposed")]
ServerInstanceDisposed {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "global.disposed")]
GlobalDisposed {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "session.created")]
SessionCreated {
properties: SessionInfoProps,
},
#[serde(rename = "session.updated")]
SessionUpdated {
properties: SessionInfoProps,
},
#[serde(rename = "session.deleted")]
SessionDeleted {
properties: SessionInfoProps,
},
#[serde(rename = "session.diff")]
SessionDiff {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "session.error")]
SessionError {
properties: SessionErrorProps,
},
#[serde(rename = "session.compacted")]
SessionCompacted {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "session.status")]
SessionStatus {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "session.idle")]
SessionIdle {
properties: SessionIdleProps,
},
#[serde(rename = "message.updated")]
MessageUpdated {
properties: Box<MessageUpdatedProps>,
},
#[serde(rename = "message.removed")]
MessageRemoved {
properties: MessageRemovedProps,
},
#[serde(rename = "message.part.updated")]
MessagePartUpdated {
properties: Box<MessagePartEventProps>,
},
#[serde(rename = "message.part.removed")]
MessagePartRemoved {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "pty.created")]
PtyCreated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "pty.updated")]
PtyUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "pty.exited")]
PtyExited {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "pty.deleted")]
PtyDeleted {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "permission.updated")]
PermissionUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "permission.replied")]
PermissionReplied {
properties: PermissionRepliedProps,
},
#[serde(rename = "permission.asked")]
PermissionAsked {
properties: PermissionAskedProps,
},
#[serde(rename = "permission.replied-next")]
PermissionRepliedNext {
properties: PermissionRepliedProps,
},
#[serde(rename = "project.updated")]
ProjectUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "file.edited")]
FileEdited {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "file.watcher.updated")]
FileWatcherUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "vcs.branch.updated")]
VcsBranchUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "lsp.updated")]
LspUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "lsp.client.diagnostics")]
LspClientDiagnostics {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "command.executed")]
CommandExecuted {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "mcp.tools.changed")]
McpToolsChanged {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "mcp.browser.open.failed")]
McpBrowserOpenFailed {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "workspace.ready")]
WorkspaceReady {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "workspace.failed")]
WorkspaceFailed {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "worktree.ready")]
WorktreeReady {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "worktree.failed")]
WorktreeFailed {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "message.part.delta")]
MessagePartDelta {
properties: Box<MessagePartEventProps>,
},
#[serde(rename = "installation.updated")]
InstallationUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "installation.update-available")]
InstallationUpdateAvailable {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "ide.installed")]
IdeInstalled {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "tui.prompt.append")]
TuiPromptAppend {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "tui.command.execute")]
TuiCommandExecute {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "tui.toast.show")]
TuiToastShow {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "tui.session.select")]
TuiSessionSelect {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(rename = "question.asked")]
QuestionAsked {
properties: QuestionAskedProps,
},
#[serde(rename = "question.replied")]
QuestionReplied {
properties: QuestionRepliedProps,
},
#[serde(rename = "question.rejected")]
QuestionRejected {
properties: QuestionRejectedProps,
},
#[serde(rename = "todo.updated")]
TodoUpdated {
#[serde(default)]
properties: serde_json::Value,
},
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionInfoProps {
pub info: Session,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionIdleProps {
#[serde(rename = "sessionID")]
pub session_id: String,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum AssistantError {
Api(APIError),
Unknown(serde_json::Value),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionErrorProps {
#[serde(default, rename = "sessionID")]
pub session_id: Option<String>,
#[serde(default)]
pub error: Option<AssistantError>,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageUpdatedProps {
pub info: crate::types::message::MessageInfo,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessageRemovedProps {
#[serde(rename = "sessionID")]
pub session_id: String,
#[serde(rename = "messageID")]
pub message_id: String,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessagePartEventProps {
#[serde(default, rename = "sessionID")]
pub session_id: Option<String>,
#[serde(default, rename = "messageID")]
pub message_id: Option<String>,
#[serde(default)]
pub index: Option<usize>,
#[serde(default)]
pub part: Option<crate::types::message::Part>,
#[serde(default)]
pub delta: Option<String>,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PermissionAskedProps {
#[serde(flatten)]
pub request: PermissionRequest,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRepliedProps {
#[serde(rename = "sessionID")]
pub session_id: String,
#[serde(rename = "requestID")]
pub request_id: String,
pub reply: PermissionReply,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct QuestionAskedProps {
#[serde(flatten)]
pub request: crate::types::question::QuestionRequest,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuestionRepliedProps {
#[serde(rename = "sessionID")]
pub session_id: String,
#[serde(rename = "requestID")]
pub request_id: String,
pub answers: Vec<Vec<String>>,
#[serde(flatten)]
pub extra: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuestionRejectedProps {
#[serde(rename = "sessionID")]
pub session_id: String,
#[serde(rename = "requestID")]
pub request_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(flatten)]
pub extra: serde_json::Value,
}
impl Event {
pub fn session_id(&self) -> Option<&str> {
match self {
Self::SessionCreated { properties }
| Self::SessionUpdated { properties }
| Self::SessionDeleted { properties } => Some(&properties.info.id),
Self::SessionIdle { properties } => Some(&properties.session_id),
Self::SessionError { properties } => properties.session_id.as_deref(),
Self::MessageUpdated { properties } => properties.info.session_id.as_deref(),
Self::MessageRemoved { properties } => Some(&properties.session_id),
Self::MessagePartUpdated { properties } | Self::MessagePartDelta { properties } => {
properties.session_id.as_deref()
}
Self::PermissionAsked { properties } => Some(&properties.request.session_id),
Self::PermissionReplied { properties } | Self::PermissionRepliedNext { properties } => {
Some(&properties.session_id)
}
Self::QuestionAsked { properties } => Some(&properties.request.session_id),
Self::QuestionReplied { properties } => Some(&properties.session_id),
Self::QuestionRejected { properties } => Some(&properties.session_id),
_ => None,
}
}
pub fn is_heartbeat(&self) -> bool {
matches!(self, Self::ServerHeartbeat { .. })
}
pub fn is_connected(&self) -> bool {
matches!(self, Self::ServerConnected { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_deserialize_session_created() {
let json = r#"{
"type": "session.created",
"properties": {
"info": {
"id": "sess-123",
"slug": "sess-123",
"title": "Test Session",
"version": "1.0"
}
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::SessionCreated { .. }));
assert_eq!(event.session_id(), Some("sess-123"));
}
#[test]
fn test_event_deserialize_heartbeat() {
let json = r#"{"type":"server.heartbeat","properties":{}}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::ServerHeartbeat { .. }));
assert!(event.is_heartbeat());
}
#[test]
fn test_event_deserialize_unknown() {
let json = r#"{"type":"some.future.event","properties":{}}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::Unknown));
}
#[test]
fn test_message_part_with_delta() {
let json = r#"{"type":"message.part.updated","properties":{"sessionID":"s1","messageID":"m1","delta":"Hello"}}"#;
let event: Event = serde_json::from_str(json).unwrap();
if let Event::MessagePartUpdated { properties } = &event {
assert_eq!(properties.delta, Some("Hello".to_string()));
} else {
panic!("Expected MessagePartUpdated");
}
}
#[test]
fn test_event_deserialize_pty_created() {
let json = r#"{"type":"pty.created","properties":{"id":"pty1"}}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::PtyCreated { .. }));
}
#[test]
fn test_event_deserialize_permission_asked() {
let json = r#"{
"type": "permission.asked",
"properties": {
"id": "req-123",
"sessionID": "sess-456",
"permission": "file.write",
"patterns": ["**/*.rs"]
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::PermissionAsked { .. }));
assert_eq!(event.session_id(), Some("sess-456"));
}
#[test]
fn test_event_deserialize_permission_replied() {
let json = r#"{
"type": "permission.replied",
"properties": {
"sessionID": "sess-456",
"requestID": "req-123",
"reply": "always"
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::PermissionReplied { .. }));
assert_eq!(event.session_id(), Some("sess-456"));
}
#[test]
fn test_event_deserialize_message_updated() {
let json = r#"{
"type": "message.updated",
"properties": {
"info": {
"id": "msg-123",
"sessionID": "sess-456",
"role": "assistant",
"time": {"created": 1234567890}
}
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::MessageUpdated { .. }));
assert_eq!(event.session_id(), Some("sess-456"));
}
#[test]
fn test_event_deserialize_message_removed() {
let json = r#"{
"type": "message.removed",
"properties": {
"sessionID": "sess-456",
"messageID": "msg-123"
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::MessageRemoved { .. }));
assert_eq!(event.session_id(), Some("sess-456"));
}
#[test]
fn test_event_deserialize_session_error() {
let json = r#"{
"type": "session.error",
"properties": {
"sessionID": "sess-456",
"error": {"message": "Something went wrong", "isRetryable": false}
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
if let Event::SessionError { properties } = &event {
assert!(properties.error.is_some());
if let Some(AssistantError::Api(err)) = &properties.error {
assert_eq!(err.message, "Something went wrong");
} else {
panic!("Expected APIError");
}
} else {
panic!("Expected SessionError");
}
}
#[test]
fn test_event_deserialize_todo_updated() {
let json = r#"{"type":"todo.updated","properties":{}}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::TodoUpdated { .. }));
}
#[test]
fn test_event_deserialize_question_asked() {
let json = r#"{
"type": "question.asked",
"properties": {
"id": "req-123",
"sessionID": "sess-456",
"questions": [{"question": "Continue?"}]
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::QuestionAsked { .. }));
assert_eq!(event.session_id(), Some("sess-456"));
}
#[test]
fn test_event_deserialize_question_replied() {
let json = r#"{
"type": "question.replied",
"properties": {
"sessionID": "sess-456",
"requestID": "req-123",
"answers": [["Yes"]]
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
if let Event::QuestionReplied { properties } = &event {
assert_eq!(properties.session_id, "sess-456");
assert_eq!(properties.request_id, "req-123");
assert_eq!(properties.answers, vec![vec!["Yes"]]);
} else {
panic!("Expected QuestionReplied");
}
}
#[test]
fn test_event_deserialize_question_rejected() {
let json = r#"{
"type": "question.rejected",
"properties": {
"sessionID": "sess-456",
"requestID": "req-123",
"reason": "User cancelled"
}
}"#;
let event: Event = serde_json::from_str(json).unwrap();
if let Event::QuestionRejected { properties } = &event {
assert_eq!(properties.session_id, "sess-456");
assert_eq!(properties.request_id, "req-123");
assert_eq!(properties.reason, Some("User cancelled".to_string()));
} else {
panic!("Expected QuestionRejected");
}
}
#[test]
fn test_message_part_delta_deserialize() {
let json = r#"{"type": "message.part.delta", "properties": {"sessionID": "ses-1", "messageID": "msg-1", "index": 0}}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::MessagePartDelta { .. }));
assert_eq!(event.session_id(), Some("ses-1"));
}
#[test]
fn test_workspace_events_deserialize() {
let ready = r#"{"type": "workspace.ready", "properties": {}}"#;
let failed = r#"{"type": "workspace.failed", "properties": {}}"#;
assert!(matches!(
serde_json::from_str::<Event>(ready).unwrap(),
Event::WorkspaceReady { .. }
));
assert!(matches!(
serde_json::from_str::<Event>(failed).unwrap(),
Event::WorkspaceFailed { .. }
));
}
#[test]
fn test_worktree_events_deserialize() {
let ready = r#"{"type": "worktree.ready", "properties": {}}"#;
let failed = r#"{"type": "worktree.failed", "properties": {}}"#;
assert!(matches!(
serde_json::from_str::<Event>(ready).unwrap(),
Event::WorktreeReady { .. }
));
assert!(matches!(
serde_json::from_str::<Event>(failed).unwrap(),
Event::WorktreeFailed { .. }
));
}
#[test]
fn test_mcp_browser_open_failed_deserialize() {
let json = r#"{"type": "mcp.browser.open.failed", "properties": {}}"#;
let event: Event = serde_json::from_str(json).unwrap();
assert!(matches!(event, Event::McpBrowserOpenFailed { .. }));
}
#[test]
fn test_global_event_directory_payload_deserialize() {
let json = r#"{
"directory": "/workspace/project",
"project": "proj-1",
"workspace": "ws-1",
"payload": {
"type": "question.asked",
"properties": {
"id": "req-123",
"sessionID": "sess-456",
"questions": [{"question": "Continue?"}]
}
}
}"#;
let event: GlobalEvent = serde_json::from_str(json).unwrap();
assert_eq!(event.directory.as_deref(), Some("/workspace/project"));
assert_eq!(event.project.as_deref(), Some("proj-1"));
assert_eq!(event.workspace.as_deref(), Some("ws-1"));
assert!(matches!(
event.payload,
GlobalEventPayload::Event(event) if matches!(*event, Event::QuestionAsked { .. })
));
}
#[test]
fn test_global_event_sync_payload_deserialize() {
let json = r#"{
"directory": "/workspace/project",
"project": "proj-1",
"workspace": "ws-1",
"payload": {
"type": "sync",
"syncEvent": {
"type": "message.updated.1",
"id": "sync-1",
"seq": 7,
"aggregateID": "sess-456",
"data": {
"sessionID": "sess-456",
"info": {
"info": {
"id": "msg-123",
"sessionID": "sess-456",
"role": "assistant",
"time": {"created": 1234567890},
"model": {
"providerID": "anthropic",
"modelID": "claude-sonnet-4",
"variant": "thinking"
}
},
"parts": []
}
}
}
}
}"#;
let event: GlobalEvent = serde_json::from_str(json).unwrap();
match event.payload {
GlobalEventPayload::Sync(sync_payload) => {
let GlobalSyncEventPayload { kind, sync_event } = *sync_payload;
assert_eq!(kind, GlobalSyncPayloadKind::Sync);
let SyncEvent::MessageUpdated {
id,
seq,
aggregate_id,
data,
} = sync_event
else {
panic!("expected message.updated.1 sync event");
};
assert_eq!(id, "sync-1");
assert_eq!(seq, 7);
assert_eq!(aggregate_id, "sess-456");
assert_eq!(data.session_id, "sess-456");
assert_eq!(data.info.info.id, "msg-123");
assert_eq!(
data.info
.info
.model
.as_ref()
.and_then(|model| model.variant.as_deref()),
Some("thinking")
);
}
GlobalEventPayload::Event(other) => {
panic!("expected sync payload, got event payload {other:?}");
}
}
}
#[test]
fn test_global_event_server_connected_without_directory() {
let json = r#"{
"payload": {
"type": "server.connected",
"properties": {}
}
}"#;
let event: GlobalEvent = serde_json::from_str(json).unwrap();
assert!(event.directory.is_none());
assert!(matches!(
event.payload,
GlobalEventPayload::Event(event) if matches!(*event, Event::ServerConnected { .. })
));
}
}