1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use uuid::Uuid;
6
7use crate::types::ids::AgentId;
8use crate::types::message::Message;
9use crate::types::message_queue_types::MessageQueueEntry;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SerializedMessage {
14 #[serde(flatten)]
15 pub base: Message,
16 pub cwd: String,
17 #[serde(rename = "userType")]
18 pub user_type: String,
19 #[serde(skip_serializing_if = "Option::is_none")]
20 pub entrypoint: Option<String>,
21 #[serde(rename = "sessionId")]
22 pub session_id: String,
23 pub timestamp: String,
24 pub version: String,
25 #[serde(skip_serializing_if = "Option::is_none")]
26 #[serde(rename = "gitBranch")]
27 pub git_branch: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub slug: Option<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct LogOption {
35 pub date: String,
36 pub messages: Vec<SerializedMessage>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 #[serde(rename = "fullPath")]
39 pub full_path: Option<String>,
40 pub value: i64,
41 pub created: chrono::DateTime<chrono::Local>,
42 pub modified: chrono::DateTime<chrono::Local>,
43 #[serde(rename = "firstPrompt")]
44 pub first_prompt: String,
45 #[serde(rename = "messageCount")]
46 pub message_count: i64,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 #[serde(rename = "fileSize")]
49 pub file_size: Option<u64>,
50 #[serde(rename = "isSidechain")]
51 pub is_sidechain: bool,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 #[serde(rename = "isLite")]
54 pub is_lite: Option<bool>,
55 #[serde(skip_serializing_if = "Option::is_none")]
56 #[serde(rename = "sessionId")]
57 pub session_id: Option<String>,
58 #[serde(skip_serializing_if = "Option::is_none")]
59 #[serde(rename = "teamName")]
60 pub team_name: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 #[serde(rename = "agentName")]
63 pub agent_name: Option<String>,
64 #[serde(skip_serializing_if = "Option::is_none")]
65 #[serde(rename = "agentColor")]
66 pub agent_color: Option<String>,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 #[serde(rename = "agentSetting")]
69 pub agent_setting: Option<String>,
70 #[serde(skip_serializing_if = "Option::is_none")]
71 #[serde(rename = "isTeammate")]
72 pub is_teammate: Option<bool>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 #[serde(rename = "leafUuid")]
75 pub leaf_uuid: Option<Uuid>,
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub summary: Option<String>,
78 #[serde(skip_serializing_if = "Option::is_none")]
79 #[serde(rename = "customTitle")]
80 pub custom_title: Option<String>,
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub tag: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
84 #[serde(rename = "fileHistorySnapshots")]
85 pub file_history_snapshots: Option<Vec<FileHistorySnapshot>>,
86 #[serde(skip_serializing_if = "Option::is_none")]
87 #[serde(rename = "attributionSnapshots")]
88 pub attribution_snapshots: Option<Vec<AttributionSnapshotMessage>>,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 #[serde(rename = "contextCollapseCommits")]
91 pub context_collapse_commits: Option<Vec<ContextCollapseCommitEntry>>,
92 #[serde(skip_serializing_if = "Option::is_none")]
93 #[serde(rename = "contextCollapseSnapshot")]
94 pub context_collapse_snapshot: Option<ContextCollapseSnapshotEntry>,
95 #[serde(skip_serializing_if = "Option::is_none")]
96 #[serde(rename = "gitBranch")]
97 pub git_branch: Option<String>,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 #[serde(rename = "projectPath")]
100 pub project_path: Option<String>,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 #[serde(rename = "prNumber")]
103 pub pr_number: Option<i64>,
104 #[serde(skip_serializing_if = "Option::is_none")]
105 #[serde(rename = "prUrl")]
106 pub pr_url: Option<String>,
107 #[serde(skip_serializing_if = "Option::is_none")]
108 #[serde(rename = "prRepository")]
109 pub pr_repository: Option<String>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub mode: Option<SessionMode>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 #[serde(rename = "worktreeSession")]
114 pub worktree_session: Option<Option<PersistedWorktreeSession>>,
115 #[serde(skip_serializing_if = "Option::is_none")]
116 #[serde(rename = "contentReplacements")]
117 pub content_replacements: Option<Vec<ContentReplacementRecord>>,
118}
119
120#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
122#[serde(rename_all = "lowercase")]
123pub enum SessionMode {
124 Coordinator,
125 Normal,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct SummaryMessage {
131 #[serde(rename = "type")]
132 pub message_type: String, #[serde(rename = "leafUuid")]
134 pub leaf_uuid: Uuid,
135 pub summary: String,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct CustomTitleMessage {
141 #[serde(rename = "type")]
142 pub message_type: String, #[serde(rename = "sessionId")]
144 pub session_id: Uuid,
145 #[serde(rename = "customTitle")]
146 pub custom_title: String,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct AiTitleMessage {
152 #[serde(rename = "type")]
153 pub message_type: String, #[serde(rename = "sessionId")]
155 pub session_id: Uuid,
156 #[serde(rename = "aiTitle")]
157 pub ai_title: String,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct LastPromptMessage {
163 #[serde(rename = "type")]
164 pub message_type: String, #[serde(rename = "sessionId")]
166 pub session_id: Uuid,
167 #[serde(rename = "lastPrompt")]
168 pub last_prompt: String,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct TaskSummaryMessage {
174 #[serde(rename = "type")]
175 pub message_type: String, #[serde(rename = "sessionId")]
177 pub session_id: Uuid,
178 pub summary: String,
179 pub timestamp: String,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct TagMessage {
185 #[serde(rename = "type")]
186 pub message_type: String, #[serde(rename = "sessionId")]
188 pub session_id: Uuid,
189 pub tag: String,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct AgentNameMessage {
195 #[serde(rename = "type")]
196 pub message_type: String, #[serde(rename = "sessionId")]
198 pub session_id: Uuid,
199 #[serde(rename = "agentName")]
200 pub agent_name: String,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct AgentColorMessage {
206 #[serde(rename = "type")]
207 pub message_type: String, #[serde(rename = "sessionId")]
209 pub session_id: Uuid,
210 #[serde(rename = "agentColor")]
211 pub agent_color: String,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct AgentSettingMessage {
217 #[serde(rename = "type")]
218 pub message_type: String, #[serde(rename = "sessionId")]
220 pub session_id: Uuid,
221 #[serde(rename = "agentSetting")]
222 pub agent_setting: String,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct PRLinkMessage {
228 #[serde(rename = "type")]
229 pub message_type: String, #[serde(rename = "sessionId")]
231 pub session_id: Uuid,
232 #[serde(rename = "prNumber")]
233 pub pr_number: i64,
234 #[serde(rename = "prUrl")]
235 pub pr_url: String,
236 #[serde(rename = "prRepository")]
237 pub pr_repository: String,
238 pub timestamp: String,
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct ModeEntry {
244 #[serde(rename = "type")]
245 pub message_type: String, #[serde(rename = "sessionId")]
247 pub session_id: Uuid,
248 pub mode: SessionMode,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct PersistedWorktreeSession {
254 #[serde(rename = "originalCwd")]
255 pub original_cwd: String,
256 #[serde(rename = "worktreePath")]
257 pub worktree_path: String,
258 #[serde(rename = "worktreeName")]
259 pub worktree_name: String,
260 #[serde(skip_serializing_if = "Option::is_none")]
261 #[serde(rename = "worktreeBranch")]
262 pub worktree_branch: Option<String>,
263 #[serde(skip_serializing_if = "Option::is_none")]
264 #[serde(rename = "originalBranch")]
265 pub original_branch: Option<String>,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 #[serde(rename = "originalHeadCommit")]
268 pub original_head_commit: Option<String>,
269 #[serde(rename = "sessionId")]
270 pub session_id: String,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 #[serde(rename = "tmuxSessionName")]
273 pub tmux_session_name: Option<String>,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub hook_based: Option<bool>,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct WorktreeStateEntry {
281 #[serde(rename = "type")]
282 pub message_type: String, #[serde(rename = "sessionId")]
284 pub session_id: Uuid,
285 #[serde(rename = "worktreeSession")]
286 pub worktree_session: Option<PersistedWorktreeSession>,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct ContentReplacementEntry {
292 #[serde(rename = "type")]
293 pub message_type: String, #[serde(rename = "sessionId")]
295 pub session_id: Uuid,
296 #[serde(skip_serializing_if = "Option::is_none")]
297 #[serde(rename = "agentId")]
298 pub agent_id: Option<AgentId>,
299 pub replacements: Vec<ContentReplacementRecord>,
300}
301
302pub type ContentReplacementRecord = HashMap<String, serde_json::Value>;
304
305pub type FileHistorySnapshot = HashMap<String, serde_json::Value>;
307
308#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct FileHistorySnapshotMessage {
311 #[serde(rename = "type")]
312 pub message_type: String, #[serde(rename = "messageId")]
314 pub message_id: Uuid,
315 pub snapshot: FileHistorySnapshot,
316 #[serde(rename = "isSnapshotUpdate")]
317 pub is_snapshot_update: bool,
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct FileAttributionState {
323 #[serde(rename = "contentHash")]
324 pub content_hash: String,
325 #[serde(rename = "claudeContribution")]
326 pub claude_contribution: i64,
327 pub mtime: i64,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct AttributionSnapshotMessage {
333 #[serde(rename = "type")]
334 pub message_type: String, #[serde(rename = "messageId")]
336 pub message_id: Uuid,
337 pub surface: String,
338 #[serde(rename = "fileStates")]
339 pub file_states: HashMap<String, FileAttributionState>,
340 #[serde(skip_serializing_if = "Option::is_none")]
341 #[serde(rename = "promptCount")]
342 pub prompt_count: Option<i64>,
343 #[serde(skip_serializing_if = "Option::is_none")]
344 #[serde(rename = "promptCountAtLastCommit")]
345 pub prompt_count_at_last_commit: Option<i64>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 #[serde(rename = "permissionPromptCount")]
348 pub permission_prompt_count: Option<i64>,
349 #[serde(skip_serializing_if = "Option::is_none")]
350 #[serde(rename = "permissionPromptCountAtLastCommit")]
351 pub permission_prompt_count_at_last_commit: Option<i64>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 #[serde(rename = "escapeCount")]
354 pub escape_count: Option<i64>,
355 #[serde(skip_serializing_if = "Option::is_none")]
356 #[serde(rename = "escapeCountAtLastCommit")]
357 pub escape_count_at_last_commit: Option<i64>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct TranscriptMessage {
363 #[serde(flatten)]
364 pub base: SerializedMessage,
365 #[serde(rename = "parentUuid")]
366 pub parent_uuid: Option<Uuid>,
367 #[serde(skip_serializing_if = "Option::is_none")]
368 #[serde(rename = "logicalParentUuid")]
369 pub logical_parent_uuid: Option<Option<Uuid>>,
370 #[serde(rename = "isSidechain")]
371 pub is_sidechain: bool,
372 #[serde(skip_serializing_if = "Option::is_none")]
373 #[serde(rename = "gitBranch")]
374 pub git_branch: Option<String>,
375 #[serde(skip_serializing_if = "Option::is_none")]
376 #[serde(rename = "agentId")]
377 pub agent_id: Option<String>,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 #[serde(rename = "teamName")]
380 pub team_name: Option<String>,
381 #[serde(skip_serializing_if = "Option::is_none")]
382 #[serde(rename = "agentName")]
383 pub agent_name: Option<String>,
384 #[serde(skip_serializing_if = "Option::is_none")]
385 #[serde(rename = "agentColor")]
386 pub agent_color: Option<String>,
387 #[serde(skip_serializing_if = "Option::is_none")]
388 #[serde(rename = "promptId")]
389 pub prompt_id: Option<String>,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct SpeculationAcceptMessage {
395 #[serde(rename = "type")]
396 pub message_type: String, pub timestamp: String,
398 #[serde(rename = "timeSavedMs")]
399 pub time_saved_ms: i64,
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct ContextCollapseCommitEntry {
405 #[serde(rename = "type")]
406 pub message_type: String, #[serde(rename = "sessionId")]
408 pub session_id: Uuid,
409 #[serde(rename = "collapseId")]
410 pub collapse_id: String,
411 #[serde(rename = "summaryUuid")]
412 pub summary_uuid: String,
413 #[serde(rename = "summaryContent")]
414 pub summary_content: String,
415 pub summary: String,
416 #[serde(rename = "firstArchivedUuid")]
417 pub first_archived_uuid: String,
418 #[serde(rename = "lastArchivedUuid")]
419 pub last_archived_uuid: String,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct ContextCollapseSnapshotEntry {
425 #[serde(rename = "type")]
426 pub message_type: String, #[serde(rename = "sessionId")]
428 pub session_id: Uuid,
429 pub staged: Vec<StagedSpan>,
430 pub armed: bool,
431 #[serde(rename = "lastSpawnTokens")]
432 pub last_spawn_tokens: i64,
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize)]
437pub struct StagedSpan {
438 #[serde(rename = "startUuid")]
439 pub start_uuid: String,
440 #[serde(rename = "endUuid")]
441 pub end_uuid: String,
442 pub summary: String,
443 pub risk: f64,
444 #[serde(rename = "stagedAt")]
445 pub staged_at: i64,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
450#[serde(tag = "type")]
451pub enum Entry {
452 #[serde(rename = "user")]
453 Transcript(TranscriptMessage),
454 #[serde(rename = "summary")]
455 Summary(SummaryMessage),
456 #[serde(rename = "custom-title")]
457 CustomTitle(CustomTitleMessage),
458 #[serde(rename = "ai-title")]
459 AiTitle(AiTitleMessage),
460 #[serde(rename = "last-prompt")]
461 LastPrompt(LastPromptMessage),
462 #[serde(rename = "task-summary")]
463 TaskSummary(TaskSummaryMessage),
464 #[serde(rename = "tag")]
465 Tag(TagMessage),
466 #[serde(rename = "agent-name")]
467 AgentName(AgentNameMessage),
468 #[serde(rename = "agent-color")]
469 AgentColor(AgentColorMessage),
470 #[serde(rename = "agent-setting")]
471 AgentSetting(AgentSettingMessage),
472 #[serde(rename = "pr-link")]
473 PRLink(PRLinkMessage),
474 #[serde(rename = "file-history-snapshot")]
475 FileHistorySnapshot(FileHistorySnapshotMessage),
476 #[serde(rename = "attribution-snapshot")]
477 AttributionSnapshot(AttributionSnapshotMessage),
478 #[serde(rename = "queue_operation")]
479 QueueOperation(MessageQueueEntry),
480 #[serde(rename = "speculation-accept")]
481 SpeculationAccept(SpeculationAcceptMessage),
482 #[serde(rename = "mode")]
483 Mode(ModeEntry),
484 #[serde(rename = "worktree-state")]
485 WorktreeState(WorktreeStateEntry),
486 #[serde(rename = "content-replacement")]
487 ContentReplacement(ContentReplacementEntry),
488 #[serde(rename = "marble-origami-commit")]
489 ContextCollapseCommit(ContextCollapseCommitEntry),
490 #[serde(rename = "marble-origami-snapshot")]
491 ContextCollapseSnapshot(ContextCollapseSnapshotEntry),
492}
493
494pub fn sort_logs(logs: &mut Vec<LogOption>) {
496 logs.sort_by(|a, b| {
497 let modified_diff = b.modified.cmp(&a.modified);
498 if modified_diff != std::cmp::Ordering::Equal {
499 return modified_diff;
500 }
501 b.created.cmp(&a.created)
502 });
503}