use serde_json::{Map, Number, Value, json};
use super::Verb;
use crate::envelope;
pub const PRICES: Verb = Verb {
word: "prices",
params: &[],
flags: &[],
summary: "the price table, the spend ceiling, and the world's spend against it",
detail: "One row per (provider, model) the engine prices, with its four \
rates in USD per million tokens as the operator wrote them — a \
`*` model means every model that provider serves and the table \
does not name, and a row of zeros is a subscription, priced at \
nothing rather than unpriced. Then the ceiling, where one is \
set, and what the world has spent against it, `at least` where \
some tokens no row priced. It takes no address, so its subject \
is EVERY channel this box holds.",
};
pub const PRICE: &str = "price";
pub const CEILING: &str = "ceiling";
pub const OFF: &str = "off";
const RATES: [&str; 4] = ["input", "output", "cache_read", "cache_write"];
pub fn prices() -> Value {
PRICES.built(Vec::new(), &[])
}
pub fn price(provider: String, model: String, rates: Option<[Number; 4]>) -> Value {
let mut map = Map::new();
map.insert(envelope::OP.to_owned(), json!(PRICE));
map.insert("provider".to_owned(), json!(provider));
map.insert("model".to_owned(), json!(model));
if let Some(rates) = rates {
let held: Map<String, Value> = RATES
.iter()
.zip(rates)
.map(|(name, rate)| ((*name).to_owned(), Value::Number(rate)))
.collect();
map.insert("rates".to_owned(), Value::Object(held));
}
Value::Object(map)
}
pub fn ceiling(usd: Option<Number>) -> Value {
match usd {
Some(usd) => json!({ envelope::OP: CEILING, "usd": usd }),
None => json!({ envelope::OP: CEILING }),
}
}
pub fn rates(words: &[&str]) -> Result<Option<[Number; 4]>, String> {
if words == [OFF] {
return Ok(None);
}
if !(2..=4).contains(&words.len()) {
return Err(format!(
"a price is `{OFF}` or two to four rates (input, output, cache read, \
cache write) and got {} word(s)",
words.len()
));
}
let mut held: [Number; 4] = std::array::from_fn(|_| Number::from(0_u8));
for (slot, word) in held.iter_mut().zip(words) {
*slot = number(word)?;
}
Ok(Some(held))
}
pub fn bound(word: &str) -> Result<Option<Number>, String> {
if word == OFF {
return Ok(None);
}
number(word).map(Some)
}
fn number(word: &str) -> Result<Number, String> {
word.trim()
.parse::<Number>()
.map_err(|_| format!("{word:?} is not a number of US dollars"))
}
#[cfg(test)]
mod tests;