@@ -14,6 +14,7 @@ import {
1414 computeCacheDiscountBase ,
1515 applyCacheDiscount ,
1616 customPricingKey ,
17+ computeCostFromAggregated ,
1718} from "../costRecalculation"
1819
1920// ── Test Helpers ────────────────────────────────────────────────────────────
@@ -261,6 +262,45 @@ describe("costRecalculation", () => {
261262 expect ( cost ) . toBeGreaterThan ( 0 )
262263 expect ( cost ) . toBeCloseTo ( 1.8 , 5 )
263264 } )
265+
266+ it ( "should use OpenAI semantics for Vertex Gemini models (inputTokens includes cached tokens)" , ( ) => {
267+ // Vertex Gemini uses OpenAI semantics: inputTokens includes cacheReadTokens and cacheWriteTokens.
268+ // gemini-3.7-flash: inputPrice=$0.75/1M, outputPrice=$3.75/1M, cacheReadsPrice=$0.075/1M, cacheWritesPrice=$0.5/1M
269+ const event = makeEvent ( {
270+ provider : "vertex" ,
271+ model : "gemini-3.7-flash" ,
272+ usage : {
273+ inputTokens : { value : 1_000_000 , source : "provider" } ,
274+ outputTokens : { value : 100_000 , source : "provider" } ,
275+ cacheWriteTokens : { value : 100_000 , source : "provider" } ,
276+ cacheReadTokens : { value : 200_000 , source : "provider" } ,
277+ } ,
278+ } )
279+ // nonCachedInputTokens = 1,000,000 - 200,000 - 100,000 = 700,000
280+ // cost = 0.7M * $0.75 + 0.1M * $0.5 + 0.2M * $0.075 + 0.1M * $3.75
281+ // = 0.525 + 0.05 + 0.015 + 0.375 = 0.965
282+ const cost = computeEventCost ( event )
283+ expect ( cost ) . toBeCloseTo ( 0.965 , 5 )
284+ } )
285+
286+ it ( "should use Anthropic semantics for Vertex Claude models (inputTokens excludes cached tokens)" , ( ) => {
287+ // Vertex Claude uses Anthropic semantics: inputTokens does NOT include cached tokens.
288+ // claude-sonnet-4-5@20250929: inputPrice=$3.0/1M, outputPrice=$15.0/1M, cacheReadsPrice=$0.30/1M, cacheWritesPrice=$3.75/1M
289+ const event = makeEvent ( {
290+ provider : "vertex" ,
291+ model : "claude-sonnet-4-5@20250929" ,
292+ usage : {
293+ inputTokens : { value : 100_000 , source : "provider" } ,
294+ outputTokens : { value : 10_000 , source : "provider" } ,
295+ cacheWriteTokens : { value : 10_000 , source : "provider" } ,
296+ cacheReadTokens : { value : 20_000 , source : "provider" } ,
297+ } ,
298+ } )
299+ // cost = 0.1M * $3.0 + 0.01M * $3.75 + 0.02M * $0.30 + 0.01M * $15.0
300+ // = 0.30 + 0.0375 + 0.006 + 0.15 = 0.4935
301+ const cost = computeEventCost ( event )
302+ expect ( cost ) . toBeCloseTo ( 0.4935 , 5 )
303+ } )
264304 } )
265305
266306 describe ( "getEffectiveCost" , ( ) => {
@@ -748,4 +788,40 @@ describe("costRecalculation", () => {
748788 expect ( discounted ) . toBeCloseTo ( 1.65 , 10 )
749789 } )
750790 } )
791+
792+ describe ( "computeCostFromAggregated" , ( ) => {
793+ it ( "should use OpenAI semantics for Vertex Gemini models" , ( ) => {
794+ // gemini-3.7-flash: inputPrice=$0.75/1M, outputPrice=$3.75/1M, cacheReadsPrice=$0.075/1M, cacheWritesPrice=$0.5/1M
795+ // inputTokens = 1,000,000, outputTokens = 100,000, cacheWriteTokens = 100,000, cacheReadTokens = 200,000
796+ // nonCachedInputTokens = 1,000,000 - 200,000 - 100,000 = 700,000
797+ // cost = 0.7M * $0.75 + 0.1M * $0.5 + 0.2M * $0.075 + 0.1M * $3.75 = 0.965
798+ const cost = computeCostFromAggregated ( "vertex" , "gemini-3.7-flash" , 1_000_000 , 100_000 , 100_000 , 200_000 )
799+ expect ( cost ) . toBeCloseTo ( 0.965 , 5 )
800+ } )
801+
802+ it ( "should use Anthropic semantics for Vertex Claude models" , ( ) => {
803+ // claude-sonnet-4-5@20250929: inputPrice=$3.0/1M, outputPrice=$15.0/1M, cacheReadsPrice=$0.30/1M, cacheWritesPrice=$3.75/1M
804+ // inputTokens = 100,000, outputTokens = 10,000, cacheWriteTokens = 10,000, cacheReadTokens = 20,000
805+ // cost = 0.1M * $3.0 + 0.01M * $3.75 + 0.02M * $0.30 + 0.01M * $15.0 = 0.4935
806+ const cost = computeCostFromAggregated (
807+ "vertex" ,
808+ "claude-sonnet-4-5@20250929" ,
809+ 100_000 ,
810+ 10_000 ,
811+ 10_000 ,
812+ 20_000 ,
813+ )
814+ expect ( cost ) . toBeCloseTo ( 0.4935 , 5 )
815+ } )
816+
817+ it ( "should return 0 when all token counts are zero" , ( ) => {
818+ const cost = computeCostFromAggregated ( "vertex" , "gemini-3.7-flash" , 0 , 0 , 0 , 0 )
819+ expect ( cost ) . toBe ( 0 )
820+ } )
821+
822+ it ( "should return 0 for unknown provider" , ( ) => {
823+ const cost = computeCostFromAggregated ( "unknown-provider" , "unknown-model" , 1000 , 1000 , 0 , 0 )
824+ expect ( cost ) . toBe ( 0 )
825+ } )
826+ } )
751827} )
0 commit comments