1pub use nanocodex_oai_api::pricing::{CostStatus, EstimatedUsdCost, ServiceTier, UsdAmount};
2use serde::{Deserialize, Serialize};
3
4#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
13#[allow(clippy::struct_field_names)]
14pub struct TurnUsage {
15 input_tokens: u64,
16 cached_input_tokens: u64,
17 cache_write_input_tokens: u64,
18 output_tokens: u64,
19 reasoning_output_tokens: u64,
20 total_tokens: u64,
21 estimated_cost: Option<Box<EstimatedUsdCost>>,
22 cost_status: CostStatus,
23}
24
25#[derive(Clone, Debug, Eq, PartialEq)]
30pub struct ReportedTurnUsage {
31 pub input_tokens: u64,
33 pub cached_input_tokens: u64,
35 pub cache_write_input_tokens: u64,
37 pub output_tokens: u64,
39 pub reasoning_output_tokens: u64,
41 pub total_tokens: u64,
43 pub estimated_cost: Option<EstimatedUsdCost>,
45 pub cost_status: CostStatus,
47}
48
49#[allow(clippy::struct_field_names)]
50#[derive(Clone)]
51#[cfg(feature = "openai")]
52pub(crate) struct TurnUsageCounts {
53 pub(crate) input_tokens: u64,
54 pub(crate) cached_input_tokens: u64,
55 pub(crate) cache_write_input_tokens: u64,
56 pub(crate) output_tokens: u64,
57 pub(crate) reasoning_output_tokens: u64,
58 pub(crate) total_tokens: u64,
59 pub(crate) reported: bool,
60 pub(crate) estimated_cost: Option<EstimatedUsdCost>,
61}
62
63impl TurnUsage {
64 #[must_use]
71 pub fn from_reported(reported: ReportedTurnUsage) -> Self {
72 Self {
73 input_tokens: reported.input_tokens,
74 cached_input_tokens: reported.cached_input_tokens,
75 cache_write_input_tokens: reported.cache_write_input_tokens,
76 output_tokens: reported.output_tokens,
77 reasoning_output_tokens: reported.reasoning_output_tokens,
78 total_tokens: reported.total_tokens,
79 estimated_cost: reported.estimated_cost.map(Box::new),
80 cost_status: reported.cost_status,
81 }
82 }
83
84 #[cfg(feature = "openai")]
85 pub(crate) fn from_counts(counts: TurnUsageCounts) -> Self {
86 let (estimated_cost, cost_status) = if !counts.reported {
87 (None, CostStatus::UsageNotReported)
88 } else {
89 (
90 counts.estimated_cost.map(Box::new),
91 CostStatus::EstimatedFromUsage,
92 )
93 };
94 Self {
95 input_tokens: counts.input_tokens,
96 cached_input_tokens: counts.cached_input_tokens,
97 cache_write_input_tokens: counts.cache_write_input_tokens,
98 output_tokens: counts.output_tokens,
99 reasoning_output_tokens: counts.reasoning_output_tokens,
100 total_tokens: counts.total_tokens,
101 estimated_cost,
102 cost_status,
103 }
104 }
105
106 #[must_use]
108 pub const fn input_tokens(&self) -> u64 {
109 self.input_tokens
110 }
111
112 #[must_use]
114 pub const fn cached_input_tokens(&self) -> u64 {
115 self.cached_input_tokens
116 }
117
118 #[must_use]
120 pub const fn cache_write_input_tokens(&self) -> u64 {
121 self.cache_write_input_tokens
122 }
123
124 #[must_use]
126 pub const fn output_tokens(&self) -> u64 {
127 self.output_tokens
128 }
129
130 #[must_use]
132 pub const fn reasoning_output_tokens(&self) -> u64 {
133 self.reasoning_output_tokens
134 }
135
136 #[must_use]
138 pub const fn total_tokens(&self) -> u64 {
139 self.total_tokens
140 }
141
142 #[must_use]
149 pub fn estimated_cost(&self) -> Option<&EstimatedUsdCost> {
150 self.estimated_cost.as_deref()
151 }
152
153 #[must_use]
155 pub const fn cost_status(&self) -> CostStatus {
156 self.cost_status
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::{CostStatus, ReportedTurnUsage, TurnUsage};
163
164 #[test]
165 fn externally_reported_usage_preserves_exact_counts_and_cost_status() {
166 let usage = TurnUsage::from_reported(ReportedTurnUsage {
167 input_tokens: 13,
168 cached_input_tokens: 5,
169 cache_write_input_tokens: 2,
170 output_tokens: 8,
171 reasoning_output_tokens: 3,
172 total_tokens: 21,
173 estimated_cost: None,
174 cost_status: CostStatus::UsageNotReported,
175 });
176
177 assert_eq!(usage.input_tokens(), 13);
178 assert_eq!(usage.cached_input_tokens(), 5);
179 assert_eq!(usage.cache_write_input_tokens(), 2);
180 assert_eq!(usage.output_tokens(), 8);
181 assert_eq!(usage.reasoning_output_tokens(), 3);
182 assert_eq!(usage.total_tokens(), 21);
183 assert_eq!(usage.cost_status(), CostStatus::UsageNotReported);
184 assert!(usage.estimated_cost().is_none());
185 }
186}