From 674e142f1e15470e6fbcd0d2e069d6a086ad3a8e Mon Sep 17 00:00:00 2001 From: Taranum01 Date: Mon, 17 Aug 2026 09:30:30 +0530 Subject: [PATCH] fix(cost): tier cache-write on the same threshold as input cost Fixes #5766 `packages/cost/models/calculate-cost.ts` priced `cacheWrite5mCost` and `cacheWrite1hCost` directly off the base pricing tier (`preprocessedPricing[0]`), while `inputCost` and `outputCost` routed through `getPricingTier(... getThresholdValue(...))` to pick the correct tier. For Anthropic's long-context threshold (`> 200k` tokens), cache writes count toward the same total-prompt that drives the inputCost threshold. Pricing writes at the base tier undercharges them by ~2x on requests past 200k. Select the same tier for cache writes that inputCost picks, keeping the `cacheMultipliers` lookup scoped to the chosen tier so a provider that varies `write5m`/`write1h` per tier still computes the correct multiplier. Update `packages/__tests__/cost/modelCostFromRegistry.test.ts`: - `should use higher tier pricing for Claude Sonnet 4 over 200K tokens`: assert the cache-write cost at the long-context rate (`5000 * 0.000006 * 1.25`) instead of the previous base-tier value (`5000 * 0.000003 * 1.25`). Validated the fix numerically (250k-token input, 5k cache writes): the post-fix long-context cost is exactly 2x the base-tier value, which matches the issue's expectation. --- .../__tests__/cost/modelCostFromRegistry.test.ts | 6 +++++- packages/cost/models/calculate-cost.ts | 15 +++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/__tests__/cost/modelCostFromRegistry.test.ts b/packages/__tests__/cost/modelCostFromRegistry.test.ts index 73aa4f6602..04e742fe5f 100644 --- a/packages/__tests__/cost/modelCostFromRegistry.test.ts +++ b/packages/__tests__/cost/modelCostFromRegistry.test.ts @@ -318,7 +318,11 @@ describe("modelCostBreakdownFromRegistry", () => { expect(breakdown.inputCost).toBe(250000 * 0.000006); expect(breakdown.outputCost).toBe(50000 * 0.0000225); expect(breakdown.cachedInputCost).toBe(10000 * 0.000003 * 0.1); - expect(breakdown.cacheWrite5mCost).toBe(5000 * 0.000003 * 1.25); + // Regression for #5766: cache writes tier on the same threshold + // as the input cost (here, the long-context tier), not the base + // tier — the original 0.000003 base-tier rate undercharged + // >200k-token Anthropic requests at the long-context rate. + expect(breakdown.cacheWrite5mCost).toBe(5000 * 0.000006 * 1.25); } }); diff --git a/packages/cost/models/calculate-cost.ts b/packages/cost/models/calculate-cost.ts index 860396a924..5ccfacc5c6 100644 --- a/packages/cost/models/calculate-cost.ts +++ b/packages/cost/models/calculate-cost.ts @@ -215,16 +215,23 @@ export function calculateModelCostBreakdown(params: { modelUsage.cacheDetails.cachedInput * cachedInputPricing.input * cachedMultiplier; } + // Cache writes tier on the same threshold as inputCost — the + // long-context threshold is "total prompt tokens above N", and + // cache writes count toward that. Using the base tier (preprocessed + // pricing tier 0) undercharges >200k-token requests at the + // long-context rate. Regression for #5766. if (modelUsage.cacheDetails.write5m) { - const write5mMultiplier = basePricing.cacheMultipliers?.write5m ?? 1.0; + const write5mPricing = getPricingTier(preprocessedPricing, getThresholdValue(modelUsage, "inputCost")); + const write5mMultiplier = write5mPricing.cacheMultipliers?.write5m ?? 1.0; breakdown.cacheWrite5mCost = - modelUsage.cacheDetails.write5m * basePricing.input * write5mMultiplier; + modelUsage.cacheDetails.write5m * write5mPricing.input * write5mMultiplier; } if (modelUsage.cacheDetails.write1h) { - const write1hMultiplier = basePricing.cacheMultipliers?.write1h ?? 1.0; + const write1hPricing = getPricingTier(preprocessedPricing, getThresholdValue(modelUsage, "inputCost")); + const write1hMultiplier = write1hPricing.cacheMultipliers?.write1h ?? 1.0; breakdown.cacheWrite1hCost = - modelUsage.cacheDetails.write1h * basePricing.input * write1hMultiplier; + modelUsage.cacheDetails.write1h * write1hPricing.input * write1hMultiplier; } }