@@ -1253,7 +1253,11 @@ const SAMPLE_CODEX_SESSION = [
12531253 } ) ,
12541254] . join ( '\n' )
12551255
1256- test ( 'codex parser appends -fast to model when config.toml has service_tier="fast"' , async ( ) => {
1256+ test ( 'codex parser does NOT append -fast when config.toml has service_tier="fast" but the session has no per-turn evidence' , async ( ) => {
1257+ // Regression: prior versions read CODEX_HOME/config.toml once per backfill
1258+ // and stamped every historical model.usage with -fast, which retroactively
1259+ // re-classified old standard-tier sessions as fast whenever the user later
1260+ // enabled fast. Only per-turn evidence inside the session counts now.
12571261 const codexHome = await mkdtemp ( path . join ( tmpdir ( ) , 'codex-home-' ) )
12581262 const sessionsDir = path . join ( codexHome , 'sessions' , 'p' )
12591263 await mkdir ( sessionsDir , { recursive : true } )
@@ -1264,20 +1268,62 @@ test('codex parser appends -fast to model when config.toml has service_tier="fas
12641268 const events = await createCodexAdapter ( ) . parseSessionFile ! ( sessionPath , { _ : [ ] } )
12651269 const usage = events . filter ( event => event . type === 'model.usage' )
12661270 assert . ok ( usage . length > 0 , 'expected at least one model.usage' )
1271+ for ( const event of usage ) {
1272+ assert . equal ( event . model , 'gpt-5-codex' )
1273+ }
1274+ } )
1275+
1276+ test ( 'codex parser appends -fast when turn_context carries service_tier="fast"' , async ( ) => {
1277+ const codexHome = await mkdtemp ( path . join ( tmpdir ( ) , 'codex-home-' ) )
1278+ const sessionsDir = path . join ( codexHome , 'sessions' , 'p' )
1279+ await mkdir ( sessionsDir , { recursive : true } )
1280+ const sessionPath = path . join ( sessionsDir , 'session.jsonl' )
1281+ await writeFile ( sessionPath , [
1282+ JSON . stringify ( { timestamp : '2026-05-13T09:00:00.000Z' , type : 'turn_context' , payload : { model : 'gpt-5-codex' , service_tier : 'fast' } } ) ,
1283+ JSON . stringify ( {
1284+ timestamp : '2026-05-13T09:01:00.000Z' ,
1285+ type : 'event_msg' ,
1286+ payload : {
1287+ type : 'token_count' ,
1288+ info : {
1289+ model : 'gpt-5-codex' ,
1290+ last_token_usage : { input_tokens : 100 , cached_input_tokens : 10 , output_tokens : 50 , reasoning_output_tokens : 5 , total_tokens : 150 } ,
1291+ total_token_usage : { input_tokens : 100 , cached_input_tokens : 10 , output_tokens : 50 , reasoning_output_tokens : 5 , total_tokens : 150 } ,
1292+ } ,
1293+ } ,
1294+ } ) ,
1295+ ] . join ( '\n' ) , 'utf8' )
1296+
1297+ const events = await createCodexAdapter ( ) . parseSessionFile ! ( sessionPath , { _ : [ ] } )
1298+ const usage = events . filter ( event => event . type === 'model.usage' )
1299+ assert . ok ( usage . length > 0 )
12671300 for ( const event of usage ) {
12681301 assert . equal ( event . model , 'gpt-5-codex-fast' )
12691302 }
12701303} )
12711304
1272- test ( 'codex parser maps service_tier="priority" to the -fast model variant' , async ( ) => {
1273- // Codex's "priority" is the second flavor of fast inference; ccusage maps both
1274- // to its single "fast" speed bucket. We do the same so backend pricing stays simple .
1305+ test ( 'codex parser maps per-turn service_tier="priority" to the -fast model variant' , async ( ) => {
1306+ // Codex's "priority" is the second flavor of fast inference; ccusage maps
1307+ // both to its single "fast" speed bucket. Backend pricing keys on model only .
12751308 const codexHome = await mkdtemp ( path . join ( tmpdir ( ) , 'codex-home-' ) )
12761309 const sessionsDir = path . join ( codexHome , 'sessions' , 'p' )
12771310 await mkdir ( sessionsDir , { recursive : true } )
1278- await writeFile ( path . join ( codexHome , 'config.toml' ) , 'service_tier = "priority"\n' , 'utf8' )
12791311 const sessionPath = path . join ( sessionsDir , 'session.jsonl' )
1280- await writeFile ( sessionPath , SAMPLE_CODEX_SESSION , 'utf8' )
1312+ await writeFile ( sessionPath , [
1313+ JSON . stringify ( { timestamp : '2026-05-13T09:00:00.000Z' , type : 'turn_context' , payload : { model : 'gpt-5-codex' , service_tier : 'priority' } } ) ,
1314+ JSON . stringify ( {
1315+ timestamp : '2026-05-13T09:01:00.000Z' ,
1316+ type : 'event_msg' ,
1317+ payload : {
1318+ type : 'token_count' ,
1319+ info : {
1320+ model : 'gpt-5-codex' ,
1321+ last_token_usage : { input_tokens : 100 , cached_input_tokens : 10 , output_tokens : 50 , reasoning_output_tokens : 5 , total_tokens : 150 } ,
1322+ total_token_usage : { input_tokens : 100 , cached_input_tokens : 10 , output_tokens : 50 , reasoning_output_tokens : 5 , total_tokens : 150 } ,
1323+ } ,
1324+ } ,
1325+ } ) ,
1326+ ] . join ( '\n' ) , 'utf8' )
12811327
12821328 const events = await createCodexAdapter ( ) . parseSessionFile ! ( sessionPath , { _ : [ ] } )
12831329 const usage = events . filter ( event => event . type === 'model.usage' )
@@ -1287,14 +1333,29 @@ test('codex parser maps service_tier="priority" to the -fast model variant', asy
12871333 }
12881334} )
12891335
1290- test ( 'codex parser keeps bare model name when config.toml omits service_tier' , async ( ) => {
1336+ test ( 'codex parser ignores config.toml fast when the turn explicitly says default' , async ( ) => {
1337+ // Mixed scenario: user has fast on now (config.toml), but an old turn
1338+ // explicitly recorded service_tier="default". The turn wins — no -fast.
12911339 const codexHome = await mkdtemp ( path . join ( tmpdir ( ) , 'codex-home-' ) )
12921340 const sessionsDir = path . join ( codexHome , 'sessions' , 'p' )
12931341 await mkdir ( sessionsDir , { recursive : true } )
1294- // config.toml exists but has no service_tier — must NOT append -fast.
1295- await writeFile ( path . join ( codexHome , 'config.toml' ) , 'model = "gpt-5-codex"\n' , 'utf8' )
1342+ await writeFile ( path . join ( codexHome , 'config.toml' ) , 'service_tier = "fast"\n' , 'utf8' )
12961343 const sessionPath = path . join ( sessionsDir , 'session.jsonl' )
1297- await writeFile ( sessionPath , SAMPLE_CODEX_SESSION , 'utf8' )
1344+ await writeFile ( sessionPath , [
1345+ JSON . stringify ( { timestamp : '2026-05-13T09:00:00.000Z' , type : 'turn_context' , payload : { model : 'gpt-5-codex' , service_tier : 'default' } } ) ,
1346+ JSON . stringify ( {
1347+ timestamp : '2026-05-13T09:01:00.000Z' ,
1348+ type : 'event_msg' ,
1349+ payload : {
1350+ type : 'token_count' ,
1351+ info : {
1352+ model : 'gpt-5-codex' ,
1353+ last_token_usage : { input_tokens : 100 , cached_input_tokens : 10 , output_tokens : 50 , reasoning_output_tokens : 5 , total_tokens : 150 } ,
1354+ total_token_usage : { input_tokens : 100 , cached_input_tokens : 10 , output_tokens : 50 , reasoning_output_tokens : 5 , total_tokens : 150 } ,
1355+ } ,
1356+ } ,
1357+ } ) ,
1358+ ] . join ( '\n' ) , 'utf8' )
12981359
12991360 const events = await createCodexAdapter ( ) . parseSessionFile ! ( sessionPath , { _ : [ ] } )
13001361 const usage = events . filter ( event => event . type === 'model.usage' )
@@ -1304,8 +1365,7 @@ test('codex parser keeps bare model name when config.toml omits service_tier', a
13041365 }
13051366} )
13061367
1307- test ( 'codex parser handles missing config.toml gracefully' , async ( ) => {
1308- // No config.toml at all — common when CODEX_HOME is fresh.
1368+ test ( 'codex parser keeps bare model name when neither config.toml nor session evidence is present' , async ( ) => {
13091369 const codexHome = await mkdtemp ( path . join ( tmpdir ( ) , 'codex-home-' ) )
13101370 const sessionsDir = path . join ( codexHome , 'sessions' , 'p' )
13111371 await mkdir ( sessionsDir , { recursive : true } )
0 commit comments