📤 fix: Show Proper Upload Options for Azure and Agent Endpoints (#11081)

* fix: only show upload to provider for azureOpenAi when use responses api is true

* fix: model_parameters not available on first load so Upload Image incorrectly shown - now we query if not populated

* test: update tests for new azureOpenAI Responses API logic

* chore: correct order of headers in OAuth request to ensure proper content type is set

* fix: add useResponsesApi prop to AttachFileMenu and DragDropModal components

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Dustin Healy 2025-12-25 12:54:15 -08:00 committed by GitHub
parent 4fe223eedd
commit 7183223e59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 131 additions and 11 deletions

View file

@ -10,7 +10,8 @@ import {
getEndpointFileConfig,
} from 'librechat-data-provider';
import type { TConversation } from 'librechat-data-provider';
import { useGetFileConfig, useGetEndpointsQuery } from '~/data-provider';
import { useGetFileConfig, useGetEndpointsQuery, useGetAgentByIdQuery } from '~/data-provider';
import { useAgentsMapContext } from '~/Providers';
import AttachFileMenu from './AttachFileMenu';
import AttachFile from './AttachFile';
@ -26,6 +27,28 @@ function AttachFileChat({
const isAgents = useMemo(() => isAgentsEndpoint(endpoint), [endpoint]);
const isAssistants = useMemo(() => isAssistantsEndpoint(endpoint), [endpoint]);
const agentsMap = useAgentsMapContext();
const needsAgentFetch = useMemo(() => {
if (!isAgents || !conversation?.agent_id) {
return false;
}
const agent = agentsMap?.[conversation.agent_id];
return !agent?.model_parameters;
}, [isAgents, conversation?.agent_id, agentsMap]);
const { data: agentData } = useGetAgentByIdQuery(conversation?.agent_id, {
enabled: needsAgentFetch,
});
const useResponsesApi = useMemo(() => {
if (!isAgents || !conversation?.agent_id || conversation?.useResponsesApi) {
return conversation?.useResponsesApi;
}
const agent = agentData || agentsMap?.[conversation.agent_id];
return agent?.model_parameters?.useResponsesApi;
}, [isAgents, conversation?.agent_id, conversation?.useResponsesApi, agentData, agentsMap]);
const { data: fileConfig = null } = useGetFileConfig({
select: (data) => mergeFileConfig(data),
});
@ -68,6 +91,7 @@ function AttachFileChat({
conversationId={conversationId}
agentId={conversation?.agent_id}
endpointFileConfig={endpointFileConfig}
useResponsesApi={useResponsesApi}
/>
);
}

View file

@ -46,6 +46,7 @@ interface AttachFileMenuProps {
conversationId: string;
endpointType?: EModelEndpoint;
endpointFileConfig?: EndpointFileConfig;
useResponsesApi?: boolean;
}
const AttachFileMenu = ({
@ -55,6 +56,7 @@ const AttachFileMenu = ({
endpointType,
conversationId,
endpointFileConfig,
useResponsesApi,
}: AttachFileMenuProps) => {
const localize = useLocalize();
const isUploadDisabled = disabled ?? false;
@ -117,9 +119,13 @@ const AttachFileMenu = ({
currentProvider = Providers.OPENROUTER;
}
const isAzureWithResponsesApi =
currentProvider === EModelEndpoint.azureOpenAI && useResponsesApi;
if (
isDocumentSupportedProvider(endpointType) ||
isDocumentSupportedProvider(currentProvider)
isDocumentSupportedProvider(currentProvider) ||
isAzureWithResponsesApi
) {
items.push({
label: localize('com_ui_upload_provider'),
@ -211,6 +217,7 @@ const AttachFileMenu = ({
provider,
endpointType,
capabilities,
useResponsesApi,
setToolResource,
setEphemeralAgent,
sharePointEnabled,

View file

@ -47,7 +47,7 @@ const DragDropModal = ({ onOptionSelect, setShowModal, files, isVisible }: DragD
* Use definition for agents endpoint for ephemeral agents
* */
const capabilities = useAgentCapabilities(agentsConfig?.capabilities ?? defaultAgentCapabilities);
const { conversationId, agentId, endpoint, endpointType } = useDragDropContext();
const { conversationId, agentId, endpoint, endpointType, useResponsesApi } = useDragDropContext();
const ephemeralAgent = useRecoilValue(ephemeralAgentByConvoId(conversationId ?? ''));
const { fileSearchAllowedByAgent, codeAllowedByAgent, provider } = useAgentToolPermissions(
agentId,
@ -66,8 +66,15 @@ const DragDropModal = ({ onOptionSelect, setShowModal, files, isVisible }: DragD
/** Helper to get inferred MIME type for a file */
const getFileType = (file: File) => inferMimeType(file.name, file.type);
const isAzureWithResponsesApi =
currentProvider === EModelEndpoint.azureOpenAI && useResponsesApi;
// Check if provider supports document upload
if (isDocumentSupportedProvider(endpointType) || isDocumentSupportedProvider(currentProvider)) {
if (
isDocumentSupportedProvider(endpointType) ||
isDocumentSupportedProvider(currentProvider) ||
isAzureWithResponsesApi
) {
const supportsImageDocVideoAudio =
currentProvider === EModelEndpoint.google || currentProvider === Providers.OPENROUTER;
const validFileTypes = supportsImageDocVideoAudio
@ -130,6 +137,7 @@ const DragDropModal = ({ onOptionSelect, setShowModal, files, isVisible }: DragD
endpoint,
endpointType,
capabilities,
useResponsesApi,
codeAllowedByAgent,
fileSearchAllowedByAgent,
]);

View file

@ -278,7 +278,6 @@ describe('AttachFileMenu', () => {
{ name: 'OpenAI', endpoint: EModelEndpoint.openAI },
{ name: 'Anthropic', endpoint: EModelEndpoint.anthropic },
{ name: 'Google', endpoint: EModelEndpoint.google },
{ name: 'Azure OpenAI', endpoint: EModelEndpoint.azureOpenAI },
{ name: 'Custom', endpoint: EModelEndpoint.custom },
];
@ -301,6 +300,45 @@ describe('AttachFileMenu', () => {
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
});
});
it('should show Upload to Provider for Azure OpenAI with useResponsesApi', () => {
mockUseAgentToolPermissions.mockReturnValue({
fileSearchAllowedByAgent: false,
codeAllowedByAgent: false,
provider: EModelEndpoint.azureOpenAI,
});
renderAttachFileMenu({
endpoint: EModelEndpoint.azureOpenAI,
endpointType: EModelEndpoint.azureOpenAI,
useResponsesApi: true,
});
const button = screen.getByRole('button', { name: /attach file options/i });
fireEvent.click(button);
expect(screen.getByText('Upload to Provider')).toBeInTheDocument();
});
it('should NOT show Upload to Provider for Azure OpenAI without useResponsesApi', () => {
mockUseAgentToolPermissions.mockReturnValue({
fileSearchAllowedByAgent: false,
codeAllowedByAgent: false,
provider: EModelEndpoint.azureOpenAI,
});
renderAttachFileMenu({
endpoint: EModelEndpoint.azureOpenAI,
endpointType: EModelEndpoint.azureOpenAI,
useResponsesApi: false,
});
const button = screen.getByRole('button', { name: /attach file options/i });
fireEvent.click(button);
expect(screen.queryByText('Upload to Provider')).not.toBeInTheDocument();
expect(screen.getByText('Upload Image')).toBeInTheDocument();
});
});
describe('Agent Capabilities', () => {

View file

@ -63,7 +63,6 @@ describe('DragDropModal - Provider Detection', () => {
{ name: 'OpenAI', value: EModelEndpoint.openAI },
{ name: 'Anthropic', value: EModelEndpoint.anthropic },
{ name: 'Google', value: EModelEndpoint.google },
{ name: 'Azure OpenAI', value: EModelEndpoint.azureOpenAI },
{ name: 'Custom', value: EModelEndpoint.custom },
];
@ -72,6 +71,10 @@ describe('DragDropModal - Provider Detection', () => {
expect(isDocumentSupportedProvider(value)).toBe(true);
});
});
it('should NOT recognize Azure OpenAI as supported (requires useResponsesApi)', () => {
expect(isDocumentSupportedProvider(EModelEndpoint.azureOpenAI)).toBe(false);
});
});
describe('real-world scenarios', () => {