Skip to content

fix(provider): derive a price tier's threshold from its own minimum - #839

Open
ANIRUDDHA ADAK (aniruddhaadak80) wants to merge 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/pricing-tier-threshold
Open

ANIRUDDHA ADAK (aniruddhaadak80) wants to merge 1 commit into
synthetic-sciences:mainfrom
aniruddhaadak80:fix/pricing-tier-threshold

Conversation

@aniruddhaadak80

Copy link
Copy Markdown
Contributor

What

ManagedPricing.parse builds each tier's threshold from the previous tier's end:

const tiers = (prices) =>
  prices
    .map((tier, index) => ({
      ...tier,
      threshold: prices[index - 1]?.max_input_tokens ?? (tier.min_input_tokens ?? 1) - 1,
    }))

and finds the base tier by falsiness:

const first = model.pricing.tiers.find((tier) => !tier.min_input_tokens)
if (!first || (first.input === 0 && first.output === 0)) continue

Why it matters

A tier's start came from the tier above ending. The ?? prefers the predecessor's max_input_tokens, which is the same number only when the catalog is contiguous. Where it is not, the threshold lands in the direction that costs the user money:

tiers: [ { max_input_tokens: 100 }, { min_input_tokens: 201 } ]
-> threshold: 100        (the tier declares it starts at 201)

Selection in src/session/index.ts:827 is .filter((tier) => promptTokens > tier.threshold), so a 150-token prompt satisfies 150 > 100 and is billed at the 201+ rate — 51 tokens before the tier is meant to apply. An overlap errs the other way.

An explicit base tier of min_input_tokens: 1 made the model vanish. !tier.min_input_tokens is false for 1, so no base tier is found and the continue drops the entire row. Token counts start at 1, so min: 1 means the same as omitting it:

[{input:2,output:12,min_input_tokens:1}, {input:4,output:18,min_input_tokens:200001}]
-> parse(...)  ===  {}                                   the model is gone

[{input:2,output:12},              {input:4,output:18,min_input_tokens:200001}]
-> cost {input:2, output:12, tiers:[{... threshold:200000}]}

The same first lookup drives fast_mode_details pricing, so a fast-tier model can disappear the same way.

Verification

New test/provider/managed-pricing-tiers.test.ts. Three of four fail before:

Expected: 200
Received: 100
(fail) a tier starts where it declares, even when the previous one ends earlier

Expected: false
Received: true
(fail) a selection above the gap but below the tier's min does not pick that tier

Expected: 2
Received: undefined
(fail) an explicit base min of 1 still prices the model

After:

(pass) a tier starts where it declares, even when the previous one ends earlier
(pass) contiguous tiers are unaffected
(pass) a selection above the gap but below the tier's min does not pick that tier
(pass) an explicit base min of 1 still prices the model
 4 pass
 0 fail

Two of the four are guards against over-correcting: the contiguous case must be unchanged (it is, and the existing managed-pricing.test.ts suite still passes — 15/15 on three consecutive runs), and the third test states the selection rule directly so the threshold cannot drift from the > comparison that consumes it.

The change

-      const first = model.pricing.tiers.find((tier) => !tier.min_input_tokens)
+      // Token counts start at 1, so an explicit min of 1 is the base tier and
+      // means the same as omitting it. Looking it up by falsiness missed that
+      // spelling and dropped the whole model out of the catalog.
+      const first = model.pricing.tiers.find((tier) => !tier.min_input_tokens || tier.min_input_tokens <= 1)
-          .map((tier, index) => ({
+          .map((tier) => ({
             ...tier,
-            threshold: prices[index - 1]?.max_input_tokens ?? (tier.min_input_tokens ?? 1) - 1,
+            // A tier applies from its own min. The previous tier's max is the
+            // same number only when the catalog is contiguous, and preferring it
+            // charged a tier's rate from as early as the tier above ended.
+            threshold: (tier.min_input_tokens ?? 1) - 1,
           }))

The index parameter is no longer used.

Touched files are Prettier-clean (verified on LF-normalized copies; this Windows checkout's core.autocrlf=true makes Prettier flag every file repo-wide).

Fixes #838

@vercel

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

ANIRUDDHA ADAK (@aniruddhaadak80) is attempting to deploy a commit to the InkVell Team on Vercel.

A member of the Team first needs to authorize it.

@aniruddhaadak80
ANIRUDDHA ADAK (aniruddhaadak80) force-pushed the fix/pricing-tier-threshold branch 2 times, most recently from 6f4aaf7 to 56f1a09 Compare September 30, 2026 09:44
The threshold came from the previous tier's max, which matches only when the catalog is contiguous; a gap charged the higher rate below the tier's declared start. Also treat an explicit min of 1 as the base tier, since token counts start at 1, instead of dropping the model from pricing.

This branch has not been deployed

No deployments
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.

A price tier's threshold comes from the tier above, so a gap charges the higher rate too early, and an explicit min of 1 drops the model

1 participant