🚀 feat: Use Model Specs + Specific Endpoints, Limit Providers for Agents (#6650)

* 🔧 refactor: Remove modelSpecs prop from ModelSelector and related components

* fix: Update submission.conversationId references in SSE hooks and data types as was incorrectly typed

* feat: Allow showing specific endpoints alongside model specs via `addedEndpoints` field

* feat: allowed agents providers via `agents.allowedProviders` field

* fix: bump dicebear/sharp dependencies to resolve CVE-2024-12905 and improve avatar gen logic

* fix: rename variable for clarity in loadDefaultInterface function

* fix: add keepAddedConvos option to newConversation calls for modular chat support

* fix: include model information in endpoint selection for improved context

* fix: update data-provider version to 0.7.78 and increment config version to 1.2.4
This commit is contained in:
Danny Avila 2025-04-01 03:50:32 -04:00 committed by GitHub
parent cd7cdaa703
commit 90b8769ef3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 905 additions and 777 deletions

View file

@ -104,7 +104,7 @@
"passport-ldapauth": "^3.0.1", "passport-ldapauth": "^3.0.1",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"rate-limit-redis": "^4.2.0", "rate-limit-redis": "^4.2.0",
"sharp": "^0.32.6", "sharp": "^0.33.5",
"tiktoken": "^1.0.15", "tiktoken": "^1.0.15",
"traverse": "^0.6.7", "traverse": "^0.6.7",
"ua-parser-js": "^1.0.36", "ua-parser-js": "^1.0.36",

View file

@ -146,7 +146,7 @@ const AppService = async (app) => {
...defaultLocals, ...defaultLocals,
fileConfig: config?.fileConfig, fileConfig: config?.fileConfig,
secureImageLinks: config?.secureImageLinks, secureImageLinks: config?.secureImageLinks,
modelSpecs: processModelSpecs(endpoints, config.modelSpecs), modelSpecs: processModelSpecs(endpoints, config.modelSpecs, interfaceConfig),
...endpointLocals, ...endpointLocals,
}; };
}; };

View file

@ -33,10 +33,12 @@ async function getEndpointsConfig(req) {
}; };
} }
if (mergedConfig[EModelEndpoint.agents] && req.app.locals?.[EModelEndpoint.agents]) { if (mergedConfig[EModelEndpoint.agents] && req.app.locals?.[EModelEndpoint.agents]) {
const { disableBuilder, capabilities, ..._rest } = req.app.locals[EModelEndpoint.agents]; const { disableBuilder, capabilities, allowedProviders, ..._rest } =
req.app.locals[EModelEndpoint.agents];
mergedConfig[EModelEndpoint.agents] = { mergedConfig[EModelEndpoint.agents] = {
...mergedConfig[EModelEndpoint.agents], ...mergedConfig[EModelEndpoint.agents],
allowedProviders,
disableBuilder, disableBuilder,
capabilities, capabilities,
}; };

View file

@ -1,5 +1,6 @@
const { createContentAggregator, Providers } = require('@librechat/agents'); const { createContentAggregator, Providers } = require('@librechat/agents');
const { const {
ErrorTypes,
EModelEndpoint, EModelEndpoint,
getResponseSender, getResponseSender,
AgentCapabilities, AgentCapabilities,
@ -117,6 +118,7 @@ function optionalChainWithEmptyCheck(...values) {
* @param {ServerRequest} params.req * @param {ServerRequest} params.req
* @param {ServerResponse} params.res * @param {ServerResponse} params.res
* @param {Agent} params.agent * @param {Agent} params.agent
* @param {Set<string>} [params.allowedProviders]
* @param {object} [params.endpointOption] * @param {object} [params.endpointOption]
* @param {boolean} [params.isInitialAgent] * @param {boolean} [params.isInitialAgent]
* @returns {Promise<Agent>} * @returns {Promise<Agent>}
@ -126,8 +128,14 @@ const initializeAgentOptions = async ({
res, res,
agent, agent,
endpointOption, endpointOption,
allowedProviders,
isInitialAgent = false, isInitialAgent = false,
}) => { }) => {
if (allowedProviders.size > 0 && !allowedProviders.has(agent.provider)) {
throw new Error(
`{ "type": "${ErrorTypes.INVALID_AGENT_PROVIDER}", "info": "${agent.provider}" }`,
);
}
let currentFiles; let currentFiles;
/** @type {Array<MongoFile>} */ /** @type {Array<MongoFile>} */
const requestFiles = req.body.files ?? []; const requestFiles = req.body.files ?? [];
@ -263,6 +271,8 @@ const initializeClient = async ({ req, res, endpointOption }) => {
} }
const agentConfigs = new Map(); const agentConfigs = new Map();
/** @type {Set<string>} */
const allowedProviders = new Set(req?.app?.locals?.[EModelEndpoint.agents]?.allowedProviders);
// Handle primary agent // Handle primary agent
const primaryConfig = await initializeAgentOptions({ const primaryConfig = await initializeAgentOptions({
@ -270,6 +280,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
res, res,
agent: primaryAgent, agent: primaryAgent,
endpointOption, endpointOption,
allowedProviders,
isInitialAgent: true, isInitialAgent: true,
}); });
@ -285,6 +296,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
res, res,
agent, agent,
endpointOption, endpointOption,
allowedProviders,
}); });
agentConfigs.set(agentId, config); agentConfigs.set(agentId, config);
} }

