mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
fix(providers): forward stored apiKey and authIndex to discovery and tests
The form's apiKey field is intentionally empty in edit mode (the
placeholder reads "leave blank to keep unchanged"), so model discovery
and the Claude connectivity test were sending requests with no
credentials and getting 401 for Codex / Claude / Gemini providers.
- BaseProviderForm derives a fallbackAuthIndex from resource.raw.authIndex
alongside the existing fallbackApiKey and passes both into the
discovery and connectivity hooks
- modelsApi.fetch{V1,Claude,Gemini,}ModelsViaApiCall accept an optional
authIndex and forward it on apiCallApi.request so the backend can
inject the OAuth/auth-file token for providers that store the key
externally
- useModelDiscovery threads the authIndex through to every brand
- useConnectivityTest threads authIndex through the Claude path and no
longer rejects the request as "API key required" when an authIndex is
available
This commit is contained in:
@@ -226,6 +226,13 @@ export function BaseProviderForm({
|
||||
return (resource.raw as { apiKey?: string } | undefined)?.apiKey ?? '';
|
||||
}, [brand, mode, resource]);
|
||||
|
||||
const fallbackAuthIndex = useMemo(() => {
|
||||
if (mode !== 'edit' || !resource) return '';
|
||||
return (
|
||||
(resource.raw as { authIndex?: string } | undefined)?.authIndex ?? ''
|
||||
);
|
||||
}, [mode, resource]);
|
||||
|
||||
const connectivityMessages = useMemo<ConnectivityErrorMessages>(
|
||||
() => ({
|
||||
baseUrlRequired: t('providersPage.connectivity.baseUrlRequired'),
|
||||
@@ -249,6 +256,7 @@ export function BaseProviderForm({
|
||||
apiKeyEntries: form.apiKeyEntries,
|
||||
apiKey: form.apiKey,
|
||||
fallbackApiKey,
|
||||
authIndex: fallbackAuthIndex,
|
||||
},
|
||||
connectivityMessages
|
||||
);
|
||||
@@ -260,6 +268,7 @@ export function BaseProviderForm({
|
||||
apiKeyEntries: form.apiKeyEntries,
|
||||
apiKey: form.apiKey,
|
||||
fallbackApiKey,
|
||||
authIndex: fallbackAuthIndex,
|
||||
});
|
||||
const [discoveryOpen, setDiscoveryOpen] = useState(false);
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ export interface UseConnectivityTestArgs {
|
||||
apiKeyEntries?: ApiKeyEntryInput[];
|
||||
apiKey?: string;
|
||||
fallbackApiKey?: string;
|
||||
authIndex?: string;
|
||||
}
|
||||
|
||||
export interface ConnectivityErrorMessages {
|
||||
@@ -110,6 +111,7 @@ export function useConnectivityTest(
|
||||
apiKeyEntries,
|
||||
apiKey,
|
||||
fallbackApiKey,
|
||||
authIndex,
|
||||
} = args;
|
||||
|
||||
const entriesCount = apiKeyEntries?.length ?? 0;
|
||||
@@ -302,8 +304,9 @@ export function useConnectivityTest(
|
||||
const headerKey = resolveBearerToken(customHeaders);
|
||||
const hasApiKeyHeader = hasHeader(customHeaders, 'x-api-key');
|
||||
const resolvedKey = explicitKey || persistedKey || headerKey;
|
||||
const resolvedAuthIndex = (authIndex ?? '').trim() || undefined;
|
||||
|
||||
if (!resolvedKey && !hasApiKeyHeader) {
|
||||
if (!resolvedKey && !hasApiKeyHeader && !resolvedAuthIndex) {
|
||||
setClaudeStatus({ state: 'error', message: messages.apiKeyRequired });
|
||||
return;
|
||||
}
|
||||
@@ -324,6 +327,7 @@ export function useConnectivityTest(
|
||||
try {
|
||||
const result = await apiCallApi.request(
|
||||
{
|
||||
authIndex: resolvedAuthIndex,
|
||||
method: 'POST',
|
||||
url: endpoint,
|
||||
header: headerObj,
|
||||
@@ -358,6 +362,7 @@ export function useConnectivityTest(
|
||||
}
|
||||
}, [
|
||||
apiKey,
|
||||
authIndex,
|
||||
baseUrl,
|
||||
brand,
|
||||
fallbackApiKey,
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface UseModelDiscoveryArgs {
|
||||
apiKeyEntries?: ApiKeyEntryInput[];
|
||||
apiKey?: string;
|
||||
fallbackApiKey?: string;
|
||||
authIndex?: string;
|
||||
}
|
||||
|
||||
export interface UseModelDiscoveryResult {
|
||||
@@ -59,8 +60,15 @@ export interface UseModelDiscoveryResult {
|
||||
export function useModelDiscovery(
|
||||
args: UseModelDiscoveryArgs
|
||||
): UseModelDiscoveryResult {
|
||||
const { brand, baseUrl, formHeaders, apiKeyEntries, apiKey, fallbackApiKey } =
|
||||
args;
|
||||
const {
|
||||
brand,
|
||||
baseUrl,
|
||||
formHeaders,
|
||||
apiKeyEntries,
|
||||
apiKey,
|
||||
fallbackApiKey,
|
||||
authIndex,
|
||||
} = args;
|
||||
|
||||
const available = isModelDiscoveryBrand(brand);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -74,27 +82,31 @@ export function useModelDiscovery(
|
||||
setError(null);
|
||||
try {
|
||||
const baseHeaders = buildHeaderObject(formHeaders);
|
||||
const resolvedAuthIndex = (authIndex ?? '').trim() || undefined;
|
||||
let next: ModelInfo[] = [];
|
||||
if (brand === 'gemini') {
|
||||
const key = (apiKey ?? '').trim() || (fallbackApiKey ?? '').trim();
|
||||
next = await modelsApi.fetchGeminiModelsViaApiCall(
|
||||
baseUrl,
|
||||
key,
|
||||
baseHeaders
|
||||
baseHeaders,
|
||||
resolvedAuthIndex
|
||||
);
|
||||
} else if (brand === 'codex') {
|
||||
const key = (apiKey ?? '').trim() || (fallbackApiKey ?? '').trim();
|
||||
next = await modelsApi.fetchV1ModelsViaApiCall(
|
||||
baseUrl,
|
||||
key,
|
||||
baseHeaders
|
||||
baseHeaders,
|
||||
resolvedAuthIndex
|
||||
);
|
||||
} else if (brand === 'claude') {
|
||||
const key = (apiKey ?? '').trim() || (fallbackApiKey ?? '').trim();
|
||||
next = await modelsApi.fetchClaudeModelsViaApiCall(
|
||||
baseUrl,
|
||||
key,
|
||||
baseHeaders
|
||||
baseHeaders,
|
||||
resolvedAuthIndex
|
||||
);
|
||||
} else if (brand === 'openaiCompatibility') {
|
||||
const firstEntry = (apiKeyEntries ?? []).find((e) =>
|
||||
@@ -129,7 +141,16 @@ export function useModelDiscovery(
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [available, apiKey, apiKeyEntries, baseUrl, brand, fallbackApiKey, formHeaders]);
|
||||
}, [
|
||||
available,
|
||||
apiKey,
|
||||
apiKeyEntries,
|
||||
authIndex,
|
||||
baseUrl,
|
||||
brand,
|
||||
fallbackApiKey,
|
||||
formHeaders,
|
||||
]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setModels([]);
|
||||
|
||||
@@ -108,7 +108,8 @@ export const modelsApi = {
|
||||
async fetchV1ModelsViaApiCall(
|
||||
baseUrl: string,
|
||||
apiKey?: string,
|
||||
headers: Record<string, string> = {}
|
||||
headers: Record<string, string> = {},
|
||||
authIndex?: string
|
||||
) {
|
||||
const endpoint = buildV1ModelsEndpoint(baseUrl);
|
||||
if (!endpoint) {
|
||||
@@ -121,6 +122,7 @@ export const modelsApi = {
|
||||
}
|
||||
|
||||
const result = await apiCallApi.request({
|
||||
authIndex: authIndex?.trim() || undefined,
|
||||
method: 'GET',
|
||||
url: endpoint,
|
||||
header: Object.keys(resolvedHeaders).length ? resolvedHeaders : undefined
|
||||
@@ -140,7 +142,8 @@ export const modelsApi = {
|
||||
async fetchModelsViaApiCall(
|
||||
baseUrl: string,
|
||||
apiKey?: string,
|
||||
headers: Record<string, string> = {}
|
||||
headers: Record<string, string> = {},
|
||||
authIndex?: string
|
||||
) {
|
||||
const endpoint = buildModelsEndpoint(baseUrl);
|
||||
if (!endpoint) {
|
||||
@@ -153,6 +156,7 @@ export const modelsApi = {
|
||||
}
|
||||
|
||||
const result = await apiCallApi.request({
|
||||
authIndex: authIndex?.trim() || undefined,
|
||||
method: 'GET',
|
||||
url: endpoint,
|
||||
header: Object.keys(resolvedHeaders).length ? resolvedHeaders : undefined
|
||||
@@ -185,7 +189,8 @@ export const modelsApi = {
|
||||
async fetchClaudeModelsViaApiCall(
|
||||
baseUrl: string,
|
||||
apiKey?: string,
|
||||
headers: Record<string, string> = {}
|
||||
headers: Record<string, string> = {},
|
||||
authIndex?: string
|
||||
) {
|
||||
const endpoint = buildClaudeModelsEndpoint(baseUrl);
|
||||
if (!endpoint) {
|
||||
@@ -205,12 +210,14 @@ export const modelsApi = {
|
||||
resolvedHeaders['anthropic-version'] = DEFAULT_ANTHROPIC_VERSION;
|
||||
}
|
||||
|
||||
const trimmedAuthIndex = authIndex?.trim() || undefined;
|
||||
const signature = buildRequestSignature(endpoint, resolvedHeaders);
|
||||
const existing = CLAUDE_MODELS_IN_FLIGHT.get(signature);
|
||||
if (existing) return existing;
|
||||
|
||||
const request = (async () => {
|
||||
const result = await apiCallApi.request({
|
||||
authIndex: trimmedAuthIndex,
|
||||
method: 'GET',
|
||||
url: endpoint,
|
||||
header: Object.keys(resolvedHeaders).length ? resolvedHeaders : undefined
|
||||
@@ -239,7 +246,8 @@ export const modelsApi = {
|
||||
async fetchGeminiModelsViaApiCall(
|
||||
baseUrl: string,
|
||||
apiKey?: string,
|
||||
headers: Record<string, string> = {}
|
||||
headers: Record<string, string> = {},
|
||||
authIndex?: string
|
||||
) {
|
||||
const endpoint = buildGeminiModelsEndpoint(baseUrl);
|
||||
if (!endpoint) {
|
||||
@@ -252,6 +260,7 @@ export const modelsApi = {
|
||||
resolvedHeaders['x-goog-api-key'] = resolvedApiKey;
|
||||
}
|
||||
|
||||
const trimmedAuthIndex = authIndex?.trim() || undefined;
|
||||
const signature = buildRequestSignature(endpoint, resolvedHeaders);
|
||||
const existing = GEMINI_MODELS_IN_FLIGHT.get(signature);
|
||||
if (existing) return existing;
|
||||
@@ -268,6 +277,7 @@ export const modelsApi = {
|
||||
}
|
||||
|
||||
const result = await apiCallApi.request({
|
||||
authIndex: trimmedAuthIndex,
|
||||
method: 'GET',
|
||||
url: url.toString(),
|
||||
header: Object.keys(resolvedHeaders).length ? resolvedHeaders : undefined
|
||||
|
||||
Reference in New Issue
Block a user