@@ -213,6 +213,25 @@ function buildProviderUrl(
213213 return `${ normalizedBaseUrl } ${ normalizedPath } ` ;
214214}
215215
216+ function buildAnthropicModelDiscoveryUrls (
217+ baseUrl : string | null | undefined ,
218+ ) : string [ ] {
219+ if ( ! baseUrl || baseUrl . trim ( ) . length === 0 ) {
220+ return [ ] ;
221+ }
222+
223+ const normalizedBaseUrl = baseUrl . trim ( ) . replace ( / \/ + $ / , "" ) ;
224+ const candidateBaseUrls = [ normalizedBaseUrl ] ;
225+
226+ if ( ! / \/ v \d + (?: a l p h a | b e t a ) ? $ / i. test ( normalizedBaseUrl ) ) {
227+ candidateBaseUrls . push ( `${ normalizedBaseUrl } /v1` ) ;
228+ }
229+
230+ return [ ...new Set ( candidateBaseUrls ) ]
231+ . map ( ( candidateBaseUrl ) => buildProviderUrl ( candidateBaseUrl , "/models" ) )
232+ . filter ( ( url ) : url is string => typeof url === "string" && url . length > 0 ) ;
233+ }
234+
216235function buildGoogleModelsUrl (
217236 baseUrl : string | null | undefined ,
218237) : string | null {
@@ -730,11 +749,21 @@ export class ModelProviderService {
730749 const resolvedBaseUrl =
731750 input . baseUrl ?? storedProvider ?. baseUrl ?? defaultBaseUrl ;
732751
733- const verifyUrl =
752+ const verifyUrls =
734753 runtimePolicy . apiKind === "google-generative-ai"
735- ? ( buildGoogleModelsUrl ( resolvedBaseUrl ) ?? "" )
736- : ( buildProviderUrl ( resolvedBaseUrl , "/models" ) ?? "" ) ;
737- if ( verifyUrl . length === 0 ) {
754+ ? [ buildGoogleModelsUrl ( resolvedBaseUrl ) ] . filter (
755+ ( url ) : url is string => typeof url === "string" && url . length > 0 ,
756+ )
757+ : runtimePolicy . apiKind === "anthropic-messages"
758+ ? buildAnthropicModelDiscoveryUrls ( resolvedBaseUrl )
759+ : [ buildProviderUrl ( resolvedBaseUrl , "/models" ) ] . filter (
760+ ( url ) : url is string => typeof url === "string" && url . length > 0 ,
761+ ) ;
762+ if ( verifyUrls . length === 0 ) {
763+ return { valid : false , error : "Unknown provider and no baseUrl given" } ;
764+ }
765+ const primaryVerifyUrl = verifyUrls [ 0 ] ;
766+ if ( ! primaryVerifyUrl ) {
738767 return { valid : false , error : "Unknown provider and no baseUrl given" } ;
739768 }
740769
@@ -746,7 +775,7 @@ export class ModelProviderService {
746775 }
747776
748777 const response = await proxyFetch (
749- buildProviderUrl ( resolvedBaseUrl , "/api/tags" ) ?? verifyUrl ,
778+ buildProviderUrl ( resolvedBaseUrl , "/api/tags" ) ?? primaryVerifyUrl ,
750779 {
751780 headers : Object . keys ( headers ) . length > 0 ? headers : undefined ,
752781 timeoutMs : 10000 ,
@@ -774,7 +803,7 @@ export class ModelProviderService {
774803 }
775804
776805 if ( runtimePolicy . apiKind === "google-generative-ai" ) {
777- const response = await proxyFetch ( verifyUrl , {
806+ const response = await proxyFetch ( primaryVerifyUrl , {
778807 headers : {
779808 "x-goog-api-key" : apiKey ,
780809 } ,
@@ -807,44 +836,80 @@ export class ModelProviderService {
807836 }
808837 : { Authorization : `Bearer ${ apiKey } ` } ;
809838
810- const response = await proxyFetch ( verifyUrl , {
811- headers,
812- timeoutMs : 10000 ,
813- } ) ;
814- if ( ! response . ok ) {
815- if ( providerId === "minimax" && response . status === 404 ) {
816- return { valid : true , models : MINI_MAX_API_MODELS } ;
839+ let lastStatus : number | null = null ;
840+
841+ for ( const verifyUrl of verifyUrls ) {
842+ const response = await proxyFetch ( verifyUrl , {
843+ headers,
844+ timeoutMs : 10000 ,
845+ } ) ;
846+ if ( ! response . ok ) {
847+ lastStatus = response . status ;
848+ if (
849+ runtimePolicy . apiKind === "anthropic-messages" &&
850+ response . status === 404
851+ ) {
852+ continue ;
853+ }
854+ if ( providerId === "minimax" && response . status === 404 ) {
855+ return { valid : true , models : MINI_MAX_API_MODELS } ;
856+ }
857+ if ( providerId === "xiaomi" && response . status === 404 ) {
858+ return {
859+ valid : true ,
860+ models : getBundledProviderModelIds ( providerId ) ,
861+ } ;
862+ }
863+ return { valid : false , error : `HTTP ${ response . status } ` } ;
864+ }
865+
866+ let payload : { data ?: Array < { id : string } > } ;
867+ try {
868+ payload = ( await response . json ( ) ) as {
869+ data ?: Array < { id : string } > ;
870+ } ;
871+ } catch {
872+ if ( runtimePolicy . apiKind === "anthropic-messages" ) {
873+ continue ;
874+ }
875+ throw new Error ( "Invalid JSON response" ) ;
817876 }
818- if ( providerId === "xiaomi" && response . status === 404 ) {
877+
878+ if ( providerId === "xiaomi" ) {
819879 return {
820880 valid : true ,
821- models : getBundledProviderModelIds ( providerId ) ,
881+ models :
882+ Array . isArray ( payload . data ) && payload . data . length > 0
883+ ? payload . data . map ( ( item ) => item . id )
884+ : getBundledProviderModelIds ( providerId ) ,
822885 } ;
823886 }
824- return { valid : false , error : `HTTP ${ response . status } ` } ;
825- }
826887
827- const payload = ( await response . json ( ) ) as {
828- data ?: Array < { id : string } > ;
829- } ;
830- if ( providerId === "xiaomi" ) {
831888 return {
832889 valid : true ,
833890 models :
834891 Array . isArray ( payload . data ) && payload . data . length > 0
835892 ? payload . data . map ( ( item ) => item . id )
836- : getBundledProviderModelIds ( providerId ) ,
893+ : providerId === "minimax"
894+ ? MINI_MAX_API_MODELS
895+ : [ ] ,
837896 } ;
838897 }
839898
840- return {
841- valid : true ,
842- models : Array . isArray ( payload . data )
843- ? payload . data . map ( ( item ) => item . id )
844- : providerId === "minimax"
845- ? MINI_MAX_API_MODELS
846- : [ ] ,
847- } ;
899+ if ( providerId === "minimax" && lastStatus === 404 ) {
900+ return { valid : true , models : MINI_MAX_API_MODELS } ;
901+ }
902+ if ( providerId === "xiaomi" && lastStatus === 404 ) {
903+ return {
904+ valid : true ,
905+ models : getBundledProviderModelIds ( providerId ) ,
906+ } ;
907+ }
908+ if ( lastStatus !== null ) {
909+ return { valid : false , error : `HTTP ${ lastStatus } ` } ;
910+ }
911+
912+ return { valid : false , error : "Invalid JSON response" } ;
848913 } catch ( error ) {
849914 return {
850915 valid : false ,
0 commit comments