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}