aimux_core/trace/
record.rs1use std::collections::HashMap;
6
7use serde::{Deserialize, Serialize};
8use serde_json::Value;
9use ts_rs::TS;
10
11use super::fingerprint::Fingerprint;
12use super::verdict::Verdict;
13
14#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
16#[ts(export)]
17pub struct UsageSnapshot {
18 pub input_total: Option<u64>,
19 pub input_no_cache: Option<u64>,
20 pub cache_read: Option<u64>,
21 pub cache_write: Option<u64>,
22 pub output_total: Option<u64>,
23 pub output_text: Option<u64>,
24 pub output_reasoning: Option<u64>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub raw: Option<Value>,
29}
30
31#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
33#[ts(export)]
34pub struct RequestCacheHints {
35 pub requested_write: bool,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, TS)]
41#[ts(export)]
42pub struct TraceRecord {
43 pub provider: String,
44 pub model: String,
45 pub request_id: Option<String>,
46 pub session_id: Option<String>,
49 pub call_id: String,
52 #[ts(type = "number")]
54 pub sent_at_unix_ms: i64,
55 #[serde(skip)]
58 #[ts(skip)]
59 pub monotonic_sent_ms: u64,
60 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub lcp_token_upper: Option<u64>,
65 #[ts(type = "number | null")]
67 pub ttft_ms: Option<u64>,
68 pub fingerprint: Fingerprint,
69 pub usage: UsageSnapshot,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub response_cache_headers: Option<HashMap<String, String>>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub request_cache_hints: Option<RequestCacheHints>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub verdict: Option<Verdict>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub error: Option<String>,
82 #[serde(skip)]
84 #[ts(skip)]
85 pub scope_key: u64,
86}
87
88impl TraceRecord {
89 #[must_use]
92 pub fn reported_hit_rate(&self) -> Option<f64> {
93 match (self.usage.cache_read, self.usage.input_total) {
94 (Some(r), Some(t)) if t > 0 => Some(r as f64 / t as f64),
95 _ => None,
96 }
97 }
98
99 #[must_use]
101 pub fn client_upper_bound_hit_rate(&self) -> Option<f64> {
102 let t = self.usage.input_total?;
103 if t == 0 {
104 return None;
105 }
106 Some(self.fingerprint.token_estimate.min(t) as f64 / t as f64)
107 }
108}