@@ -259,6 +259,23 @@ impl PricingMap {
259259 . unwrap_or ( 1.0 ) ,
260260 } ,
261261 ) ;
262+ self . entries . insert (
263+ "claude-opus-4-8" . to_string ( ) ,
264+ Pricing {
265+ input : 5e-6 ,
266+ output : 25e-6 ,
267+ cache_create : 6.25e-6 ,
268+ cache_read : 0.5e-6 ,
269+ cache_read_explicit : true ,
270+ input_above_200k : None ,
271+ output_above_200k : None ,
272+ cache_create_above_200k : None ,
273+ cache_read_above_200k : None ,
274+ fast_multiplier : fast_multiplier_overrides
275+ . multiplier_for ( "claude-opus-4-8" )
276+ . unwrap_or ( 1.0 ) ,
277+ } ,
278+ ) ;
262279 self . entries . insert (
263280 "claude-haiku-4-5" . to_string ( ) ,
264281 Pricing {
@@ -550,7 +567,12 @@ impl PricingMap {
550567 self . context_limits
551568 . insert ( "grok-4.3" . to_string ( ) , 1_000_000 ) ;
552569 self . context_limits . insert ( "gpt-5.4" . to_string ( ) , 1_050_000 ) ;
553- for model in [ "claude-opus-4-7" , "claude-opus-4-6" , "claude-sonnet-4-6" ] {
570+ for model in [
571+ "claude-opus-4-8" ,
572+ "claude-opus-4-7" ,
573+ "claude-opus-4-6" ,
574+ "claude-sonnet-4-6" ,
575+ ] {
554576 self . context_limits . insert ( model. to_string ( ) , 1_000_000 ) ;
555577 }
556578 self . context_limits
@@ -591,8 +613,8 @@ fn contains_pricing_key(value: &str, key: &str) -> bool {
591613 . checked_sub ( 1 )
592614 . and_then ( |before| value. as_bytes ( ) . get ( before) )
593615 . copied ( ) ;
594- let after = value. as_bytes ( ) . get ( index + key. len ( ) ) . copied ( ) ;
595- before. is_none_or ( is_pricing_key_boundary) && after . is_none_or ( is_pricing_key_boundary )
616+ let suffix = & value[ index + key. len ( ) .. ] ;
617+ before. is_none_or ( is_pricing_key_boundary) && suffix_allows_pricing_key_match ( key , suffix )
596618 } )
597619}
598620
@@ -601,6 +623,37 @@ fn is_pricing_key_boundary(byte: u8) -> bool {
601623 !byte. is_ascii_alphanumeric ( )
602624}
603625
626+ fn suffix_allows_pricing_key_match ( key : & str , suffix : & str ) -> bool {
627+ let Some ( separator) = suffix. as_bytes ( ) . first ( ) . copied ( ) else {
628+ return true ;
629+ } ;
630+ if !is_pricing_key_boundary ( separator) {
631+ return false ;
632+ }
633+ !suffix_starts_with_numeric_model_version ( key, suffix)
634+ }
635+
636+ fn suffix_starts_with_numeric_model_version ( key : & str , suffix : & str ) -> bool {
637+ if !key. as_bytes ( ) . last ( ) . is_some_and ( u8:: is_ascii_digit) {
638+ return false ;
639+ }
640+ if !matches ! ( suffix. as_bytes( ) . first( ) , Some ( b'-' | b'.' ) ) {
641+ return false ;
642+ }
643+
644+ let rest = & suffix[ 1 ..] ;
645+ let digit_len = rest
646+ . as_bytes ( )
647+ . iter ( )
648+ . take_while ( |byte| byte. is_ascii_digit ( ) )
649+ . count ( ) ;
650+ if digit_len == 0 {
651+ return false ;
652+ }
653+ let after_digits = rest. as_bytes ( ) . get ( digit_len) . copied ( ) ;
654+ !( digit_len == 8 && after_digits. is_none_or ( is_pricing_key_boundary) )
655+ }
656+
604657/// Normalizes known model separator variants without allocating for canonical keys.
605658fn normalized_pricing_key ( value : & str ) -> Cow < ' _ , str > {
606659 if value. contains ( [ '.' , '@' ] ) {
@@ -807,6 +860,13 @@ mod tests {
807860 . fast_multiplier,
808861 6.0
809862 ) ;
863+ assert_eq ! (
864+ pricing
865+ . find( "anthropic.claude-opus-4-8" )
866+ . unwrap( )
867+ . fast_multiplier,
868+ 2.0
869+ ) ;
810870 }
811871
812872 #[ test]
@@ -827,6 +887,18 @@ mod tests {
827887 ) ;
828888 }
829889
890+ #[ test]
891+ fn embedded_pricing_resolves_opus_48_dot_model_names ( ) {
892+ let pricing = PricingMap :: load_embedded ( ) ;
893+
894+ let opus_48 = pricing. find ( "claude-opus-4.8-20260528" ) . unwrap ( ) ;
895+ assert_eq ! ( opus_48. input, 5e-6 ) ;
896+ assert_eq ! ( opus_48. output, 25e-6 ) ;
897+ assert_eq ! ( opus_48. cache_create, 6.25e-6 ) ;
898+ assert_eq ! ( opus_48. cache_read, 0.5e-6 ) ;
899+ assert_eq ! ( pricing. context_limit( "claude-opus-4.8" ) , Some ( 1_000_000 ) ) ;
900+ }
901+
830902 #[ test]
831903 fn embedded_pricing_resolves_separator_aliases_for_other_claude_models ( ) {
832904 let pricing = PricingMap :: load_embedded ( ) ;
@@ -885,7 +957,31 @@ mod tests {
885957 } ,
886958 ) ;
887959
888- assert_eq ! ( pricing. find( "claude-opus-4.70" ) . unwrap( ) . input, 15e-6 ) ;
960+ assert ! ( pricing. find( "claude-opus-4.70" ) . is_none( ) ) ;
961+ }
962+
963+ #[ test]
964+ fn fuzzy_match_does_not_fall_back_across_numeric_model_versions ( ) {
965+ let mut pricing = PricingMap :: default ( ) ;
966+ pricing. entries . insert (
967+ "claude-opus-4" . to_string ( ) ,
968+ Pricing {
969+ input : 15e-6 ,
970+ output : 75e-6 ,
971+ cache_create : 18.75e-6 ,
972+ cache_read : 1.5e-6 ,
973+ cache_read_explicit : true ,
974+ input_above_200k : None ,
975+ output_above_200k : None ,
976+ cache_create_above_200k : None ,
977+ cache_read_above_200k : None ,
978+ fast_multiplier : 1.0 ,
979+ } ,
980+ ) ;
981+
982+ assert ! ( pricing. find( "claude-opus-4.8-20260528" ) . is_none( ) ) ;
983+ assert ! ( pricing. find( "claude-opus-4.70" ) . is_none( ) ) ;
984+ assert ! ( pricing. find( "claude-opus-4-20250514" ) . is_some( ) ) ;
889985 }
890986
891987 #[ test]
@@ -939,6 +1035,10 @@ mod tests {
9391035 "input_cost_per_token": 0.000005,
9401036 "output_cost_per_token": 0.000025
9411037 },
1038+ "claude-opus-4.8-20260528": {
1039+ "input_cost_per_token": 0.000005,
1040+ "output_cost_per_token": 0.000025
1041+ },
9421042 "claude-opus-4-70": {
9431043 "input_cost_per_token": 0.000005,
9441044 "output_cost_per_token": 0.000025
@@ -967,6 +1067,13 @@ mod tests {
9671067 . fast_multiplier,
9681068 6.0
9691069 ) ;
1070+ assert_eq ! (
1071+ pricing
1072+ . find( "claude-opus-4.8-20260528" )
1073+ . unwrap( )
1074+ . fast_multiplier,
1075+ 2.0
1076+ ) ;
9701077 assert_eq ! (
9711078 pricing. find( "claude-opus-4-70" ) . unwrap( ) . fast_multiplier,
9721079 1.0
0 commit comments