@@ -36,7 +36,7 @@ use crate::server::AppState;
3636use super :: proxy:: {
3737 check_sdk_version, compatibility_model_uri, is_openai_compat_forwarded_header,
3838 is_openai_compat_inner_request_header, is_valid_compat_model_id, proxy_request,
39- request_body_error, CompatibilityRoute ,
39+ request_body_error, resolve_model_and_bundle , CompatibilityRoute ,
4040} ;
4141
4242/// Request body cap. A System One request is one text state and its
@@ -459,6 +459,13 @@ async fn translate_error(response: Response) -> Response {
459459 Err ( _) => Value :: Null ,
460460 } ;
461461 let ( code, message) = error_parts ( & parsed) ;
462+ let code = code. map ( |code| {
463+ if parts. status == StatusCode :: NOT_FOUND && code. eq_ignore_ascii_case ( "MODEL_NOT_FOUND" ) {
464+ "MODEL_NOT_FOUND"
465+ } else {
466+ code
467+ }
468+ } ) ;
462469 if message. is_none ( ) {
463470 warn ! (
464471 status = parts. status. as_u16( ) ,
@@ -542,7 +549,12 @@ pub async fn proxy_systemone(State(state): State<Arc<AppState>>, req: Request) -
542549 if !is_valid_compat_model_id ( & request. model ) {
543550 return validation_error ( & invalid ( & [ "model" ] , "invalid model id" ) , None ) ;
544551 }
545- match is_decision_model ( & state, & request. model ) {
552+ let ( resolved_model, _) =
553+ match resolve_model_and_bundle ( & state, & request. model , & parts. extensions ) {
554+ Ok ( resolved) => resolved,
555+ Err ( response) => return translate_error ( response) . await ,
556+ } ;
557+ match is_decision_model ( & state, & resolved_model) {
546558 None => {
547559 return api_error (
548560 StatusCode :: NOT_FOUND ,
@@ -944,6 +956,121 @@ mod tests {
944956 assert_eq ! ( dispatched[ 0 ] . model, "acme/decide" ) ;
945957 }
946958
959+ #[ tokio:: test]
960+ async fn configured_aliases_reach_the_native_extract_queue ( ) {
961+ for model in [ "decision" , "DECISION" , "profiled" , "bundled" ] {
962+ let model_yaml = format ! (
963+ "{LOCAL_DECISION_MODEL} alternate:\n adapter_path: sie_server.adapters.gliner2.decide:GLiNER2DecideAdapter\n max_batch_tokens: 8192\n "
964+ ) ;
965+ let mut gateway = TestGateway :: new ( & [ & model_yaml] ) . await ;
966+ gateway. add_model_alias ( "decision" , "acme/decide" ) ;
967+ gateway. add_model_alias ( "profiled" , "acme/decide:alternate" ) ;
968+ gateway. add_model_alias ( "bundled" , "default:/acme/decide" ) ;
969+ gateway
970+ . add_verified_worker (
971+ "local-1" ,
972+ LOCAL_LANE ,
973+ & [ "acme/decide" , "acme/decide:alternate" ] ,
974+ )
975+ . await ;
976+ gateway. dispatcher . return_extract_data ( json ! ( {
977+ "q" : { "type" : "noul" , "noul" : 0.9 , "answer" : true }
978+ } ) ) ;
979+ let response = proxy_systemone (
980+ State ( Arc :: clone ( & gateway. state ) ) ,
981+ systemone_request ( json ! ( {
982+ "model" : model, "state" : "Continue?" , "questions" : { "q" : { "type" : "noul" } }
983+ } ) ) ,
984+ )
985+ . await ;
986+ let status = response. status ( ) ;
987+ let body = body_json ( response) . await ;
988+ assert_eq ! ( status, StatusCode :: OK , "{model}: {body}" ) ;
989+ assert_eq ! ( body[ "model" ] , model) ;
990+ assert_eq ! ( body[ "answers" ] [ "q" ] [ "answer" ] , true ) ;
991+ let dispatched = gateway. dispatcher . dispatched ( ) ;
992+ assert_eq ! ( dispatched. len( ) , 1 ) ;
993+ assert_eq ! (
994+ dispatched[ 0 ] . model,
995+ if model == "profiled" {
996+ "acme/decide:alternate"
997+ } else {
998+ "acme/decide"
999+ }
1000+ ) ;
1001+ assert_eq ! ( dispatched[ 0 ] . endpoint, "extract" ) ;
1002+ }
1003+ }
1004+
1005+ struct HiddenModels ;
1006+
1007+ impl crate :: server:: ModelAccessPolicy for HiddenModels {
1008+ fn visible ( & self , _: & str , _: & axum:: http:: Extensions ) -> bool {
1009+ false
1010+ }
1011+ }
1012+
1013+ #[ tokio:: test]
1014+ async fn hidden_and_absent_aliases_have_identical_not_found_responses ( ) {
1015+ for populated in [ false , true ] {
1016+ let hidden_models = if populated {
1017+ vec ! [ LOCAL_DECISION_MODEL ]
1018+ } else {
1019+ vec ! [ ]
1020+ } ;
1021+ let absent_models = if populated {
1022+ vec ! [ LOCAL_ENCODE_MODEL ]
1023+ } else {
1024+ vec ! [ ]
1025+ } ;
1026+ let mut hidden = TestGateway :: new ( & hidden_models) . await ;
1027+ let mut absent = TestGateway :: new ( & absent_models) . await ;
1028+ hidden. add_model_alias ( "decision" , "acme/decide" ) ;
1029+ absent. add_model_alias ( "decision" , "acme/decide" ) ;
1030+ hidden. install_policy ( Arc :: new ( HiddenModels ) ) ;
1031+ for model in [ "decision" , "DECISION" ] {
1032+ let payload = json ! ( { "model" : model, "state" : "Continue?" , "questions" : { "q" : { "type" : "noul" } } } ) ;
1033+ let denied = proxy_systemone (
1034+ State ( Arc :: clone ( & hidden. state ) ) ,
1035+ systemone_request ( payload. clone ( ) ) ,
1036+ )
1037+ . await ;
1038+ let missing =
1039+ proxy_systemone ( State ( Arc :: clone ( & absent. state ) ) , systemone_request ( payload) )
1040+ . await ;
1041+ assert_eq ! ( denied. status( ) , StatusCode :: NOT_FOUND ) ;
1042+ assert_eq ! ( missing. status( ) , StatusCode :: NOT_FOUND ) ;
1043+ let denied = body_json ( denied) . await ;
1044+ assert_eq ! ( denied[ "detail" ] [ "code" ] , "MODEL_NOT_FOUND" ) ;
1045+ assert_eq ! ( denied, body_json( missing) . await ) ;
1046+ assert ! ( !denied. to_string( ) . contains( "acme/" ) ) ;
1047+ }
1048+ assert ! ( hidden. dispatcher. dispatched( ) . is_empty( ) ) ;
1049+ assert ! ( absent. dispatcher. dispatched( ) . is_empty( ) ) ;
1050+ }
1051+ }
1052+
1053+ #[ tokio:: test]
1054+ async fn configured_aliases_preserve_decision_capability_checks ( ) {
1055+ let mut gateway = TestGateway :: new ( & [ LOCAL_DECISION_MODEL , LOCAL_ENCODE_MODEL ] ) . await ;
1056+ gateway. add_model_alias ( "encode" , "acme/local" ) ;
1057+ gateway. add_model_alias ( "missing" , "acme/missing" ) ;
1058+ for ( model, status) in [
1059+ ( "encode" , StatusCode :: UNPROCESSABLE_ENTITY ) ,
1060+ ( "missing" , StatusCode :: NOT_FOUND ) ,
1061+ ] {
1062+ let response = proxy_systemone (
1063+ State ( Arc :: clone ( & gateway. state ) ) ,
1064+ systemone_request ( json ! ( {
1065+ "model" : model, "state" : "Continue?" , "questions" : { "q" : { "type" : "noul" } }
1066+ } ) ) ,
1067+ )
1068+ . await ;
1069+ assert_eq ! ( response. status( ) , status, "{model}" ) ;
1070+ }
1071+ assert ! ( gateway. dispatcher. dispatched( ) . is_empty( ) ) ;
1072+ }
1073+
9471074 #[ tokio:: test]
9481075 async fn an_undelivered_result_is_a_gateway_owned_fault ( ) {
9491076 let gateway = TestGateway :: new ( & [ LOCAL_DECISION_MODEL ] ) . await ;
0 commit comments