mod bills;
mod last;
pub use bills::{Scope, StepBill, bills, total, wall};
pub use last::{context_window, last_usage};
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 event in usage_events(bytes) {
total.add(counters(&event));
}
total
}
pub(crate) fn usage_events(bytes: &[u8]) -> impl Iterator<Item = serde_json::Value> + '_ {
bytes.split(|b| *b == b'\n').filter_map(|line| {
let value: serde_json::Value = serde_json::from_slice(line).ok()?;
(value.get("type")?.as_str()? == "usage").then_some(value)
})
}
pub(crate) fn counters(event: &serde_json::Value) -> BudgetSpend {
BudgetSpend {
input_tokens: prompt(event).unwrap_or(0),
output_tokens: counter(event, "output_tokens").unwrap_or(0),
cache_read_tokens: counter(event, "cache_read_tokens").unwrap_or(0),
cache_write_tokens: counter(event, "cache_write_tokens").unwrap_or(0),
}
}
fn prompt(event: &serde_json::Value) -> Option<u64> {
counter(event, "input_total_tokens").or_else(|| counter(event, "input_tokens"))
}
pub(crate) fn counter(event: &serde_json::Value, key: &str) -> Option<u64> {
event.get(key).and_then(serde_json::Value::as_u64)
}
#[cfg(test)]
mod tests;