11import {
22 BailianError ,
3- ExitCode ,
43 getApiKeyLoginKind ,
54 normalizeModelBaseUrl ,
65 requestJson ,
@@ -53,7 +52,6 @@ export interface ApiKeyValidationResult {
5352}
5453
5554const API_KEY_VALIDATION_TIMEOUT_SECONDS = 5 ;
56- const DEFINITIVE_VALIDATION_FAILURE_STATUSES = new Set ( [ 200 , 400 , 401 , 403 , 404 , 405 ] ) ;
5755const ORDINARY_WORKSPACE_REGIONS = [
5856 "cn-beijing" ,
5957 "ap-southeast-1" ,
@@ -76,12 +74,15 @@ const KNOWN_BASE_URLS: Readonly<Record<ApiKeyEndpointKind, readonly string[]>> =
7674 ] ,
7775} ;
7876
77+ function usesCompatibleModelsApi ( baseUrl : string , kind : ApiKeyEndpointKind ) : boolean {
78+ const hostname = new URL ( normalizeModelBaseUrl ( baseUrl ) ) . hostname ;
79+ return kind === "token-plan" || hostname . endsWith ( ".maas.aliyuncs.com" ) ;
80+ }
81+
7982function modelsUrl ( baseUrl : string , kind : ApiKeyEndpointKind ) : string {
80- const url = new URL (
81- kind === "token-plan" ? "/compatible-mode/v1/models" : "/api/v1/models" ,
82- `${ baseUrl } /` ,
83- ) ;
84- if ( kind === "ordinary" ) {
83+ const compatible = usesCompatibleModelsApi ( baseUrl , kind ) ;
84+ const url = new URL ( compatible ? "/compatible-mode/v1/models" : "/api/v1/models" , `${ baseUrl } /` ) ;
85+ if ( ! compatible ) {
8586 url . searchParams . set ( "page_no" , "1" ) ;
8687 url . searchParams . set ( "page_size" , "1" ) ;
8788 }
@@ -97,6 +98,14 @@ function isTokenPlanBaseUrl(baseUrl: string): boolean {
9798 return new URL ( normalizeModelBaseUrl ( baseUrl ) ) . hostname . startsWith ( "token-plan." ) ;
9899}
99100
101+ function kindsForBaseUrl (
102+ kinds : readonly ApiKeyEndpointKind [ ] ,
103+ baseUrl : string ,
104+ ) : ApiKeyEndpointKind [ ] {
105+ if ( ! isTokenPlanBaseUrl ( baseUrl ) || ! kinds . includes ( "token-plan" ) ) return [ ...kinds ] ;
106+ return [ "token-plan" , ...kinds . filter ( ( kind ) => kind !== "token-plan" ) ] ;
107+ }
108+
100109function addCandidate (
101110 candidates : ApiKeyValidationCandidate [ ] ,
102111 seenUrls : Set < string > ,
@@ -120,13 +129,15 @@ export function apiKeyValidationCandidates(
120129 const seenUrls = new Set < string > ( ) ;
121130
122131 if ( options . explicitBaseUrl ) {
123- for ( const kind of kinds ) addCandidate ( candidates , seenUrls , options . explicitBaseUrl , kind ) ;
132+ for ( const kind of kindsForBaseUrl ( kinds , options . explicitBaseUrl ) ) {
133+ addCandidate ( candidates , seenUrls , options . explicitBaseUrl , kind ) ;
134+ }
124135 return candidates ;
125136 }
126137
127138 if ( options . storedBaseUrl ) {
128139 const storedIsTokenPlan = isTokenPlanBaseUrl ( options . storedBaseUrl ) ;
129- for ( const kind of kinds ) {
140+ for ( const kind of kindsForBaseUrl ( kinds , options . storedBaseUrl ) ) {
130141 if ( apiKeyKind === "unknown" || storedIsTokenPlan === ( kind === "token-plan" ) ) {
131142 addCandidate ( candidates , seenUrls , options . storedBaseUrl , kind ) ;
132143 }
@@ -152,10 +163,25 @@ export function apiKeyValidationCandidates(
152163 return candidates ;
153164}
154165
155- function isDefinitiveCandidateFailure ( error : unknown ) : boolean {
156- if ( ! ( error instanceof BailianError ) ) return false ;
157- const status = error . api ?. httpStatus ;
158- return status !== undefined && DEFINITIVE_VALIDATION_FAILURE_STATUSES . has ( status ) ;
166+ function validationFailurePriority ( error : unknown ) : number {
167+ if ( ! ( error instanceof BailianError ) || error . api ?. httpStatus === undefined ) return 0 ;
168+ const status = error . api . httpStatus ;
169+ if ( status === 429 || status >= 500 ) return 1 ;
170+ return 2 ;
171+ }
172+
173+ function selectValidationFailure ( results : readonly { error ?: unknown } [ ] ) : unknown {
174+ let selectedError : unknown ;
175+ let selectedPriority = Number . POSITIVE_INFINITY ;
176+ for ( const result of results ) {
177+ if ( result . error === undefined ) continue ;
178+ const priority = validationFailurePriority ( result . error ) ;
179+ if ( priority < selectedPriority ) {
180+ selectedError = result . error ;
181+ selectedPriority = priority ;
182+ }
183+ }
184+ return selectedError ;
159185}
160186
161187/** Validate a key without consuming model quota and return the first matching endpoint. */
@@ -183,17 +209,10 @@ export async function validateApiKey(
183209 const success = results . find ( ( result ) => result . error === undefined ) ;
184210 if ( success ) return { baseUrl : success . candidate . baseUrl , kind : success . candidate . kind } ;
185211
186- const inconclusive = results . find ( ( result ) => ! isDefinitiveCandidateFailure ( result . error ) ) ;
187- if ( inconclusive ?. error !== undefined ) throw inconclusive . error ;
212+ const failure = selectValidationFailure ( results ) ;
213+ if ( failure !== undefined ) throw failure ;
188214
189- const explicitHint = options . explicitBaseUrl
190- ? "Check that the API key belongs to this Base URL."
191- : "For a workspace-specific or custom endpoint, retry with --base-url <url>." ;
192- throw new BailianError (
193- "API key validation failed: the key was not accepted by any supported model endpoint." ,
194- ExitCode . AUTH ,
195- explicitHint ,
196- ) ;
215+ throw new TypeError ( "API key validation completed without a success or failure result." ) ;
197216}
198217
199218/**
0 commit comments