Skip to content

fix(pricing): let custom-pricing.json express a free model - #1069

Merged
junhoyeo merged 2 commits into
mainfrom
fix/custom-pricing-zero-cost
Aug 8, 2026
Merged

fix(pricing): let custom-pricing.json express a free model#1069
junhoyeo merged 2 commits into
mainfrom
fix/custom-pricing-zero-cost

Conversation

@junhoyeo

@junhoyeo junhoyeo commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Addresses the one genuinely-open ask in #1021.

Problem

custom-pricing.json is the documented escape hatch for models no upstream dataset publishes. But custom.rs required at least one rate to be positive:

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());
}

So a free tier was unexpressable. The user knows the price is $0, the file refuses to record it, and the usage stays unpriced — excluded from submission with no way to correct it. As @sigco3111 put it: "I can't use the documented escape hatch for free-tier models."

Fix

Reject only absence, not zero.

0.0 is an assertion — "this model is free". None is an absence — "unknown". Reading a rate-less row as free would invent a $0 total for genuinely unknown pricing, so that case still errors.

This is the same free-vs-absent confusion as the Cursor cache-creation gap in #1067, at the user-facing layer instead of the built-in table.

Negative rates still rejected — a negative price is nonsense, not a statement about cost.

The reporter's other two asks need no code

  • --prune-unpriced (their open question 2) — fix(submit): exclude unpriced usage without aborting #1053 made unpriced rows exclude-with-warning instead of failing the batch, and made it the default rather than a flag. Shipped in v4.11.0.
  • Missing zai/ provider prefix — no longer reproducible. Verified: tokscale pricing zai/glm-4.5-flashzai/glm-4.5-flash, and bare glm-4.5-flash resolves to the same key.

Their first ask — a subscription-pricing carrier for github-copilot/ — is a larger design question I'd rather not fold in here; Copilot already prices at the underlying model's rates (github_copilot/gpt-4ogpt-4o), so the gap is narrower than when it was filed.

Tests

  • cargo test -p tokscale-core --lib — 1,490 passed, 0 failed
  • fmt and clippy --all-targets -- -D warnings clean
  • drops_entries_with_zero_prices renamed to keeps_free_models_but_still_drops_negative_prices and its all-zero assertion inverted; it now asserts the row loads with Some(0.0) on both rates, while the negative-price case stays dropped

Summary by cubic

Allow custom-pricing.json to declare free models by accepting 0.0 token rates, so free-tier usage is priced at $0 instead of excluded. Updated READMEs to document that an explicit zero is allowed and different from an omitted (unknown) field.

  • Bug Fixes
    • Accept 0.0 for input/output rates; only error if both rates are absent.
    • Continue rejecting negative rates.
    • Updated test to assert all-zero rows load with Some(0.0) and negative-rate rows are dropped.
    • Updated README.md, README.ja.md, README.ko.md, README.zh-cn.md to explain that 0 means free while omission means unknown.

Written for commit ca39b51. Summary will update on new commits.

Review in cubic

custom-pricing.json is the documented escape hatch for models no upstream
dataset publishes, but it required at least one rate to be POSITIVE. A free
tier could therefore not be expressed at all: the user knows the price is
$0, the file refuses to record it, and the usage stays unpriced and excluded
from submission with no way to correct it (#1021).

This is the same free-vs-absent confusion as the Cursor cache-creation gap:
0.0 is an assertion ("this is free"), None is an absence ("unknown"). The
validation now rejects only absence, which is the case that genuinely says
nothing — reading a rate-less row as free would invent a $0 total for unknown
pricing.

Negative rates are still rejected; a negative price is nonsense rather than a
statement about cost.

The reporter's other two asks are already resolved and need no code here:
#1053 made unpriced rows exclude-with-warning instead of failing the batch,
which is the `--prune-unpriced` behaviour they asked for, as the default; and
`zai/glm-4.5-flash` resolves today (verified), so the missing `zai/` provider
prefix they reported is no longer reproducible.

Confidence: high
Scope-risk: narrow
Directive: 0.0 and None are not interchangeable in pricing rows anywhere in
this crate — 0.0 prices, None excludes
Not-tested: whether a user-declared free model should suppress the "excluded
unpriced" warning for sibling rows of the same model
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
tokscale Ignored Ignored Preview Aug 8, 2026 12:01am

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

The loader now accepts `0` so users can declare a free model, but all four
READMEs still told them at least one rate "must be present and positive".
Anyone following the documented contract would therefore never reach the
escape hatch this was added for.

The wording draws the distinction the code now makes: an explicit `0` is a
statement ("this costs nothing"), while an omitted field means the rate is
unknown and leaves the usage unpriced.

Confidence: high
Scope-risk: narrow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant