Skip to main content

trace_share_core/
models.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct ToolInfo {
6    pub name: String,
7    pub args_json: Option<String>,
8    pub result_json: Option<String>,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct EventMeta {
13    pub cwd: Option<String>,
14    pub repo: Option<String>,
15    pub exit_code: Option<i32>,
16    pub model: Option<String>,
17    pub tags: Vec<String>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct CanonicalEvent {
22    pub source: String,
23    pub session_id: String,
24    pub ts: DateTime<Utc>,
25    pub kind: String,
26    pub text: String,
27    pub tool: Option<ToolInfo>,
28    pub meta: Option<EventMeta>,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ChunkMetadata {
33    pub source: String,
34    pub session_id: String,
35    pub chunk_index: usize,
36    pub ts_start: String,
37    pub ts_end: String,
38    pub tool_names: Vec<String>,
39    pub error_types: Vec<String>,
40    pub repo_fingerprint: Option<String>,
41    pub language: Option<String>,
42    pub policy_version: String,
43    pub sanitizer_version: String,
44    pub content_hash: String,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ChunkDocument {
49    pub id: String,
50    pub text: String,
51    pub metadata: ChunkMetadata,
52}
53
54pub fn normalize_text(input: &str) -> String {
55    input
56        .lines()
57        .map(str::trim_end)
58        .collect::<Vec<_>>()
59        .join("\n")
60        .trim()
61        .to_string()
62}
63
64pub fn content_hash(normalized_sanitized_text: &str) -> String {
65    blake3::hash(normalized_sanitized_text.as_bytes())
66        .to_hex()
67        .to_string()
68}
69
70pub fn doc_id(source: &str, session_id: &str, chunk_index: usize, content_hash: &str) -> String {
71    let seed = format!("{source}|{session_id}|{chunk_index}|{content_hash}");
72    blake3::hash(seed.as_bytes()).to_hex().to_string()
73}
74
75#[cfg(test)]
76mod tests {
77    use super::{content_hash, doc_id, normalize_text};
78
79    #[test]
80    fn deterministic_hash_and_id() {
81        let normalized = normalize_text("hello\nworld\n");
82        let h1 = content_hash(&normalized);
83        let h2 = content_hash(&normalized);
84        assert_eq!(h1, h2);
85
86        let d1 = doc_id("codex_cli", "abc", 1, &h1);
87        let d2 = doc_id("codex_cli", "abc", 1, &h1);
88        assert_eq!(d1, d2);
89    }
90}