@@ -8,7 +8,10 @@ use serde_json::{Map, Value};
88use std:: pin:: Pin ;
99
1010use crate :: reasoning:: { resolve_reasoning, ReasoningStyle , ResolvedReasoning } ;
11- use crate :: { build_http_client, resolve_chat_url, ProviderAdapter , ProviderRequestContext } ;
11+ use crate :: {
12+ build_http_client, resolve_chat_url, resolve_models_url, ProviderAdapter ,
13+ ProviderRequestContext ,
14+ } ;
1215
1316pub ( crate ) trait OpenAICompatPolicy : Clone + Send + Sync + ' static {
1417 fn default_base_url ( & self ) -> & ' static str {
@@ -813,6 +816,7 @@ mod tests {
813816 use crate :: siliconflow:: SiliconFlowPolicy ;
814817 use crate :: xai:: XAIPolicy ;
815818 use serde_json:: json;
819+ use tokio:: io:: { AsyncReadExt , AsyncWriteExt } ;
816820
817821 fn base_chat_request ( model : & str ) -> ChatRequest {
818822 ChatRequest {
@@ -853,6 +857,41 @@ mod tests {
853857 }
854858 }
855859
860+ async fn spawn_models_response ( ) -> ( std:: net:: SocketAddr , tokio:: task:: JoinHandle < String > ) {
861+ let listener = tokio:: net:: TcpListener :: bind ( "127.0.0.1:0" )
862+ . await
863+ . expect ( "bind test server" ) ;
864+ let addr = listener. local_addr ( ) . expect ( "server addr" ) ;
865+ let server = tokio:: spawn ( async move {
866+ let ( mut socket, _) = listener. accept ( ) . await . expect ( "accept request" ) ;
867+ let mut request = Vec :: new ( ) ;
868+ let mut buffer = [ 0u8 ; 1024 ] ;
869+ loop {
870+ let read = socket. read ( & mut buffer) . await . expect ( "read request" ) ;
871+ if read == 0 {
872+ break ;
873+ }
874+ request. extend_from_slice ( & buffer[ ..read] ) ;
875+ if request. windows ( 4 ) . any ( |window| window == b"\r \n \r \n " ) {
876+ break ;
877+ }
878+ }
879+ let body = r#"{"data":[{"id":"gpt-test"}]}"# ;
880+ let response = format ! (
881+ "HTTP/1.1 200 OK\r \n content-type: application/json\r \n content-length: {}\r \n connection: close\r \n \r \n {}" ,
882+ body. len( ) ,
883+ body
884+ ) ;
885+ socket
886+ . write_all ( response. as_bytes ( ) )
887+ . await
888+ . expect ( "write response" ) ;
889+
890+ String :: from_utf8_lossy ( & request) . into_owned ( )
891+ } ) ;
892+ ( addr, server)
893+ }
894+
856895 #[ test]
857896 fn convert_messages_omits_null_fields_for_openai_compatible_requests ( ) {
858897 let messages = convert_messages (
@@ -891,6 +930,29 @@ mod tests {
891930 ) ;
892931 }
893932
933+ #[ tokio:: test]
934+ async fn list_models_uses_resolved_base_models_url ( ) {
935+ let ( addr, server) = spawn_models_response ( ) . await ;
936+ let base_url =
937+ crate :: resolve_base_url_for_type ( & format ! ( "http://{}" , addr) , & ProviderType :: OpenAI ) ;
938+ let ctx = ProviderRequestContext {
939+ api_key : "sk-test" . to_string ( ) ,
940+ key_id : "key-1" . to_string ( ) ,
941+ provider_id : "provider-1" . to_string ( ) ,
942+ base_url : Some ( base_url) ,
943+ api_path : Some ( "/v1/chat/completions" . to_string ( ) ) ,
944+ proxy_config : None ,
945+ custom_headers : None ,
946+ } ;
947+ let adapter = OpenAICompatAdapter :: new ( OpenAIPolicy ) ;
948+
949+ let models = adapter. list_models ( & ctx) . await . expect ( "list models" ) ;
950+ let request = server. await . expect ( "server request" ) ;
951+
952+ assert_eq ! ( models[ 0 ] . model_id, "gpt-test" ) ;
953+ assert ! ( request. starts_with( "GET /v1/models HTTP/1.1" ) , "{request}" ) ;
954+ }
955+
894956 #[ test]
895957 fn deepseek_thinking_keeps_max_tokens_when_completion_tokens_not_enabled ( ) {
896958 let mut request = base_chat_request ( "deepseek-v4" ) ;
@@ -1640,7 +1702,7 @@ where
16401702 }
16411703
16421704 async fn list_models ( & self , ctx : & ProviderRequestContext ) -> Result < Vec < Model > > {
1643- let url = format ! ( "{}/models" , self . base_url( ctx) ) ;
1705+ let url = resolve_models_url ( & self . base_url ( ctx) ) ;
16441706
16451707 let resp = crate :: apply_request_headers (
16461708 self . get_client ( ctx) ?
@@ -1738,7 +1800,7 @@ where
17381800 return Ok ( true ) ;
17391801 }
17401802 // Fallback: probe /models endpoint, valid key → 200/400, invalid → 401/403
1741- let url = format ! ( "{}/models" , self . base_url( ctx) ) ;
1803+ let url = resolve_models_url ( & self . base_url ( ctx) ) ;
17421804 let resp = crate :: apply_request_headers (
17431805 self . get_client ( ctx) ?
17441806 . get ( & url)
0 commit comments