mod bills;
pub use bills::{Scope, StepBill, bills, total, wall};
const STEPS_DIR: &str = "steps";
const RESPONSE_FILE: &str = "response.json";
const STEP_SEQ_WIDTH: usize = 3;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BudgetSpend {
pub input_tokens: u64,
pub output_tokens: u64,
pub cache_read_tokens: u64,
pub cache_write_tokens: u64,
}
impl BudgetSpend {
pub fn cached_tokens(&self) -> u64 {
self.cache_read_tokens
.saturating_add(self.cache_write_tokens)
}
pub fn prompt_tokens(&self) -> u64 {
self.input_tokens.max(self.cached_tokens())
}
pub fn uncached_prompt_tokens(&self) -> u64 {
self.input_tokens.saturating_sub(self.cached_tokens())
}
pub fn total_tokens(&self) -> u64 {
self.prompt_tokens().saturating_add(self.output_tokens)
}
fn add(&mut self, other: BudgetSpend) {
self.input_tokens = self.input_tokens.saturating_add(other.input_tokens);
self.output_tokens = self.output_tokens.saturating_add(other.output_tokens);
self.cache_read_tokens = self
.cache_read_tokens
.saturating_add(other.cache_read_tokens);
self.cache_write_tokens = self
.cache_write_tokens
.saturating_add(other.cache_write_tokens);
}
}
pub fn spend_from_bytes(bytes: &[u8]) -> BudgetSpend {
let mut total = BudgetSpend::default();
for line in bytes.split(|b| *b == b'\n') {
if let Some(spend) = usage_line(line) {
total.add(spend);
}
}
total
}
pub fn last_usage(bytes: &[u8]) -> BudgetSpend {
bytes
.split(|b| *b == b'\n')
.filter_map(usage_line)
.next_back()
.unwrap_or_default()
}
fn usage_line(line: &[u8]) -> Option<BudgetSpend> {
let value: serde_json::Value = serde_json::from_slice(line).ok()?;
if value.get("type")?.as_str()? != "usage" {
return None;
}
Some(BudgetSpend {
input_tokens: counter(&value, "input_tokens"),
output_tokens: counter(&value, "output_tokens"),
cache_read_tokens: counter(&value, "cache_read_tokens"),
cache_write_tokens: counter(&value, "cache_write_tokens"),
})
}
fn counter(value: &serde_json::Value, key: &str) -> u64 {
value
.get(key)
.and_then(serde_json::Value::as_u64)
.unwrap_or(0)
}
#[cfg(test)]
mod tests;