LibreChat/client/src/hooks/Endpoint/useEndpoints.ts
Danny Avila d57e7aec73
🥷 fix: Correct Agents Handling for Marketplace Users (#9065)
* refactor: Introduce ModelSelectorChatContext and integrate with ModelSelector

* fix: agents handling in ModelSelector to show expected agents if user has marketplace access
2025-08-14 19:13:48 -04:00

188 lines
5.9 KiB
TypeScript

import React, { useMemo, useCallback } from 'react';
import { useGetModelsQuery } from 'librechat-data-provider/react-query';
import {
Permissions,
alternateName,
EModelEndpoint,
PermissionTypes,
} from 'librechat-data-provider';
import type {
TEndpointsConfig,
TAssistantsMap,
TStartupConfig,
Assistant,
Agent,
} from 'librechat-data-provider';
import type { Endpoint } from '~/common';
import { mapEndpoints, getIconKey, getEndpointField } from '~/utils';
import { useGetEndpointsQuery } from '~/data-provider';
import { useHasAccess } from '~/hooks';
import { icons } from './Icons';
export const useEndpoints = ({
agents,
assistantsMap,
endpointsConfig,
startupConfig,
}: {
agents?: Agent[] | null;
assistantsMap?: TAssistantsMap;
endpointsConfig: TEndpointsConfig;
startupConfig: TStartupConfig | undefined;
}) => {
const modelsQuery = useGetModelsQuery();
const { data: endpoints = [] } = useGetEndpointsQuery({ select: mapEndpoints });
const interfaceConfig = startupConfig?.interface ?? {};
const includedEndpoints = useMemo(
() => new Set(startupConfig?.modelSpecs?.addedEndpoints ?? []),
[startupConfig?.modelSpecs?.addedEndpoints],
);
const hasAgentAccess = useHasAccess({
permissionType: PermissionTypes.AGENTS,
permission: Permissions.USE,
});
const assistants: Assistant[] = useMemo(
() => Object.values(assistantsMap?.[EModelEndpoint.assistants] ?? {}),
[assistantsMap],
);
const azureAssistants: Assistant[] = useMemo(
() => Object.values(assistantsMap?.[EModelEndpoint.azureAssistants] ?? {}),
[assistantsMap],
);
const filteredEndpoints = useMemo(() => {
if (!interfaceConfig.modelSelect) {
return [];
}
const result: EModelEndpoint[] = [];
for (let i = 0; i < endpoints.length; i++) {
if (endpoints[i] === EModelEndpoint.agents && !hasAgentAccess) {
continue;
}
if (includedEndpoints.size > 0 && !includedEndpoints.has(endpoints[i])) {
continue;
}
result.push(endpoints[i]);
}
return result;
}, [endpoints, hasAgentAccess, includedEndpoints, interfaceConfig.modelSelect]);
const endpointRequiresUserKey = useCallback(
(ep: string) => {
return !!getEndpointField(endpointsConfig, ep, 'userProvide');
},
[endpointsConfig],
);
const mappedEndpoints: Endpoint[] = useMemo(() => {
return filteredEndpoints.map((ep) => {
const endpointType = getEndpointField(endpointsConfig, ep, 'type');
const iconKey = getIconKey({ endpoint: ep, endpointsConfig, endpointType });
const Icon = icons[iconKey];
const endpointIconURL = getEndpointField(endpointsConfig, ep, 'iconURL');
const hasModels =
(ep === EModelEndpoint.agents && (agents?.length ?? 0) > 0) ||
(ep === EModelEndpoint.assistants && assistants?.length > 0) ||
(ep !== EModelEndpoint.assistants &&
ep !== EModelEndpoint.agents &&
(modelsQuery.data?.[ep]?.length ?? 0) > 0);
// Base result object with formatted default icon
const result: Endpoint = {
value: ep,
label: alternateName[ep] || ep,
hasModels,
icon: Icon
? React.createElement(Icon, {
size: 20,
className: 'text-text-primary shrink-0 icon-md',
iconURL: endpointIconURL,
endpoint: ep,
})
: null,
};
// Handle agents case
if (ep === EModelEndpoint.agents && (agents?.length ?? 0) > 0) {
result.models = agents?.map((agent) => ({
name: agent.id,
isGlobal: agent.isPublic ?? false,
}));
result.agentNames = agents?.reduce((acc, agent) => {
acc[agent.id] = agent.name || '';
return acc;
}, {});
result.modelIcons = agents?.reduce((acc, agent) => {
acc[agent.id] = agent?.avatar?.filepath;
return acc;
}, {});
}
// Handle assistants case
else if (ep === EModelEndpoint.assistants && assistants.length > 0) {
result.models = assistants.map((assistant: { id: string }) => ({
name: assistant.id,
isGlobal: false,
}));
result.assistantNames = assistants.reduce(
(acc: Record<string, string>, assistant: Assistant) => {
acc[assistant.id] = assistant.name || '';
return acc;
},
{},
);
result.modelIcons = assistants.reduce(
(acc: Record<string, string | undefined>, assistant: Assistant) => {
acc[assistant.id] = assistant.metadata?.avatar;
return acc;
},
{},
);
} else if (ep === EModelEndpoint.azureAssistants && azureAssistants.length > 0) {
result.models = azureAssistants.map((assistant: { id: string }) => ({
name: assistant.id,
isGlobal: false,
}));
result.assistantNames = azureAssistants.reduce(
(acc: Record<string, string>, assistant: Assistant) => {
acc[assistant.id] = assistant.name || '';
return acc;
},
{},
);
result.modelIcons = azureAssistants.reduce(
(acc: Record<string, string | undefined>, assistant: Assistant) => {
acc[assistant.id] = assistant.metadata?.avatar;
return acc;
},
{},
);
}
// For other endpoints with models from the modelsQuery
else if (
ep !== EModelEndpoint.agents &&
ep !== EModelEndpoint.assistants &&
(modelsQuery.data?.[ep]?.length ?? 0) > 0
) {
result.models = modelsQuery.data?.[ep]?.map((model) => ({
name: model,
isGlobal: false,
}));
}
return result;
});
}, [filteredEndpoints, endpointsConfig, modelsQuery.data, agents, assistants, azureAssistants]);
return {
mappedEndpoints,
endpointRequiresUserKey,
};
};
export default useEndpoints;