From d0939f217bb609d250a56ad5f8a0657c8ae13e48 Mon Sep 17 00:00:00 2001 From: eeshsaxena <139802361+eeshsaxena@users.noreply.github.com> Date: Sun, 9 Aug 2026 10:30:27 +0530 Subject: [PATCH 1/2] fix(cost): tier Anthropic cached-input reads by long-context threshold --- packages/cost/models/calculate-cost.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/cost/models/calculate-cost.ts b/packages/cost/models/calculate-cost.ts index 860396a924..7e897e49fc 100644 --- a/packages/cost/models/calculate-cost.ts +++ b/packages/cost/models/calculate-cost.ts @@ -142,9 +142,14 @@ function getThresholdValueFunction(provider: ModelProviderName): (usage: ModelUs switch (field) { case "inputCost": case "outputCost": - return usage.input + - (usage.cacheDetails?.cachedInput ?? 0) + - (usage.cacheDetails?.write5m ?? 0) + + // Cached reads are tiered by the same long-context threshold as input/output: + // above 200k prompt tokens Anthropic charges the higher input rate, and the + // cached-read price is a multiple of that rate. Without this case the field fell + // through to 0 and cached reads were always priced at the base tier. + case "cachedInputCost": + return usage.input + + (usage.cacheDetails?.cachedInput ?? 0) + + (usage.cacheDetails?.write5m ?? 0) + (usage.cacheDetails?.write1h ?? 0); default: return 0; From c2abd996118cc35d8d324352437e41b9b9d635ee Mon Sep 17 00:00:00 2001 From: eeshsaxena <139802361+eeshsaxena@users.noreply.github.com> Date: Sun, 9 Aug 2026 10:30:29 +0530 Subject: [PATCH 2/2] test(cost): cover Anthropic long-context cached-read tiering --- .../cost/anthropic-cache-tier.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/__tests__/cost/anthropic-cache-tier.test.ts diff --git a/packages/__tests__/cost/anthropic-cache-tier.test.ts b/packages/__tests__/cost/anthropic-cache-tier.test.ts new file mode 100644 index 0000000000..41299f1f7b --- /dev/null +++ b/packages/__tests__/cost/anthropic-cache-tier.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "@jest/globals"; +import { calculateModelCostBreakdown } from "../../cost/models/calculate-cost"; + +// claude-4.5-sonnet:anthropic has two pricing tiers: +// threshold 0: input 0.000003, cacheMultipliers.cachedInput 0.1 +// threshold 200000: input 0.000006 (cacheMultipliers inherited from the base tier) +// +// Above 200k prompt tokens the input rate doubles, and a cached read is priced as a +// multiple of the input rate, so it should double too. This pins that cached reads pick +// the same long-context tier as input and output do. +describe("Anthropic long-context cache pricing", () => { + const providerModelId = "claude-sonnet-4-5-20250929"; + + it("prices cached reads at the >200k tier, like input and output", () => { + const breakdown = calculateModelCostBreakdown({ + modelUsage: { + input: 250_000, // above the 200k threshold on its own + output: 1_000, + cacheDetails: { cachedInput: 100_000 }, + }, + providerModelId, + provider: "anthropic", + }); + + expect(breakdown).not.toBeNull(); + // input: 250_000 * 0.000006 = 1.5 + // cached: 100_000 * 0.000006 * 0.1 = 0.06 (base tier would give 0.03) + expect(breakdown?.inputCost).toBeCloseTo(1.5, 10); + expect(breakdown?.cachedInputCost).toBeCloseTo(0.06, 10); + }); + + it("prices cached reads at the base tier below the threshold", () => { + const breakdown = calculateModelCostBreakdown({ + modelUsage: { + input: 10_000, + output: 1_000, + cacheDetails: { cachedInput: 5_000 }, + }, + providerModelId, + provider: "anthropic", + }); + + expect(breakdown).not.toBeNull(); + // Base tier: cached 5_000 * 0.000003 * 0.1 = 0.0015 + expect(breakdown?.cachedInputCost).toBeCloseTo(0.0015, 10); + }); +});