View file

@ -18,12 +18,15 @@ async function loadDefaultInterface(config, configDefaults, roleName = SystemRol
const { interface: interfaceConfig } = config ?? {}; const { interface: interfaceConfig } = config ?? {};
const { interface: defaults } = configDefaults; const { interface: defaults } = configDefaults;
const hasModelSpecs = config?.modelSpecs?.list?.length > 0; const hasModelSpecs = config?.modelSpecs?.list?.length > 0;
const includesAddedEndpoints = config?.modelSpecs?.addedEndpoints?.length > 0;
/** @type {TCustomConfig['interface']} */ /** @type {TCustomConfig['interface']} */
const loadedInterface = removeNullishValues({ const loadedInterface = removeNullishValues({
endpointsMenu: endpointsMenu:
interfaceConfig?.endpointsMenu ?? (hasModelSpecs ? false : defaults.endpointsMenu), interfaceConfig?.endpointsMenu ?? (hasModelSpecs ? false : defaults.endpointsMenu),
modelSelect: interfaceConfig?.modelSelect ?? (hasModelSpecs ? false : defaults.modelSelect), modelSelect:
interfaceConfig?.modelSelect ??
(hasModelSpecs ? includesAddedEndpoints : defaults.modelSelect),
parameters: interfaceConfig?.parameters ?? (hasModelSpecs ? false : defaults.parameters), parameters: interfaceConfig?.parameters ?? (hasModelSpecs ? false : defaults.parameters),
presets: interfaceConfig?.presets ?? (hasModelSpecs ? false : defaults.presets), presets: interfaceConfig?.presets ?? (hasModelSpecs ? false : defaults.presets),
sidePanel: interfaceConfig?.sidePanel ?? defaults.sidePanel, sidePanel: interfaceConfig?.sidePanel ?? defaults.sidePanel,

View file

@ -6,9 +6,10 @@ const { logger } = require('~/config');
* Sets up Model Specs from the config (`librechat.yaml`) file. * Sets up Model Specs from the config (`librechat.yaml`) file.
* @param {TCustomConfig['endpoints']} [endpoints] - The loaded custom configuration for endpoints. * @param {TCustomConfig['endpoints']} [endpoints] - The loaded custom configuration for endpoints.
* @param {TCustomConfig['modelSpecs'] | undefined} [modelSpecs] - The loaded custom configuration for model specs. * @param {TCustomConfig['modelSpecs'] | undefined} [modelSpecs] - The loaded custom configuration for model specs.
* @param {TCustomConfig['interface'] | undefined} [interfaceConfig] - The loaded interface configuration.
* @returns {TCustomConfig['modelSpecs'] | undefined} The processed model specs, if any. * @returns {TCustomConfig['modelSpecs'] | undefined} The processed model specs, if any.
*/ */
function processModelSpecs(endpoints, _modelSpecs) { function processModelSpecs(endpoints, _modelSpecs, interfaceConfig) {
if (!_modelSpecs) { if (!_modelSpecs) {
return undefined; return undefined;
} }
@ -20,6 +21,19 @@ function processModelSpecs(endpoints, _modelSpecs) {
const customEndpoints = endpoints?.[EModelEndpoint.custom] ?? []; const customEndpoints = endpoints?.[EModelEndpoint.custom] ?? [];
if (interfaceConfig.modelSelect !== true && _modelSpecs.addedEndpoints.length > 0) {
logger.warn(
`To utilize \`addedEndpoints\`, which allows provider/model selections alongside model specs, set \`modelSelect: true\` in the interface configuration.
Example:
\`\`\`yaml
interface:
modelSelect: true
\`\`\`
`,
);
}
for (const spec of list) { for (const spec of list) {
if (EModelEndpoint[spec.preset.endpoint] && spec.preset.endpoint !== EModelEndpoint.custom) { if (EModelEndpoint[spec.preset.endpoint] && spec.preset.endpoint !== EModelEndpoint.custom) {
modelSpecs.push(spec); modelSpecs.push(spec);

View file

@ -31,8 +31,8 @@
"@ariakit/react": "^0.4.15", "@ariakit/react": "^0.4.15",
"@ariakit/react-core": "^0.4.15", "@ariakit/react-core": "^0.4.15",
"@codesandbox/sandpack-react": "^2.19.10", "@codesandbox/sandpack-react": "^2.19.10",
"@dicebear/collection": "^7.0.4", "@dicebear/collection": "^9.2.2",
"@dicebear/core": "^7.0.4", "@dicebear/core": "^9.2.2",
"@headlessui/react": "^2.1.2", "@headlessui/react": "^2.1.2",
"@radix-ui/react-accordion": "^1.1.2", "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.2", "@radix-ui/react-alert-dialog": "^1.0.2",

View file

@ -20,5 +20,4 @@ export interface SelectedValues {
export interface ModelSelectorProps { export interface ModelSelectorProps {
startupConfig: TStartupConfig | undefined; startupConfig: TStartupConfig | undefined;
modelSpecs: TModelSpec[];
} }

View file

@ -16,7 +16,6 @@ const defaultInterface = getConfigDefaults().interface;
export default function Header() { export default function Header() {
const { data: startupConfig } = useGetStartupConfig(); const { data: startupConfig } = useGetStartupConfig();
const { navVisible } = useOutletContext<ContextType>(); const { navVisible } = useOutletContext<ContextType>();
const modelSpecs = useMemo(() => startupConfig?.modelSpecs?.list ?? [], [startupConfig]);
const interfaceConfig = useMemo( const interfaceConfig = useMemo(
() => startupConfig?.interface ?? defaultInterface, () => startupConfig?.interface ?? defaultInterface,
[startupConfig], [startupConfig],
@ -39,7 +38,7 @@ export default function Header() {
<div className="hide-scrollbar flex w-full items-center justify-between gap-2 overflow-x-auto"> <div className="hide-scrollbar flex w-full items-center justify-between gap-2 overflow-x-auto">
<div className="mx-2 flex items-center gap-2"> <div className="mx-2 flex items-center gap-2">
{!navVisible && <HeaderNewChat />} {!navVisible && <HeaderNewChat />}
{<ModelSelector startupConfig={startupConfig} modelSpecs={modelSpecs} />} {<ModelSelector startupConfig={startupConfig} />}
{interfaceConfig.presets === true && interfaceConfig.modelSelect && <PresetsMenu />} {interfaceConfig.presets === true && interfaceConfig.modelSelect && <PresetsMenu />}
{hasAccessToBookmarks === true && <BookmarkMenu />} {hasAccessToBookmarks === true && <BookmarkMenu />}
{hasAccessToMultiConvo === true && <AddMultiConvo />} {hasAccessToMultiConvo === true && <AddMultiConvo />}

View file

@ -98,9 +98,9 @@ function ModelSelectorContent() {
); );
} }
export default function ModelSelector({ startupConfig, modelSpecs }: ModelSelectorProps) { export default function ModelSelector({ startupConfig }: ModelSelectorProps) {
return ( return (
<ModelSelectorProvider modelSpecs={modelSpecs} startupConfig={startupConfig}> <ModelSelectorProvider startupConfig={startupConfig}>
<ModelSelectorContent /> <ModelSelectorContent />
</ModelSelectorProvider> </ModelSelectorProvider>
); );

View file

@ -44,24 +44,20 @@ export function useModelSelectorContext() {
interface ModelSelectorProviderProps { interface ModelSelectorProviderProps {
children: React.ReactNode; children: React.ReactNode;
modelSpecs: t.TModelSpec[];
startupConfig: t.TStartupConfig | undefined; startupConfig: t.TStartupConfig | undefined;
} }
export function ModelSelectorProvider({ export function ModelSelectorProvider({ children, startupConfig }: ModelSelectorProviderProps) {
children,
modelSpecs,
startupConfig,
}: ModelSelectorProviderProps) {
const agentsMap = useAgentsMapContext(); const agentsMap = useAgentsMapContext();
const assistantsMap = useAssistantsMapContext(); const assistantsMap = useAssistantsMapContext();
const { data: endpointsConfig } = useGetEndpointsQuery(); const { data: endpointsConfig } = useGetEndpointsQuery();
const { conversation, newConversation } = useChatContext(); const { conversation, newConversation } = useChatContext();
const modelSpecs = useMemo(() => startupConfig?.modelSpecs?.list ?? [], [startupConfig]);
const { mappedEndpoints, endpointRequiresUserKey } = useEndpoints({ const { mappedEndpoints, endpointRequiresUserKey } = useEndpoints({
agentsMap, agentsMap,
assistantsMap, assistantsMap,
endpointsConfig,
startupConfig, startupConfig,
endpointsConfig,
}); });
const { onSelectEndpoint, onSelectSpec } = useSelectMention({ const { onSelectEndpoint, onSelectSpec } = useSelectMention({
// presets, // presets,
@ -146,6 +142,7 @@ export function ModelSelectorProvider({
if (isAgentsEndpoint(endpoint.value)) { if (isAgentsEndpoint(endpoint.value)) {
onSelectEndpoint?.(endpoint.value, { onSelectEndpoint?.(endpoint.value, {
agent_id: model, agent_id: model,
model: agentsMap?.[model]?.model ?? '',
}); });
} else if (isAssistantsEndpoint(endpoint.value)) { } else if (isAssistantsEndpoint(endpoint.value)) {
onSelectEndpoint?.(endpoint.value, { onSelectEndpoint?.(endpoint.value, {
@ -157,7 +154,7 @@ export function ModelSelectorProvider({
} }
setSelectedValues({ setSelectedValues({
endpoint: endpoint.value, endpoint: endpoint.value,
model: model, model,
modelSpec: '', modelSpec: '',
}); });
}; };

View file

@ -1,5 +1,5 @@
// file deepcode ignore HardcodedNonCryptoSecret: No hardcoded secrets // file deepcode ignore HardcodedNonCryptoSecret: No hardcoded secrets
import { ViolationTypes, ErrorTypes } from 'librechat-data-provider'; import { ViolationTypes, ErrorTypes, alternateName } from 'librechat-data-provider';
import type { TOpenAIMessage } from 'librechat-data-provider'; import type { TOpenAIMessage } from 'librechat-data-provider';
import type { LocalizeFunction } from '~/common'; import type { LocalizeFunction } from '~/common';
import { formatJSON, extractJson, isJson } from '~/utils/json'; import { formatJSON, extractJson, isJson } from '~/utils/json';
@ -53,6 +53,11 @@ const errorMessages = {
const { info } = json; const { info } = json;
return localize('com_error_input_length', { 0: info }); return localize('com_error_input_length', { 0: info });
}, },
[ErrorTypes.INVALID_AGENT_PROVIDER]: (json: TGenericError, localize: LocalizeFunction) => {
const { info } = json;
const provider = (alternateName[info] as string | undefined) ?? info;
return localize('com_error_invalid_agent_provider', { 0: provider });
},
[ErrorTypes.GOOGLE_ERROR]: (json: TGenericError) => { [ErrorTypes.GOOGLE_ERROR]: (json: TGenericError) => {
const { info } = json; const { info } = json;
return info; return info;

View file

@ -1,6 +1,6 @@
import { useState, memo } from 'react';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import * as Select from '@ariakit/react/select'; import * as Select from '@ariakit/react/select';
import { Fragment, useState, memo } from 'react';
import { FileText, LogOut } from 'lucide-react'; import { FileText, LogOut } from 'lucide-react';
import { LinkIcon, GearIcon, DropdownMenuSeparator } from '~/components'; import { LinkIcon, GearIcon, DropdownMenuSeparator } from '~/components';
import { useGetStartupConfig, useGetUserBalance } from '~/data-provider'; import { useGetStartupConfig, useGetUserBalance } from '~/data-provider';
@ -23,7 +23,7 @@ function AccountSettings() {
const [showFiles, setShowFiles] = useRecoilState(store.showFiles); const [showFiles, setShowFiles] = useRecoilState(store.showFiles);
const avatarSrc = useAvatar(user); const avatarSrc = useAvatar(user);
const name = user?.avatar ?? user?.username ?? ''; const avatarSeed = user?.avatar || user?.name || user?.username || '';
return ( return (
<Select.SelectProvider> <Select.SelectProvider>
@ -34,7 +34,7 @@ function AccountSettings() {
> >
<div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0"> <div className="-ml-0.9 -mt-0.8 h-8 w-8 flex-shrink-0">
<div className="relative flex"> <div className="relative flex">
{name.length === 0 ? ( {avatarSeed.length === 0 ? (
<div <div
style={{ style={{
backgroundColor: 'rgb(121, 137, 255)', backgroundColor: 'rgb(121, 137, 255)',
@ -51,7 +51,7 @@ function AccountSettings() {
<img <img
className="rounded-full" className="rounded-full"
src={(user?.avatar ?? '') || avatarSrc} src={(user?.avatar ?? '') || avatarSrc}
alt={`${name}'s avatar`} alt={`${user?.name || user?.username || user?.email || ''}'s avatar`}
/> />
)} )}
</div> </div>

View file

@ -56,18 +56,24 @@ export default function AgentPanel({
const { control, handleSubmit, reset } = methods; const { control, handleSubmit, reset } = methods;
const agent_id = useWatch({ control, name: 'id' }); const agent_id = useWatch({ control, name: 'id' });
const allowedProviders = useMemo(
() => new Set(agentsConfig?.allowedProviders),
[agentsConfig?.allowedProviders],
);
const providers = useMemo( const providers = useMemo(
() => () =>
Object.keys(endpointsConfig ?? {}) Object.keys(endpointsConfig ?? {})
.filter( .filter(
(key) => (key) =>
!isAssistantsEndpoint(key) && !isAssistantsEndpoint(key) &&
(allowedProviders.size > 0 ? allowedProviders.has(key) : true) &&
key !== EModelEndpoint.agents && key !== EModelEndpoint.agents &&
key !== EModelEndpoint.chatGPTBrowser && key !== EModelEndpoint.chatGPTBrowser &&
key !== EModelEndpoint.gptPlugins, key !== EModelEndpoint.gptPlugins,
) )
.map((provider) => createProviderOption(provider)), .map((provider) => createProviderOption(provider)),
[endpointsConfig], [endpointsConfig, allowedProviders],
); );
/* Mutations */ /* Mutations */

View file

@ -12,7 +12,7 @@ import { getEndpointField, cn } from '~/utils';
import { useLocalize } from '~/hooks'; import { useLocalize } from '~/hooks';
import { Panel } from '~/common'; import { Panel } from '~/common';
export default function Parameters({ export default function ModelPanel({
setActivePanel, setActivePanel,
providers, providers,
models: modelsData, models: modelsData,

View file

@ -37,6 +37,10 @@ export const useEndpoints = ({
const { data: endpoints = [] } = useGetEndpointsQuery({ select: mapEndpoints }); const { data: endpoints = [] } = useGetEndpointsQuery({ select: mapEndpoints });
const { instanceProjectId } = startupConfig ?? {}; const { instanceProjectId } = startupConfig ?? {};
const interfaceConfig = startupConfig?.interface ?? {}; const interfaceConfig = startupConfig?.interface ?? {};
const includedEndpoints = useMemo(
() => new Set(startupConfig?.modelSpecs?.addedEndpoints ?? []),
[startupConfig?.modelSpecs?.addedEndpoints],
);
const { endpoint } = conversation ?? {}; const { endpoint } = conversation ?? {};
@ -73,11 +77,14 @@ export const useEndpoints = ({
if (endpoints[i] === EModelEndpoint.agents && !hasAgentAccess) { if (endpoints[i] === EModelEndpoint.agents && !hasAgentAccess) {
continue; continue;
} }
if (includedEndpoints.size > 0 && !includedEndpoints.has(endpoints[i])) {
continue;
}
result.push(endpoints[i]); result.push(endpoints[i]);
} }
return result; return result;
}, [endpoints, hasAgentAccess]); }, [endpoints, hasAgentAccess, includedEndpoints]);
const endpointRequiresUserKey = useCallback( const endpointRequiresUserKey = useCallback(
(ep: string) => { (ep: string) => {

View file

@ -172,13 +172,19 @@ export default function useSelectMention({
}); });
/* We don't reset the latest message, only when changing settings mid-converstion */ /* We don't reset the latest message, only when changing settings mid-converstion */
newConversation({ template: currentConvo, preset: currentConvo, keepLatestMessage: true }); newConversation({
template: currentConvo,
preset: currentConvo,
keepLatestMessage: true,
keepAddedConvos: true,
});
return; return;
} }
newConversation({ newConversation({
template: { ...(template as Partial<TConversation>) }, template: { ...(template as Partial<TConversation>) },
preset: { ...kwargs, spec: null, iconURL: null, modelLabel: null, endpoint: newEndpoint }, preset: { ...kwargs, spec: null, iconURL: null, modelLabel: null, endpoint: newEndpoint },
keepAddedConvos: isNewModular,
}); });
}, },
[conversation, getDefaultConversation, modularChat, newConversation, endpointsConfig], [conversation, getDefaultConversation, modularChat, newConversation, endpointsConfig],
@ -233,7 +239,7 @@ export default function useSelectMention({
return; return;
} }
newConversation({ preset: newPreset, keepAddedConvos: true }); newConversation({ preset: newPreset, keepAddedConvos: isModular });
}, },
[ [
modularChat, modularChat,

View file

@ -7,36 +7,35 @@ const avatarCache: Record<string, string> = {};
const useAvatar = (user: TUser | undefined) => { const useAvatar = (user: TUser | undefined) => {
return useMemo(() => { return useMemo(() => {
if (!user?.username) { const { username, name } = user ?? {};
const seed = name || username;
if (!seed) {
return ''; return '';
} }
if (user.avatar) { if (user?.avatar && user?.avatar !== '') {
return user.avatar; return user.avatar;
} }
const { username } = user; if (avatarCache[seed]) {
return avatarCache[seed];
if (avatarCache[username]) {
return avatarCache[username];
} }
const avatar = createAvatar(initials, { const avatar = createAvatar(initials, {
seed: username, seed,
fontFamily: ['Verdana'], fontFamily: ['Verdana'],
fontSize: 36, fontSize: 36,
}); });
let avatarDataUri = ''; let avatarDataUri = '';
avatar try {
.toDataUri() avatarDataUri = avatar.toDataUri();
.then((dataUri) => { if (avatarDataUri) {
avatarDataUri = dataUri; avatarCache[seed] = avatarDataUri;
avatarCache[username] = dataUri; // Store in cache }
}) } catch (error) {
.catch((error) => { console.error('Failed to generate avatar:', error);
console.error('Failed to generate avatar:', error); }
});
return avatarDataUri; return avatarDataUri;
}, [user]); }, [user]);

View file

@ -528,7 +528,8 @@ export default function useEventHandlers({
setCompleted((prev) => new Set(prev.add(initialResponse.messageId))); setCompleted((prev) => new Set(prev.add(initialResponse.messageId)));
const conversationId = userMessage.conversationId ?? submission.conversationId ?? ''; const conversationId =
userMessage.conversationId ?? submission.conversation?.conversationId ?? '';
const parseErrorResponse = (data: TResData | Partial<TMessage>) => { const parseErrorResponse = (data: TResData | Partial<TMessage>) => {
const metadata = data['responseMessage'] ?? data; const metadata = data['responseMessage'] ?? data;

View file

@ -124,7 +124,7 @@ export default function useSSE(
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
if (data.final != null) { if (data.final != null) {
clearDraft(submission.conversationId); clearDraft(submission.conversation?.conversationId);
const { plugins } = data; const { plugins } = data;
finalHandler(data, { ...submission, plugins } as EventSubmission); finalHandler(data, { ...submission, plugins } as EventSubmission);
(startupConfig?.balance?.enabled ?? false) && balanceQuery.refetch(); (startupConfig?.balance?.enabled ?? false) && balanceQuery.refetch();
@ -190,7 +190,10 @@ export default function useSSE(
const latestMessages = getMessages(); const latestMessages = getMessages();
const conversationId = latestMessages?.[latestMessages.length - 1]?.conversationId; const conversationId = latestMessages?.[latestMessages.length - 1]?.conversationId;
return await abortConversation( return await abortConversation(
conversationId ?? userMessage.conversationId ?? submission.conversationId, conversationId ??
userMessage.conversationId ??
submission.conversation?.conversationId ??
'',
submission as EventSubmission, submission as EventSubmission,
latestMessages, latestMessages,
); );

View file

@ -268,6 +268,7 @@
"com_error_files_upload_canceled": "The file upload request was canceled. Note: the file upload may still be processing and will need to be manually deleted.", "com_error_files_upload_canceled": "The file upload request was canceled. Note: the file upload may still be processing and will need to be manually deleted.",
"com_error_files_validation": "An error occurred while validating the file.", "com_error_files_validation": "An error occurred while validating the file.",
"com_error_input_length": "The latest message token count is too long, exceeding the token limit, or your token limit parameters are misconfigured, adversely affecting the context window. More info: {{0}}. Please shorten your message, adjust the max context size from the conversation parameters, or fork the conversation to continue.", "com_error_input_length": "The latest message token count is too long, exceeding the token limit, or your token limit parameters are misconfigured, adversely affecting the context window. More info: {{0}}. Please shorten your message, adjust the max context size from the conversation parameters, or fork the conversation to continue.",
"com_error_invalid_agent_provider": "The \"{{0}}\" provider is not available for use with Agents. Please go to your agent's settings and select a currently available provider.",
"com_error_invalid_user_key": "Invalid key provided. Please provide a valid key and try again.", "com_error_invalid_user_key": "Invalid key provided. Please provide a valid key and try again.",
"com_error_moderation": "It appears that the content submitted has been flagged by our moderation system for not aligning with our community guidelines. We're unable to proceed with this specific topic. If you have any other questions or topics you'd like to explore, please edit your message, or create a new conversation.", "com_error_moderation": "It appears that the content submitted has been flagged by our moderation system for not aligning with our community guidelines. We're unable to proceed with this specific topic. If you have any other questions or topics you'd like to explore, please edit your message, or create a new conversation.",
"com_error_no_base_url": "No base URL found. Please provide one and try again.", "com_error_no_base_url": "No base URL found. Please provide one and try again.",
@ -863,4 +864,4 @@
"com_endpoint_deprecated_info_a11y": "The plugin endpoint is deprecated and may be removed in future versions, please use the agent endpoint instead", "com_endpoint_deprecated_info_a11y": "The plugin endpoint is deprecated and may be removed in future versions, please use the agent endpoint instead",
"com_user_message": "You", "com_user_message": "You",
"com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint." "com_warning_resubmit_unsupported": "Resubmitting the AI message is not supported for this endpoint."
} }

View file

@ -1,8 +1,8 @@
import { EarthIcon } from 'lucide-react'; import { EarthIcon } from 'lucide-react';
import { import {
FileSources,
alternateName, alternateName,
EModelEndpoint, EModelEndpoint,
FileSources,
EToolResources, EToolResources,
} from 'librechat-data-provider'; } from 'librechat-data-provider';
import type { Agent, TFile } from 'librechat-data-provider'; import type { Agent, TFile } from 'librechat-data-provider';

1515
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "librechat-data-provider", "name": "librechat-data-provider",
"version": "0.7.77", "version": "0.7.78",
"description": "data services for librechat apps", "description": "data services for librechat apps",
"main": "dist/index.js", "main": "dist/index.js",
"module": "dist/index.es.js", "module": "dist/index.es.js",

View file

@ -237,6 +237,7 @@ export const agentsEndpointSChema = baseEndpointSchema.merge(
recursionLimit: z.number().optional(), recursionLimit: z.number().optional(),
disableBuilder: z.boolean().optional(), disableBuilder: z.boolean().optional(),
maxRecursionLimit: z.number().optional(), maxRecursionLimit: z.number().optional(),
allowedProviders: z.array(z.union([z.string(), eModelEndpointSchema])).optional(),
capabilities: z capabilities: z
.array(z.nativeEnum(AgentCapabilities)) .array(z.nativeEnum(AgentCapabilities))
.optional() .optional()
@ -1102,6 +1103,10 @@ export enum ErrorTypes {
* Google provider returned an error * Google provider returned an error
*/ */
GOOGLE_ERROR = 'google_error', GOOGLE_ERROR = 'google_error',
/**
* Invalid Agent Provider (excluded by Admin)
*/
INVALID_AGENT_PROVIDER = 'invalid_agent_provider',
} }
/** /**
@ -1214,7 +1219,7 @@ export enum Constants {
/** Key for the app's version. */ /** Key for the app's version. */
VERSION = 'v0.7.7', VERSION = 'v0.7.7',
/** Key for the Custom Config's version (librechat.yaml). */ /** Key for the Custom Config's version (librechat.yaml). */
CONFIG_VERSION = '1.2.3', CONFIG_VERSION = '1.2.4',
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */ /** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
NO_PARENT = '00000000-0000-0000-0000-000000000000', NO_PARENT = '00000000-0000-0000-0000-000000000000',
/** Standard value for the initial conversationId before a request is sent */ /** Standard value for the initial conversationId before a request is sent */

View file

@ -38,6 +38,7 @@ export const specsConfigSchema = z.object({
enforce: z.boolean().default(false), enforce: z.boolean().default(false),
prioritize: z.boolean().default(true), prioritize: z.boolean().default(true),
list: z.array(tModelSpecSchema).min(1), list: z.array(tModelSpecSchema).min(1),
addedEndpoints: z.array(z.union([z.string(), eModelEndpointSchema])).optional(),
}); });
export type TSpecsConfig = z.infer<typeof specsConfigSchema>; export type TSpecsConfig = z.infer<typeof specsConfigSchema>;

View file

@ -57,7 +57,6 @@ export type TSubmission = {
isTemporary: boolean; isTemporary: boolean;
messages: TMessage[]; messages: TMessage[];
isRegenerate?: boolean; isRegenerate?: boolean;
conversationId?: string;
initialResponse?: TMessage; initialResponse?: TMessage;
conversation: Partial<TConversation>; conversation: Partial<TConversation>;
endpointOption: TEndpointOption; endpointOption: TEndpointOption;