tracevault_core/
streaming.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sha2::{Digest, Sha256};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
6#[serde(rename_all = "snake_case")]
7pub enum StreamEventType {
8 ToolUse,
9 Transcript,
10 SessionStart,
11 SessionEnd,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct StreamEventRequest {
16 pub protocol_version: u32,
17 pub event_type: StreamEventType,
18 pub session_id: String,
19 pub timestamp: DateTime<Utc>,
20 pub hook_event_name: Option<String>,
21 pub tool_name: Option<String>,
22 pub tool_input: Option<serde_json::Value>,
23 pub tool_response: Option<serde_json::Value>,
24 pub event_index: Option<i32>,
25 pub transcript_lines: Option<Vec<serde_json::Value>>,
26 pub transcript_offset: Option<i64>,
27 pub model: Option<String>,
28 pub cwd: Option<String>,
29 pub final_stats: Option<SessionFinalStats>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct SessionFinalStats {
34 pub duration_ms: Option<i64>,
35 pub total_tokens: Option<i64>,
36 pub input_tokens: Option<i64>,
37 pub output_tokens: Option<i64>,
38 pub cache_read_tokens: Option<i64>,
39 pub cache_write_tokens: Option<i64>,
40 pub user_messages: Option<i32>,
41 pub assistant_messages: Option<i32>,
42 pub total_tool_calls: Option<i32>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct StreamEventResponse {
47 pub session_db_id: uuid::Uuid,
48 pub event_db_id: Option<uuid::Uuid>,
49 pub status: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct CommitPushRequest {
54 pub commit_sha: String,
55 pub branch: Option<String>,
56 pub author: String,
57 pub message: Option<String>,
58 pub diff_data: Option<serde_json::Value>,
59 pub committed_at: Option<DateTime<Utc>>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct CommitPushResponse {
64 pub commit_db_id: uuid::Uuid,
65 pub attributions_count: i64,
66}
67
68#[derive(Debug, Clone)]
69pub struct ExtractedFileChange {
70 pub file_path: String,
71 pub change_type: String,
72 pub diff_text: Option<String>,
73 pub content_hash: Option<String>,
74}
75
76pub fn is_file_modifying_tool(tool_name: &str) -> bool {
77 matches!(tool_name, "Write" | "Edit" | "Bash")
78}
79
80pub fn extract_file_change(
81 tool_name: &str,
82 tool_input: &serde_json::Value,
83) -> Option<ExtractedFileChange> {
84 match tool_name {
85 "Write" => {
86 let file_path = tool_input.get("file_path")?.as_str()?.to_string();
87 let content = tool_input.get("content")?.as_str()?;
88 let mut hasher = Sha256::new();
89 hasher.update(content.as_bytes());
90 let hash = format!("{:x}", hasher.finalize());
91 let diff = content
92 .lines()
93 .map(|l| format!("+{l}"))
94 .collect::<Vec<_>>()
95 .join("\n");
96 Some(ExtractedFileChange {
97 file_path,
98 change_type: "create".to_string(),
99 diff_text: Some(diff),
100 content_hash: Some(hash),
101 })
102 }
103 "Edit" => {
104 let file_path = tool_input.get("file_path")?.as_str()?.to_string();
105 let old_string = tool_input.get("old_string")?.as_str()?;
106 let new_string = tool_input.get("new_string")?.as_str()?;
107 let diff = format!("--- {old_string}\n+++ {new_string}");
108 Some(ExtractedFileChange {
109 file_path,
110 change_type: "edit".to_string(),
111 diff_text: Some(diff),
112 content_hash: None,
113 })
114 }
115 _ => None,
116 }
117}