Skip to main content

nanocodex_agent/
usage.rs

1pub use nanocodex_oai_api::pricing::{CostStatus, EstimatedUsdCost, ServiceTier, UsdAmount};
2use nanocodex_oai_api::{
3    pricing,
4    responses::{InputTokenDetails, Usage},
5};
6use serde::{Deserialize, Serialize};
7
8/// Exact token accounting for every Responses call in one logical agent turn.
9///
10/// Cache-read and cache-write tokens are subsets of input tokens. Reasoning
11/// tokens are a subset of output tokens. The values are summed from provider
12/// usage records across warmup, generation, tool continuation, steering, and
13/// compaction calls made before the turn reaches its terminal boundary. Check
14/// [`Self::cost_status`] to distinguish a provider-omitted usage record from a
15/// genuine zero-token total.
16#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
17#[allow(clippy::struct_field_names)]
18pub struct TurnUsage {
19    input_tokens: u64,
20    cached_input_tokens: u64,
21    cache_write_input_tokens: u64,
22    output_tokens: u64,
23    reasoning_output_tokens: u64,
24    total_tokens: u64,
25    estimated_cost: Option<Box<EstimatedUsdCost>>,
26    cost_status: CostStatus,
27}
28
29#[allow(clippy::struct_field_names)]
30#[derive(Clone, Copy)]
31pub(crate) struct TurnUsageCounts {
32    pub(crate) input_tokens: u64,
33    pub(crate) cached_input_tokens: u64,
34    pub(crate) cache_write_input_tokens: u64,
35    pub(crate) output_tokens: u64,
36    pub(crate) reasoning_output_tokens: u64,
37    pub(crate) total_tokens: u64,
38    pub(crate) reported: bool,
39}
40
41impl TurnUsage {
42    pub(crate) fn from_counts(counts: TurnUsageCounts, fast_mode: bool) -> Self {
43        let (estimated_cost, cost_status) = if counts.reported {
44            let usage = Usage {
45                input_tokens: counts.input_tokens,
46                input_tokens_details: Some(InputTokenDetails {
47                    cached_tokens: counts.cached_input_tokens,
48                    cache_write_tokens: counts.cache_write_input_tokens,
49                }),
50                output_tokens: counts.output_tokens,
51                output_tokens_details: None,
52                total_tokens: counts.total_tokens,
53            };
54            (
55                Some(Box::new(pricing::estimate(
56                    &usage,
57                    if fast_mode {
58                        ServiceTier::Priority
59                    } else {
60                        ServiceTier::Standard
61                    },
62                ))),
63                CostStatus::EstimatedFromUsage,
64            )
65        } else {
66            (None, CostStatus::UsageNotReported)
67        };
68        Self {
69            input_tokens: counts.input_tokens,
70            cached_input_tokens: counts.cached_input_tokens,
71            cache_write_input_tokens: counts.cache_write_input_tokens,
72            output_tokens: counts.output_tokens,
73            reasoning_output_tokens: counts.reasoning_output_tokens,
74            total_tokens: counts.total_tokens,
75            estimated_cost,
76            cost_status,
77        }
78    }
79
80    /// Returns all input tokens billed or reported by the provider.
81    #[must_use]
82    pub const fn input_tokens(&self) -> u64 {
83        self.input_tokens
84    }
85
86    /// Returns input tokens served from the provider's prompt cache.
87    #[must_use]
88    pub const fn cached_input_tokens(&self) -> u64 {
89        self.cached_input_tokens
90    }
91
92    /// Returns input tokens newly written into the provider's prompt cache.
93    #[must_use]
94    pub const fn cache_write_input_tokens(&self) -> u64 {
95        self.cache_write_input_tokens
96    }
97
98    /// Returns all output tokens billed or reported by the provider.
99    #[must_use]
100    pub const fn output_tokens(&self) -> u64 {
101        self.output_tokens
102    }
103
104    /// Returns reasoning tokens included within [`Self::output_tokens`].
105    #[must_use]
106    pub const fn reasoning_output_tokens(&self) -> u64 {
107        self.reasoning_output_tokens
108    }
109
110    /// Returns the provider-reported total token count.
111    #[must_use]
112    pub const fn total_tokens(&self) -> u64 {
113        self.total_tokens
114    }
115
116    /// Returns the automatic local USD estimate.
117    ///
118    /// Nanocodex applies the built-in standard or priority `gpt-5.6-sol`
119    /// rates. `None` means the provider omitted usage; absence is never
120    /// serialized as a misleading zero.
121    #[must_use]
122    pub fn estimated_cost(&self) -> Option<&EstimatedUsdCost> {
123        self.estimated_cost.as_deref()
124    }
125
126    /// Returns why an estimate is present or unavailable.
127    #[must_use]
128    pub const fn cost_status(&self) -> CostStatus {
129        self.cost_status
130    }
131}