@@ -7,7 +7,18 @@ import { prefetchCodexPricing } from './_macro.ts' with { type: 'macro' };
77import { logger } from './logger.ts' ;
88
99const CODEX_PROVIDER_PREFIXES = [ 'openai/' , 'azure/' , 'openrouter/openai/' ] ;
10- const CODEX_MODEL_ALIASES_MAP = new Map < string , string > ( [ [ 'gpt-5-codex' , 'gpt-5' ] ] ) ;
10+ const CODEX_MODEL_ALIASES_MAP = new Map < string , string > ( [
11+ [ 'gpt-5-codex' , 'gpt-5' ] ,
12+ [ 'gpt-5.3-codex' , 'gpt-5.2-codex' ] ,
13+ ] ) ;
14+
15+ function hasNonZeroTokenPricing ( pricing : LiteLLMModelPricing ) : boolean {
16+ return (
17+ ( pricing . input_cost_per_token ?? 0 ) > 0 ||
18+ ( pricing . output_cost_per_token ?? 0 ) > 0 ||
19+ ( pricing . cache_read_input_token_cost ?? 0 ) > 0
20+ ) ;
21+ }
1122
1223function toPerMillion ( value : number | undefined , fallback ?: number ) : number {
1324 const perToken = value ?? fallback ?? 0 ;
@@ -44,13 +55,13 @@ export class CodexPricingSource implements PricingSource, Disposable {
4455 }
4556
4657 let pricing = directLookup . value ;
47- if ( pricing == null ) {
48- const alias = CODEX_MODEL_ALIASES_MAP . get ( model ) ;
49- if ( alias != null ) {
50- const aliasLookup = await this . fetcher . getModelPricing ( alias ) ;
51- if ( Result . isFailure ( aliasLookup ) ) {
52- throw aliasLookup . error ;
53- }
58+ const alias = CODEX_MODEL_ALIASES_MAP . get ( model ) ;
59+ if ( alias != null && ( pricing == null || ! hasNonZeroTokenPricing ( pricing ) ) ) {
60+ const aliasLookup = await this . fetcher . getModelPricing ( alias ) ;
61+ if ( Result . isFailure ( aliasLookup ) ) {
62+ throw aliasLookup . error ;
63+ }
64+ if ( aliasLookup . value != null && hasNonZeroTokenPricing ( aliasLo
10BC0
okup . value ) ) {
5465 pricing = aliasLookup . value ;
5566 }
5667 }
@@ -89,5 +100,51 @@ if (import.meta.vitest != null) {
89100 expect ( pricing . outputCostPerMToken ) . toBeCloseTo ( 10 ) ;
90101 expect ( pricing . cachedInputCostPerMToken ) . toBeCloseTo ( 0.125 ) ;
91102 } ) ;
103+
104+ it ( 'falls back to alias pricing when direct model pricing is all zeros' , async ( ) => {
105+ using source = new CodexPricingSource ( {
106+ offline : true ,
107+ offlineLoader : async ( ) => ( {
108+ 'gpt-5.3-codex' : {
109+ input_cost_per_token : 0 ,
110+ output_cost_per_token : 0 ,
111+ cache_read_input_token_cost : 0 ,
112+ } ,
113+ 'gpt-5.2-codex' : {
114+ input_cost_per_token : 1.75e-6 ,
115+ output_cost_per_token : 1.4e-5 ,
116+ cache_read_input_token_cost : 1.75e-7 ,
117+ } ,
118+ } ) ,
119+ } ) ;
120+
121+ const pricing = await source . getPricing ( 'gpt-5.3-codex' ) ;
122+ expect ( pricing . inputCostPerMToken ) . toBeCloseTo ( 1.75 ) ;
123+ expect ( pricing . outputCostPerMToken ) . toBeCloseTo ( 14 ) ;
124+ expect ( pricing . cachedInputCostPerMToken ) . toBeCloseTo ( 0.175 ) ;
125+ } ) ;
126+
127+ it ( 'prefers direct pricing when non-zero pricing is available' , async ( ) => {
128+ using source = new CodexPricingSource ( {
129+ offline : true ,
130+ offlineLoader : async ( ) => ( {
131+ 'gpt-5.3-codex' : {
132+ input_cost_per_token : 1.9e-6 ,
133+ output_cost_per_token : 1.5e-5 ,
134+ cache_read_input_token_cost : 1.9e-7 ,
135+ } ,
136+ 'gpt-5.2-codex' : {
137+ input_cost_per_token : 1.75e-6 ,
138+ output_cost_per_token : 1.4e-5 ,
139+ cache_read_input_token_cost : 1.75e-7 ,
140+ } ,
141+ } ) ,
142+ } ) ;
143+
144+ const pricing = await source . getPricing ( 'gpt-5.3-codex' ) ;
145+ expect ( pricing . inputCostPerMToken ) . toBeCloseTo ( 1.9 ) ;
146+ expect ( pricing . outputCostPerMToken ) . toBeCloseTo ( 15 ) ;
147+ expect ( pricing . cachedInputCostPerMToken ) . toBeCloseTo ( 0.19 ) ;
148+ } ) ;
92149 } ) ;
93150}
0 commit comments