Skip to main content

dynamo_bench/coding/
common.rs

1// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use anyhow::{Context, Result, bail};
5use chrono::{DateTime, Utc};
6use serde_json::{Map, Value};
7use sha2::{Digest, Sha256};
8use std::collections::HashSet;
9use std::path::{Path, PathBuf};
10
11pub const DEFAULT_TOKENIZER: &str = "deepseek-ai/DeepSeek-R1-Distill-Llama-8B";
12pub const DEFAULT_BLOCK_SIZE: usize = 64;
13pub const DEFAULT_OUTPUT_NAME: &str = "claude_request_trace.jsonl";
14pub const SIDE_CAR_TOKEN: &str = ".sidecar";
15
16pub fn parse_utc_timestamp_ms(value: &str) -> Result<i64> {
17    if value.is_empty() {
18        bail!("missing timestamp");
19    }
20    let parsed = DateTime::parse_from_rfc3339(value)
21        .with_context(|| format!("invalid RFC3339 timestamp: {value}"))?;
22    Ok(parsed.with_timezone(&Utc).timestamp_millis())
23}
24
25pub fn anonymized_session_id(session_id: &str) -> String {
26    let digest = Sha256::digest(session_id.as_bytes());
27    let mut hex = String::with_capacity(12);
28    for byte in digest.iter().take(6) {
29        hex.push_str(&format!("{byte:02x}"));
30    }
31    format!("session_{hex}")
32}
33
34pub fn sidecar_path_for(output_path: &Path) -> PathBuf {
35    match (output_path.file_stem(), output_path.extension()) {
36        (Some(stem), Some(ext)) => output_path.with_file_name(format!(
37            "{}{}{ext_sep}{}",
38            stem.to_string_lossy(),
39            SIDE_CAR_TOKEN,
40            ext.to_string_lossy(),
41            ext_sep = "."
42        )),
43        _ => output_path.with_file_name(format!(
44            "{}{}.jsonl",
45            output_path
46                .file_name()
47                .unwrap_or_default()
48                .to_string_lossy(),
49            SIDE_CAR_TOKEN
50        )),
51    }
52}
53
54pub fn dedupe_paths(paths: Vec<PathBuf>) -> Vec<PathBuf> {
55    let mut seen = HashSet::new();
56    let mut deduped = Vec::new();
57    for path in paths {
58        let resolved = path.canonicalize().unwrap_or(path);
59        if seen.insert(resolved.clone()) {
60            deduped.push(resolved);
61        }
62    }
63    deduped
64}
65
66pub fn expand_user_path(raw: &str) -> PathBuf {
67    if raw == "~" {
68        return home_dir().unwrap_or_else(|| PathBuf::from(raw));
69    }
70    if let Some(rest) = raw.strip_prefix("~/") {
71        return home_dir()
72            .map(|home| home.join(rest))
73            .unwrap_or_else(|| PathBuf::from(raw));
74    }
75    PathBuf::from(raw)
76}
77
78pub fn home_dir() -> Option<PathBuf> {
79    std::env::var_os("HOME").map(PathBuf::from)
80}
81
82pub fn canonical_json_string(value: &Value) -> Result<String> {
83    let mut rendered = String::new();
84    write_canonical_json(&mut rendered, value)?;
85    Ok(rendered)
86}
87
88fn write_canonical_json(buffer: &mut String, value: &Value) -> Result<()> {
89    match value {
90        Value::Null => buffer.push_str("null"),
91        Value::Bool(flag) => {
92            if *flag {
93                buffer.push_str("true");
94            } else {
95                buffer.push_str("false");
96            }
97        }
98        Value::Number(number) => buffer.push_str(&number.to_string()),
99        Value::String(text) => buffer.push_str(&serde_json::to_string(text)?),
100        Value::Array(items) => {
101            buffer.push('[');
102            for (index, item) in items.iter().enumerate() {
103                if index > 0 {
104                    buffer.push(',');
105                }
106                write_canonical_json(buffer, item)?;
107            }
108            buffer.push(']');
109        }
110        Value::Object(map) => {
111            buffer.push('{');
112            let mut keys: Vec<&String> = map.keys().collect();
113            keys.sort_unstable();
114            for (index, key) in keys.into_iter().enumerate() {
115                if index > 0 {
116                    buffer.push(',');
117                }
118                buffer.push_str(&serde_json::to_string(key)?);
119                buffer.push(':');
120                write_canonical_json(buffer, &map[key])?;
121            }
122            buffer.push('}');
123        }
124    }
125    Ok(())
126}
127
128pub fn content_blocks(content: Option<&Value>) -> Vec<Value> {
129    match content {
130        Some(Value::String(text)) => {
131            vec![Value::Object(
132                [
133                    ("type".to_string(), Value::String("text".to_string())),
134                    ("text".to_string(), Value::String(text.clone())),
135                ]
136                .into_iter()
137                .collect(),
138            )]
139        }
140        Some(Value::Array(items)) => items
141            .iter()
142            .filter(|item| item.is_object())
143            .cloned()
144            .collect(),
145        _ => Vec::new(),
146    }
147}
148
149pub fn flatten_block_content_text(value: &Value) -> Result<String> {
150    match value {
151        Value::String(text) => Ok(text.clone()),
152        Value::Array(items) => {
153            let mut parts = Vec::new();
154            for item in items {
155                match item {
156                    Value::String(text) => parts.push(text.clone()),
157                    Value::Object(map) => {
158                        if let Some(text) = map.get("text").and_then(Value::as_str) {
159                            parts.push(text.to_string());
160                        } else {
161                            parts.push(canonical_json_string(item)?);
162                        }
163                    }
164                    _ => parts.push(canonical_json_string(item)?),
165                }
166            }
167            Ok(parts
168                .into_iter()
169                .filter(|part| !part.is_empty())
170                .collect::<Vec<_>>()
171                .join("\n"))
172        }
173        Value::Object(map) => {
174            if let Some(text) = map.get("text").and_then(Value::as_str) {
175                return Ok(text.to_string());
176            }
177            canonical_json_string(value)
178        }
179        _ => canonical_json_string(value),
180    }
181}
182
183pub fn object_field<'a>(value: &'a Value, field: &str) -> Option<&'a Map<String, Value>> {
184    value.get(field)?.as_object()
185}