nemo_relay/codec/
response.rs1use serde::Deserialize;
7
8pub use nemo_relay_types::codec::response::*;
9
10pub use super::model_pricing::{
11 CacheReadAccounting, ModelPricing, PricingCatalog, PricingCatalogError, PricingConfig,
12 PricingResolver, PricingSource, PricingSourceConfig, PricingUnit, PromptCachePricing,
13 TokenPricingRates, active_pricing_resolver, attach_estimated_cost,
14 attach_estimated_cost_for_provider, estimate_cost, estimate_cost_for_provider,
15 estimate_cost_with_catalog, estimate_cost_with_provider, infer_model_provider,
16 pricing_for_model, pricing_for_provider, reset_active_pricing_resolver,
17 set_active_pricing_resolver,
18};
19
20#[derive(Debug, Clone, Default, Deserialize)]
22pub(crate) struct RawUsageCost {
23 pub total: Option<f64>,
25 pub input: Option<f64>,
27 pub output: Option<f64>,
29 pub cache_read: Option<f64>,
31 pub cache_write: Option<f64>,
33 pub currency: Option<String>,
35 pub pricing_provider: Option<String>,
37 pub pricing_model: Option<String>,
39 pub pricing_as_of: Option<String>,
41 pub pricing_source: Option<String>,
43}
44
45pub(crate) fn provider_reported_cost(
46 provider_total_cost: Option<f64>,
47 cost: Option<RawUsageCost>,
48) -> Option<CostEstimate> {
49 let cost = cost.unwrap_or_default();
50 let provider_total_uses_default_currency = provider_total_cost.is_some();
51 let nested_currency_is_default = cost
52 .currency
53 .as_deref()
54 .is_none_or(|currency| currency.eq_ignore_ascii_case("USD"));
55 let keep_component_costs = !provider_total_uses_default_currency || nested_currency_is_default;
56 let input = keep_component_costs.then_some(cost.input).flatten();
57 let output = keep_component_costs.then_some(cost.output).flatten();
58 let cache_read = keep_component_costs.then_some(cost.cache_read).flatten();
59 let cache_write = keep_component_costs.then_some(cost.cache_write).flatten();
60 let has_currency_native_amount = cost.total.is_some()
61 || cost.input.is_some()
62 || cost.output.is_some()
63 || cost.cache_read.is_some()
64 || cost.cache_write.is_some();
65 let component_total = [input, output, cache_read, cache_write]
66 .into_iter()
67 .flatten()
68 .sum();
69 let has_component_cost =
70 input.is_some() || output.is_some() || cache_read.is_some() || cache_write.is_some();
71 let total = provider_total_cost
72 .or(cost.total)
73 .or_else(|| has_component_cost.then_some(component_total));
74
75 if total.is_none()
76 && input.is_none()
77 && output.is_none()
78 && cache_read.is_none()
79 && cache_write.is_none()
80 {
81 return None;
82 }
83
84 Some(CostEstimate {
85 total,
86 currency: if provider_total_uses_default_currency {
87 default_cost_currency()
88 } else if has_currency_native_amount {
89 cost.currency.unwrap_or_else(default_cost_currency)
90 } else {
91 default_cost_currency()
92 },
93 input,
94 output,
95 cache_read,
96 cache_write,
97 source: CostSource::ProviderReported,
98 pricing_provider: cost.pricing_provider,
99 pricing_model: cost.pricing_model,
100 pricing_as_of: cost.pricing_as_of,
101 pricing_source: cost.pricing_source,
102 })
103}
104
105fn default_cost_currency() -> String {
106 "USD".into()
107}
108
109#[cfg(test)]
110#[path = "../../tests/unit/codec/response_tests.rs"]
111mod tests;