@@ -567,6 +567,7 @@ pub fn project_reasoning_catalog_with_limit_and_auto_binding(
567567 } ;
568568
569569 let mut descriptors = BTreeMap :: < String , ReasoningPresetDescriptor > :: new ( ) ;
570+ let mut unavailable_descriptors = BTreeMap :: < String , ReasoningPresetDescriptor > :: new ( ) ;
570571 let mut has_unmapped_reasoning = false ;
571572 if let Some ( ( source_provider, source_model) ) = source_match {
572573 if source_model. reasoning {
@@ -606,6 +607,37 @@ pub fn project_reasoning_catalog_with_limit_and_auto_binding(
606607 | ModelsDevReasoningOption :: Toggle
607608 | ModelsDevReasoningOption :: BudgetTokens { .. } => {
608609 has_unmapped_reasoning = true ;
610+ if matches ! ( binding, ReasoningCatalogBinding :: ModelsDev { .. } ) {
611+ let unavailable = match option {
612+ ModelsDevReasoningOption :: Effort { values } => effort_descriptors (
613+ values,
614+ support. nullable_effort ,
615+ ReasoningPresetSource :: ModelsDev ,
616+ source_provider,
617+ & source_model. id ,
618+ ) ,
619+ ModelsDevReasoningOption :: Toggle => toggle_descriptors (
620+ ReasoningPresetSource :: ModelsDev ,
621+ source_provider,
622+ & source_model. id ,
623+ ) ,
624+ ModelsDevReasoningOption :: BudgetTokens { min, max } => {
625+ budget_descriptors (
626+ * min,
627+ * max,
628+ source_model. output_limit ,
629+ effective_max_output_tokens,
630+ provider,
631+ ReasoningPresetSource :: ModelsDev ,
632+ source_provider,
633+ & source_model. id ,
634+ )
635+ }
636+ } ;
637+ for descriptor in unavailable {
638+ unavailable_descriptors. insert ( descriptor. id . clone ( ) , descriptor) ;
639+ }
640+ }
609641 Vec :: new ( )
610642 }
611643 } ;
@@ -673,12 +705,15 @@ pub fn project_reasoning_catalog_with_limit_and_auto_binding(
673705 }
674706 if preset. disabled {
675707 descriptors. remove ( preset_id) ;
708+ unavailable_descriptors. remove ( preset_id) ;
676709 continue ;
677710 }
678711 if preset. actions . is_empty ( ) {
679712 descriptors. remove ( preset_id) ;
713+ unavailable_descriptors. remove ( preset_id) ;
680714 continue ;
681715 }
716+ unavailable_descriptors. remove ( preset_id) ;
682717 descriptors. insert (
683718 preset_id. to_string ( ) ,
684719 ReasoningPresetDescriptor {
@@ -704,6 +739,12 @@ pub fn project_reasoning_catalog_with_limit_and_auto_binding(
704739 . cmp ( & right. order )
705740 . then_with ( || left. id . cmp ( & right. id ) )
706741 } ) ;
742+ let mut unavailable_presets = unavailable_descriptors. into_values ( ) . collect :: < Vec < _ > > ( ) ;
743+ unavailable_presets. sort_by ( |left, right| {
744+ left. order
745+ . cmp ( & right. order )
746+ . then_with ( || left. id . cmp ( & right. id ) )
747+ } ) ;
707748 let status = if !presets. is_empty ( ) {
708749 ReasoningCapabilityStatus :: Known
709750 } else if matches ! ( binding, ReasoningCatalogBinding :: Disabled ) {
@@ -731,6 +772,7 @@ pub fn project_reasoning_catalog_with_limit_and_auto_binding(
731772 status,
732773 default_preset : default_preset. map ( ToOwned :: to_owned) ,
733774 presets,
775+ unavailable_presets,
734776 }
735777}
736778
@@ -1193,7 +1235,9 @@ mod tests {
11931235 }},
11941236 "anthropic": {"models": {
11951237 "claude-sonnet-4-6": {"id":"claude-sonnet-4-6","reasoning":true,
1196- "reasoning_options":[{"type":"effort","values":["low","high"]},{"type":"budget_tokens","min":1024}]}
1238+ "reasoning_options":[{"type":"effort","values":["low","high"]},{"type":"budget_tokens","min":1024}]},
1239+ "claude-fable-5": {"id":"claude-fable-5","reasoning":true,
1240+ "reasoning_options":{"type":"effort","values":["low","medium","high","xhigh","max"]}}
11971241 }},
11981242 "deepseek": {"models": {
11991243 "deepseek-v4-flash": {"id":"deepseek-v4-flash","reasoning":true,
@@ -1916,6 +1960,35 @@ mod tests {
19161960 } ) ) ;
19171961 }
19181962
1963+ #[ test]
1964+ fn explicit_anthropic_binding_reports_efforts_unavailable_to_openai_chat ( ) {
1965+ let configured = ReasoningConfig {
1966+ catalog : ReasoningCatalogBinding :: ModelsDev {
1967+ provider : "anthropic" . to_string ( ) ,
1968+ model : "claude-fable-5" . to_string ( ) ,
1969+ } ,
1970+ ..Default :: default ( )
1971+ } ;
1972+ let projection = project_reasoning_catalog (
1973+ "openai" ,
1974+ "dummy-model" ,
1975+ "http://localhost:8000/v1/chat/completions" ,
1976+ Some ( & configured) ,
1977+ Some ( & catalog ( ) ) ,
1978+ ) ;
1979+
1980+ assert_eq ! ( projection. status, ReasoningCapabilityStatus :: Unknown ) ;
1981+ assert ! ( projection. presets. is_empty( ) ) ;
1982+ assert_eq ! (
1983+ projection
1984+ . unavailable_presets
1985+ . iter( )
1986+ . map( |preset| preset. id. as_str( ) )
1987+ . collect:: <Vec <_>>( ) ,
1988+ [ "low" , "medium" , "high" , "xhigh" , "max" ]
1989+ ) ;
1990+ }
1991+
19191992 #[ test]
19201993 fn custom_presets_keep_the_explicit_catalog_identity_for_adapter_compilation ( ) {
19211994 let configured = ReasoningConfig {
0 commit comments