@@ -449,6 +449,80 @@ describe("session cost usage", () => {
449449 }
450450 } ) ;
451451
452+ it ( "preserves a provider-billed zero total when total provenance is explicit" , async ( ) => {
453+ const root = await makeSessionCostRoot ( "cost-provider-billed-zero-origin" ) ;
454+ const sessionsDir = path . join ( root , "agents" , "main" , "sessions" ) ;
455+ await fs . mkdir ( sessionsDir , { recursive : true } ) ;
456+ const sessionFile = path . join ( sessionsDir , "sess-openrouter-billed-zero.jsonl" ) ;
457+ const entry = {
458+ type : "message" ,
459+ timestamp : "2026-02-05T12:00:00.000Z" ,
460+ message : {
461+ role : "assistant" ,
462+ provider : "openrouter" ,
463+ model : "openai/gpt-5.5" ,
464+ content : "provider-billed free route" ,
465+ usage : {
466+ input : 10_000 ,
467+ output : 5_000 ,
468+ cacheRead : 0 ,
469+ cacheWrite : 0 ,
470+ totalTokens : 15_000 ,
471+ cost : {
472+ input : 0 ,
473+ output : 0 ,
474+ cacheRead : 0 ,
475+ cacheWrite : 0 ,
476+ total : 0 ,
477+ totalOrigin : "provider-billed" ,
478+ } ,
479+ } ,
480+ } ,
481+ } ;
482+ await fs . writeFile ( sessionFile , transcriptText ( "sess-openrouter-billed-zero" , entry ) , "utf-8" ) ;
483+
484+ const config = {
485+ models : {
486+ providers : {
487+ openrouter : {
488+ models : [
489+ {
490+ id : "openai/gpt-5.5" ,
491+ cost : { input : 1 , output : 2 , cacheRead : 0.5 , cacheWrite : 0 } ,
492+ } ,
493+ ] ,
494+ } ,
495+ } ,
496+ } ,
497+ } as unknown as OpenClawConfig ;
498+
499+ clearGatewayModelPricingCacheState ( ) ;
500+ await withStateDir ( root , async ( ) => {
501+ const summary = await loadCostUsageSummary ( {
502+ startMs : Date . UTC ( 2026 , 1 , 5 ) ,
503+ endMs : Date . UTC ( 2026 , 1 , 5 , 23 , 59 , 59 , 999 ) ,
504+ config,
505+ } ) ;
506+ expect ( summary . totals . totalTokens ) . toBe ( 15_000 ) ;
507+ expect ( summary . totals . totalCost ) . toBe ( 0 ) ;
508+ expect ( summary . totals . missingCostEntries ) . toBe ( 0 ) ;
509+
510+ await refreshCostUsageCache ( { config, sessionFiles : [ sessionFile ] } ) ;
511+ const cached = await loadCostUsageSummaryFromCache ( {
512+ startMs : Date . UTC ( 2026 , 1 , 5 ) ,
513+ endMs : Date . UTC ( 2026 , 1 , 5 , 23 , 59 , 59 , 999 ) ,
514+ config,
515+ requestRefresh : false ,
516+ } ) ;
517+ expect ( cached . totals . totalCost ) . toBe ( 0 ) ;
518+ expect ( cached . totals . missingCostEntries ) . toBe ( 0 ) ;
519+
520+ const logs = await loadSessionLogs ( { sessionFile, config } ) ;
521+ expect ( logs ?. [ 0 ] ?. tokens ) . toBe ( 15_000 ) ;
522+ expect ( logs ?. [ 0 ] ?. cost ) . toBe ( 0 ) ;
523+ } ) ;
524+ } ) ;
525+
452526 it ( "uses top-level transcript provider and model when recomputing session-log cost" , async ( ) => {
453527 const root = await makeSessionCostRoot ( "cost-known-pricing-top-level-metadata" ) ;
454528 const sessionsDir = path . join ( root , "agents" , "main" , "sessions" ) ;
@@ -529,7 +603,7 @@ describe("session cost usage", () => {
529603 await refreshCostUsageCache ( { sessionFiles : [ sessionFile ] } ) ;
530604 const cachePath = path . join ( sessionsDir , ".usage-cost-cache.json" ) ;
531605 const cache = JSON . parse ( await fs . readFile ( cachePath , "utf-8" ) ) as { version : number } ;
532- cache . version = 5 ;
606+ cache . version = 6 ;
533607 await fs . writeFile ( cachePath , `${ JSON . stringify ( cache ) } \n` , "utf-8" ) ;
534608
535609 // The pre-upgrade cache must be treated as stale (not served), forcing a rebuild
0 commit comments