mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-16 04:36:34 +01:00
Merge remote-tracking branch 'upstream/main' into feature/entra-id-azure-integration
This commit is contained in:
commit
6ba2d8e967
41 changed files with 2255 additions and 1361 deletions
21
.github/workflows/cache-integration-tests.yml
vendored
21
.github/workflows/cache-integration-tests.yml
vendored
|
|
@ -61,30 +61,23 @@ jobs:
|
||||||
npm run build:data-schemas
|
npm run build:data-schemas
|
||||||
npm run build:api
|
npm run build:api
|
||||||
|
|
||||||
- name: Run cache integration tests
|
- name: Run all cache integration tests (Single Redis Node)
|
||||||
working-directory: packages/api
|
working-directory: packages/api
|
||||||
env:
|
env:
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
USE_REDIS: true
|
USE_REDIS: true
|
||||||
|
USE_REDIS_CLUSTER: false
|
||||||
REDIS_URI: redis://127.0.0.1:6379
|
REDIS_URI: redis://127.0.0.1:6379
|
||||||
REDIS_CLUSTER_URI: redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003
|
run: npm run test:cache-integration
|
||||||
run: npm run test:cache-integration:core
|
|
||||||
|
|
||||||
- name: Run cluster integration tests
|
- name: Run all cache integration tests (Redis Cluster)
|
||||||
working-directory: packages/api
|
working-directory: packages/api
|
||||||
env:
|
env:
|
||||||
NODE_ENV: test
|
NODE_ENV: test
|
||||||
USE_REDIS: true
|
USE_REDIS: true
|
||||||
REDIS_URI: redis://127.0.0.1:6379
|
USE_REDIS_CLUSTER: true
|
||||||
run: npm run test:cache-integration:cluster
|
REDIS_URI: redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003
|
||||||
|
run: npm run test:cache-integration
|
||||||
- name: Run mcp integration tests
|
|
||||||
working-directory: packages/api
|
|
||||||
env:
|
|
||||||
NODE_ENV: test
|
|
||||||
USE_REDIS: true
|
|
||||||
REDIS_URI: redis://127.0.0.1:6379
|
|
||||||
run: npm run test:cache-integration:mcp
|
|
||||||
|
|
||||||
- name: Stop Redis Cluster
|
- name: Stop Redis Cluster
|
||||||
if: always()
|
if: always()
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"https-proxy-agent": "^7.0.6",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"jwks-rsa": "^3.2.0",
|
"jwks-rsa": "^3.2.0",
|
||||||
"keyv": "^5.3.2",
|
"keyv": "^5.3.2",
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jest": "^29.7.0",
|
"jest": "^30.2.0",
|
||||||
"mongodb-memory-server": "^10.1.4",
|
"mongodb-memory-server": "^10.1.4",
|
||||||
"nodemon": "^3.0.3",
|
"nodemon": "^3.0.3",
|
||||||
"supertest": "^7.1.0"
|
"supertest": "^7.1.0"
|
||||||
|
|
|
||||||
|
|
@ -989,7 +989,7 @@ describe('AgentClient - titleConvo', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate the getOptions logic that handles GPT-5+ models
|
// Simulate the getOptions logic that handles GPT-5+ models
|
||||||
if (/\bgpt-[5-9]\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
if (/\bgpt-[5-9](?:\.\d+)?\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
||||||
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
||||||
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
||||||
delete clientOptions.maxTokens;
|
delete clientOptions.maxTokens;
|
||||||
|
|
@ -1009,7 +1009,7 @@ describe('AgentClient - titleConvo', () => {
|
||||||
useResponsesApi: true,
|
useResponsesApi: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (/\bgpt-[5-9]\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
if (/\bgpt-[5-9](?:\.\d+)?\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
||||||
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
||||||
const paramName =
|
const paramName =
|
||||||
clientOptions.useResponsesApi === true ? 'max_output_tokens' : 'max_completion_tokens';
|
clientOptions.useResponsesApi === true ? 'max_output_tokens' : 'max_completion_tokens';
|
||||||
|
|
@ -1034,7 +1034,7 @@ describe('AgentClient - titleConvo', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate the getOptions logic
|
// Simulate the getOptions logic
|
||||||
if (/\bgpt-[5-9]\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
if (/\bgpt-[5-9](?:\.\d+)?\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
||||||
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
||||||
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
||||||
delete clientOptions.maxTokens;
|
delete clientOptions.maxTokens;
|
||||||
|
|
@ -1055,7 +1055,7 @@ describe('AgentClient - titleConvo', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate the getOptions logic
|
// Simulate the getOptions logic
|
||||||
if (/\bgpt-[5-9]\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
if (/\bgpt-[5-9](?:\.\d+)?\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
||||||
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
||||||
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
||||||
delete clientOptions.maxTokens;
|
delete clientOptions.maxTokens;
|
||||||
|
|
@ -1068,6 +1068,9 @@ describe('AgentClient - titleConvo', () => {
|
||||||
|
|
||||||
it('should handle various GPT-5+ model formats', () => {
|
it('should handle various GPT-5+ model formats', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
|
{ model: 'gpt-5.1', shouldTransform: true },
|
||||||
|
{ model: 'gpt-5.1-chat-latest', shouldTransform: true },
|
||||||
|
{ model: 'gpt-5.1-codex', shouldTransform: true },
|
||||||
{ model: 'gpt-5', shouldTransform: true },
|
{ model: 'gpt-5', shouldTransform: true },
|
||||||
{ model: 'gpt-5-turbo', shouldTransform: true },
|
{ model: 'gpt-5-turbo', shouldTransform: true },
|
||||||
{ model: 'gpt-6', shouldTransform: true },
|
{ model: 'gpt-6', shouldTransform: true },
|
||||||
|
|
@ -1087,7 +1090,10 @@ describe('AgentClient - titleConvo', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate the getOptions logic
|
// Simulate the getOptions logic
|
||||||
if (/\bgpt-[5-9]\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
if (
|
||||||
|
/\bgpt-[5-9](?:\.\d+)?\b/i.test(clientOptions.model) &&
|
||||||
|
clientOptions.maxTokens != null
|
||||||
|
) {
|
||||||
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
||||||
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
||||||
delete clientOptions.maxTokens;
|
delete clientOptions.maxTokens;
|
||||||
|
|
@ -1105,6 +1111,9 @@ describe('AgentClient - titleConvo', () => {
|
||||||
|
|
||||||
it('should not swap max token param for older models when using useResponsesApi', () => {
|
it('should not swap max token param for older models when using useResponsesApi', () => {
|
||||||
const testCases = [
|
const testCases = [
|
||||||
|
{ model: 'gpt-5.1', shouldTransform: true },
|
||||||
|
{ model: 'gpt-5.1-chat-latest', shouldTransform: true },
|
||||||
|
{ model: 'gpt-5.1-codex', shouldTransform: true },
|
||||||
{ model: 'gpt-5', shouldTransform: true },
|
{ model: 'gpt-5', shouldTransform: true },
|
||||||
{ model: 'gpt-5-turbo', shouldTransform: true },
|
{ model: 'gpt-5-turbo', shouldTransform: true },
|
||||||
{ model: 'gpt-6', shouldTransform: true },
|
{ model: 'gpt-6', shouldTransform: true },
|
||||||
|
|
@ -1124,7 +1133,10 @@ describe('AgentClient - titleConvo', () => {
|
||||||
useResponsesApi: true,
|
useResponsesApi: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (/\bgpt-[5-9]\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
if (
|
||||||
|
/\bgpt-[5-9](?:\.\d+)?\b/i.test(clientOptions.model) &&
|
||||||
|
clientOptions.maxTokens != null
|
||||||
|
) {
|
||||||
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
||||||
const paramName =
|
const paramName =
|
||||||
clientOptions.useResponsesApi === true ? 'max_output_tokens' : 'max_completion_tokens';
|
clientOptions.useResponsesApi === true ? 'max_output_tokens' : 'max_completion_tokens';
|
||||||
|
|
@ -1157,7 +1169,10 @@ describe('AgentClient - titleConvo', () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate the getOptions logic
|
// Simulate the getOptions logic
|
||||||
if (/\bgpt-[5-9]\b/i.test(clientOptions.model) && clientOptions.maxTokens != null) {
|
if (
|
||||||
|
/\bgpt-[5-9](?:\.\d+)?\b/i.test(clientOptions.model) &&
|
||||||
|
clientOptions.maxTokens != null
|
||||||
|
) {
|
||||||
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
clientOptions.modelKwargs = clientOptions.modelKwargs ?? {};
|
||||||
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
clientOptions.modelKwargs.max_completion_tokens = clientOptions.maxTokens;
|
||||||
delete clientOptions.maxTokens;
|
delete clientOptions.maxTokens;
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ const {
|
||||||
ResourceType,
|
ResourceType,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
PermissionBits,
|
PermissionBits,
|
||||||
isAgentsEndpoint,
|
|
||||||
checkOpenAIStorage,
|
checkOpenAIStorage,
|
||||||
|
isAssistantsEndpoint,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
filterFile,
|
filterFile,
|
||||||
|
|
@ -376,11 +376,11 @@ router.post('/', async (req, res) => {
|
||||||
metadata.temp_file_id = metadata.file_id;
|
metadata.temp_file_id = metadata.file_id;
|
||||||
metadata.file_id = req.file_id;
|
metadata.file_id = req.file_id;
|
||||||
|
|
||||||
if (isAgentsEndpoint(metadata.endpoint)) {
|
if (isAssistantsEndpoint(metadata.endpoint)) {
|
||||||
return await processAgentFileUpload({ req, res, metadata });
|
return await processFileUpload({ req, res, metadata });
|
||||||
}
|
}
|
||||||
|
|
||||||
await processFileUpload({ req, res, metadata });
|
return await processAgentFileUpload({ req, res, metadata });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'Error processing file';
|
let message = 'Error processing file';
|
||||||
logger.error('[/files] Error processing file:', error);
|
logger.error('[/files] Error processing file:', error);
|
||||||
|
|
|
||||||
|
|
@ -135,10 +135,10 @@
|
||||||
"babel-plugin-root-import": "^6.6.0",
|
"babel-plugin-root-import": "^6.6.0",
|
||||||
"babel-plugin-transform-import-meta": "^2.3.2",
|
"babel-plugin-transform-import-meta": "^2.3.2",
|
||||||
"babel-plugin-transform-vite-meta-env": "^1.0.3",
|
"babel-plugin-transform-vite-meta-env": "^1.0.3",
|
||||||
"eslint-plugin-jest": "^28.11.0",
|
"eslint-plugin-jest": "^29.1.0",
|
||||||
"fs-extra": "^11.3.2",
|
"fs-extra": "^11.3.2",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^30.2.0",
|
||||||
"jest-canvas-mock": "^2.5.2",
|
"jest-canvas-mock": "^2.5.2",
|
||||||
"jest-environment-jsdom": "^29.7.0",
|
"jest-environment-jsdom": "^29.7.0",
|
||||||
"jest-file-loader": "^1.0.3",
|
"jest-file-loader": "^1.0.3",
|
||||||
|
|
@ -147,7 +147,7 @@
|
||||||
"postcss-loader": "^7.1.0",
|
"postcss-loader": "^7.1.0",
|
||||||
"postcss-preset-env": "^8.2.0",
|
"postcss-preset-env": "^8.2.0",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.4.5",
|
||||||
"typescript": "^5.3.3",
|
"typescript": "^5.3.3",
|
||||||
"vite": "^6.4.1",
|
"vite": "^6.4.1",
|
||||||
"vite-plugin-compression2": "^2.2.1",
|
"vite-plugin-compression2": "^2.2.1",
|
||||||
|
|
|
||||||
|
|
@ -179,9 +179,10 @@ export const ArtifactCodeEditor = function ({
|
||||||
bundlerURL: template === 'static' ? config.staticBundlerURL : config.bundlerURL,
|
bundlerURL: template === 'static' ? config.staticBundlerURL : config.bundlerURL,
|
||||||
};
|
};
|
||||||
}, [config, template, fileKey]);
|
}, [config, template, fileKey]);
|
||||||
const [readOnly, setReadOnly] = useState(externalReadOnly ?? isSubmitting ?? false);
|
const initialReadOnly = (externalReadOnly ?? false) || (isSubmitting ?? false);
|
||||||
|
const [readOnly, setReadOnly] = useState(initialReadOnly);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setReadOnly(externalReadOnly ?? isSubmitting ?? false);
|
setReadOnly((externalReadOnly ?? false) || (isSubmitting ?? false));
|
||||||
}, [isSubmitting, externalReadOnly]);
|
}, [isSubmitting, externalReadOnly]);
|
||||||
|
|
||||||
if (Object.keys(files).length === 0) {
|
if (Object.keys(files).length === 0) {
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ const Reasoning = memo(({ reasoning, isLast }: ReasoningProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="group/reasoning">
|
<div className="group/reasoning">
|
||||||
<div className="group/thinking-container">
|
<div className="group/thinking-container">
|
||||||
<div className="sticky top-0 z-10 mb-2 pb-2 pt-2">
|
<div className="sticky top-0 z-10 mb-2 bg-presentation pb-2 pt-2">
|
||||||
<ThinkingButton
|
<ThinkingButton
|
||||||
isExpanded={isExpanded}
|
isExpanded={isExpanded}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ const Thinking: React.ElementType = memo(({ children }: { children: React.ReactN
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="group/thinking-container">
|
<div className="group/thinking-container">
|
||||||
<div className="sticky top-0 z-10 mb-4 bg-surface-primary pb-2 pt-2">
|
<div className="sticky top-0 z-10 mb-4 bg-presentation pb-2 pt-2">
|
||||||
<ThinkingButton
|
<ThinkingButton
|
||||||
isExpanded={isExpanded}
|
isExpanded={isExpanded}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,11 @@ export default function MinimalHoverButtons({ message, searchResults }: THoverBu
|
||||||
isCopied ? localize('com_ui_copied_to_clipboard') : localize('com_ui_copy_to_clipboard')
|
isCopied ? localize('com_ui_copied_to_clipboard') : localize('com_ui_copy_to_clipboard')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isCopied ? <CheckMark className="h-[18px] w-[18px]" /> : <Clipboard size="19" />}
|
{isCopied ? (
|
||||||
|
<CheckMark className="h-[19px] w-[19px]" />
|
||||||
|
) : (
|
||||||
|
<Clipboard className="h-[19px] w-[19px]" />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ type EndpointIcon = {
|
||||||
|
|
||||||
function getOpenAIColor(_model: string | null | undefined) {
|
function getOpenAIColor(_model: string | null | undefined) {
|
||||||
const model = _model?.toLowerCase() ?? '';
|
const model = _model?.toLowerCase() ?? '';
|
||||||
if (model && (/\b(o\d)\b/i.test(model) || /\bgpt-[5-9]\b/i.test(model))) {
|
if (model && (/\b(o\d)\b/i.test(model) || /\bgpt-[5-9](?:\.\d+)?\b/i.test(model))) {
|
||||||
return '#000000';
|
return '#000000';
|
||||||
}
|
}
|
||||||
return model.includes('gpt-4') ? '#AB68FF' : '#19C37D';
|
return model.includes('gpt-4') ? '#AB68FF' : '#19C37D';
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ import { useRef, useEffect, memo } from 'react';
|
||||||
import { ResizableHandleAlt, ResizablePanel } from '@librechat/client';
|
import { ResizableHandleAlt, ResizablePanel } from '@librechat/client';
|
||||||
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
import type { ImperativePanelHandle } from 'react-resizable-panels';
|
||||||
|
|
||||||
const ANIMATION_DURATION = 500;
|
|
||||||
|
|
||||||
interface ArtifactsPanelProps {
|
interface ArtifactsPanelProps {
|
||||||
artifacts: React.ReactNode | null;
|
artifacts: React.ReactNode | null;
|
||||||
currentLayout: number[];
|
currentLayout: number[];
|
||||||
|
|
@ -24,14 +22,9 @@ const ArtifactsPanel = memo(function ArtifactsPanel({
|
||||||
onRenderChange,
|
onRenderChange,
|
||||||
}: ArtifactsPanelProps) {
|
}: ArtifactsPanelProps) {
|
||||||
const artifactsPanelRef = useRef<ImperativePanelHandle>(null);
|
const artifactsPanelRef = useRef<ImperativePanelHandle>(null);
|
||||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (artifacts != null) {
|
if (artifacts != null) {
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
timeoutRef.current = null;
|
|
||||||
}
|
|
||||||
onRenderChange(true);
|
onRenderChange(true);
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
|
|
@ -39,17 +32,8 @@ const ArtifactsPanel = memo(function ArtifactsPanel({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else if (shouldRender) {
|
} else if (shouldRender) {
|
||||||
artifactsPanelRef.current?.collapse();
|
onRenderChange(false);
|
||||||
timeoutRef.current = setTimeout(() => {
|
|
||||||
onRenderChange(false);
|
|
||||||
}, ANIMATION_DURATION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [artifacts, shouldRender, onRenderChange]);
|
}, [artifacts, shouldRender, onRenderChange]);
|
||||||
|
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,9 @@ const SidePanel = ({
|
||||||
transition: 'width 0.2s ease, visibility 0s linear 0.2s',
|
transition: 'width 0.2s ease, visibility 0s linear 0.2s',
|
||||||
}}
|
}}
|
||||||
onExpand={() => {
|
onExpand={() => {
|
||||||
|
if (isCollapsed && (fullCollapse || collapsedSize === 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
setIsCollapsed(false);
|
setIsCollapsed(false);
|
||||||
localStorage.setItem('react-resizable-panels:collapsed', 'false');
|
localStorage.setItem('react-resizable-panels:collapsed', 'false');
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
|
|
@ -138,9 +138,9 @@ const SidePanelGroup = memo(
|
||||||
setCollapsedSize={setCollapsedSize}
|
setCollapsedSize={setCollapsedSize}
|
||||||
fullCollapse={fullCollapse}
|
fullCollapse={fullCollapse}
|
||||||
setFullCollapse={setFullCollapse}
|
setFullCollapse={setFullCollapse}
|
||||||
defaultSize={currentLayout[currentLayout.length - 1]}
|
|
||||||
hasArtifacts={artifacts != null}
|
|
||||||
interfaceConfig={interfaceConfig}
|
interfaceConfig={interfaceConfig}
|
||||||
|
hasArtifacts={shouldRenderArtifacts}
|
||||||
|
defaultSize={currentLayout[currentLayout.length - 1]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
|
|
|
||||||
|
|
@ -378,6 +378,7 @@
|
||||||
"com_error_moderation": "Šķiet, ka mūsu moderācijas sistēma ir atzīmējusi nosūtīto saturu kā neatbilstošu mūsu vadlīnijām. Mēs nevaram turpināt darbu ar šo konkrēto tēmu. Ja jums ir vēl kādi jautājumi vai tēmas, kuras vēlaties izpētīt, lūdzu, rediģējiet savu ziņu vai izveidojiet jaunu sarunu.",
|
"com_error_moderation": "Šķiet, ka mūsu moderācijas sistēma ir atzīmējusi nosūtīto saturu kā neatbilstošu mūsu vadlīnijām. Mēs nevaram turpināt darbu ar šo konkrēto tēmu. Ja jums ir vēl kādi jautājumi vai tēmas, kuras vēlaties izpētīt, lūdzu, rediģējiet savu ziņu vai izveidojiet jaunu sarunu.",
|
||||||
"com_error_no_base_url": "Nav atrasts bāzes URL. Lūdzu, norādiet to un mēģiniet vēlreiz.",
|
"com_error_no_base_url": "Nav atrasts bāzes URL. Lūdzu, norādiet to un mēģiniet vēlreiz.",
|
||||||
"com_error_no_user_key": "Atslēga nav atrasta. Lūdzu, norādiet atslēgu un mēģiniet vēlreiz.",
|
"com_error_no_user_key": "Atslēga nav atrasta. Lūdzu, norādiet atslēgu un mēģiniet vēlreiz.",
|
||||||
|
"com_error_refusal": "Drošības filtri noraidīja atbildi. Pārrakstiet savu ziņojumu un mēģiniet vēlreiz. Ja, lietojot Claude Sonnet 4.5 vai Opus 4.1, ar šo problēmu bieži saskaraties, varat izmēģināt Sonnet 4, kuram ir atšķirīgi lietošanas ierobežojumi.",
|
||||||
"com_file_pages": "Lapas: {{pages}}",
|
"com_file_pages": "Lapas: {{pages}}",
|
||||||
"com_file_source": "Fails",
|
"com_file_source": "Fails",
|
||||||
"com_file_unknown": "Nezināms fails",
|
"com_file_unknown": "Nezināms fails",
|
||||||
|
|
@ -768,10 +769,12 @@
|
||||||
"com_ui_cancel": "Atcelt",
|
"com_ui_cancel": "Atcelt",
|
||||||
"com_ui_cancelled": "Atcelts",
|
"com_ui_cancelled": "Atcelts",
|
||||||
"com_ui_category": "Kategorija",
|
"com_ui_category": "Kategorija",
|
||||||
|
"com_ui_change_version": "Mainīt versiju",
|
||||||
"com_ui_chat": "Saruna",
|
"com_ui_chat": "Saruna",
|
||||||
"com_ui_chat_history": "Sarunu vēsture",
|
"com_ui_chat_history": "Sarunu vēsture",
|
||||||
"com_ui_clear": "Notīrīt",
|
"com_ui_clear": "Notīrīt",
|
||||||
"com_ui_clear_all": "Notīrīt visu",
|
"com_ui_clear_all": "Notīrīt visu",
|
||||||
|
"com_ui_click_to_close": "Noklikšķiniet, lai aizvērtu",
|
||||||
"com_ui_client_id": "Klienta ID",
|
"com_ui_client_id": "Klienta ID",
|
||||||
"com_ui_client_secret": "Klienta noslēpums",
|
"com_ui_client_secret": "Klienta noslēpums",
|
||||||
"com_ui_close": "Aizvērt",
|
"com_ui_close": "Aizvērt",
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ const shouldRebase = process.argv.includes('--rebase');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.purple('Removing previously made Docker container...');
|
console.purple('Removing previously made Docker container...');
|
||||||
const downCommand = 'sudo docker-compose -f ./deploy-compose.yml down';
|
const downCommand = 'sudo docker compose -f ./deploy-compose.yml down';
|
||||||
console.orange(downCommand);
|
console.orange(downCommand);
|
||||||
execSync(downCommand, { stdio: 'inherit' });
|
execSync(downCommand, { stdio: 'inherit' });
|
||||||
|
|
||||||
|
|
@ -54,15 +54,15 @@ const shouldRebase = process.argv.includes('--rebase');
|
||||||
});
|
});
|
||||||
|
|
||||||
console.purple('Pulling latest LibreChat images...');
|
console.purple('Pulling latest LibreChat images...');
|
||||||
const pullCommand = 'sudo docker-compose -f ./deploy-compose.yml pull api';
|
const pullCommand = 'sudo docker compose -f ./deploy-compose.yml pull api';
|
||||||
console.orange(pullCommand);
|
console.orange(pullCommand);
|
||||||
execSync(pullCommand, { stdio: 'inherit' });
|
execSync(pullCommand, { stdio: 'inherit' });
|
||||||
|
|
||||||
let startCommand = 'sudo docker-compose -f ./deploy-compose.yml up -d';
|
let startCommand = 'sudo docker compose -f ./deploy-compose.yml up -d';
|
||||||
console.green('Your LibreChat app is now up to date! Start the app with the following command:');
|
console.green('Your LibreChat app is now up to date! Start the app with the following command:');
|
||||||
console.purple(startCommand);
|
console.purple(startCommand);
|
||||||
console.orange(
|
console.orange(
|
||||||
'Note: it\'s also recommended to clear your browser cookies and localStorage for LibreChat to assure a fully clean installation.',
|
"Note: it's also recommended to clear your browser cookies and localStorage for LibreChat to assure a fully clean installation.",
|
||||||
);
|
);
|
||||||
console.orange('Also: Don\'t worry, your data is safe :)');
|
console.orange("Also: Don't worry, your data is safe :)");
|
||||||
})();
|
})();
|
||||||
|
|
|
||||||
26
helm/librechat/templates/configmaps-additional.yaml
Executable file
26
helm/librechat/templates/configmaps-additional.yaml
Executable file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{{- range $key, $value := .Values.additionalConfigMaps }}
|
||||||
|
{{- if or .data .binaryData }}
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "librechat.fullname" $ }}-{{ default "custom" $key }}
|
||||||
|
{{- with .labels }}
|
||||||
|
labels:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .data }}
|
||||||
|
data:
|
||||||
|
{{- toYaml . | nindent 2 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .binaryData }}
|
||||||
|
binaryData:
|
||||||
|
{{- toYaml . | nindent 2 }}
|
||||||
|
immutable: {{ default false .immutable }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
@ -51,6 +51,15 @@ spec:
|
||||||
serviceAccountName: {{ include "librechat.serviceAccountName" . }}
|
serviceAccountName: {{ include "librechat.serviceAccountName" . }}
|
||||||
securityContext:
|
securityContext:
|
||||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||||
|
{{- if .Values.initContainers }}
|
||||||
|
initContainers:
|
||||||
|
{{- range $key, $value := .Values.initContainers }}
|
||||||
|
{{- if . }}
|
||||||
|
- name: {{ $key }}
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
containers:
|
containers:
|
||||||
- name: {{ include "librechat.fullname" $ }}
|
- name: {{ include "librechat.fullname" $ }}
|
||||||
securityContext:
|
securityContext:
|
||||||
|
|
|
||||||
|
|
@ -219,6 +219,13 @@ readinessProbe:
|
||||||
path: /health
|
path: /health
|
||||||
port: 3080
|
port: 3080
|
||||||
|
|
||||||
|
# Additional init containers on the output Deployment definition.
|
||||||
|
initContainers: {}
|
||||||
|
# foo: # the name of the init container
|
||||||
|
# image: busybox
|
||||||
|
# command: ['sh', '-c', 'echo The app is starting! && sleep 5']
|
||||||
|
# # ... add more init containers as needed
|
||||||
|
|
||||||
# Additional volumes on the output Deployment definition.
|
# Additional volumes on the output Deployment definition.
|
||||||
volumes: []
|
volumes: []
|
||||||
# - name: foo
|
# - name: foo
|
||||||
|
|
@ -269,6 +276,16 @@ dnsConfig: {}
|
||||||
updateStrategy:
|
updateStrategy:
|
||||||
type: RollingUpdate
|
type: RollingUpdate
|
||||||
|
|
||||||
|
# Extra ConfigMaps to be created alongside the main ones
|
||||||
|
additionalConfigMaps: {}
|
||||||
|
# custom: # suffix of the ConfigMap name
|
||||||
|
# labels: {}
|
||||||
|
# annotations: {}
|
||||||
|
# data: {}
|
||||||
|
# binaryData: {}
|
||||||
|
# immutable: false
|
||||||
|
# # ... add more ConfigMaps as needed
|
||||||
|
|
||||||
# MongoDB Parameters
|
# MongoDB Parameters
|
||||||
mongodb:
|
mongodb:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
||||||
3194
package-lock.json
generated
3194
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
|
@ -97,7 +97,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@axe-core/playwright": "^4.10.1",
|
"@axe-core/playwright": "^4.10.1",
|
||||||
"@eslint/compat": "^1.2.6",
|
"@eslint/compat": "^1.2.6",
|
||||||
"@eslint/eslintrc": "^3.2.0",
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
"@eslint/js": "^9.20.0",
|
"@eslint/js": "^9.20.0",
|
||||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||||
"@playwright/test": "^1.56.1",
|
"@playwright/test": "^1.56.1",
|
||||||
|
|
@ -105,12 +105,12 @@
|
||||||
"caniuse-lite": "^1.0.30001741",
|
"caniuse-lite": "^1.0.30001741",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"elliptic": "^6.6.1",
|
"elliptic": "^6.6.1",
|
||||||
"eslint": "^9.20.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-import-resolver-typescript": "^3.7.0",
|
"eslint-import-resolver-typescript": "^3.7.0",
|
||||||
"eslint-plugin-i18next": "^6.1.1",
|
"eslint-plugin-i18next": "^6.1.1",
|
||||||
"eslint-plugin-import": "^2.31.0",
|
"eslint-plugin-import": "^2.31.0",
|
||||||
"eslint-plugin-jest": "^28.11.0",
|
"eslint-plugin-jest": "^29.1.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
"eslint-plugin-prettier": "^5.2.3",
|
"eslint-plugin-prettier": "^5.2.3",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react": "^7.37.4",
|
||||||
|
|
@ -118,10 +118,10 @@
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jest": "^29.7.0",
|
"jest": "^30.2.0",
|
||||||
"lint-staged": "^15.4.3",
|
"lint-staged": "^15.4.3",
|
||||||
"prettier": "^3.5.0",
|
"prettier": "^3.5.0",
|
||||||
"prettier-eslint": "^16.3.0",
|
"prettier-eslint": "^16.4.2",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"typescript-eslint": "^8.24.0"
|
"typescript-eslint": "^8.24.0"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
"test:cache-integration:core": "jest --testPathPattern=\"src/cache/.*\\.cache_integration\\.spec\\.ts$\" --coverage=false",
|
"test:cache-integration:core": "jest --testPathPattern=\"src/cache/.*\\.cache_integration\\.spec\\.ts$\" --coverage=false",
|
||||||
"test:cache-integration:cluster": "jest --testPathPattern=\"src/cluster/.*\\.cache_integration\\.spec\\.ts$\" --coverage=false --runInBand",
|
"test:cache-integration:cluster": "jest --testPathPattern=\"src/cluster/.*\\.cache_integration\\.spec\\.ts$\" --coverage=false --runInBand",
|
||||||
"test:cache-integration:mcp": "jest --testPathPattern=\"src/mcp/.*\\.cache_integration\\.spec\\.ts$\" --coverage=false",
|
"test:cache-integration:mcp": "jest --testPathPattern=\"src/mcp/.*\\.cache_integration\\.spec\\.ts$\" --coverage=false",
|
||||||
|
"test:cache-integration": "npm run test:cache-integration:core && npm run test:cache-integration:cluster && npm run test:cache-integration:mcp",
|
||||||
"verify": "npm run test:ci",
|
"verify": "npm run test:ci",
|
||||||
"b:clean": "bun run rimraf dist",
|
"b:clean": "bun run rimraf dist",
|
||||||
"b:build": "bun run b:clean && bun run rollup -c --silent --bundleConfigAsCjs",
|
"b:build": "bun run b:clean && bun run rollup -c --silent --bundleConfigAsCjs",
|
||||||
|
|
@ -63,7 +64,7 @@
|
||||||
"@types/node-fetch": "^2.6.13",
|
"@types/node-fetch": "^2.6.13",
|
||||||
"@types/react": "^18.2.18",
|
"@types/react": "^18.2.18",
|
||||||
"@types/winston": "^2.4.4",
|
"@types/winston": "^2.4.4",
|
||||||
"jest": "^29.5.0",
|
"jest": "^30.2.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"librechat-data-provider": "*",
|
"librechat-data-provider": "*",
|
||||||
"mongodb": "^6.14.2",
|
"mongodb": "^6.14.2",
|
||||||
|
|
@ -95,7 +96,7 @@
|
||||||
"firebase": "^11.0.2",
|
"firebase": "^11.0.2",
|
||||||
"form-data": "^4.0.4",
|
"form-data": "^4.0.4",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"keyv": "^5.3.2",
|
"keyv": "^5.3.2",
|
||||||
"keyv-file": "^5.1.2",
|
"keyv-file": "^5.1.2",
|
||||||
|
|
|
||||||
|
|
@ -345,7 +345,7 @@ ${memory ?? 'No existing memories'}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle GPT-5+ models
|
// Handle GPT-5+ models
|
||||||
if ('model' in finalLLMConfig && /\bgpt-[5-9]\b/i.test(finalLLMConfig.model ?? '')) {
|
if ('model' in finalLLMConfig && /\bgpt-[5-9](?:\.\d+)?\b/i.test(finalLLMConfig.model ?? '')) {
|
||||||
// Remove temperature for GPT-5+ models
|
// Remove temperature for GPT-5+ models
|
||||||
delete finalLLMConfig.temperature;
|
delete finalLLMConfig.temperature;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,17 +7,13 @@ describe('limiterCache', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalEnv = { ...process.env };
|
originalEnv = { ...process.env };
|
||||||
|
|
||||||
// Clear cache-related env vars
|
// Set test configuration with fallback defaults for local testing
|
||||||
delete process.env.USE_REDIS;
|
|
||||||
delete process.env.REDIS_URI;
|
|
||||||
delete process.env.USE_REDIS_CLUSTER;
|
|
||||||
delete process.env.REDIS_PING_INTERVAL;
|
|
||||||
delete process.env.REDIS_KEY_PREFIX;
|
|
||||||
|
|
||||||
// Set test configuration
|
|
||||||
process.env.REDIS_PING_INTERVAL = '0';
|
process.env.REDIS_PING_INTERVAL = '0';
|
||||||
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
||||||
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
||||||
|
process.env.USE_REDIS = process.env.USE_REDIS || 'true';
|
||||||
|
process.env.USE_REDIS_CLUSTER = process.env.USE_REDIS_CLUSTER || 'false';
|
||||||
|
process.env.REDIS_URI = process.env.REDIS_URI || 'redis://127.0.0.1:6379';
|
||||||
|
|
||||||
// Clear require cache to reload modules
|
// Clear require cache to reload modules
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
|
|
@ -43,10 +39,6 @@ describe('limiterCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return RedisStore with sendCommand when USE_REDIS is true', async () => {
|
test('should return RedisStore with sendCommand when USE_REDIS is true', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
|
||||||
|
|
@ -33,17 +33,13 @@ describe('sessionCache', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalEnv = { ...process.env };
|
originalEnv = { ...process.env };
|
||||||
|
|
||||||
// Clear cache-related env vars
|
// Set test configuration with fallback defaults for local testing
|
||||||
delete process.env.USE_REDIS;
|
|
||||||
delete process.env.REDIS_URI;
|
|
||||||
delete process.env.USE_REDIS_CLUSTER;
|
|
||||||
delete process.env.REDIS_PING_INTERVAL;
|
|
||||||
delete process.env.REDIS_KEY_PREFIX;
|
|
||||||
|
|
||||||
// Set test configuration
|
|
||||||
process.env.REDIS_PING_INTERVAL = '0';
|
process.env.REDIS_PING_INTERVAL = '0';
|
||||||
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
||||||
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
||||||
|
process.env.USE_REDIS = process.env.USE_REDIS || 'true';
|
||||||
|
process.env.USE_REDIS_CLUSTER = process.env.USE_REDIS_CLUSTER || 'false';
|
||||||
|
process.env.REDIS_URI = process.env.REDIS_URI || 'redis://127.0.0.1:6379';
|
||||||
|
|
||||||
// Clear require cache to reload modules
|
// Clear require cache to reload modules
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
|
|
@ -55,10 +51,6 @@ describe('sessionCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return ConnectRedis store when USE_REDIS is true', async () => {
|
test('should return ConnectRedis store when USE_REDIS is true', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
@ -138,10 +130,6 @@ describe('sessionCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle namespace with and without trailing colon', async () => {
|
test('should handle namespace with and without trailing colon', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
|
|
||||||
const store1 = cacheFactory.sessionCache('namespace1');
|
const store1 = cacheFactory.sessionCache('namespace1');
|
||||||
|
|
@ -152,10 +140,6 @@ describe('sessionCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should register error handler for Redis connection', async () => {
|
test('should register error handler for Redis connection', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
@ -173,10 +157,6 @@ describe('sessionCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle session expiration with TTL', async () => {
|
test('should handle session expiration with TTL', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
|
||||||
|
|
@ -30,18 +30,13 @@ describe('standardCache', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalEnv = { ...process.env };
|
originalEnv = { ...process.env };
|
||||||
|
|
||||||
// Clear cache-related env vars
|
// Set test configuration with fallback defaults for local testing
|
||||||
delete process.env.USE_REDIS;
|
|
||||||
delete process.env.REDIS_URI;
|
|
||||||
delete process.env.USE_REDIS_CLUSTER;
|
|
||||||
delete process.env.REDIS_PING_INTERVAL;
|
|
||||||
delete process.env.REDIS_KEY_PREFIX;
|
|
||||||
delete process.env.FORCED_IN_MEMORY_CACHE_NAMESPACES;
|
|
||||||
|
|
||||||
// Set test configuration
|
|
||||||
process.env.REDIS_PING_INTERVAL = '0';
|
process.env.REDIS_PING_INTERVAL = '0';
|
||||||
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
||||||
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
||||||
|
process.env.USE_REDIS = process.env.USE_REDIS || 'true';
|
||||||
|
process.env.USE_REDIS_CLUSTER = 'false';
|
||||||
|
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
||||||
|
|
||||||
// Clear require cache to reload modules
|
// Clear require cache to reload modules
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
|
|
@ -119,10 +114,6 @@ describe('standardCache', () => {
|
||||||
|
|
||||||
describe('when connecting to a Redis server', () => {
|
describe('when connecting to a Redis server', () => {
|
||||||
test('should handle different namespaces with correct prefixes', async () => {
|
test('should handle different namespaces with correct prefixes', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
|
|
||||||
const cache1 = cacheFactory.standardCache('namespace-one');
|
const cache1 = cacheFactory.standardCache('namespace-one');
|
||||||
|
|
@ -148,9 +139,6 @@ describe('standardCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should respect FORCED_IN_MEMORY_CACHE_NAMESPACES', async () => {
|
test('should respect FORCED_IN_MEMORY_CACHE_NAMESPACES', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
process.env.FORCED_IN_MEMORY_CACHE_NAMESPACES = 'ROLES'; // Use a valid cache key
|
process.env.FORCED_IN_MEMORY_CACHE_NAMESPACES = 'ROLES'; // Use a valid cache key
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
|
|
@ -167,10 +155,6 @@ describe('standardCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle TTL correctly', async () => {
|
test('should handle TTL correctly', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
testCache = cacheFactory.standardCache('ttl-test', 1000); // 1 second TTL
|
testCache = cacheFactory.standardCache('ttl-test', 1000); // 1 second TTL
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,13 @@ describe('violationCache', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalEnv = { ...process.env };
|
originalEnv = { ...process.env };
|
||||||
|
|
||||||
// Clear cache-related env vars
|
// Set test configuration with fallback defaults for local testing
|
||||||
delete process.env.USE_REDIS;
|
|
||||||
delete process.env.REDIS_URI;
|
|
||||||
delete process.env.USE_REDIS_CLUSTER;
|
|
||||||
delete process.env.REDIS_PING_INTERVAL;
|
|
||||||
delete process.env.REDIS_KEY_PREFIX;
|
|
||||||
|
|
||||||
// Set test configuration
|
|
||||||
process.env.REDIS_PING_INTERVAL = '0';
|
process.env.REDIS_PING_INTERVAL = '0';
|
||||||
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
process.env.REDIS_KEY_PREFIX = 'Cache-Integration-Test';
|
||||||
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
||||||
|
process.env.USE_REDIS = process.env.USE_REDIS || 'true';
|
||||||
|
process.env.USE_REDIS_CLUSTER = process.env.USE_REDIS_CLUSTER || 'false';
|
||||||
|
process.env.REDIS_URI = process.env.REDIS_URI || 'redis://127.0.0.1:6379';
|
||||||
|
|
||||||
// Clear require cache to reload modules
|
// Clear require cache to reload modules
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
|
|
@ -48,10 +44,6 @@ describe('violationCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should create violation cache with Redis when USE_REDIS is true', async () => {
|
test('should create violation cache with Redis when USE_REDIS is true', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
@ -119,10 +111,6 @@ describe('violationCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should respect namespace prefixing', async () => {
|
test('should respect namespace prefixing', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
@ -157,10 +145,6 @@ describe('violationCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should respect TTL settings', async () => {
|
test('should respect TTL settings', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
@ -193,10 +177,6 @@ describe('violationCache', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle complex violation data structures', async () => {
|
test('should handle complex violation data structures', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const cacheFactory = await import('../../cacheFactory');
|
const cacheFactory = await import('../../cacheFactory');
|
||||||
const redisClients = await import('../../redisClients');
|
const redisClients = await import('../../redisClients');
|
||||||
const { ioredisClient } = redisClients;
|
const { ioredisClient } = redisClients;
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,13 @@ describe('redisClients Integration Tests', () => {
|
||||||
let keyvRedisClient: RedisClientType | RedisClusterType | null = null;
|
let keyvRedisClient: RedisClientType | RedisClusterType | null = null;
|
||||||
|
|
||||||
// Helper function to test set/get/delete operations
|
// Helper function to test set/get/delete operations
|
||||||
const testRedisOperations = async (client: RedisClient, keyPrefix: string): Promise<void> => {
|
const testRedisOperations = async (
|
||||||
// Wait cluster to fully initialize
|
client: RedisClient,
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
keyPrefix: string,
|
||||||
|
readyPromise?: Promise<void>,
|
||||||
|
): Promise<void> => {
|
||||||
|
// Wait for connection and topology discovery to complete
|
||||||
|
if (readyPromise) await readyPromise;
|
||||||
|
|
||||||
const testKey = `${keyPrefix}-test-key`;
|
const testKey = `${keyPrefix}-test-key`;
|
||||||
const testValue = `${keyPrefix}-test-value`;
|
const testValue = `${keyPrefix}-test-value`;
|
||||||
|
|
@ -35,18 +39,13 @@ describe('redisClients Integration Tests', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
originalEnv = { ...process.env };
|
originalEnv = { ...process.env };
|
||||||
|
|
||||||
// Clear Redis-related env vars
|
// Set common test configuration with fallback defaults for local testing
|
||||||
delete process.env.USE_REDIS;
|
process.env.REDIS_PING_INTERVAL = '1000';
|
||||||
delete process.env.REDIS_URI;
|
|
||||||
delete process.env.USE_REDIS_CLUSTER;
|
|
||||||
delete process.env.REDIS_PING_INTERVAL;
|
|
||||||
delete process.env.REDIS_KEY_PREFIX;
|
|
||||||
|
|
||||||
// Set common test configuration
|
|
||||||
process.env.REDIS_PING_INTERVAL = '0';
|
|
||||||
process.env.REDIS_KEY_PREFIX = 'Redis-Integration-Test';
|
process.env.REDIS_KEY_PREFIX = 'Redis-Integration-Test';
|
||||||
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
process.env.REDIS_RETRY_MAX_ATTEMPTS = '5';
|
||||||
process.env.REDIS_PING_INTERVAL = '1000';
|
process.env.USE_REDIS = process.env.USE_REDIS || 'true';
|
||||||
|
process.env.USE_REDIS_CLUSTER = process.env.USE_REDIS_CLUSTER || 'false';
|
||||||
|
process.env.REDIS_URI = process.env.REDIS_URI || 'redis://127.0.0.1:6379';
|
||||||
|
|
||||||
// Clear module cache to reload module
|
// Clear module cache to reload module
|
||||||
jest.resetModules();
|
jest.resetModules();
|
||||||
|
|
@ -105,10 +104,6 @@ describe('redisClients Integration Tests', () => {
|
||||||
|
|
||||||
describe('when connecting to a Redis instance', () => {
|
describe('when connecting to a Redis instance', () => {
|
||||||
test('should connect and perform set/get/delete operations', async () => {
|
test('should connect and perform set/get/delete operations', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const clients = await import('../redisClients');
|
const clients = await import('../redisClients');
|
||||||
ioredisClient = clients.ioredisClient;
|
ioredisClient = clients.ioredisClient;
|
||||||
await testRedisOperations(ioredisClient!, 'ioredis-single');
|
await testRedisOperations(ioredisClient!, 'ioredis-single');
|
||||||
|
|
@ -117,7 +112,6 @@ describe('redisClients Integration Tests', () => {
|
||||||
|
|
||||||
describe('when connecting to a Redis cluster', () => {
|
describe('when connecting to a Redis cluster', () => {
|
||||||
test('should connect to cluster and perform set/get/delete operations', async () => {
|
test('should connect to cluster and perform set/get/delete operations', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'true';
|
process.env.USE_REDIS_CLUSTER = 'true';
|
||||||
process.env.REDIS_URI =
|
process.env.REDIS_URI =
|
||||||
'redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003';
|
'redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003';
|
||||||
|
|
@ -142,26 +136,21 @@ describe('redisClients Integration Tests', () => {
|
||||||
|
|
||||||
describe('when connecting to a Redis instance', () => {
|
describe('when connecting to a Redis instance', () => {
|
||||||
test('should connect and perform set/get/delete operations', async () => {
|
test('should connect and perform set/get/delete operations', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'false';
|
|
||||||
process.env.REDIS_URI = 'redis://127.0.0.1:6379';
|
|
||||||
|
|
||||||
const clients = await import('../redisClients');
|
const clients = await import('../redisClients');
|
||||||
keyvRedisClient = clients.keyvRedisClient;
|
keyvRedisClient = clients.keyvRedisClient;
|
||||||
await testRedisOperations(keyvRedisClient!, 'keyv-single');
|
await testRedisOperations(keyvRedisClient!, 'keyv-single', clients.keyvRedisClientReady!);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when connecting to a Redis cluster', () => {
|
describe('when connecting to a Redis cluster', () => {
|
||||||
test('should connect to cluster and perform set/get/delete operations', async () => {
|
test('should connect to cluster and perform set/get/delete operations', async () => {
|
||||||
process.env.USE_REDIS = 'true';
|
|
||||||
process.env.USE_REDIS_CLUSTER = 'true';
|
process.env.USE_REDIS_CLUSTER = 'true';
|
||||||
process.env.REDIS_URI =
|
process.env.REDIS_URI =
|
||||||
'redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003';
|
'redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003';
|
||||||
|
|
||||||
const clients = await import('../redisClients');
|
const clients = await import('../redisClients');
|
||||||
keyvRedisClient = clients.keyvRedisClient;
|
keyvRedisClient = clients.keyvRedisClient;
|
||||||
await testRedisOperations(keyvRedisClient!, 'keyv-cluster');
|
await testRedisOperations(keyvRedisClient!, 'keyv-cluster', clients.keyvRedisClientReady!);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
29
packages/api/src/cache/redisClients.ts
vendored
29
packages/api/src/cache/redisClients.ts
vendored
|
|
@ -3,6 +3,7 @@ import type { Redis, Cluster } from 'ioredis';
|
||||||
import { logger } from '@librechat/data-schemas';
|
import { logger } from '@librechat/data-schemas';
|
||||||
import { createClient, createCluster } from '@keyv/redis';
|
import { createClient, createCluster } from '@keyv/redis';
|
||||||
import type { RedisClientType, RedisClusterType } from '@redis/client';
|
import type { RedisClientType, RedisClusterType } from '@redis/client';
|
||||||
|
import type { ScanCommandOptions } from '@redis/client/dist/lib/commands/SCAN';
|
||||||
import { cacheConfig } from './cacheConfig';
|
import { cacheConfig } from './cacheConfig';
|
||||||
|
|
||||||
const urls = cacheConfig.REDIS_URI?.split(',').map((uri) => new URL(uri)) || [];
|
const urls = cacheConfig.REDIS_URI?.split(',').map((uri) => new URL(uri)) || [];
|
||||||
|
|
@ -121,6 +122,11 @@ if (cacheConfig.USE_REDIS) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let keyvRedisClient: RedisClientType | RedisClusterType | null = null;
|
let keyvRedisClient: RedisClientType | RedisClusterType | null = null;
|
||||||
|
let keyvRedisClientReady:
|
||||||
|
| Promise<void>
|
||||||
|
| Promise<RedisClientType<Record<string, never>, Record<string, never>, Record<string, never>>>
|
||||||
|
| null = null;
|
||||||
|
|
||||||
if (cacheConfig.USE_REDIS) {
|
if (cacheConfig.USE_REDIS) {
|
||||||
/**
|
/**
|
||||||
* ** WARNING ** Keyv Redis client does not support Prefix like ioredis above.
|
* ** WARNING ** Keyv Redis client does not support Prefix like ioredis above.
|
||||||
|
|
@ -162,6 +168,22 @@ if (cacheConfig.USE_REDIS) {
|
||||||
defaults: redisOptions,
|
defaults: redisOptions,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add scanIterator method to cluster client for API consistency with standalone client
|
||||||
|
if (!('scanIterator' in keyvRedisClient)) {
|
||||||
|
const clusterClient = keyvRedisClient as RedisClusterType;
|
||||||
|
(keyvRedisClient as unknown as RedisClientType).scanIterator = async function* (
|
||||||
|
options?: ScanCommandOptions,
|
||||||
|
) {
|
||||||
|
const masters = clusterClient.masters;
|
||||||
|
for (const master of masters) {
|
||||||
|
const nodeClient = await clusterClient.nodeClient(master);
|
||||||
|
for await (const key of nodeClient.scanIterator(options)) {
|
||||||
|
yield key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
keyvRedisClient.setMaxListeners(cacheConfig.REDIS_MAX_LISTENERS);
|
keyvRedisClient.setMaxListeners(cacheConfig.REDIS_MAX_LISTENERS);
|
||||||
|
|
||||||
keyvRedisClient.on('error', (err) => {
|
keyvRedisClient.on('error', (err) => {
|
||||||
|
|
@ -184,10 +206,13 @@ if (cacheConfig.USE_REDIS) {
|
||||||
logger.warn('@keyv/redis client disconnected');
|
logger.warn('@keyv/redis client disconnected');
|
||||||
});
|
});
|
||||||
|
|
||||||
keyvRedisClient.connect().catch((err) => {
|
// Start connection immediately
|
||||||
|
keyvRedisClientReady = keyvRedisClient.connect();
|
||||||
|
|
||||||
|
keyvRedisClientReady.catch((err): void => {
|
||||||
logger.error('@keyv/redis initial connection failed:', err);
|
logger.error('@keyv/redis initial connection failed:', err);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ioredisClient, keyvRedisClient };
|
export { ioredisClient, keyvRedisClient, keyvRedisClientReady };
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,8 @@ describe('LeaderElection with Redis', () => {
|
||||||
throw new Error('Redis client is not initialized');
|
throw new Error('Redis client is not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for Redis to be ready
|
// Wait for connection and topology discovery to complete
|
||||||
if (!keyvRedisClient.isOpen) {
|
await redisClients.keyvRedisClientReady;
|
||||||
await keyvRedisClient.connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase max listeners to handle many instances in tests
|
// Increase max listeners to handle many instances in tests
|
||||||
process.setMaxListeners(200);
|
process.setMaxListeners(200);
|
||||||
|
|
|
||||||
|
|
@ -940,6 +940,16 @@ describe('getOpenAIConfig', () => {
|
||||||
{ reasoning_effort: null, reasoning_summary: null, shouldHaveReasoning: false },
|
{ reasoning_effort: null, reasoning_summary: null, shouldHaveReasoning: false },
|
||||||
{ reasoning_effort: undefined, reasoning_summary: undefined, shouldHaveReasoning: false },
|
{ reasoning_effort: undefined, reasoning_summary: undefined, shouldHaveReasoning: false },
|
||||||
{ reasoning_effort: '', reasoning_summary: '', shouldHaveReasoning: false },
|
{ reasoning_effort: '', reasoning_summary: '', shouldHaveReasoning: false },
|
||||||
|
{
|
||||||
|
reasoning_effort: ReasoningEffort.unset,
|
||||||
|
reasoning_summary: '',
|
||||||
|
shouldHaveReasoning: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reasoning_effort: ReasoningEffort.none,
|
||||||
|
reasoning_summary: null,
|
||||||
|
shouldHaveReasoning: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reasoning_effort: null,
|
reasoning_effort: null,
|
||||||
reasoning_summary: ReasoningSummary.concise,
|
reasoning_summary: ReasoningSummary.concise,
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,11 @@ export function getOpenAILLMConfig({
|
||||||
delete modelKwargs.verbosity;
|
delete modelKwargs.verbosity;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (llmConfig.model && /\bgpt-[5-9]\b/i.test(llmConfig.model) && llmConfig.maxTokens != null) {
|
if (
|
||||||
|
llmConfig.model &&
|
||||||
|
/\bgpt-[5-9](?:\.\d+)?\b/i.test(llmConfig.model) &&
|
||||||
|
llmConfig.maxTokens != null
|
||||||
|
) {
|
||||||
const paramName =
|
const paramName =
|
||||||
llmConfig.useResponsesApi === true ? 'max_output_tokens' : 'max_completion_tokens';
|
llmConfig.useResponsesApi === true ? 'max_output_tokens' : 'max_completion_tokens';
|
||||||
modelKwargs[paramName] = llmConfig.maxTokens;
|
modelKwargs[paramName] = llmConfig.maxTokens;
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,8 @@ describe('MCPServersInitializer Redis Integration Tests', () => {
|
||||||
// Ensure Redis is connected
|
// Ensure Redis is connected
|
||||||
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
||||||
|
|
||||||
// Wait for Redis to be ready
|
// Wait for connection and topology discovery to complete
|
||||||
if (!keyvRedisClient.isOpen) await keyvRedisClient.connect();
|
await redisClients.keyvRedisClientReady;
|
||||||
|
|
||||||
// Become leader so we can perform write operations
|
// Become leader so we can perform write operations
|
||||||
leaderInstance = new LeaderElection();
|
leaderInstance = new LeaderElection();
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ describe('MCPServersRegistry Redis Integration Tests', () => {
|
||||||
// Ensure Redis is connected
|
// Ensure Redis is connected
|
||||||
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
||||||
|
|
||||||
// Wait for Redis to be ready
|
// Wait for connection and topology discovery to complete
|
||||||
if (!keyvRedisClient.isOpen) await keyvRedisClient.connect();
|
await redisClients.keyvRedisClientReady;
|
||||||
|
|
||||||
// Become leader so we can perform write operations
|
// Become leader so we can perform write operations
|
||||||
leaderInstance = new LeaderElection();
|
leaderInstance = new LeaderElection();
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,8 @@ export class ServerConfigsCacheRedis extends BaseRegistryCache {
|
||||||
entries.push([keyName, value as ParsedServerConfig]);
|
entries.push([keyName, value as ParsedServerConfig]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Redis client with scanIterator not available.');
|
||||||
}
|
}
|
||||||
|
|
||||||
return fromPairs(entries);
|
return fromPairs(entries);
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ describe('RegistryStatusCache Integration Tests', () => {
|
||||||
// Ensure Redis is connected
|
// Ensure Redis is connected
|
||||||
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
||||||
|
|
||||||
// Wait for Redis to be ready
|
// Wait for connection and topology discovery to complete
|
||||||
if (!keyvRedisClient.isOpen) await keyvRedisClient.connect();
|
await redisClients.keyvRedisClientReady;
|
||||||
|
|
||||||
// Become leader so we can perform write operations
|
// Become leader so we can perform write operations
|
||||||
leaderInstance = new LeaderElection();
|
leaderInstance = new LeaderElection();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,10 @@ describe('ServerConfigsCacheRedis Integration Tests', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// Set up environment variables for Redis (only if not already set)
|
// Set up environment variables for Redis (only if not already set)
|
||||||
process.env.USE_REDIS = process.env.USE_REDIS ?? 'true';
|
process.env.USE_REDIS = process.env.USE_REDIS ?? 'true';
|
||||||
process.env.REDIS_URI = process.env.REDIS_URI ?? 'redis://127.0.0.1:6379';
|
process.env.USE_REDIS_CLUSTER = process.env.USE_REDIS_CLUSTER ?? 'true';
|
||||||
|
process.env.REDIS_URI =
|
||||||
|
process.env.REDIS_URI ??
|
||||||
|
'redis://127.0.0.1:7001,redis://127.0.0.1:7002,redis://127.0.0.1:7003';
|
||||||
process.env.REDIS_KEY_PREFIX =
|
process.env.REDIS_KEY_PREFIX =
|
||||||
process.env.REDIS_KEY_PREFIX ?? 'ServerConfigsCacheRedis-IntegrationTest';
|
process.env.REDIS_KEY_PREFIX ?? 'ServerConfigsCacheRedis-IntegrationTest';
|
||||||
|
|
||||||
|
|
@ -49,8 +52,8 @@ describe('ServerConfigsCacheRedis Integration Tests', () => {
|
||||||
// Ensure Redis is connected
|
// Ensure Redis is connected
|
||||||
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
if (!keyvRedisClient) throw new Error('Redis client is not initialized');
|
||||||
|
|
||||||
// Wait for Redis to be ready
|
// Wait for connection and topology discovery to complete
|
||||||
if (!keyvRedisClient.isOpen) await keyvRedisClient.connect();
|
await redisClients.keyvRedisClientReady;
|
||||||
|
|
||||||
// Clear any existing leader key to ensure clean state
|
// Clear any existing leader key to ensure clean state
|
||||||
await keyvRedisClient.del(LeaderElection.LEADER_KEY);
|
await keyvRedisClient.del(LeaderElection.LEADER_KEY);
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.12.1",
|
"axios": "^1.12.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
"@types/react": "^18.2.18",
|
"@types/react": "^18.2.18",
|
||||||
"@types/winston": "^2.4.4",
|
"@types/winston": "^2.4.4",
|
||||||
"jest": "^29.5.0",
|
"jest": "^30.2.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"openapi-types": "^12.1.3",
|
"openapi-types": "^12.1.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
|
|
|
||||||
|
|
@ -927,7 +927,7 @@ export enum KnownEndpoints {
|
||||||
|
|
||||||
export enum FetchTokenConfig {
|
export enum FetchTokenConfig {
|
||||||
openrouter = KnownEndpoints.openrouter,
|
openrouter = KnownEndpoints.openrouter,
|
||||||
helicone = KnownEndpoints.helicone
|
helicone = KnownEndpoints.helicone,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultEndpoints: EModelEndpoint[] = [
|
export const defaultEndpoints: EModelEndpoint[] = [
|
||||||
|
|
@ -964,6 +964,10 @@ export const alternateName = {
|
||||||
};
|
};
|
||||||
|
|
||||||
const sharedOpenAIModels = [
|
const sharedOpenAIModels = [
|
||||||
|
'gpt-5.1',
|
||||||
|
'gpt-5.1-chat-latest',
|
||||||
|
'gpt-5.1-codex',
|
||||||
|
'gpt-5.1-codex-mini',
|
||||||
'gpt-5',
|
'gpt-5',
|
||||||
'gpt-5-mini',
|
'gpt-5-mini',
|
||||||
'gpt-5-nano',
|
'gpt-5-nano',
|
||||||
|
|
|
||||||
|
|
@ -230,9 +230,10 @@ const openAIParams: Record<string, SettingDefinition> = {
|
||||||
description: 'com_endpoint_openai_reasoning_effort',
|
description: 'com_endpoint_openai_reasoning_effort',
|
||||||
descriptionCode: true,
|
descriptionCode: true,
|
||||||
type: 'enum',
|
type: 'enum',
|
||||||
default: ReasoningEffort.none,
|
default: ReasoningEffort.unset,
|
||||||
component: 'slider',
|
component: 'slider',
|
||||||
options: [
|
options: [
|
||||||
|
ReasoningEffort.unset,
|
||||||
ReasoningEffort.none,
|
ReasoningEffort.none,
|
||||||
ReasoningEffort.minimal,
|
ReasoningEffort.minimal,
|
||||||
ReasoningEffort.low,
|
ReasoningEffort.low,
|
||||||
|
|
@ -240,6 +241,7 @@ const openAIParams: Record<string, SettingDefinition> = {
|
||||||
ReasoningEffort.high,
|
ReasoningEffort.high,
|
||||||
],
|
],
|
||||||
enumMappings: {
|
enumMappings: {
|
||||||
|
[ReasoningEffort.unset]: 'com_ui_auto',
|
||||||
[ReasoningEffort.none]: 'com_ui_none',
|
[ReasoningEffort.none]: 'com_ui_none',
|
||||||
[ReasoningEffort.minimal]: 'com_ui_minimal',
|
[ReasoningEffort.minimal]: 'com_ui_minimal',
|
||||||
[ReasoningEffort.low]: 'com_ui_low',
|
[ReasoningEffort.low]: 'com_ui_low',
|
||||||
|
|
@ -291,7 +293,7 @@ const openAIParams: Record<string, SettingDefinition> = {
|
||||||
ReasoningSummary.detailed,
|
ReasoningSummary.detailed,
|
||||||
],
|
],
|
||||||
enumMappings: {
|
enumMappings: {
|
||||||
[ReasoningSummary.none]: 'com_ui_none',
|
[ReasoningSummary.none]: 'com_ui_unset',
|
||||||
[ReasoningSummary.auto]: 'com_ui_auto',
|
[ReasoningSummary.auto]: 'com_ui_auto',
|
||||||
[ReasoningSummary.concise]: 'com_ui_concise',
|
[ReasoningSummary.concise]: 'com_ui_concise',
|
||||||
[ReasoningSummary.detailed]: 'com_ui_detailed',
|
[ReasoningSummary.detailed]: 'com_ui_detailed',
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,8 @@ export enum ImageDetail {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ReasoningEffort {
|
export enum ReasoningEffort {
|
||||||
none = '',
|
unset = '',
|
||||||
|
none = 'none',
|
||||||
minimal = 'minimal',
|
minimal = 'minimal',
|
||||||
low = 'low',
|
low = 'low',
|
||||||
medium = 'medium',
|
medium = 'medium',
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.2",
|
"@types/jest": "^29.5.2",
|
||||||
"@types/node": "^20.3.0",
|
"@types/node": "^20.3.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^30.2.0",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"mongodb-memory-server": "^10.1.4",
|
"mongodb-memory-server": "^10.1.4",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue