@@ -35,16 +35,27 @@ export type OpenAIModelPricing = {
3535 outputUsdPer1MTokens : number ;
3636} ;
3737
38+ export type OpenAIUsageCostSummary = {
39+ usage : OpenAITokenUsage | null ;
40+ estimatedCostUsd : number | null ;
41+ modelsWithoutPricing : string [ ] ;
42+ } ;
43+
3844export const OPENAI_MODEL_PRICING : Record < string , OpenAIModelPricing > = {
39- 'gpt-5-mini ' : {
40- inputUsdPer1MTokens : 0.25 ,
41- cachedInputUsdPer1MTokens : 0.025 ,
42- outputUsdPer1MTokens : 2 ,
45+ 'gpt-5.4 ' : {
46+ inputUsdPer1MTokens : 2.5 ,
47+ cachedInputUsdPer1MTokens : 0.25 ,
48+ outputUsdPer1MTokens : 15 ,
4349 } ,
44- 'gpt-5-nano' : {
45- inputUsdPer1MTokens : 0.05 ,
46- cachedInputUsdPer1MTokens : 0.005 ,
47- outputUsdPer1MTokens : 0.4 ,
50+ 'gpt-5.4-mini' : {
51+ inputUsdPer1MTokens : 0.75 ,
52+ cachedInputUsdPer1MTokens : 0.075 ,
53+ outputUsdPer1MTokens : 4.5 ,
54+ } ,
55+ 'gpt-5.4-nano' : {
56+ inputUsdPer1MTokens : 0.2 ,
57+ cachedInputUsdPer1MTokens : 0.02 ,
58+ outputUsdPer1MTokens : 1.25 ,
4859 } ,
4960} ;
5061
@@ -114,3 +125,85 @@ export function estimateOpenAICostFromUsage({
114125 pricing,
115126 } ;
116127}
128+
129+ export function sumOpenAITokenUsage (
130+ left : OpenAITokenUsage | null ,
131+ right : OpenAITokenUsage | null ,
132+ ) : OpenAITokenUsage | null {
133+ if ( left == null ) {
134+ return right ;
135+ }
136+ if ( right == null ) {
137+ return left ;
138+ }
139+
140+ return {
141+ inputTokens : left . inputTokens + right . inputTokens ,
142+ cachedInputTokens : left . cachedInputTokens + right . cachedInputTokens ,
143+ outputTokens : left . outputTokens + right . outputTokens ,
144+ totalTokens : left . totalTokens + right . totalTokens ,
145+ } ;
146+ }
147+
148+ export class OpenAIUsageCostTracker {
149+ private usage : OpenAITokenUsage | null = null ;
150+ private estimatedCostUsd = 0 ;
151+ private readonly modelsWithoutPricing = new Set < string > ( ) ;
152+
153+ add ( { model, usage } : { model : string ; usage : OpenAITokenUsage | null } ) {
154+ if ( usage == null ) {
155+ return ;
156+ }
157+
158+ this . usage = sumOpenAITokenUsage ( this . usage , usage ) ;
159+
160+ const estimate = estimateOpenAICostFromUsage ( { model, usage } ) ;
161+ if ( estimate == null ) {
162+ this . modelsWithoutPricing . add ( model ) ;
163+ return ;
164+ }
165+
166+ this . estimatedCostUsd += estimate . estimatedCostUsd ;
167+ }
168+
169+ summary ( ) : OpenAIUsageCostSummary {
170+ return {
171+ usage : this . usage ,
172+ estimatedCostUsd :
173+ this . modelsWithoutPricing . size === 0 ? this . estimatedCostUsd : null ,
174+ modelsWithoutPricing : [ ...this . modelsWithoutPricing ] . sort ( ) ,
175+ } ;
176+ }
177+ }
178+
179+ export function logOpenAIUsageCostSummary ( {
180+ label,
181+ summary,
182+ } : {
183+ label : string ;
184+ summary : OpenAIUsageCostSummary ;
185+ } ) {
186+ if ( summary . usage == null ) {
187+ console . log ( `[${ label } ] Usage is unavailable` ) ;
188+ return ;
189+ }
190+
191+ console . log ( `[${ label } ] Total usage:` , {
192+ inputTokens : summary . usage . inputTokens ,
193+ cachedInputTokens : summary . usage . cachedInputTokens ,
194+ outputTokens : summary . usage . outputTokens ,
195+ totalTokens : summary . usage . totalTokens ,
196+ } ) ;
197+
198+ if ( summary . estimatedCostUsd != null ) {
199+ console . log (
200+ `[${ label } ] Total estimated cost (USD):` ,
201+ summary . estimatedCostUsd . toFixed ( 8 ) ,
202+ ) ;
203+ return ;
204+ }
205+
206+ console . log (
207+ `[${ label } ] No pricing configured for model(s): ${ summary . modelsWithoutPricing . join ( ', ' ) } ` ,
208+ ) ;
209+ }
0 commit comments