1import { describe, expect, it } from "@effect/vitest";
2
3import { cursorRateModel } from "@t3tools/provider-cursor/server/accountUsage";
4import type { UsageSpeed } from "@t3tools/provider-core/server/usage";
5import {
6 cacheSavingsUsd,
7 createOverrideRateTable,
8 lookupRate,
9 parseRateTable,
10 priceUsage,
11} from "./usagePricing.ts";
12
13const rate = (input: number, cacheRead?: number) => ({
14 input_cost_per_token: input,
15 output_cost_per_token: input * 5,
16 ...(cacheRead === undefined ? {} : { cache_read_input_token_cost: cacheRead }),
17});
18
19describe("usage pricing", () => {
20 const totals = {
21 uncachedInputTokens: 1_000_000,
22 cachedInputTokens: 1_000_000,
23 cacheCreationTokens: 1_000_000,
24 outputTokens: 1_000_000,
25 reasoningTokens: 500_000,
26 };
27 const record = (
28 model: string,
29 reportedCostUsd: number | null = null,
30 speed: UsageSpeed = "standard",
31 ) => ({
32 model,
33 totals,
34 reportedCostUsd,
35 speed,
36 });
37
38 it("uses custom token rates ahead of public and provider-reported costs", () => {
39 const table = parseRateTable({ "example-model": rate(1) });
40 const overrides = createOverrideRateTable({
41 "example-model": {
42 inputCostPerMillionTokens: 2,
43 outputCostPerMillionTokens: 8,
44 cacheReadCostPerMillionTokens: 0.5,
45 cacheWriteCostPerMillionTokens: 3,
46 },
47 });
48
49 for (const reportedCostUsd of [null, 99]) {
50 expect(priceUsage(table, record("example-model", reportedCostUsd), overrides)).toMatchObject({
51 costUsd: 13.5,
52 costSource: "modelPriced",
53 });
54 }
55 expect(cacheSavingsUsd(table, record("example-model"), overrides)).toBe(1.5);
56 });
57
58 it("prices Cursor cache savings at the base model rate", () => {
59 const table = parseRateTable({
60 "claude-fable-5-1": rate(10e-6, 1e-6),
61 "xai/grok-4.7": rate(2e-6, 0.5e-6),
62 "openrouter/x-ai/grok-4.7": rate(3e-6, 0.5e-6),
63 });
64 const cursorRecord = (model: string) => ({
65 ...record(model, 0.25),
66 rateModel: cursorRateModel(model),
67 });
68
69 expect(cacheSavingsUsd(table, cursorRecord("claude-fable-5-1-thinking-high"))).toBeCloseTo(9);
70 expect(cacheSavingsUsd(table, cursorRecord("cursor-grok-4.7-high-fast"))).toBeCloseTo(1.5);
71 expect(cacheSavingsUsd(table, cursorRecord("default"))).toBe(0);
72 expect(priceUsage(table, cursorRecord("grok-4.7-xhigh-fast"))).toMatchObject({
73 costUsd: 0.25,
74 costSource: "providerReported",
75 });
76 });
77
78 it("prices unknown models offline and uses input prices for omitted cache rates", () => {
79 const table = parseRateTable({});
80 const overrides = createOverrideRateTable({
81 "example-model": { inputCostPerMillionTokens: 2, outputCostPerMillionTokens: 8 },
82 });
83
84 expect(priceUsage(table, record("example-model"), overrides)).toMatchObject({
85 costUsd: 14,
86 costSource: "modelPriced",
87 });
88 expect(cacheSavingsUsd(table, record("example-model"), overrides)).toBe(0);
89 });
90
91 it("preserves explicit zero rates and matches only the exact trimmed model ID", () => {
92 const table = parseRateTable({});
93 const overrides = createOverrideRateTable({
94 " vendor/example-model[1m] ": {
95 inputCostPerMillionTokens: 0,
96 outputCostPerMillionTokens: 0,
97 },
98 });
99 expect(priceUsage(table, record(" vendor/example-model[1m] ", 99), overrides)).toMatchObject({
100 costUsd: 0,
101 costSource: "modelPriced",
102 });
103 for (const model of [
104 "example-model[1m]",
105 "vendor/example-model",
106 "vendor/Example-model[1m]",
107 "other/example-model[1m]",
108 ]) {
109 expect(priceUsage(table, record(model), overrides).costSource).toBe("unpriced");
110 expect(priceUsage(table, record(model, 99), overrides)).toEqual({
111 costUsd: 99,
112 costSource: "providerReported",
113 categoryCostUsd: null,
114 speedPremiumUsd: 0,
115 });
116 }
117 });
118
119 it("prices fast-mode requests at the model's published fast multiple", () => {
120 const table = parseRateTable({
121 "claude-opus-5-5": { ...rate(4e-6, 2e-7), provider_specific_entry: { fast: 2, us: 1.1 } },
122 "claude-fable-5-1": { ...rate(1e-5, 2.5e-7), provider_specific_entry: { us: 1.1 } },
123 });
124 const overrides = createOverrideRateTable({
125 "claude-opus-5-5": { inputCostPerMillionTokens: 4, outputCostPerMillionTokens: 20 },
126 });
127 const cost = (model: string, speed: UsageSpeed, custom?: typeof overrides) =>
128 priceUsage(table, record(model, null, speed), custom).costUsd;
129
130 expect(cost("claude-opus-5-5", "fast")).toBeCloseTo(2 * cost("claude-opus-5-5", "standard"));
131 expect(cacheSavingsUsd(table, record("claude-opus-5-5", null, "fast"))).toBeCloseTo(
132 2 * cacheSavingsUsd(table, record("claude-opus-5-5")),
133 );
134 // No published fast tier, and custom prices, both stay at the standard rate.
135 expect(cost("claude-fable-5-1", "fast")).toBe(cost("claude-fable-5-1", "standard"));
136 expect(cost("claude-opus-5-5", "fast", overrides)).toBe(
137 cost("claude-opus-5-5", "standard", overrides),
138 );
139 });
140
141 it("splits cost by category and prices the speed premium", () => {
142 const table = parseRateTable({
143 "claude-opus-5-5": {
144 ...rate(4e-6, 4e-7),
145 cache_creation_input_token_cost: 5e-6,
146 provider_specific_entry: { fast: 2 },
147 },
148 });
149 const split = (input: number, cacheRead: number, cacheWrite: number, output: number) => ({
150 input: expect.closeTo(input),
151 cacheRead: expect.closeTo(cacheRead),
152 cacheWrite: expect.closeTo(cacheWrite),
153 output: expect.closeTo(output),
154 });
155
156 expect(priceUsage(table, record("claude-opus-5-5", null, "fast"))).toEqual({
157 costUsd: expect.closeTo(58.8),
158 costSource: "modelPriced",
159 categoryCostUsd: split(8, 0.8, 10, 40),
160 speedPremiumUsd: expect.closeTo(29.4),
161 });
162 // A reported cost keeps its total and splits in proportion to list rates.
163 expect(priceUsage(table, record("claude-opus-5-5", 29.4, "fast"))).toEqual({
164 costUsd: 29.4,
165 costSource: "providerReported",
166 categoryCostUsd: split(4, 0.4, 5, 20),
167 speedPremiumUsd: expect.closeTo(14.7),
168 });
169 // Without rates there is nothing to split it by.
170 expect(priceUsage(table, record("unknown-model", 29.4, "fast"))).toMatchObject({
171 costUsd: 29.4,
172 categoryCostUsd: null,
173 speedPremiumUsd: 0,
174 });
175 });
176
177 it("prices Codex priority and ultrafast requests at their published tier rates", () => {
178 const table = parseRateTable({
179 "gpt-6-astra": {
180 ...rate(1e-5, 1e-6),
181 input_cost_per_token_priority: 2e-5,
182 output_cost_per_token_priority: 1e-4,
183 cache_read_input_token_cost_priority: 2e-6,
184 input_cost_per_token_ultrafast: 6e-5,
185 output_cost_per_token_ultrafast: 3e-4,
186 // No ultrafast cache rate: keeps the standard 10:1 input-to-cache ratio.
187 },
188 "gpt-6-sol": rate(2e-6, 2e-7),
189 });
190 const cost = (model: string, speed: UsageSpeed) =>
191 priceUsage(table, record(model, null, speed)).costUsd;
192 const standard = cost("gpt-6-astra", "standard");
193
194 expect(cost("gpt-6-astra", "fast")).toBeCloseTo(2 * standard);
195 expect(cost("gpt-6-astra", "ultrafast")).toBeCloseTo(6 * standard);
196 expect(cacheSavingsUsd(table, record("gpt-6-astra", null, "ultrafast"))).toBeCloseTo(
197 6 * cacheSavingsUsd(table, record("gpt-6-astra")),
198 );
199 // A tier the model does not publish bills at the standard rate.
200 expect(cost("gpt-6-sol", "ultrafast")).toBe(cost("gpt-6-sol", "standard"));
201 });
202
203 it("keeps the canonical Fable rate separate from DeepInfra in either order", () => {
204 const canonical = ["claude-fable-5", rate(1e-5, 1e-6)] as const;
205 const deepInfra = ["deepinfra/anthropic/claude-fable-5", rate(1e-5)] as const;
206
207 for (const entries of [
208 [canonical, deepInfra],
209 [deepInfra, canonical],
210 ]) {
211 const table = parseRateTable(Object.fromEntries(entries));
212
213 expect(lookupRate(table, "claude-fable-5")?.cacheReadCostPerToken).toBe(1e-6);
214 expect(lookupRate(table, "deepinfra/anthropic/claude-fable-5")?.cacheReadCostPerToken).toBe(
215 1e-5,
216 );
217 expect(lookupRate(table, "other/claude-fable-5")).toBeNull();
218 }
219 });
220
221 it("prices a bracketed context-tier variant at the base model's rate", () => {
222 const table = parseRateTable({ "claude-fable-5-1": rate(1e-5, 2.5e-7) });
223
224 expect(lookupRate(table, "claude-fable-5-1[1m]")).toEqual(
225 lookupRate(table, "claude-fable-5-1"),
226 );
227 expect(lookupRate(table, "anthropic/Claude-Fable-5-1[1m]")).toBeNull();
228 });
229
230 it("adds a bare alias when every qualified entry has the same rate", () => {
231 const table = parseRateTable({
232 "provider-a/example-model": rate(1),
233 "provider-b/example-model": rate(1),
234 });
235
236 expect(lookupRate(table, "example-model")).toEqual(
237 lookupRate(table, "provider-a/example-model"),
238 );
239 });
240
241 it("leaves an ambiguous bare name unpriced", () => {
242 const table = parseRateTable({
243 "provider-a/example-model": rate(1),
244 "provider-b/example-model": rate(3),
245 });
246
247 expect(lookupRate(table, "provider-a/example-model")?.inputCostPerToken).toBe(1);
248 expect(lookupRate(table, "provider-b/example-model")?.inputCostPerToken).toBe(3);
249 expect(lookupRate(table, "example-model")).toBeNull();
250 });
251});