Skip to main content

ai_agent/types/
logs.rs

1// Source: ~/claudecode/openclaudecode/src/types/logs.ts
2
3use 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/// A serialized message with session metadata.
12#[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/// Log option representing a session log entry.
33#[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/// Session mode.
121#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
122#[serde(rename_all = "lowercase")]
123pub enum SessionMode {
124    Coordinator,
125    Normal,
126}
127
128/// Summary message.
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct SummaryMessage {
131    #[serde(rename = "type")]
132    pub message_type: String, // "summary"
133    #[serde(rename = "leafUuid")]
134    pub leaf_uuid: Uuid,
135    pub summary: String,
136}
137
138/// Custom title message.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct CustomTitleMessage {
141    #[serde(rename = "type")]
142    pub message_type: String, // "custom-title"
143    #[serde(rename = "sessionId")]
144    pub session_id: Uuid,
145    #[serde(rename = "customTitle")]
146    pub custom_title: String,
147}
148
149/// AI-generated session title.
150#[derive(Debug, Clone, Serialize, Deserialize)]
151pub struct AiTitleMessage {
152    #[serde(rename = "type")]
153    pub message_type: String, // "ai-title"
154    #[serde(rename = "sessionId")]
155    pub session_id: Uuid,
156    #[serde(rename = "aiTitle")]
157    pub ai_title: String,
158}
159
160/// Last prompt message.
161#[derive(Debug, Clone, Serialize, Deserialize)]
162pub struct LastPromptMessage {
163    #[serde(rename = "type")]
164    pub message_type: String, // "last-prompt"
165    #[serde(rename = "sessionId")]
166    pub session_id: Uuid,
167    #[serde(rename = "lastPrompt")]
168    pub last_prompt: String,
169}
170
171/// Periodic fork-generated summary of what the agent is currently doing.
172#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct TaskSummaryMessage {
174    #[serde(rename = "type")]
175    pub message_type: String, // "task-summary"
176    #[serde(rename = "sessionId")]
177    pub session_id: Uuid,
178    pub summary: String,
179    pub timestamp: String,
180}
181
182/// Tag message.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct TagMessage {
185    #[serde(rename = "type")]
186    pub message_type: String, // "tag"
187    #[serde(rename = "sessionId")]
188    pub session_id: Uuid,
189    pub tag: String,
190}
191
192/// Agent name message.
193#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct AgentNameMessage {
195    #[serde(rename = "type")]
196    pub message_type: String, // "agent-name"
197    #[serde(rename = "sessionId")]
198    pub session_id: Uuid,
199    #[serde(rename = "agentName")]
200    pub agent_name: String,
201}
202
203/// Agent color message.
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct AgentColorMessage {
206    #[serde(rename = "type")]
207    pub message_type: String, // "agent-color"
208    #[serde(rename = "sessionId")]
209    pub session_id: Uuid,
210    #[serde(rename = "agentColor")]
211    pub agent_color: String,
212}
213
214/// Agent setting message.
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct AgentSettingMessage {
217    #[serde(rename = "type")]
218    pub message_type: String, // "agent-setting"
219    #[serde(rename = "sessionId")]
220    pub session_id: Uuid,
221    #[serde(rename = "agentSetting")]
222    pub agent_setting: String,
223}
224
225/// PR link message stored in session transcript.
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct PRLinkMessage {
228    #[serde(rename = "type")]
229    pub message_type: String, // "pr-link"
230    #[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/// Mode entry for session mode tracking.
242#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct ModeEntry {
244    #[serde(rename = "type")]
245    pub message_type: String, // "mode"
246    #[serde(rename = "sessionId")]
247    pub session_id: Uuid,
248    pub mode: SessionMode,
249}
250
251/// Worktree session state persisted to the transcript for resume.
252#[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/// Worktree session state entry.
279#[derive(Debug, Clone, Serialize, Deserialize)]
280pub struct WorktreeStateEntry {
281    #[serde(rename = "type")]
282    pub message_type: String, // "worktree-state"
283    #[serde(rename = "sessionId")]
284    pub session_id: Uuid,
285    #[serde(rename = "worktreeSession")]
286    pub worktree_session: Option<PersistedWorktreeSession>,
287}
288
289/// Content replacement entry for resume reconstruction.
290#[derive(Debug, Clone, Serialize, Deserialize)]
291pub struct ContentReplacementEntry {
292    #[serde(rename = "type")]
293    pub message_type: String, // "content-replacement"
294    #[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
302/// Content replacement record.
303pub type ContentReplacementRecord = HashMap<String, serde_json::Value>;
304
305/// File history snapshot.
306pub type FileHistorySnapshot = HashMap<String, serde_json::Value>;
307
308/// File history snapshot message.
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct FileHistorySnapshotMessage {
311    #[serde(rename = "type")]
312    pub message_type: String, // "file-history-snapshot"
313    #[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/// Per-file attribution state tracking Claude's character contributions.
321#[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/// Attribution snapshot message stored in session transcript.
331#[derive(Debug, Clone, Serialize, Deserialize)]
332pub struct AttributionSnapshotMessage {
333    #[serde(rename = "type")]
334    pub message_type: String, // "attribution-snapshot"
335    #[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/// Transcript message with additional fields.
361#[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/// Speculation accept message.
393#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct SpeculationAcceptMessage {
395    #[serde(rename = "type")]
396    pub message_type: String, // "speculation-accept"
397    pub timestamp: String,
398    #[serde(rename = "timeSavedMs")]
399    pub time_saved_ms: i64,
400}
401
402/// Persisted context-collapse commit.
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct ContextCollapseCommitEntry {
405    #[serde(rename = "type")]
406    pub message_type: String, // "marble-origami-commit"
407    #[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/// Snapshot of the staged queue and spawn trigger state.
423#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct ContextCollapseSnapshotEntry {
425    #[serde(rename = "type")]
426    pub message_type: String, // "marble-origami-snapshot"
427    #[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/// A staged span in the context collapse snapshot.
436#[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/// Entry union of all transcript entry types.
449#[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
494/// Sort logs by modified date (newest first), then by created date.
495pub 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}