mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-03 17:18:51 +01:00
📤 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:
parent
4fe223eedd
commit
7183223e59
9 changed files with 131 additions and 11 deletions
|
|
@ -1,7 +1,8 @@
|
|||
import React, { createContext, useContext, useMemo } from 'react';
|
||||
import { getEndpointField } from 'librechat-data-provider';
|
||||
import { getEndpointField, isAgentsEndpoint } from 'librechat-data-provider';
|
||||
import type { EModelEndpoint } from 'librechat-data-provider';
|
||||
import { useGetEndpointsQuery } from '~/data-provider';
|
||||
import { useGetEndpointsQuery, useGetAgentByIdQuery } from '~/data-provider';
|
||||
import { useAgentsMapContext } from './AgentsMapContext';
|
||||
import { useChatContext } from './ChatContext';
|
||||
|
||||
interface DragDropContextValue {
|
||||
|
|
@ -9,6 +10,7 @@ interface DragDropContextValue {
|
|||
agentId: string | null | undefined;
|
||||
endpoint: string | null | undefined;
|
||||
endpointType?: EModelEndpoint | undefined;
|
||||
useResponsesApi?: boolean;
|
||||
}
|
||||
|
||||
const DragDropContext = createContext<DragDropContextValue | undefined>(undefined);
|
||||
|
|
@ -16,6 +18,7 @@ const DragDropContext = createContext<DragDropContextValue | undefined>(undefine
|
|||
export function DragDropProvider({ children }: { children: React.ReactNode }) {
|
||||
const { conversation } = useChatContext();
|
||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||
const agentsMap = useAgentsMapContext();
|
||||
|
||||
const endpointType = useMemo(() => {
|
||||
return (
|
||||
|
|
@ -24,6 +27,34 @@ export function DragDropProvider({ children }: { children: React.ReactNode }) {
|
|||
);
|
||||
}, [conversation?.endpoint, endpointsConfig]);
|
||||
|
||||
const needsAgentFetch = useMemo(() => {
|
||||
const isAgents = isAgentsEndpoint(conversation?.endpoint);
|
||||
if (!isAgents || !conversation?.agent_id) {
|
||||
return false;
|
||||
}
|
||||
const agent = agentsMap?.[conversation.agent_id];
|
||||
return !agent?.model_parameters;
|
||||
}, [conversation?.endpoint, conversation?.agent_id, agentsMap]);
|
||||
|
||||
const { data: agentData } = useGetAgentByIdQuery(conversation?.agent_id, {
|
||||
enabled: needsAgentFetch,
|
||||
});
|
||||
|
||||
const useResponsesApi = useMemo(() => {
|
||||
const isAgents = isAgentsEndpoint(conversation?.endpoint);
|
||||
if (!isAgents || !conversation?.agent_id || conversation?.useResponsesApi) {
|
||||
return conversation?.useResponsesApi;
|
||||
}
|
||||
const agent = agentData || agentsMap?.[conversation.agent_id];
|
||||
return agent?.model_parameters?.useResponsesApi;
|
||||
}, [
|
||||
conversation?.endpoint,
|
||||
conversation?.agent_id,
|
||||
conversation?.useResponsesApi,
|
||||
agentData,
|
||||
agentsMap,
|
||||
]);
|
||||
|
||||
/** Context value only created when conversation fields change */
|
||||
const contextValue = useMemo<DragDropContextValue>(
|
||||
() => ({
|
||||
|
|
@ -31,8 +62,15 @@ export function DragDropProvider({ children }: { children: React.ReactNode }) {
|
|||
agentId: conversation?.agent_id,
|
||||
endpoint: conversation?.endpoint,
|
||||
endpointType: endpointType,
|
||||
useResponsesApi: useResponsesApi,
|
||||
}),
|
||||
[conversation?.conversationId, conversation?.agent_id, conversation?.endpoint, endpointType],
|
||||
[
|
||||
conversation?.conversationId,
|
||||
conversation?.agent_id,
|
||||
conversation?.endpoint,
|
||||
useResponsesApi,
|
||||
endpointType,
|
||||
],
|
||||
);
|
||||
|
||||
return <DragDropContext.Provider value={contextValue}>{children}</DragDropContext.Provider>;
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -970,8 +970,8 @@ export class MCPOAuthHandler {
|
|||
});
|
||||
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
...oauthHeaders,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ export const documentSupportedProviders = new Set<string>([
|
|||
EModelEndpoint.anthropic,
|
||||
EModelEndpoint.openAI,
|
||||
EModelEndpoint.custom,
|
||||
EModelEndpoint.azureOpenAI,
|
||||
// handled in AttachFileMenu and DragDropModal since azureOpenAI only supports documents with Use Responses API set to true
|
||||
// EModelEndpoint.azureOpenAI,
|
||||
EModelEndpoint.google,
|
||||
Providers.VERTEXAI,
|
||||
Providers.MISTRALAI,
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ export type AgentModelParameters = {
|
|||
top_p: AgentParameterValue;
|
||||
frequency_penalty: AgentParameterValue;
|
||||
presence_penalty: AgentParameterValue;
|
||||
useResponsesApi?: boolean;
|
||||
};
|
||||
|
||||
export interface AgentBaseResource {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue