Skip to main content

claude_session_types/events/
metadata.rs

1//! Common metadata structures shared across event types
2//!
3//! All events in Claude Code sessions share common metadata fields that provide
4//! execution context, session identity, and graph traversal capabilities.
5
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8
9/// Common metadata present in most events
10///
11/// This structure captures the execution context for an event:
12/// - **Session identity**: `session_id` links events to sessions
13/// - **Graph structure**: `parent_uuid` forms conversation tree
14/// - **Execution context**: `cwd`, `git_branch` track environment
15/// - **Timing**: `timestamp` for chronological ordering
16///
17/// # Links and Relationships
18///
19/// - `uuid`: Unique identifier for this event
20/// - `parent_uuid`: Links to previous message (conversation chain)
21/// - `session_id`: Groups events into sessions
22/// - `logical_parent_uuid`: Used for compact boundaries to preserve logical flow
23///
24/// # Frequency in Sessions
25///
26/// Present in ~95% of events (all except some system events)
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct EventMetadata {
29    /// Unique identifier for this event
30    ///
31    /// UUIDs are used to build conversation graphs and link related events.
32    pub uuid: String,
33
34    /// Links to parent message in conversation chain
35    ///
36    /// Forms a tree structure where:
37    /// - `None` = root message (session start)
38    /// - `Some(uuid)` = response to/result of another event
39    #[serde(rename = "parentUuid")]
40    pub parent_uuid: Option<String>,
41
42    /// Session identifier (groups related events)
43    ///
44    /// All events in a session file share the same `session_id`.
45    /// Format: `{uuid}` (e.g., "7b1f4d79-4ab1-4d12-913d-e367cb3a5387")
46    #[serde(rename = "sessionId")]
47    pub session_id: String,
48
49    /// Event timestamp (UTC)
50    ///
51    /// Used for chronological ordering and session duration calculation.
52    pub timestamp: DateTime<Utc>,
53
54    /// Whether this event is part of a sidechain (branching conversation)
55    ///
56    /// Sidechains occur when users:
57    /// - Undo/redo operations
58    /// - Branch from earlier conversation points
59    #[serde(rename = "isSidechain")]
60    pub is_sidechain: bool,
61
62    /// User type discriminator
63    ///
64    /// - `"external"` = Human user input
65    /// - `"internal"` = Tool result or system message
66    /// - `None` = Not applicable (system events)
67    #[serde(rename = "userType")]
68    pub user_type: Option<String>,
69
70    /// Current working directory when event occurred
71    ///
72    /// Examples:
73    /// - Windows: `"C:\\Users\\user\\project"`
74    /// - Unix: `"/home/user/project"`
75    pub cwd: Option<String>,
76
77    /// Claude Code version
78    ///
79    /// Format: `"2.1.19"` or similar semver
80    pub version: Option<String>,
81
82    /// Git branch when event occurred
83    ///
84    /// Useful for correlating sessions with code branches.
85    /// Example: `"main"`, `"feature-xyz"`
86    #[serde(rename = "gitBranch")]
87    pub git_branch: Option<String>,
88
89    /// Agent slug for delegated operations
90    ///
91    /// Present in progress events when using agents.
92    /// Examples: `"rust-implementer"`, `"research-agent"`
93    pub slug: Option<String>,
94}
95
96/// Logical parent UUID (for compact boundaries)
97///
98/// Compact boundaries have both:
99/// - `parent_uuid` - points to the compact boundary system message
100/// - `logical_parent_uuid` - points to the last real message before compaction
101///
102/// This preserves conversation flow while marking compaction points.
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct LogicalParentMetadata {
105    /// Logical parent UUID (preserves conversation flow across compaction)
106    #[serde(rename = "logicalParentUuid")]
107    pub logical_parent_uuid: Option<String>,
108}
109
110/// Tool use linking metadata
111///
112/// Present in progress events to link progress updates back to the
113/// tool invocation that triggered them.
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct ToolUseMetadata {
116    /// Tool use ID that triggered this progress
117    ///
118    /// Links back to `ContentBlock::ToolUse.id` in assistant message
119    #[serde(rename = "toolUseID")]
120    pub tool_use_id: Option<String>,
121
122    /// Parent tool use ID (for nested tool invocations)
123    ///
124    /// Used when agents spawn sub-agents or tools invoke other tools
125    #[serde(rename = "parentToolUseID")]
126    pub parent_tool_use_id: Option<String>,
127}
128
129/// Source tool assistant linking
130///
131/// Present in user messages that are tool results, linking back to the
132/// assistant message that invoked the tool.
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct SourceToolMetadata {
135    /// UUID of assistant message that invoked this tool
136    ///
137    /// Links `user` (tool result) → `assistant` (tool invocation)
138    #[serde(rename = "sourceToolAssistantUUID")]
139    pub source_tool_assistant_uuid: Option<String>,
140}
141
142impl EventMetadata {
143    /// Check if this is a human user prompt (vs tool result)
144    pub fn is_user_prompt(&self) -> bool {
145        self.user_type.as_deref() == Some("external")
146    }
147
148    /// Check if this is an internal message (tool result)
149    pub fn is_internal(&self) -> bool {
150        self.user_type.as_deref() == Some("internal")
151    }
152
153    /// Check if this is part of a sidechain
154    pub fn is_sidechain(&self) -> bool {
155        self.is_sidechain
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn test_metadata_user_type_checks() {
165        let metadata = EventMetadata {
166            uuid: "test-uuid".to_string(),
167            parent_uuid: None,
168            session_id: "session-123".to_string(),
169            timestamp: Utc::now(),
170            is_sidechain: false,
171            user_type: Some("external".to_string()),
172            cwd: Some("/test".to_string()),
173            version: Some("2.1.19".to_string()),
174            git_branch: Some("main".to_string()),
175            slug: None,
176        };
177
178        assert!(metadata.is_user_prompt());
179        assert!(!metadata.is_internal());
180        assert!(!metadata.is_sidechain());
181    }
182
183    #[test]
184    fn test_metadata_internal_type() {
185        let metadata = EventMetadata {
186            uuid: "test-uuid".to_string(),
187            parent_uuid: Some("parent-uuid".to_string()),
188            session_id: "session-123".to_string(),
189            timestamp: Utc::now(),
190            is_sidechain: false,
191            user_type: Some("internal".to_string()),
192            cwd: Some("/test".to_string()),
193            version: Some("2.1.19".to_string()),
194            git_branch: Some("main".to_string()),
195            slug: None,
196        };
197
198        assert!(!metadata.is_user_prompt());
199        assert!(metadata.is_internal());
200    }
201
202    #[test]
203    fn test_metadata_sidechain() {
204        let metadata = EventMetadata {
205            uuid: "test-uuid".to_string(),
206            parent_uuid: Some("parent-uuid".to_string()),
207            session_id: "session-123".to_string(),
208            timestamp: Utc::now(),
209            is_sidechain: true,
210            user_type: Some("external".to_string()),
211            cwd: Some("/test".to_string()),
212            version: Some("2.1.19".to_string()),
213            git_branch: Some("main".to_string()),
214            slug: None,
215        };
216
217        assert!(metadata.is_sidechain());
218    }
219}