use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use super::metadata::EventMetadata;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemEvent {
pub uuid: Option<String>,
#[serde(rename = "parentUuid")]
pub parent_uuid: Option<String>,
#[serde(rename = "logicalParentUuid")]
pub logical_parent_uuid: Option<String>,
#[serde(rename = "sessionId")]
pub session_id: String,
pub timestamp: DateTime<Utc>,
#[serde(rename = "isSidechain")]
pub is_sidechain: bool,
#[serde(rename = "userType")]
pub user_type: Option<String>,
pub cwd: Option<String>,
pub version: Option<String>,
#[serde(rename = "gitBranch")]
pub git_branch: Option<String>,
pub slug: Option<String>,
pub subtype: Option<String>,
pub content: Option<String>,
#[serde(rename = "isMeta")]
pub is_meta: Option<bool>,
pub level: Option<String>,
#[serde(rename = "compactMetadata")]
pub compact_metadata: Option<CompactMetadata>,
pub error: Option<ErrorDetails>,
}
impl SystemEvent {
pub fn is_compact_boundary(&self) -> bool {
self.subtype.as_deref() == Some("compact_boundary")
}
pub fn is_microcompact_boundary(&self) -> bool {
self.subtype.as_deref() == Some("microcompact_boundary")
}
pub fn is_error(&self) -> bool {
self.subtype.as_deref() == Some("error")
}
pub fn compact_metadata(&self) -> Option<&CompactMetadata> {
self.compact_metadata.as_ref()
}
pub fn metadata(&self) -> EventMetadata {
EventMetadata {
uuid: self.uuid.clone().unwrap_or_default(),
parent_uuid: self.parent_uuid.clone(),
session_id: self.session_id.clone(),
timestamp: self.timestamp,
is_sidechain: self.is_sidechain,
user_type: self.user_type.clone(),
cwd: self.cwd.clone(),
version: self.version.clone(),
git_branch: self.git_branch.clone(),
slug: self.slug.clone(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CompactMetadata {
pub trigger: String,
#[serde(rename = "preTokens")]
pub pre_tokens: u64,
#[serde(rename = "postTokens", skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub post_tokens: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ErrorDetails {
#[serde(rename = "type")]
pub error_type: String,
pub message: String,
#[serde(flatten)]
pub extra: JsonValue,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_compact_boundary() {
let json = r#"{
"type": "system",
"subtype": "compact_boundary",
"uuid": "boundary-uuid",
"parentUuid": null,
"logicalParentUuid": "last-message-uuid",
"sessionId": "session-123",
"timestamp": "2024-01-01T00:00:00Z",
"isSidechain": false,
"cwd": "/test",
"version": "2.1.19",
"gitBranch": "main",
"content": "Conversation compacted",
"level": "info",
"compactMetadata": {
"trigger": "auto",
"preTokens": 156594,
"postTokens": 50000
}
}"#;
let event: SystemEvent = serde_json::from_str(json).unwrap();
assert!(event.is_compact_boundary());
assert!(!event.is_microcompact_boundary());
assert!(!event.is_error());
let metadata = event.compact_metadata().unwrap();
assert_eq!(metadata.trigger, "auto");
assert_eq!(metadata.pre_tokens, 156_594);
assert_eq!(metadata.post_tokens, Some(50_000));
assert_eq!(
event.logical_parent_uuid,
Some("last-message-uuid".to_string())
);
}
#[test]
fn test_parse_microcompact_boundary() {
let json = r#"{
"type": "system",
"subtype": "microcompact_boundary",
"uuid": "micro-uuid",
"parentUuid": null,
"sessionId": "session-123",
"timestamp": "2024-01-01T00:00:00Z",
"isSidechain": false,
"cwd": "/test"
}"#;
let event: SystemEvent = serde_json::from_str(json).unwrap();
assert!(event.is_microcompact_boundary());
assert!(!event.is_compact_boundary());
}
#[test]
fn test_parse_error_event() {
let json = r#"{
"type": "system",
"subtype": "error",
"uuid": "error-uuid",
"parentUuid": null,
"sessionId": "session-123",
"timestamp": "2024-01-01T00:00:00Z",
"isSidechain": false,
"cwd": "/test",
"level": "error",
"content": "API overloaded",
"error": {
"type": "overloaded_error",
"message": "API is currently overloaded, please try again"
}
}"#;
let event: SystemEvent = serde_json::from_str(json).unwrap();
assert!(event.is_error());
assert_eq!(event.level, Some("error".to_string()));
assert_eq!(event.content, Some("API overloaded".to_string()));
if let Some(error) = &event.error {
assert_eq!(error.error_type, "overloaded_error");
assert_eq!(
error.message,
"API is currently overloaded, please try again"
);
}
}
#[test]
fn test_system_event_metadata() {
let event = SystemEvent {
uuid: Some("test-uuid".to_string()),
parent_uuid: Some("parent-uuid".to_string()),
logical_parent_uuid: None,
session_id: "session-123".to_string(),
timestamp: Utc::now(),
is_sidechain: false,
user_type: None,
cwd: Some("/test".to_string()),
version: Some("2.1.19".to_string()),
git_branch: Some("main".to_string()),
slug: None,
subtype: Some("compact_boundary".to_string()),
content: None,
is_meta: None,
level: None,
compact_metadata: None,
error: None,
};
let metadata = event.metadata();
assert_eq!(metadata.uuid, "test-uuid");
assert_eq!(metadata.parent_uuid, Some("parent-uuid".to_string()));
assert_eq!(metadata.session_id, "session-123");
}
#[test]
fn test_system_event_without_uuid() {
let json = r#"{
"type": "system",
"subtype": "info",
"parentUuid": null,
"sessionId": "session-123",
"timestamp": "2024-01-01T00:00:00Z",
"isSidechain": false,
"cwd": "/test",
"content": "System notification"
}"#;
let event: SystemEvent = serde_json::from_str(json).unwrap();
assert!(event.uuid.is_none());
let metadata = event.metadata();
assert_eq!(metadata.uuid, "");
}
}