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(
43        counts: TurnUsageCounts,
44        model: nanocodex_oai_api::Model,
45        fast_mode: bool,
46    ) -> Self {
47        let (estimated_cost, cost_status) = if !counts.reported {
48            (None, CostStatus::UsageNotReported)
49        } else {
50            let usage = Usage {
51                input_tokens: counts.input_tokens,
52                input_tokens_details: Some(InputTokenDetails {
53                    cached_tokens: counts.cached_input_tokens,
54                    cache_write_tokens: counts.cache_write_input_tokens,
55                }),
56                output_tokens: counts.output_tokens,
57                output_tokens_details: None,
58                total_tokens: counts.total_tokens,
59            };
60            (
61                Some(Box::new(pricing::estimate_for_model(
62                    &usage,
63                    model,
64                    if fast_mode {
65                        ServiceTier::Priority
66                    } else {
67                        ServiceTier::Standard
68                    },
69                ))),
70                CostStatus::EstimatedFromUsage,
71            )
72        };
73        Self {
74            input_tokens: counts.input_tokens,
75            cached_input_tokens: counts.cached_input_tokens,
76            cache_write_input_tokens: counts.cache_write_input_tokens,
77            output_tokens: counts.output_tokens,
78            reasoning_output_tokens: counts.reasoning_output_tokens,
79            total_tokens: counts.total_tokens,
80            estimated_cost,
81            cost_status,
82        }
83    }
84
85    /// Returns all input tokens billed or reported by the provider.
86    #[must_use]
87    pub const fn input_tokens(&self) -> u64 {
88        self.input_tokens
89    }
90
91    /// Returns input tokens served from the provider's prompt cache.
92    #[must_use]
93    pub const fn cached_input_tokens(&self) -> u64 {
94        self.cached_input_tokens
95    }
96
97    /// Returns input tokens newly written into the provider's prompt cache.
98    #[must_use]
99    pub const fn cache_write_input_tokens(&self) -> u64 {
100        self.cache_write_input_tokens
101    }
102
103    /// Returns all output tokens billed or reported by the provider.
104    #[must_use]
105    pub const fn output_tokens(&self) -> u64 {
106        self.output_tokens
107    }
108
109    /// Returns reasoning tokens included within [`Self::output_tokens`].
110    #[must_use]
111    pub const fn reasoning_output_tokens(&self) -> u64 {
112        self.reasoning_output_tokens
113    }
114
115    /// Returns the provider-reported total token count.
116    #[must_use]
117    pub const fn total_tokens(&self) -> u64 {
118        self.total_tokens
119    }
120
121    /// Returns the automatic local USD estimate.
122    ///
123    /// Nanocodex applies the selected model's built-in standard or priority
124    /// rates. `None` means the provider omitted usage; [`Self::cost_status`]
125    /// distinguishes that from a genuine zero-token estimate.
126    #[must_use]
127    pub fn estimated_cost(&self) -> Option<&EstimatedUsdCost> {
128        self.estimated_cost.as_deref()
129    }
130
131    /// Returns why an estimate is present or unavailable.
132    #[must_use]
133    pub const fn cost_status(&self) -> CostStatus {
134        self.cost_status
135    }
136}