77 *
88 * @module usagePricing
99 */
10- import type {
11- UsageCostSource ,
12- UsageModelPriceOverride ,
13- UsageTokenTotals ,
14- } from "@t3tools/contracts" ;
10+ import type { UsageCostSource , UsageModelPriceOverride } from "@t3tools/contracts" ;
11+
12+ import type { UsageRecord } from "./usageTranscripts.ts" ;
1513
1614/**
1715 * The subset of a LiteLLM entry we price against. All values are USD per token.
@@ -26,11 +24,19 @@ export interface ModelRate {
2624 readonly outputCostPerToken : number ;
2725 readonly cacheReadCostPerToken : number ;
2826 readonly cacheCreationCostPerToken : number ;
27+ /**
28+ * Multiple of the rates above billed for a fast-mode request, from LiteLLM's
29+ * `provider_specific_entry.fast`. `1` when the model publishes no fast tier.
30+ */
31+ readonly fastMultiplier : number ;
2932}
3033
3134export type RateTable = ReadonlyMap < string , ModelRate > ;
3235
33- /** Custom IDs keep their case, provider prefix, and variant suffix. */
36+ /**
37+ * Custom IDs keep their case, provider prefix, and variant suffix. Custom rates
38+ * apply as entered, fast-mode requests included.
39+ */
3440export function createOverrideRateTable (
3541 overrides : Readonly < Record < string , UsageModelPriceOverride > > ,
3642) : RateTable {
@@ -44,6 +50,7 @@ export function createOverrideRateTable(
4450 ( prices . cacheReadCostPerMillionTokens ?? prices . inputCostPerMillionTokens ) / 1_000_000 ,
4551 cacheCreationCostPerToken :
4652 ( prices . cacheWriteCostPerMillionTokens ?? prices . inputCostPerMillionTokens ) / 1_000_000 ,
53+ fastMultiplier : 1 ,
4754 } ,
4855 ] ) ,
4956 ) ;
@@ -55,12 +62,21 @@ interface LiteLlmEntry {
5562 readonly output_cost_per_token ?: unknown ;
5663 readonly cache_read_input_token_cost ?: unknown ;
5764 readonly cache_creation_input_token_cost ?: unknown ;
65+ readonly provider_specific_entry ?: unknown ;
5866}
5967
6068function finiteNumber ( value : unknown ) : number | null {
6169 return typeof value === "number" && Number . isFinite ( value ) ? value : null ;
6270}
6371
72+ /** Reads `provider_specific_entry.fast`, e.g. `2` for Claude Opus 5.5. */
73+ function fastMultiplier ( entry : LiteLlmEntry ) : number {
74+ const specific = entry . provider_specific_entry ;
75+ if ( typeof specific !== "object" || specific === null ) return 1 ;
76+ const fast = finiteNumber ( ( specific as Record < string , unknown > ) [ "fast" ] ) ;
77+ return fast !== null && fast > 0 ? fast : 1 ;
78+ }
79+
6480/**
6581 * Projects the LiteLLM document into a rate table.
6682 *
@@ -92,6 +108,7 @@ export function parseRateTable(document: unknown): RateTable {
92108 // input rather than as free.
93109 cacheReadCostPerToken : finiteNumber ( entry . cache_read_input_token_cost ) ?? input ,
94110 cacheCreationCostPerToken : finiteNumber ( entry . cache_creation_input_token_cost ) ?? input ,
111+ fastMultiplier : fastMultiplier ( entry ) ,
95112 } ) ;
96113 }
97114
@@ -119,7 +136,8 @@ function sameRate(a: ModelRate, b: ModelRate): boolean {
119136 a . inputCostPerToken === b . inputCostPerToken &&
120137 a . outputCostPerToken === b . outputCostPerToken &&
121138 a . cacheReadCostPerToken === b . cacheReadCostPerToken &&
122- a . cacheCreationCostPerToken === b . cacheCreationCostPerToken
139+ a . cacheCreationCostPerToken === b . cacheCreationCostPerToken &&
140+ a . fastMultiplier === b . fastMultiplier
123141 ) ;
124142}
125143
@@ -165,24 +183,26 @@ export function lookupRate(table: RateTable, model: string): ModelRate | null {
165183 return table . get ( key ) ?? null ;
166184}
167185
186+ /** The parts of a transcript record that decide its price. */
187+ export type PricedRecord = Pick < UsageRecord , "model" | "totals" | "fast" | "reportedCostUsd" > ;
188+
168189export interface PricedUsage {
169190 readonly costUsd : number ;
170191 readonly costSource : UsageCostSource ;
171192}
172193
173194/**
174- * Prices a bucket 's tokens.
195+ * Prices one record 's tokens.
175196 *
176197 * `reasoningTokens` is intentionally not charged separately: it is already
177198 * counted inside `outputTokens`.
178199 */
179200export function priceUsage (
180201 table : RateTable ,
181- model : string ,
182- totals : UsageTokenTotals ,
183- reportedCostUsd : number | null ,
202+ record : PricedRecord ,
184203 overrides ?: RateTable ,
185204) : PricedUsage {
205+ const { model, totals, reportedCostUsd } = record ;
186206 const override = overrides ?. get ( model . trim ( ) ) ;
187207 if ( override === undefined && reportedCostUsd !== null && Number . isFinite ( reportedCostUsd ) ) {
188208 return { costUsd : reportedCostUsd , costSource : "providerReported" } ;
@@ -191,13 +211,16 @@ export function priceUsage(
191211 const rate = override ?? lookupRate ( table , model ) ;
192212 if ( rate === null ) return { costUsd : 0 , costSource : "unpriced" } ;
193213
194- const costUsd =
214+ const standardCostUsd =
195215 totals . uncachedInputTokens * rate . inputCostPerToken +
196216 totals . cachedInputTokens * rate . cacheReadCostPerToken +
197217 totals . cacheCreationTokens * rate . cacheCreationCostPerToken +
198218 totals . outputTokens * rate . outputCostPerToken ;
199219
200- return { costUsd, costSource : "modelPriced" } ;
220+ return {
221+ costUsd : standardCostUsd * ( record . fast ? rate . fastMultiplier : 1 ) ,
222+ costSource : "modelPriced" ,
223+ } ;
201224}
202225
203226/**
@@ -206,11 +229,14 @@ export function priceUsage(
206229 */
207230export function cacheSavingsUsd (
208231 table : RateTable ,
209- model : string ,
210- totals : UsageTokenTotals ,
232+ record : PricedRecord ,
211233 overrides ?: RateTable ,
212234) : number {
213- const rate = overrides ?. get ( model . trim ( ) ) ?? lookupRate ( table , model ) ;
235+ const rate = overrides ?. get ( record . model . trim ( ) ) ?? lookupRate ( table , record . model ) ;
214236 if ( rate === null ) return 0 ;
215- return totals . cachedInputTokens * ( rate . inputCostPerToken - rate . cacheReadCostPerToken ) ;
237+ return (
238+ record . totals . cachedInputTokens *
239+ ( rate . inputCostPerToken - rate . cacheReadCostPerToken ) *
240+ ( record . fast ? rate . fastMultiplier : 1 )
241+ ) ;
216242}
0 commit comments