Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 23 additions & 8 deletions crates/tokscale-core/src/pricing/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,20 @@ impl CustomModelPricing {
"output_cost_per_token",
)?;

if !input_cost_per_token.is_some_and(|value| value > 0.0)
&& !output_cost_per_token.is_some_and(|value| value > 0.0)
{
return Err(
"at least one of input or output pricing must be present and positive".into(),
);
// @keep: requiring a POSITIVE rate here locked users out of the one
// escape hatch they have for free models.
//
// A rate of 0.0 is a statement — "this model is free" — and it is
// exactly what a user needs to express for a free tier that no upstream
// dataset publishes. Demanding a positive rate made that unsayable, so
// free usage stayed unpriced and was excluded from submission with no
// way to correct it (#1021).
//
// Absence is still rejected: a row that names no rate at all says
// nothing, and silently reading that as free would invent a $0 total
// for genuinely unknown pricing.
if input_cost_per_token.is_none() && output_cost_per_token.is_none() {
return Err("at least one of input or output pricing must be present".into());
}

Ok(ModelPricing {
Expand Down Expand Up @@ -657,7 +665,7 @@ mod tests {
}

#[test]
fn drops_entries_with_zero_prices() {
fn keeps_free_models_but_still_drops_negative_prices() {
let temp = TempDir::new().unwrap();
let path = temp.path().join("custom-pricing.json");
fs::write(
Expand All @@ -683,11 +691,18 @@ mod tests {

let loaded = CustomPricing::load_from_path(&path);

assert!(loaded.lookup("all-zero").is_none());
// A free model is the case this file exists to let users express when
// no upstream dataset publishes the model (#1021). 0.0 is an assertion
// ("free"), not an absence, so an all-zero row is kept.
let all_zero = loaded.lookup("all-zero").expect("free model must load");
assert_eq!(all_zero.input_cost_per_token, Some(0.0));
assert_eq!(all_zero.output_cost_per_token, Some(0.0));

assert_eq!(
loaded.lookup("free-input").unwrap().output_cost_per_token,
Some(0.000008)
);
// Unchanged: a negative rate is nonsense, not a statement about price.
assert!(loaded.lookup("negative-output").is_none());
}

Expand Down
Loading