🩹 fix: Address codex/copilot review on API Keys tab

- Exclude `userProvideURL` from the eligibility predicate. A user-provided
  baseURL alone does not imply a user key, but the tab would still prompt
  for one via `SetKeyDialog`'s required-fields validation.
- Extract `isUserProvidedEndpointConfig` into a shared util so the
  predicate cannot drift between `Settings.tsx` (tab visibility) and
  `APIKeys.tsx` (row listing).
- Drop the `endpoint as EModelEndpoint` cast in `APIKeyRow`. `SetKeyDialog`
  already accepts `EModelEndpoint | string`; the cast was unsafe for
  custom endpoint names.
- Add a regression test pinning the baseURL-only exclusion.
This commit is contained in:
Danny Avila 2026-05-25 13:42:25 -04:00
parent 13042266d6
commit 511b6307f7
5 changed files with 36 additions and 25 deletions

View file

@ -101,6 +101,16 @@ describe('Settings', () => {
expect(screen.getByText('com_nav_setting_api_keys')).toBeInTheDocument();
});
it('hides the API Keys tab for endpoints with a user-provided baseURL but a fixed API key', () => {
mockUseGetEndpointsQuery.mockReturnValue({
data: { LiteLLM: { userProvide: false, userProvideURL: true, order: 0 } },
});
renderSettings();
expect(screen.queryByText('com_nav_setting_api_keys')).not.toBeInTheDocument();
});
it('resets the active tab when loaded config disables About', async () => {
const user = userEvent.setup();
const { rerender } = renderSettings();

View file

@ -24,6 +24,7 @@ import {
APIKeys,
About,
} from './SettingsTabs';
import { isUserProvidedEndpointConfig } from './SettingsTabs/APIKeys/utils';
import usePersonalizationAccess from '~/hooks/usePersonalizationAccess';
import { useLocalize, TranslationKeys } from '~/hooks';
import { useGetEndpointsQuery, useGetStartupConfig } from '~/data-provider';
@ -45,18 +46,7 @@ export default function Settings({ open, onOpenChange }: TDialogProps) {
}
const values = Object.values(endpointsConfig);
for (let i = 0; i < values.length; i++) {
const config = values[i];
if (!config) {
continue;
}
if (
config.userProvide ||
config.userProvideURL ||
config.userProvideAccessKeyId ||
config.userProvideSecretAccessKey ||
config.userProvideSessionToken ||
config.userProvideBearerToken
) {
if (isUserProvidedEndpointConfig(values[i])) {
return true;
}
}

View file

@ -1,7 +1,7 @@
import React, { useMemo, useState } from 'react';
import { Button } from '@librechat/client';
import { alternateName, getEndpointField } from 'librechat-data-provider';
import type { EModelEndpoint, TEndpointsConfig } from 'librechat-data-provider';
import type { TEndpointsConfig } from 'librechat-data-provider';
import { SetKeyDialog } from '~/components/Input/SetKeyDialog';
import { icons } from '~/hooks/Endpoint/Icons';
import { useUserKey, useLocalize } from '~/hooks';
@ -62,7 +62,7 @@ const APIKeyRow = ({ endpoint, endpointsConfig }: APIKeyRowProps) => {
<SetKeyDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
endpoint={endpoint as EModelEndpoint}
endpoint={endpoint}
endpointType={endpointType}
userProvideURL={getEndpointField(endpointsConfig, endpoint, 'userProvideURL')}
userProvideAccessKeyId={getEndpointField(

View file

@ -1,6 +1,7 @@
import React, { useMemo } from 'react';
import { useGetEndpointsQuery } from '~/data-provider';
import { useLocalize } from '~/hooks';
import { isUserProvidedEndpointConfig } from './utils';
import APIKeyRow from './APIKeyRow';
function APIKeys() {
@ -15,17 +16,7 @@ function APIKeys() {
const result: string[] = [];
for (let i = 0; i < entries.length; i++) {
const [endpoint, config] = entries[i];
if (!config) {
continue;
}
if (
config.userProvide ||
config.userProvideURL ||
config.userProvideAccessKeyId ||
config.userProvideSecretAccessKey ||
config.userProvideSessionToken ||
config.userProvideBearerToken
) {
if (isUserProvidedEndpointConfig(config)) {
result.push(endpoint);
}
}

View file

@ -0,0 +1,20 @@
import type { TConfig } from 'librechat-data-provider';
/**
* Whether an endpoint config requires a user-provided credential
* (API key or any Bedrock credential field) i.e. should appear in
* the API Keys settings tab. `userProvideURL` is intentionally excluded
* since a user-provided base URL alone does not imply a user key.
*/
export const isUserProvidedEndpointConfig = (config: TConfig | null | undefined): boolean => {
if (!config) {
return false;
}
return (
!!config.userProvide ||
!!config.userProvideAccessKeyId ||
!!config.userProvideSecretAccessKey ||
!!config.userProvideSessionToken ||
!!config.userProvideBearerToken
);
};