Skip to main content

tracevault_core/
streaming.rs

1use 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 diff_data: Option<serde_json::Value>,
58    pub committed_at: Option<DateTime<Utc>>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct CommitPushResponse {
63    pub commit_db_id: uuid::Uuid,
64    pub attributions_count: i64,
65}
66
67#[derive(Debug, Clone)]
68pub struct ExtractedFileChange {
69    pub file_path: String,
70    pub change_type: String,
71    pub diff_text: Option<String>,
72    pub content_hash: Option<String>,
73}
74
75pub fn is_file_modifying_tool(tool_name: &str) -> bool {
76    matches!(tool_name, "Write" | "Edit" | "Bash")
77}
78
79pub fn extract_file_change(
80    tool_name: &str,
81    tool_input: &serde_json::Value,
82) -> Option<ExtractedFileChange> {
83    match tool_name {
84        "Write" => {
85            let file_path = tool_input.get("file_path")?.as_str()?.to_string();
86            let content = tool_input.get("content")?.as_str()?;
87            let mut hasher = Sha256::new();
88            hasher.update(content.as_bytes());
89            let hash = format!("{:x}", hasher.finalize());
90            let diff = content
91                .lines()
92                .map(|l| format!("+{l}"))
93                .collect::<Vec<_>>()
94                .join("\n");
95            Some(ExtractedFileChange {
96                file_path,
97                change_type: "create".to_string(),
98                diff_text: Some(diff),
99                content_hash: Some(hash),
100            })
101        }
102        "Edit" => {
103            let file_path = tool_input.get("file_path")?.as_str()?.to_string();
104            let old_string = tool_input.get("old_string")?.as_str()?;
105            let new_string = tool_input.get("new_string")?.as_str()?;
106            let diff = format!("--- {old_string}\n+++ {new_string}");
107            Some(ExtractedFileChange {
108                file_path,
109                change_type: "edit".to_string(),
110                diff_text: Some(diff),
111                content_hash: None,
112            })
113        }
114        _ => None,
115    }
116}