cost.rs
Language Rust2.7 KB78 lines
//! Money. Stored as whole nano-dollars (1e-9 USD) in an i64, so sums over a//! month never drift the way floats do. A message keeps the prices it was//! billed at, so a later price change never rewrites history.use crate::types::{Prices, Usage};pub const NANO_PER_USD: f64 = 1e9;/// Cost of one completion. Cached prompt tokens bill at the cache-read price/// when the model has one, otherwise at the input price.pub fn cost_nanousd(usage: &Usage, prices: &Prices) -> i64 {let cached = usage.cached_tokens.clamp(0, usage.prompt_tokens.max(0));let uncached = (usage.prompt_tokens - cached).max(0);let cache_price = prices.cache_read_per_m.unwrap_or(prices.input_per_m);// $/1M tokens * tokens / 1e6 * 1e9 nano = price * tokens * 1000let nano = uncached as f64 * prices.input_per_m * 1000.0+ cached as f64 * cache_price * 1000.0+ usage.completion_tokens.max(0) as f64 * prices.output_per_m * 1000.0;nano.round() as i64}pub fn to_usd(nano: i64) -> f64 {nano as f64 / NANO_PER_USD}/// "$0.0012", "$1.24", "<$0.0001", "$0".pub fn format_usd(nano: i64) -> String {if nano == 0 {return "$0".into();}let usd = to_usd(nano);if usd < 0.0001 {"<$0.0001".into()} else if usd < 1.0 {format!("${usd:.4}")} else {format!("${usd:.2}")}}#[cfg(test)]mod tests {use super::*;const P: Prices = Prices { input_per_m: 0.5, output_per_m: 1.5, cache_read_per_m: Some(0.1) };#[test]fn hand_calculated() {// 1,000 in at $0.50/M = $0.0005; 2,000 out at $1.50/M = $0.003. Total $0.0035.let u = Usage { prompt_tokens: 1000, completion_tokens: 2000, cached_tokens: 0 };assert_eq!(cost_nanousd(&u, &P), 3_500_000);assert_eq!(format_usd(3_500_000), "$0.0035");}#[test]fn cached_tokens_bill_at_cache_price() {// 600 uncached * 0.5 + 400 cached * 0.1 = 300,000 + 40,000 nano (x1000 scale)let u = Usage { prompt_tokens: 1000, completion_tokens: 0, cached_tokens: 400 };assert_eq!(cost_nanousd(&u, &P), 340_000);let no_cache_price = Prices { cache_read_per_m: None, ..P };assert_eq!(cost_nanousd(&u, &no_cache_price), 500_000);}#[test]fn odd_inputs_are_safe() {let u = Usage { prompt_tokens: 10, completion_tokens: -5, cached_tokens: 99 };assert_eq!(cost_nanousd(&u, &P), 1_000); // cached clamps to 10, negatives to 0assert_eq!(cost_nanousd(&Usage::default(), &Prices::default()), 0);}#[test]fn formatting() {assert_eq!(format_usd(0), "$0");assert_eq!(format_usd(12), "<$0.0001");assert_eq!(format_usd(1_240_000_000), "$1.24");assert_eq!(format_usd(120_000), "$0.0001");}}