mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-01-28 13:16:13 +01:00
Merge branch 'dev' into feat/multi-lang-Terms-of-service
This commit is contained in:
commit
97a6074edc
660 changed files with 35171 additions and 17122 deletions
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "librechat-data-provider",
|
||||
"version": "0.7.86",
|
||||
"version": "0.7.899",
|
||||
"description": "data services for librechat apps",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.es.js",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import axios from 'axios';
|
||||
import { z } from 'zod';
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
import axios from 'axios';
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
import type { ParametersSchema } from '../src/actions';
|
||||
import type { FlowchartSchema } from './openapiSpecs';
|
||||
import {
|
||||
createURL,
|
||||
resolveRef,
|
||||
|
|
@ -15,9 +17,7 @@ import {
|
|||
scholarAIOpenapiSpec,
|
||||
swapidev,
|
||||
} from './openapiSpecs';
|
||||
import { AuthorizationTypeEnum, AuthTypeEnum } from '../src/types/assistants';
|
||||
import type { FlowchartSchema } from './openapiSpecs';
|
||||
import type { ParametersSchema } from '../src/actions';
|
||||
import { AuthorizationTypeEnum, AuthTypeEnum } from '../src/types/agents';
|
||||
|
||||
jest.mock('axios');
|
||||
const mockedAxios = axios as jest.Mocked<typeof axios>;
|
||||
|
|
@ -275,8 +275,7 @@ describe('ActionRequest', () => {
|
|||
expect(config?.headers).toEqual({
|
||||
'some-header': 'header-var',
|
||||
});
|
||||
expect(config?.params).toEqual({
|
||||
});
|
||||
expect(config?.params).toEqual({});
|
||||
expect(response.data.success).toBe(true);
|
||||
});
|
||||
|
||||
|
|
@ -285,13 +284,13 @@ describe('ActionRequest', () => {
|
|||
|
||||
const data: Record<string, unknown> = {
|
||||
'api-version': '2025-01-01',
|
||||
'message': 'a body parameter',
|
||||
message: 'a body parameter',
|
||||
'some-header': 'header-var',
|
||||
};
|
||||
|
||||
const loc: Record<string, 'query' | 'path' | 'header' | 'body'> = {
|
||||
'api-version': 'query',
|
||||
'message': 'body',
|
||||
message: 'body',
|
||||
'some-header': 'header',
|
||||
};
|
||||
|
||||
|
|
@ -326,13 +325,13 @@ describe('ActionRequest', () => {
|
|||
|
||||
const data: Record<string, unknown> = {
|
||||
'api-version': '2025-01-01',
|
||||
'message': 'a body parameter',
|
||||
message: 'a body parameter',
|
||||
'some-header': 'header-var',
|
||||
};
|
||||
|
||||
const loc: Record<string, 'query' | 'path' | 'header' | 'body'> = {
|
||||
'api-version': 'query',
|
||||
'message': 'body',
|
||||
message: 'body',
|
||||
'some-header': 'header',
|
||||
};
|
||||
|
||||
|
|
@ -367,13 +366,13 @@ describe('ActionRequest', () => {
|
|||
|
||||
const data: Record<string, unknown> = {
|
||||
'api-version': '2025-01-01',
|
||||
'message': 'a body parameter',
|
||||
message: 'a body parameter',
|
||||
'some-header': 'header-var',
|
||||
};
|
||||
|
||||
const loc: Record<string, 'query' | 'path' | 'header' | 'body'> = {
|
||||
'api-version': 'query',
|
||||
'message': 'body',
|
||||
message: 'body',
|
||||
'some-header': 'header',
|
||||
};
|
||||
|
||||
|
|
@ -443,7 +442,6 @@ describe('ActionRequest', () => {
|
|||
});
|
||||
expect(response.data.success).toBe(true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('throws an error for unsupported HTTP method', async () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable jest/no-conditional-expect */
|
||||
import { ZodError, z } from 'zod';
|
||||
import { generateDynamicSchema, validateSettingDefinitions, OptionTypes } from '../src/generate';
|
||||
import type { SettingsConfiguration } from '../src/generate';
|
||||
|
|
@ -97,6 +96,37 @@ describe('generateDynamicSchema', () => {
|
|||
expect(result['data']).toEqual({ testEnum: 'option2' });
|
||||
});
|
||||
|
||||
it('should generate a schema for enum settings with empty string option', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'testEnumWithEmpty',
|
||||
description: 'A test enum setting with empty string',
|
||||
type: 'enum',
|
||||
default: '',
|
||||
options: ['', 'option1', 'option2'],
|
||||
enumMappings: {
|
||||
'': 'None',
|
||||
option1: 'First Option',
|
||||
option2: 'Second Option',
|
||||
},
|
||||
component: 'slider',
|
||||
columnSpan: 2,
|
||||
label: 'Test Enum with Empty String',
|
||||
},
|
||||
];
|
||||
|
||||
const schema = generateDynamicSchema(settings);
|
||||
const result = schema.safeParse({ testEnumWithEmpty: '' });
|
||||
|
||||
expect(result.success).toBeTruthy();
|
||||
expect(result['data']).toEqual({ testEnumWithEmpty: '' });
|
||||
|
||||
// Test with non-empty option
|
||||
const result2 = schema.safeParse({ testEnumWithEmpty: 'option1' });
|
||||
expect(result2.success).toBeTruthy();
|
||||
expect(result2['data']).toEqual({ testEnumWithEmpty: 'option1' });
|
||||
});
|
||||
|
||||
it('should fail for incorrect enum value', () => {
|
||||
const settings: SettingsConfiguration = [
|
||||
{
|
||||
|
|
@ -481,6 +511,47 @@ describe('validateSettingDefinitions', () => {
|
|||
|
||||
expect(() => validateSettingDefinitions(settingsExceedingMaxTags)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Test for incomplete enumMappings
|
||||
test('should throw error for incomplete enumMappings', () => {
|
||||
const settingsWithIncompleteEnumMappings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'displayMode',
|
||||
type: 'enum',
|
||||
component: 'dropdown',
|
||||
options: ['light', 'dark', 'auto'],
|
||||
enumMappings: {
|
||||
light: 'Light Mode',
|
||||
dark: 'Dark Mode',
|
||||
// Missing mapping for 'auto'
|
||||
},
|
||||
optionType: OptionTypes.Custom,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settingsWithIncompleteEnumMappings)).toThrow(ZodError);
|
||||
});
|
||||
|
||||
// Test for complete enumMappings including empty string
|
||||
test('should not throw error for complete enumMappings including empty string', () => {
|
||||
const settingsWithCompleteEnumMappings: SettingsConfiguration = [
|
||||
{
|
||||
key: 'selectionMode',
|
||||
type: 'enum',
|
||||
component: 'slider',
|
||||
options: ['', 'single', 'multiple'],
|
||||
enumMappings: {
|
||||
'': 'None',
|
||||
single: 'Single Selection',
|
||||
multiple: 'Multiple Selection',
|
||||
},
|
||||
default: '',
|
||||
optionType: OptionTypes.Custom,
|
||||
},
|
||||
];
|
||||
|
||||
expect(() => validateSettingDefinitions(settingsWithCompleteEnumMappings)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
const settingsConfiguration: SettingsConfiguration = [
|
||||
|
|
@ -515,7 +586,7 @@ const settingsConfiguration: SettingsConfiguration = [
|
|||
{
|
||||
key: 'presence_penalty',
|
||||
description:
|
||||
'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.',
|
||||
"Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.",
|
||||
type: 'number',
|
||||
default: 0,
|
||||
range: {
|
||||
|
|
@ -529,7 +600,7 @@ const settingsConfiguration: SettingsConfiguration = [
|
|||
{
|
||||
key: 'frequency_penalty',
|
||||
description:
|
||||
'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.',
|
||||
"Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.",
|
||||
type: 'number',
|
||||
default: 0,
|
||||
range: {
|
||||
|
|
|
|||
|
|
@ -1,277 +0,0 @@
|
|||
import { StdioOptionsSchema, StreamableHTTPOptionsSchema, processMCPEnv, MCPOptions } from '../src/mcp';
|
||||
|
||||
describe('Environment Variable Extraction (MCP)', () => {
|
||||
const originalEnv = process.env;
|
||||
|
||||
beforeEach(() => {
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
TEST_API_KEY: 'test-api-key-value',
|
||||
ANOTHER_SECRET: 'another-secret-value',
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('StdioOptionsSchema', () => {
|
||||
it('should transform environment variables in the env field', () => {
|
||||
const options = {
|
||||
command: 'node',
|
||||
args: ['server.js'],
|
||||
env: {
|
||||
API_KEY: '${TEST_API_KEY}',
|
||||
ANOTHER_KEY: '${ANOTHER_SECRET}',
|
||||
PLAIN_VALUE: 'plain-value',
|
||||
NON_EXISTENT: '${NON_EXISTENT_VAR}',
|
||||
},
|
||||
};
|
||||
|
||||
const result = StdioOptionsSchema.parse(options);
|
||||
|
||||
expect(result.env).toEqual({
|
||||
API_KEY: 'test-api-key-value',
|
||||
ANOTHER_KEY: 'another-secret-value',
|
||||
PLAIN_VALUE: 'plain-value',
|
||||
NON_EXISTENT: '${NON_EXISTENT_VAR}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle undefined env field', () => {
|
||||
const options = {
|
||||
command: 'node',
|
||||
args: ['server.js'],
|
||||
};
|
||||
|
||||
const result = StdioOptionsSchema.parse(options);
|
||||
|
||||
expect(result.env).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('StreamableHTTPOptionsSchema', () => {
|
||||
it('should validate a valid streamable-http configuration', () => {
|
||||
const options = {
|
||||
type: 'streamable-http',
|
||||
url: 'https://example.com/api',
|
||||
headers: {
|
||||
Authorization: 'Bearer token',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const result = StreamableHTTPOptionsSchema.parse(options);
|
||||
|
||||
expect(result).toEqual(options);
|
||||
});
|
||||
|
||||
it('should reject websocket URLs', () => {
|
||||
const options = {
|
||||
type: 'streamable-http',
|
||||
url: 'ws://example.com/socket',
|
||||
};
|
||||
|
||||
expect(() => StreamableHTTPOptionsSchema.parse(options)).toThrow();
|
||||
});
|
||||
|
||||
it('should reject secure websocket URLs', () => {
|
||||
const options = {
|
||||
type: 'streamable-http',
|
||||
url: 'wss://example.com/socket',
|
||||
};
|
||||
|
||||
expect(() => StreamableHTTPOptionsSchema.parse(options)).toThrow();
|
||||
});
|
||||
|
||||
it('should require type field to be set explicitly', () => {
|
||||
const options = {
|
||||
url: 'https://example.com/api',
|
||||
};
|
||||
|
||||
// Type is now required, so parsing should fail
|
||||
expect(() => StreamableHTTPOptionsSchema.parse(options)).toThrow();
|
||||
|
||||
// With type provided, it should pass
|
||||
const validOptions = {
|
||||
type: 'streamable-http' as const,
|
||||
url: 'https://example.com/api',
|
||||
};
|
||||
|
||||
const result = StreamableHTTPOptionsSchema.parse(validOptions);
|
||||
expect(result.type).toBe('streamable-http');
|
||||
});
|
||||
|
||||
it('should validate headers as record of strings', () => {
|
||||
const options = {
|
||||
type: 'streamable-http',
|
||||
url: 'https://example.com/api',
|
||||
headers: {
|
||||
'X-API-Key': '123456',
|
||||
'User-Agent': 'MCP Client',
|
||||
},
|
||||
};
|
||||
|
||||
const result = StreamableHTTPOptionsSchema.parse(options);
|
||||
|
||||
expect(result.headers).toEqual(options.headers);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processMCPEnv', () => {
|
||||
it('should create a deep clone of the input object', () => {
|
||||
const originalObj: MCPOptions = {
|
||||
command: 'node',
|
||||
args: ['server.js'],
|
||||
env: {
|
||||
API_KEY: '${TEST_API_KEY}',
|
||||
PLAIN_VALUE: 'plain-value',
|
||||
},
|
||||
};
|
||||
|
||||
const result = processMCPEnv(originalObj);
|
||||
|
||||
// Verify it's not the same object reference
|
||||
expect(result).not.toBe(originalObj);
|
||||
|
||||
// Modify the result and ensure original is unchanged
|
||||
if ('env' in result && result.env) {
|
||||
result.env.API_KEY = 'modified-value';
|
||||
}
|
||||
|
||||
expect(originalObj.env?.API_KEY).toBe('${TEST_API_KEY}');
|
||||
});
|
||||
|
||||
it('should process environment variables in env field', () => {
|
||||
const obj: MCPOptions = {
|
||||
command: 'node',
|
||||
args: ['server.js'],
|
||||
env: {
|
||||
API_KEY: '${TEST_API_KEY}',
|
||||
ANOTHER_KEY: '${ANOTHER_SECRET}',
|
||||
PLAIN_VALUE: 'plain-value',
|
||||
NON_EXISTENT: '${NON_EXISTENT_VAR}',
|
||||
},
|
||||
};
|
||||
|
||||
const result = processMCPEnv(obj);
|
||||
|
||||
expect('env' in result && result.env).toEqual({
|
||||
API_KEY: 'test-api-key-value',
|
||||
ANOTHER_KEY: 'another-secret-value',
|
||||
PLAIN_VALUE: 'plain-value',
|
||||
NON_EXISTENT: '${NON_EXISTENT_VAR}',
|
||||
});
|
||||
});
|
||||
|
||||
it('should process user ID in headers field', () => {
|
||||
const userId = 'test-user-123';
|
||||
const obj: MCPOptions = {
|
||||
type: 'sse',
|
||||
url: 'https://example.com',
|
||||
headers: {
|
||||
Authorization: '${TEST_API_KEY}',
|
||||
'User-Id': '{{LIBRECHAT_USER_ID}}',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const result = processMCPEnv(obj, userId);
|
||||
|
||||
expect('headers' in result && result.headers).toEqual({
|
||||
Authorization: 'test-api-key-value',
|
||||
'User-Id': 'test-user-123',
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle null or undefined input', () => {
|
||||
// @ts-ignore - Testing null/undefined handling
|
||||
expect(processMCPEnv(null)).toBeNull();
|
||||
// @ts-ignore - Testing null/undefined handling
|
||||
expect(processMCPEnv(undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not modify objects without env or headers', () => {
|
||||
const obj: MCPOptions = {
|
||||
command: 'node',
|
||||
args: ['server.js'],
|
||||
timeout: 5000,
|
||||
};
|
||||
|
||||
const result = processMCPEnv(obj);
|
||||
|
||||
expect(result).toEqual(obj);
|
||||
expect(result).not.toBe(obj); // Still a different object (deep clone)
|
||||
});
|
||||
|
||||
it('should ensure different users with same starting config get separate values', () => {
|
||||
// Create a single base configuration
|
||||
const baseConfig: MCPOptions = {
|
||||
type: 'sse',
|
||||
url: 'https://example.com',
|
||||
headers: {
|
||||
'User-Id': '{{LIBRECHAT_USER_ID}}',
|
||||
'API-Key': '${TEST_API_KEY}',
|
||||
},
|
||||
};
|
||||
|
||||
// Process for two different users
|
||||
const user1Id = 'user-123';
|
||||
const user2Id = 'user-456';
|
||||
|
||||
const resultUser1 = processMCPEnv(baseConfig, user1Id);
|
||||
const resultUser2 = processMCPEnv(baseConfig, user2Id);
|
||||
|
||||
// Verify each has the correct user ID
|
||||
expect('headers' in resultUser1 && resultUser1.headers?.['User-Id']).toBe(user1Id);
|
||||
expect('headers' in resultUser2 && resultUser2.headers?.['User-Id']).toBe(user2Id);
|
||||
|
||||
// Verify they're different objects
|
||||
expect(resultUser1).not.toBe(resultUser2);
|
||||
|
||||
// Modify one result and ensure it doesn't affect the other
|
||||
if ('headers' in resultUser1 && resultUser1.headers) {
|
||||
resultUser1.headers['User-Id'] = 'modified-user';
|
||||
}
|
||||
|
||||
// Original config should be unchanged
|
||||
expect(baseConfig.headers?.['User-Id']).toBe('{{LIBRECHAT_USER_ID}}');
|
||||
|
||||
// Second user's config should be unchanged
|
||||
expect('headers' in resultUser2 && resultUser2.headers?.['User-Id']).toBe(user2Id);
|
||||
});
|
||||
|
||||
it('should process headers in streamable-http options', () => {
|
||||
const userId = 'test-user-123';
|
||||
const obj: MCPOptions = {
|
||||
type: 'streamable-http',
|
||||
url: 'https://example.com',
|
||||
headers: {
|
||||
Authorization: '${TEST_API_KEY}',
|
||||
'User-Id': '{{LIBRECHAT_USER_ID}}',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
const result = processMCPEnv(obj, userId);
|
||||
|
||||
expect('headers' in result && result.headers).toEqual({
|
||||
Authorization: 'test-api-key-value',
|
||||
'User-Id': 'test-user-123',
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
});
|
||||
|
||||
it('should maintain streamable-http type in processed options', () => {
|
||||
const obj: MCPOptions = {
|
||||
type: 'streamable-http',
|
||||
url: 'https://example.com/api',
|
||||
};
|
||||
|
||||
const result = processMCPEnv(obj);
|
||||
|
||||
expect(result.type).toBe('streamable-http');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -3,15 +3,11 @@ import _axios from 'axios';
|
|||
import { URL } from 'url';
|
||||
import crypto from 'crypto';
|
||||
import { load } from 'js-yaml';
|
||||
import type {
|
||||
FunctionTool,
|
||||
Schema,
|
||||
Reference,
|
||||
ActionMetadata,
|
||||
ActionMetadataRuntime,
|
||||
} from './types/assistants';
|
||||
import type { ActionMetadata, ActionMetadataRuntime } from './types/agents';
|
||||
import type { FunctionTool, Schema, Reference } from './types/assistants';
|
||||
import { AuthTypeEnum, AuthorizationTypeEnum } from './types/agents';
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
import { Tools, AuthTypeEnum, AuthorizationTypeEnum } from './types/assistants';
|
||||
import { Tools } from './types/assistants';
|
||||
|
||||
export type ParametersSchema = {
|
||||
type: string;
|
||||
|
|
@ -303,7 +299,8 @@ class RequestExecutor {
|
|||
if (this.config.parameterLocations && this.params) {
|
||||
for (const key of Object.keys(this.params)) {
|
||||
// Determine parameter placement; default to "query" for GET and "body" for others.
|
||||
const loc: 'query' | 'path' | 'header' | 'body' = this.config.parameterLocations[key] || (method === 'get' ? 'query' : 'body');
|
||||
const loc: 'query' | 'path' | 'header' | 'body' =
|
||||
this.config.parameterLocations[key] || (method === 'get' ? 'query' : 'body');
|
||||
|
||||
const val = this.params[key];
|
||||
if (loc === 'query') {
|
||||
|
|
@ -351,7 +348,15 @@ export class ActionRequest {
|
|||
contentType: string,
|
||||
parameterLocations?: Record<string, 'query' | 'path' | 'header' | 'body'>,
|
||||
) {
|
||||
this.config = new RequestConfig(domain, path, method, operation, isConsequential, contentType, parameterLocations);
|
||||
this.config = new RequestConfig(
|
||||
domain,
|
||||
path,
|
||||
method,
|
||||
operation,
|
||||
isConsequential,
|
||||
contentType,
|
||||
parameterLocations,
|
||||
);
|
||||
}
|
||||
|
||||
// Add getters to maintain backward compatibility
|
||||
|
|
@ -486,12 +491,12 @@ export function openapiToFunction(
|
|||
}
|
||||
// Record the parameter location from the OpenAPI "in" field.
|
||||
paramLocations[paramName] =
|
||||
(resolvedParam.in === 'query' ||
|
||||
resolvedParam.in === 'path' ||
|
||||
resolvedParam.in === 'header' ||
|
||||
resolvedParam.in === 'body')
|
||||
? resolvedParam.in
|
||||
: 'query';
|
||||
resolvedParam.in === 'query' ||
|
||||
resolvedParam.in === 'path' ||
|
||||
resolvedParam.in === 'header' ||
|
||||
resolvedParam.in === 'body'
|
||||
? resolvedParam.in
|
||||
: 'query';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -70,8 +70,6 @@ export const revokeUserKey = (name: string) => `${keysEndpoint}/${name}`;
|
|||
|
||||
export const revokeAllUserKeys = () => `${keysEndpoint}?all=true`;
|
||||
|
||||
export const abortRequest = (endpoint: string) => `/api/ask/${endpoint}/abort`;
|
||||
|
||||
export const conversationsRoot = '/api/convos';
|
||||
|
||||
export const conversations = (params: q.ConversationListParams) => {
|
||||
|
|
@ -254,6 +252,7 @@ export const getAllPromptGroups = () => `${prompts()}/all`;
|
|||
export const roles = () => '/api/roles';
|
||||
export const getRole = (roleName: string) => `${roles()}/${roleName.toLowerCase()}`;
|
||||
export const updatePromptPermissions = (roleName: string) => `${getRole(roleName)}/prompts`;
|
||||
export const updateMemoryPermissions = (roleName: string) => `${getRole(roleName)}/memories`;
|
||||
export const updateAgentPermissions = (roleName: string) => `${getRole(roleName)}/agents`;
|
||||
|
||||
/* Conversation Tags */
|
||||
|
|
@ -272,6 +271,10 @@ export const userTerms = () => '/api/user/terms';
|
|||
export const acceptUserTerms = () => '/api/user/terms/accept';
|
||||
export const banner = () => '/api/banner';
|
||||
|
||||
// Message Feedback
|
||||
export const feedback = (conversationId: string, messageId: string) =>
|
||||
`/api/messages/${conversationId}/${messageId}/feedback`;
|
||||
|
||||
// Two-Factor Endpoints
|
||||
export const enableTwoFactor = () => '/api/auth/2fa/enable';
|
||||
export const verifyTwoFactor = () => '/api/auth/2fa/verify';
|
||||
|
|
@ -279,3 +282,8 @@ export const confirmTwoFactor = () => '/api/auth/2fa/confirm';
|
|||
export const disableTwoFactor = () => '/api/auth/2fa/disable';
|
||||
export const regenerateBackupCodes = () => '/api/auth/2fa/backup/regenerate';
|
||||
export const verifyTwoFactorTemp = () => '/api/auth/2fa/verify-temp';
|
||||
|
||||
/* Memories */
|
||||
export const memories = () => '/api/memories';
|
||||
export const memory = (key: string) => `${memories()}/${encodeURIComponent(key)}`;
|
||||
export const memoryPreferences = () => `${memories()}/preferences`;
|
||||
|
|
|
|||
|
|
@ -244,21 +244,26 @@ export const defaultAgentCapabilities = [
|
|||
AgentCapabilities.ocr,
|
||||
];
|
||||
|
||||
export const agentsEndpointSChema = baseEndpointSchema.merge(
|
||||
z.object({
|
||||
/* agents specific */
|
||||
recursionLimit: z.number().optional(),
|
||||
disableBuilder: z.boolean().optional(),
|
||||
maxRecursionLimit: z.number().optional(),
|
||||
allowedProviders: z.array(z.union([z.string(), eModelEndpointSchema])).optional(),
|
||||
capabilities: z
|
||||
.array(z.nativeEnum(AgentCapabilities))
|
||||
.optional()
|
||||
.default(defaultAgentCapabilities),
|
||||
}),
|
||||
);
|
||||
export const agentsEndpointSchema = baseEndpointSchema
|
||||
.merge(
|
||||
z.object({
|
||||
/* agents specific */
|
||||
recursionLimit: z.number().optional(),
|
||||
disableBuilder: z.boolean().optional().default(false),
|
||||
maxRecursionLimit: z.number().optional(),
|
||||
allowedProviders: z.array(z.union([z.string(), eModelEndpointSchema])).optional(),
|
||||
capabilities: z
|
||||
.array(z.nativeEnum(AgentCapabilities))
|
||||
.optional()
|
||||
.default(defaultAgentCapabilities),
|
||||
}),
|
||||
)
|
||||
.default({
|
||||
disableBuilder: false,
|
||||
capabilities: defaultAgentCapabilities,
|
||||
});
|
||||
|
||||
export type TAgentsEndpoint = z.infer<typeof agentsEndpointSChema>;
|
||||
export type TAgentsEndpoint = z.infer<typeof agentsEndpointSchema>;
|
||||
|
||||
export const endpointSchema = baseEndpointSchema.merge(
|
||||
z.object({
|
||||
|
|
@ -476,6 +481,12 @@ const termsOfServiceSchema = z.object({
|
|||
|
||||
export type TTermsOfService = z.infer<typeof termsOfServiceSchema>;
|
||||
|
||||
const mcpServersSchema = z.object({
|
||||
placeholder: z.string().optional(),
|
||||
});
|
||||
|
||||
export type TMcpServersConfig = z.infer<typeof mcpServersSchema>;
|
||||
|
||||
export const intefaceSchema = z
|
||||
.object({
|
||||
privacyPolicy: z
|
||||
|
|
@ -486,16 +497,19 @@ export const intefaceSchema = z
|
|||
.optional(),
|
||||
termsOfService: termsOfServiceSchema.optional(),
|
||||
customWelcome: z.string().optional(),
|
||||
mcpServers: mcpServersSchema.optional(),
|
||||
endpointsMenu: z.boolean().optional(),
|
||||
modelSelect: z.boolean().optional(),
|
||||
parameters: z.boolean().optional(),
|
||||
sidePanel: z.boolean().optional(),
|
||||
multiConvo: z.boolean().optional(),
|
||||
bookmarks: z.boolean().optional(),
|
||||
memories: z.boolean().optional(),
|
||||
presets: z.boolean().optional(),
|
||||
prompts: z.boolean().optional(),
|
||||
agents: z.boolean().optional(),
|
||||
temporaryChat: z.boolean().optional(),
|
||||
temporaryChatRetention: z.number().min(1).max(8760).optional(),
|
||||
runCode: z.boolean().optional(),
|
||||
webSearch: z.boolean().optional(),
|
||||
})
|
||||
|
|
@ -507,6 +521,7 @@ export const intefaceSchema = z
|
|||
presets: true,
|
||||
multiConvo: true,
|
||||
bookmarks: true,
|
||||
memories: true,
|
||||
prompts: true,
|
||||
agents: true,
|
||||
temporaryChat: true,
|
||||
|
|
@ -580,11 +595,26 @@ export type TStartupConfig = {
|
|||
scraperType?: ScraperTypes;
|
||||
rerankerType?: RerankerTypes;
|
||||
};
|
||||
mcpServers?: Record<
|
||||
string,
|
||||
{
|
||||
customUserVars: Record<
|
||||
string,
|
||||
{
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
>;
|
||||
}
|
||||
>;
|
||||
mcpPlaceholder?: string;
|
||||
};
|
||||
|
||||
export enum OCRStrategy {
|
||||
MISTRAL_OCR = 'mistral_ocr',
|
||||
CUSTOM_OCR = 'custom_ocr',
|
||||
AZURE_MISTRAL_OCR = 'azure_mistral_ocr',
|
||||
VERTEXAI_MISTRAL_OCR = 'vertexai_mistral_ocr',
|
||||
}
|
||||
|
||||
export enum SearchCategories {
|
||||
|
|
@ -648,11 +678,35 @@ export const balanceSchema = z.object({
|
|||
refillAmount: z.number().optional().default(10000),
|
||||
});
|
||||
|
||||
export const memorySchema = z.object({
|
||||
disabled: z.boolean().optional(),
|
||||
validKeys: z.array(z.string()).optional(),
|
||||
tokenLimit: z.number().optional(),
|
||||
personalize: z.boolean().default(true),
|
||||
messageWindowSize: z.number().optional().default(5),
|
||||
agent: z
|
||||
.union([
|
||||
z.object({
|
||||
id: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
provider: z.string(),
|
||||
model: z.string(),
|
||||
instructions: z.string().optional(),
|
||||
model_parameters: z.record(z.any()).optional(),
|
||||
}),
|
||||
])
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export type TMemoryConfig = z.infer<typeof memorySchema>;
|
||||
|
||||
export const configSchema = z.object({
|
||||
version: z.string(),
|
||||
cache: z.boolean().default(true),
|
||||
ocr: ocrSchema.optional(),
|
||||
webSearch: webSearchSchema.optional(),
|
||||
memory: memorySchema.optional(),
|
||||
secureImageLinks: z.boolean().optional(),
|
||||
imageOutputType: z.nativeEnum(EImageOutputType).default(EImageOutputType.PNG),
|
||||
includedTools: z.array(z.string()).optional(),
|
||||
|
|
@ -693,7 +747,7 @@ export const configSchema = z.object({
|
|||
[EModelEndpoint.azureOpenAI]: azureEndpointSchema.optional(),
|
||||
[EModelEndpoint.azureAssistants]: assistantEndpointSchema.optional(),
|
||||
[EModelEndpoint.assistants]: assistantEndpointSchema.optional(),
|
||||
[EModelEndpoint.agents]: agentsEndpointSChema.optional(),
|
||||
[EModelEndpoint.agents]: agentsEndpointSchema.optional(),
|
||||
[EModelEndpoint.custom]: z.array(endpointSchema.partial()).optional(),
|
||||
[EModelEndpoint.bedrock]: baseEndpointSchema.optional(),
|
||||
})
|
||||
|
|
@ -852,7 +906,6 @@ export const defaultModels = {
|
|||
[EModelEndpoint.assistants]: [...sharedOpenAIModels, 'chatgpt-4o-latest'],
|
||||
[EModelEndpoint.agents]: sharedOpenAIModels, // TODO: Add agent models (agentsModels)
|
||||
[EModelEndpoint.google]: [
|
||||
// Shared Google Models between Vertex AI & Gen AI
|
||||
// Gemini 2.0 Models
|
||||
'gemini-2.0-flash-001',
|
||||
'gemini-2.0-flash-exp',
|
||||
|
|
@ -896,19 +949,11 @@ export const initialModelsConfig: TModelsConfig = {
|
|||
[EModelEndpoint.bedrock]: defaultModels[EModelEndpoint.bedrock],
|
||||
};
|
||||
|
||||
export const EndpointURLs: { [key in EModelEndpoint]: string } = {
|
||||
[EModelEndpoint.openAI]: `/api/ask/${EModelEndpoint.openAI}`,
|
||||
[EModelEndpoint.google]: `/api/ask/${EModelEndpoint.google}`,
|
||||
[EModelEndpoint.custom]: `/api/ask/${EModelEndpoint.custom}`,
|
||||
[EModelEndpoint.anthropic]: `/api/ask/${EModelEndpoint.anthropic}`,
|
||||
[EModelEndpoint.gptPlugins]: `/api/ask/${EModelEndpoint.gptPlugins}`,
|
||||
[EModelEndpoint.azureOpenAI]: `/api/ask/${EModelEndpoint.azureOpenAI}`,
|
||||
[EModelEndpoint.chatGPTBrowser]: `/api/ask/${EModelEndpoint.chatGPTBrowser}`,
|
||||
[EModelEndpoint.azureAssistants]: '/api/assistants/v1/chat',
|
||||
export const EndpointURLs = {
|
||||
[EModelEndpoint.assistants]: '/api/assistants/v2/chat',
|
||||
[EModelEndpoint.azureAssistants]: '/api/assistants/v1/chat',
|
||||
[EModelEndpoint.agents]: `/api/${EModelEndpoint.agents}/chat`,
|
||||
[EModelEndpoint.bedrock]: `/api/${EModelEndpoint.bedrock}/chat`,
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const modularEndpoints = new Set<EModelEndpoint | string>([
|
||||
EModelEndpoint.gptPlugins,
|
||||
|
|
@ -1103,6 +1148,10 @@ export enum CacheKeys {
|
|||
* Key for in-progress flow states.
|
||||
*/
|
||||
FLOWS = 'flows',
|
||||
/**
|
||||
* Key for individual MCP Tool Manifests.
|
||||
*/
|
||||
MCP_TOOLS = 'mcp_tools',
|
||||
/**
|
||||
* Key for pending chat requests (concurrency check)
|
||||
*/
|
||||
|
|
@ -1207,6 +1256,10 @@ export enum ErrorTypes {
|
|||
* Google provider returned an error
|
||||
*/
|
||||
GOOGLE_ERROR = 'google_error',
|
||||
/**
|
||||
* Google provider does not allow custom tools with built-in tools
|
||||
*/
|
||||
GOOGLE_TOOL_CONFLICT = 'google_tool_conflict',
|
||||
/**
|
||||
* Invalid Agent Provider (excluded by Admin)
|
||||
*/
|
||||
|
|
@ -1290,6 +1343,10 @@ export enum SettingsTabValues {
|
|||
* Chat input commands
|
||||
*/
|
||||
COMMANDS = 'commands',
|
||||
/**
|
||||
* Tab for Personalization Settings
|
||||
*/
|
||||
PERSONALIZATION = 'personalization',
|
||||
}
|
||||
|
||||
export enum STTProviders {
|
||||
|
|
@ -1325,9 +1382,9 @@ export enum TTSProviders {
|
|||
/** Enum for app-wide constants */
|
||||
export enum Constants {
|
||||
/** Key for the app's version. */
|
||||
VERSION = 'v0.7.8',
|
||||
VERSION = 'v0.7.9-rc1',
|
||||
/** Key for the Custom Config's version (librechat.yaml). */
|
||||
CONFIG_VERSION = '1.2.6',
|
||||
CONFIG_VERSION = '1.2.8',
|
||||
/** Standard value for the first message's `parentMessageId` value, to indicate no parent exists. */
|
||||
NO_PARENT = '00000000-0000-0000-0000-000000000000',
|
||||
/** Standard value for the initial conversationId before a request is sent */
|
||||
|
|
@ -1354,6 +1411,8 @@ export enum Constants {
|
|||
GLOBAL_PROJECT_NAME = 'instance',
|
||||
/** Delimiter for MCP tools */
|
||||
mcp_delimiter = '_mcp_',
|
||||
/** Prefix for MCP plugins */
|
||||
mcp_prefix = 'mcp_',
|
||||
/** Placeholder Agent ID for Ephemeral Agents */
|
||||
EPHEMERAL_AGENT_ID = 'ephemeral',
|
||||
}
|
||||
|
|
@ -1397,6 +1456,18 @@ export enum LocalStorageKeys {
|
|||
LAST_CODE_TOGGLE_ = 'LAST_CODE_TOGGLE_',
|
||||
/** Last checked toggle for Web Search per conversation ID */
|
||||
LAST_WEB_SEARCH_TOGGLE_ = 'LAST_WEB_SEARCH_TOGGLE_',
|
||||
/** Last checked toggle for File Search per conversation ID */
|
||||
LAST_FILE_SEARCH_TOGGLE_ = 'LAST_FILE_SEARCH_TOGGLE_',
|
||||
/** Key for the last selected agent provider */
|
||||
LAST_AGENT_PROVIDER = 'lastAgentProvider',
|
||||
/** Key for the last selected agent model */
|
||||
LAST_AGENT_MODEL = 'lastAgentModel',
|
||||
/** Pin state for MCP tools per conversation ID */
|
||||
PIN_MCP_ = 'PIN_MCP_',
|
||||
/** Pin state for Web Search per conversation ID */
|
||||
PIN_WEB_SEARCH_ = 'PIN_WEB_SEARCH_',
|
||||
/** Pin state for Code Interpreter per conversation ID */
|
||||
PIN_CODE_INTERPRETER_ = 'PIN_CODE_INTERPRETER_',
|
||||
}
|
||||
|
||||
export enum ForkOptions {
|
||||
|
|
|
|||
|
|
@ -11,31 +11,31 @@ export default function createPayload(submission: t.TSubmission) {
|
|||
isContinued,
|
||||
isTemporary,
|
||||
ephemeralAgent,
|
||||
editedContent,
|
||||
} = submission;
|
||||
const { conversationId } = s.tConvoUpdateSchema.parse(conversation);
|
||||
const { endpoint, endpointType } = endpointOption as {
|
||||
const { endpoint: _e, endpointType } = endpointOption as {
|
||||
endpoint: s.EModelEndpoint;
|
||||
endpointType?: s.EModelEndpoint;
|
||||
};
|
||||
|
||||
let server = EndpointURLs[endpointType ?? endpoint];
|
||||
const isEphemeral = s.isEphemeralAgent(endpoint, ephemeralAgent);
|
||||
|
||||
if (isEdited && s.isAssistantsEndpoint(endpoint)) {
|
||||
server += '/modify';
|
||||
} else if (isEdited) {
|
||||
server = server.replace('/ask/', '/edit/');
|
||||
} else if (isEphemeral) {
|
||||
server = `${EndpointURLs[s.EModelEndpoint.agents]}/${endpoint}`;
|
||||
const endpoint = _e as s.EModelEndpoint;
|
||||
let server = `${EndpointURLs[s.EModelEndpoint.agents]}/${endpoint}`;
|
||||
if (s.isAssistantsEndpoint(endpoint)) {
|
||||
server =
|
||||
EndpointURLs[(endpointType ?? endpoint) as 'assistants' | 'azureAssistants'] +
|
||||
(isEdited ? '/modify' : '');
|
||||
}
|
||||
|
||||
const payload: t.TPayload = {
|
||||
...userMessage,
|
||||
...endpointOption,
|
||||
ephemeralAgent: isEphemeral ? ephemeralAgent : undefined,
|
||||
endpoint,
|
||||
ephemeralAgent: s.isAssistantsEndpoint(endpoint) ? undefined : ephemeralAgent,
|
||||
isContinued: !!(isEdited && isContinued),
|
||||
conversationId,
|
||||
isTemporary,
|
||||
editedContent,
|
||||
};
|
||||
|
||||
return { server, payload };
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { AxiosResponse } from 'axios';
|
|||
import type * as t from './types';
|
||||
import * as endpoints from './api-endpoints';
|
||||
import * as a from './types/assistants';
|
||||
import * as ag from './types/agents';
|
||||
import * as m from './types/mutations';
|
||||
import * as q from './types/queries';
|
||||
import * as f from './types/files';
|
||||
|
|
@ -10,14 +11,6 @@ import request from './request';
|
|||
import * as s from './schemas';
|
||||
import * as r from './roles';
|
||||
|
||||
export function abortRequestWithMessage(
|
||||
endpoint: string,
|
||||
abortKey: string,
|
||||
message: string,
|
||||
): Promise<void> {
|
||||
return request.post(endpoints.abortRequest(endpoint), { arg: { abortKey, message } });
|
||||
}
|
||||
|
||||
export function revokeUserKey(name: string): Promise<unknown> {
|
||||
return request.delete(endpoints.revokeUserKey(name));
|
||||
}
|
||||
|
|
@ -150,7 +143,11 @@ export const updateUserPlugins = (payload: t.TUpdateUserPlugins) => {
|
|||
|
||||
/* Config */
|
||||
|
||||
export const getStartupConfig = (): Promise<config.TStartupConfig> => {
|
||||
export const getStartupConfig = (): Promise<
|
||||
config.TStartupConfig & {
|
||||
mcpCustomUserVars?: Record<string, { title: string; description: string }>;
|
||||
}
|
||||
> => {
|
||||
return request.get(endpoints.config());
|
||||
};
|
||||
|
||||
|
|
@ -351,7 +348,7 @@ export const updateAction = (data: m.UpdateActionVariables): Promise<m.UpdateAct
|
|||
);
|
||||
};
|
||||
|
||||
export function getActions(): Promise<a.Action[]> {
|
||||
export function getActions(): Promise<ag.Action[]> {
|
||||
return request.get(
|
||||
endpoints.agents({
|
||||
path: 'actions',
|
||||
|
|
@ -407,7 +404,7 @@ export const updateAgent = ({
|
|||
|
||||
export const duplicateAgent = ({
|
||||
agent_id,
|
||||
}: m.DuplicateAgentBody): Promise<{ agent: a.Agent; actions: a.Action[] }> => {
|
||||
}: m.DuplicateAgentBody): Promise<{ agent: a.Agent; actions: ag.Action[] }> => {
|
||||
return request.post(
|
||||
endpoints.agents({
|
||||
path: `${agent_id}/duplicate`,
|
||||
|
|
@ -718,6 +715,12 @@ export function updateAgentPermissions(
|
|||
return request.put(endpoints.updateAgentPermissions(variables.roleName), variables.updates);
|
||||
}
|
||||
|
||||
export function updateMemoryPermissions(
|
||||
variables: m.UpdateMemoryPermVars,
|
||||
): Promise<m.UpdatePermResponse> {
|
||||
return request.put(endpoints.updateMemoryPermissions(variables.roleName), variables.updates);
|
||||
}
|
||||
|
||||
/* Tags */
|
||||
export function getConversationTags(): Promise<t.TConversationTagsResponse> {
|
||||
return request.get(endpoints.conversationTags());
|
||||
|
|
@ -765,6 +768,15 @@ export function getBanner(): Promise<t.TBannerResponse> {
|
|||
return request.get(endpoints.banner());
|
||||
}
|
||||
|
||||
export function updateFeedback(
|
||||
conversationId: string,
|
||||
messageId: string,
|
||||
payload: t.TUpdateFeedbackRequest,
|
||||
): Promise<t.TUpdateFeedbackResponse> {
|
||||
return request.put(endpoints.feedback(conversationId, messageId), payload);
|
||||
}
|
||||
|
||||
// 2FA
|
||||
export function enableTwoFactor(): Promise<t.TEnable2FAResponse> {
|
||||
return request.get(endpoints.enableTwoFactor());
|
||||
}
|
||||
|
|
@ -790,3 +802,33 @@ export function verifyTwoFactorTemp(
|
|||
): Promise<t.TVerify2FATempResponse> {
|
||||
return request.post(endpoints.verifyTwoFactorTemp(), payload);
|
||||
}
|
||||
|
||||
/* Memories */
|
||||
export const getMemories = (): Promise<q.MemoriesResponse> => {
|
||||
return request.get(endpoints.memories());
|
||||
};
|
||||
|
||||
export const deleteMemory = (key: string): Promise<void> => {
|
||||
return request.delete(endpoints.memory(key));
|
||||
};
|
||||
|
||||
export const updateMemory = (
|
||||
key: string,
|
||||
value: string,
|
||||
originalKey?: string,
|
||||
): Promise<q.TUserMemory> => {
|
||||
return request.patch(endpoints.memory(originalKey || key), { key, value });
|
||||
};
|
||||
|
||||
export const updateMemoryPreferences = (preferences: {
|
||||
memories: boolean;
|
||||
}): Promise<{ updated: boolean; preferences: { memories: boolean } }> => {
|
||||
return request.patch(endpoints.memoryPreferences(), preferences);
|
||||
};
|
||||
|
||||
export const createMemory = (data: {
|
||||
key: string;
|
||||
value: string;
|
||||
}): Promise<{ created: boolean; memory: q.TUserMemory }> => {
|
||||
return request.post(endpoints.memories(), data);
|
||||
};
|
||||
|
|
|
|||
141
packages/data-provider/src/feedback.ts
Normal file
141
packages/data-provider/src/feedback.ts
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
export type TFeedbackRating = 'thumbsUp' | 'thumbsDown';
|
||||
export const FEEDBACK_RATINGS = ['thumbsUp', 'thumbsDown'] as const;
|
||||
|
||||
export const FEEDBACK_REASON_KEYS = [
|
||||
// Down
|
||||
'not_matched',
|
||||
'inaccurate',
|
||||
'bad_style',
|
||||
'missing_image',
|
||||
'unjustified_refusal',
|
||||
'not_helpful',
|
||||
'other',
|
||||
// Up
|
||||
'accurate_reliable',
|
||||
'creative_solution',
|
||||
'clear_well_written',
|
||||
'attention_to_detail',
|
||||
] as const;
|
||||
|
||||
export type TFeedbackTagKey = (typeof FEEDBACK_REASON_KEYS)[number];
|
||||
|
||||
export interface TFeedbackTag {
|
||||
key: TFeedbackTagKey;
|
||||
label: string;
|
||||
direction: TFeedbackRating;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
// --- Tag Registry ---
|
||||
export const FEEDBACK_TAGS: TFeedbackTag[] = [
|
||||
// Thumbs Down
|
||||
{
|
||||
key: 'not_matched',
|
||||
label: 'com_ui_feedback_tag_not_matched',
|
||||
direction: 'thumbsDown',
|
||||
icon: 'AlertCircle',
|
||||
},
|
||||
{
|
||||
key: 'inaccurate',
|
||||
label: 'com_ui_feedback_tag_inaccurate',
|
||||
direction: 'thumbsDown',
|
||||
icon: 'AlertCircle',
|
||||
},
|
||||
{
|
||||
key: 'bad_style',
|
||||
label: 'com_ui_feedback_tag_bad_style',
|
||||
direction: 'thumbsDown',
|
||||
icon: 'PenTool',
|
||||
},
|
||||
{
|
||||
key: 'missing_image',
|
||||
label: 'com_ui_feedback_tag_missing_image',
|
||||
direction: 'thumbsDown',
|
||||
icon: 'ImageOff',
|
||||
},
|
||||
{
|
||||
key: 'unjustified_refusal',
|
||||
label: 'com_ui_feedback_tag_unjustified_refusal',
|
||||
direction: 'thumbsDown',
|
||||
icon: 'Ban',
|
||||
},
|
||||
{
|
||||
key: 'not_helpful',
|
||||
label: 'com_ui_feedback_tag_not_helpful',
|
||||
direction: 'thumbsDown',
|
||||
icon: 'ThumbsDown',
|
||||
},
|
||||
{
|
||||
key: 'other',
|
||||
label: 'com_ui_feedback_tag_other',
|
||||
direction: 'thumbsDown',
|
||||
icon: 'HelpCircle',
|
||||
},
|
||||
// Thumbs Up
|
||||
{
|
||||
key: 'accurate_reliable',
|
||||
label: 'com_ui_feedback_tag_accurate_reliable',
|
||||
direction: 'thumbsUp',
|
||||
icon: 'CheckCircle',
|
||||
},
|
||||
{
|
||||
key: 'creative_solution',
|
||||
label: 'com_ui_feedback_tag_creative_solution',
|
||||
direction: 'thumbsUp',
|
||||
icon: 'Lightbulb',
|
||||
},
|
||||
{
|
||||
key: 'clear_well_written',
|
||||
label: 'com_ui_feedback_tag_clear_well_written',
|
||||
direction: 'thumbsUp',
|
||||
icon: 'PenTool',
|
||||
},
|
||||
{
|
||||
key: 'attention_to_detail',
|
||||
label: 'com_ui_feedback_tag_attention_to_detail',
|
||||
direction: 'thumbsUp',
|
||||
icon: 'Search',
|
||||
},
|
||||
];
|
||||
|
||||
export function getTagsForRating(rating: TFeedbackRating): TFeedbackTag[] {
|
||||
return FEEDBACK_TAGS.filter((tag) => tag.direction === rating);
|
||||
}
|
||||
|
||||
export const feedbackTagKeySchema = z.enum(FEEDBACK_REASON_KEYS);
|
||||
export const feedbackRatingSchema = z.enum(FEEDBACK_RATINGS);
|
||||
|
||||
export const feedbackSchema = z.object({
|
||||
rating: feedbackRatingSchema,
|
||||
tag: feedbackTagKeySchema,
|
||||
text: z.string().max(1024).optional(),
|
||||
});
|
||||
|
||||
export type TMinimalFeedback = z.infer<typeof feedbackSchema>;
|
||||
|
||||
export type TFeedback = {
|
||||
rating: TFeedbackRating;
|
||||
tag: TFeedbackTag | undefined;
|
||||
text?: string;
|
||||
};
|
||||
|
||||
export function toMinimalFeedback(feedback: TFeedback | undefined): TMinimalFeedback | undefined {
|
||||
if (!feedback?.rating || !feedback?.tag || !feedback.tag.key) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
rating: feedback.rating,
|
||||
tag: feedback.tag.key,
|
||||
text: feedback.text,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTagByKey(key: TFeedbackTagKey | undefined): TFeedbackTag | undefined {
|
||||
if (!key) {
|
||||
return undefined;
|
||||
}
|
||||
return FEEDBACK_TAGS.find((tag) => tag.key === key);
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
/* eslint-disable max-len */
|
||||
import { z } from 'zod';
|
||||
import { EModelEndpoint } from './schemas';
|
||||
import type { FileConfig, EndpointFileConfig } from './types/files';
|
||||
import type { EndpointFileConfig, FileConfig } from './types/files';
|
||||
|
||||
export const supportsFiles = {
|
||||
[EModelEndpoint.openAI]: true,
|
||||
|
|
@ -50,6 +49,8 @@ export const fullMimeTypesList = [
|
|||
'text/javascript',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/heic',
|
||||
'image/heif',
|
||||
'application/x-tar',
|
||||
'application/typescript',
|
||||
'application/xml',
|
||||
|
|
@ -81,6 +82,8 @@ export const codeInterpreterMimeTypesList = [
|
|||
'text/javascript',
|
||||
'image/gif',
|
||||
'image/png',
|
||||
'image/heic',
|
||||
'image/heif',
|
||||
'application/x-tar',
|
||||
'application/typescript',
|
||||
'application/xml',
|
||||
|
|
@ -106,18 +109,18 @@ export const retrievalMimeTypesList = [
|
|||
'text/plain',
|
||||
];
|
||||
|
||||
export const imageExtRegex = /\.(jpg|jpeg|png|gif|webp)$/i;
|
||||
export const imageExtRegex = /\.(jpg|jpeg|png|gif|webp|heic|heif)$/i;
|
||||
|
||||
export const excelMimeTypes =
|
||||
/^application\/(vnd\.ms-excel|msexcel|x-msexcel|x-ms-excel|x-excel|x-dos_ms_excel|xls|x-xls|vnd\.openxmlformats-officedocument\.spreadsheetml\.sheet)$/;
|
||||
|
||||
export const textMimeTypes =
|
||||
/^(text\/(x-c|x-csharp|tab-separated-values|x-c\+\+|x-java|html|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|css|vtt|javascript|csv))$/;
|
||||
/^(text\/(x-c|x-csharp|tab-separated-values|x-c\+\+|x-h|x-java|html|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|css|vtt|javascript|csv))$/;
|
||||
|
||||
export const applicationMimeTypes =
|
||||
/^(application\/(epub\+zip|csv|json|pdf|x-tar|typescript|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation|spreadsheetml\.sheet)|xml|zip))$/;
|
||||
|
||||
export const imageMimeTypes = /^image\/(jpeg|gif|png|webp)$/;
|
||||
export const imageMimeTypes = /^image\/(jpeg|gif|png|webp|heic|heif)$/;
|
||||
|
||||
export const supportedMimeTypes = [
|
||||
textMimeTypes,
|
||||
|
|
@ -139,6 +142,7 @@ export const codeTypeMapping: { [key: string]: string } = {
|
|||
c: 'text/x-c',
|
||||
cs: 'text/x-csharp',
|
||||
cpp: 'text/x-c++',
|
||||
h: 'text/x-h',
|
||||
md: 'text/markdown',
|
||||
php: 'text/x-php',
|
||||
py: 'text/x-python',
|
||||
|
|
@ -156,7 +160,7 @@ export const codeTypeMapping: { [key: string]: string } = {
|
|||
};
|
||||
|
||||
export const retrievalMimeTypes = [
|
||||
/^(text\/(x-c|x-c\+\+|html|x-java|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|vtt|xml))$/,
|
||||
/^(text\/(x-c|x-c\+\+|x-h|html|x-java|markdown|x-php|x-python|x-script\.python|x-ruby|x-tex|plain|vtt|xml))$/,
|
||||
/^(application\/(json|pdf|vnd\.openxmlformats-officedocument\.(wordprocessingml\.document|presentationml\.presentation)))$/,
|
||||
];
|
||||
|
||||
|
|
@ -188,6 +192,12 @@ export const fileConfig = {
|
|||
},
|
||||
serverFileSizeLimit: defaultSizeLimit,
|
||||
avatarSizeLimit: mbToBytes(2),
|
||||
clientImageResize: {
|
||||
enabled: false,
|
||||
maxWidth: 1900,
|
||||
maxHeight: 1900,
|
||||
quality: 0.92,
|
||||
},
|
||||
checkType: function (fileType: string, supportedTypes: RegExp[] = supportedMimeTypes) {
|
||||
return supportedTypes.some((regex) => regex.test(fileType));
|
||||
},
|
||||
|
|
@ -228,6 +238,14 @@ export const fileConfigSchema = z.object({
|
|||
px: z.number().min(0).optional(),
|
||||
})
|
||||
.optional(),
|
||||
clientImageResize: z
|
||||
.object({
|
||||
enabled: z.boolean().optional(),
|
||||
maxWidth: z.number().min(0).optional(),
|
||||
maxHeight: z.number().min(0).optional(),
|
||||
quality: z.number().min(0).max(1).optional(),
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
/** Helper function to safely convert string patterns to RegExp objects */
|
||||
|
|
@ -256,6 +274,14 @@ export function mergeFileConfig(dynamic: z.infer<typeof fileConfigSchema> | unde
|
|||
mergedConfig.avatarSizeLimit = mbToBytes(dynamic.avatarSizeLimit);
|
||||
}
|
||||
|
||||
// Merge clientImageResize configuration
|
||||
if (dynamic.clientImageResize !== undefined) {
|
||||
mergedConfig.clientImageResize = {
|
||||
...mergedConfig.clientImageResize,
|
||||
...dynamic.clientImageResize,
|
||||
};
|
||||
}
|
||||
|
||||
if (!dynamic.endpoints) {
|
||||
return mergedConfig;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -467,7 +467,11 @@ export function validateSettingDefinitions(settings: SettingsConfiguration): voi
|
|||
}
|
||||
|
||||
/* Default value checks */
|
||||
if (setting.type === SettingTypes.Number && isNaN(setting.default as number) && setting.default != null) {
|
||||
if (
|
||||
setting.type === SettingTypes.Number &&
|
||||
isNaN(setting.default as number) &&
|
||||
setting.default != null
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid default value for setting ${setting.key}. Must be a number.`,
|
||||
|
|
@ -475,7 +479,11 @@ export function validateSettingDefinitions(settings: SettingsConfiguration): voi
|
|||
});
|
||||
}
|
||||
|
||||
if (setting.type === SettingTypes.Boolean && typeof setting.default !== 'boolean' && setting.default != null) {
|
||||
if (
|
||||
setting.type === SettingTypes.Boolean &&
|
||||
typeof setting.default !== 'boolean' &&
|
||||
setting.default != null
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Invalid default value for setting ${setting.key}. Must be a boolean.`,
|
||||
|
|
@ -485,7 +493,8 @@ export function validateSettingDefinitions(settings: SettingsConfiguration): voi
|
|||
|
||||
if (
|
||||
(setting.type === SettingTypes.String || setting.type === SettingTypes.Enum) &&
|
||||
typeof setting.default !== 'string' && setting.default != null
|
||||
typeof setting.default !== 'string' &&
|
||||
setting.default != null
|
||||
) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
|
|
@ -520,6 +529,19 @@ export function validateSettingDefinitions(settings: SettingsConfiguration): voi
|
|||
path: ['default'],
|
||||
});
|
||||
}
|
||||
|
||||
// Validate enumMappings
|
||||
if (setting.enumMappings && setting.type === SettingTypes.Enum && setting.options) {
|
||||
for (const option of setting.options) {
|
||||
if (!(option in setting.enumMappings)) {
|
||||
errors.push({
|
||||
code: ZodIssueCode.custom,
|
||||
message: `Missing enumMapping for option "${option}" in setting ${setting.key}.`,
|
||||
path: ['enumMappings'],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export * from './models';
|
|||
export * from './mcp';
|
||||
/* web search */
|
||||
export * from './web';
|
||||
/* memory */
|
||||
export * from './memory';
|
||||
/* RBAC */
|
||||
export * from './permissions';
|
||||
export * from './roles';
|
||||
|
|
@ -39,4 +41,6 @@ import * as dataService from './data-service';
|
|||
export * from './utils';
|
||||
export * from './actions';
|
||||
export { default as createPayload } from './createPayload';
|
||||
/* feedback */
|
||||
export * from './feedback';
|
||||
export * from './parameterSettings';
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ export enum QueryKeys {
|
|||
health = 'health',
|
||||
userTerms = 'userTerms',
|
||||
banner = 'banner',
|
||||
/* Memories */
|
||||
memories = 'memories',
|
||||
}
|
||||
|
||||
export enum MutationKeys {
|
||||
|
|
@ -70,4 +72,5 @@ export enum MutationKeys {
|
|||
updateRole = 'updateRole',
|
||||
enableTwoFactor = 'enableTwoFactor',
|
||||
verifyTwoFactor = 'verifyTwoFactor',
|
||||
updateMemoryPreferences = 'updateMemoryPreferences',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { TokenExchangeMethodEnum } from './types/agents';
|
||||
import { extractEnvVariable } from './utils';
|
||||
|
||||
const BaseOptionsSchema = z.object({
|
||||
|
|
@ -7,6 +8,45 @@ const BaseOptionsSchema = z.object({
|
|||
initTimeout: z.number().optional(),
|
||||
/** Controls visibility in chat dropdown menu (MCPSelect) */
|
||||
chatMenu: z.boolean().optional(),
|
||||
/**
|
||||
* Controls server instruction behavior:
|
||||
* - undefined/not set: No instructions included (default)
|
||||
* - true: Use server-provided instructions
|
||||
* - string: Use custom instructions (overrides server-provided)
|
||||
*/
|
||||
serverInstructions: z.union([z.boolean(), z.string()]).optional(),
|
||||
/**
|
||||
* OAuth configuration for SSE and Streamable HTTP transports
|
||||
* - Optional: OAuth can be auto-discovered on 401 responses
|
||||
* - Pre-configured values will skip discovery steps
|
||||
*/
|
||||
oauth: z
|
||||
.object({
|
||||
/** OAuth authorization endpoint (optional - can be auto-discovered) */
|
||||
authorization_url: z.string().url().optional(),
|
||||
/** OAuth token endpoint (optional - can be auto-discovered) */
|
||||
token_url: z.string().url().optional(),
|
||||
/** OAuth client ID (optional - can use dynamic registration) */
|
||||
client_id: z.string().optional(),
|
||||
/** OAuth client secret (optional - can use dynamic registration) */
|
||||
client_secret: z.string().optional(),
|
||||
/** OAuth scopes to request */
|
||||
scope: z.string().optional(),
|
||||
/** OAuth redirect URI (defaults to /api/mcp/{serverName}/oauth/callback) */
|
||||
redirect_uri: z.string().url().optional(),
|
||||
/** Token exchange method */
|
||||
token_exchange_method: z.nativeEnum(TokenExchangeMethodEnum).optional(),
|
||||
})
|
||||
.optional(),
|
||||
customUserVars: z
|
||||
.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const StdioOptionsSchema = BaseOptionsSchema.extend({
|
||||
|
|
@ -112,41 +152,3 @@ export const MCPOptionsSchema = z.union([
|
|||
export const MCPServersSchema = z.record(z.string(), MCPOptionsSchema);
|
||||
|
||||
export type MCPOptions = z.infer<typeof MCPOptionsSchema>;
|
||||
|
||||
/**
|
||||
* Recursively processes an object to replace environment variables in string values
|
||||
* @param {MCPOptions} obj - The object to process
|
||||
* @param {string} [userId] - The user ID
|
||||
* @returns {MCPOptions} - The processed object with environment variables replaced
|
||||
*/
|
||||
export function processMCPEnv(obj: Readonly<MCPOptions>, userId?: string): MCPOptions {
|
||||
if (obj === null || obj === undefined) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
const newObj: MCPOptions = structuredClone(obj);
|
||||
|
||||
if ('env' in newObj && newObj.env) {
|
||||
const processedEnv: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(newObj.env)) {
|
||||
processedEnv[key] = extractEnvVariable(value);
|
||||
}
|
||||
newObj.env = processedEnv;
|
||||
} else if ('headers' in newObj && newObj.headers) {
|
||||
const processedHeaders: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(newObj.headers)) {
|
||||
if (value === '{{LIBRECHAT_USER_ID}}' && userId != null && userId) {
|
||||
processedHeaders[key] = userId;
|
||||
continue;
|
||||
}
|
||||
processedHeaders[key] = extractEnvVariable(value);
|
||||
}
|
||||
newObj.headers = processedHeaders;
|
||||
}
|
||||
|
||||
if ('url' in newObj && newObj.url) {
|
||||
newObj.url = extractEnvVariable(newObj.url);
|
||||
}
|
||||
|
||||
return newObj;
|
||||
}
|
||||
|
|
|
|||
62
packages/data-provider/src/memory.ts
Normal file
62
packages/data-provider/src/memory.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import type { TCustomConfig, TMemoryConfig } from './config';
|
||||
|
||||
/**
|
||||
* Loads the memory configuration and validates it
|
||||
* @param config - The memory configuration from librechat.yaml
|
||||
* @returns The validated memory configuration
|
||||
*/
|
||||
export function loadMemoryConfig(config: TCustomConfig['memory']): TMemoryConfig | undefined {
|
||||
if (!config) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// If disabled is explicitly true, return the config as-is
|
||||
if (config.disabled === true) {
|
||||
return config;
|
||||
}
|
||||
|
||||
// Check if the agent configuration is valid
|
||||
const hasValidAgent =
|
||||
config.agent &&
|
||||
(('id' in config.agent && !!config.agent.id) ||
|
||||
('provider' in config.agent &&
|
||||
'model' in config.agent &&
|
||||
!!config.agent.provider &&
|
||||
!!config.agent.model));
|
||||
|
||||
// If agent config is invalid, treat as disabled
|
||||
if (!hasValidAgent) {
|
||||
return {
|
||||
...config,
|
||||
disabled: true,
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if memory feature is enabled based on the configuration
|
||||
* @param config - The memory configuration
|
||||
* @returns True if memory is enabled, false otherwise
|
||||
*/
|
||||
export function isMemoryEnabled(config: TMemoryConfig | undefined): boolean {
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config.disabled === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if agent configuration is valid
|
||||
const hasValidAgent =
|
||||
config.agent &&
|
||||
(('id' in config.agent && !!config.agent.id) ||
|
||||
('provider' in config.agent &&
|
||||
'model' in config.agent &&
|
||||
!!config.agent.provider &&
|
||||
!!config.agent.model));
|
||||
|
||||
return !!hasValidAgent;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import {
|
|||
openAISettings,
|
||||
googleSettings,
|
||||
ReasoningEffort,
|
||||
ReasoningSummary,
|
||||
BedrockProviders,
|
||||
anthropicSettings,
|
||||
} from './types';
|
||||
|
|
@ -71,6 +72,11 @@ const baseDefinitions: Record<string, SettingDefinition> = {
|
|||
default: ImageDetail.auto,
|
||||
component: 'slider',
|
||||
options: [ImageDetail.low, ImageDetail.auto, ImageDetail.high],
|
||||
enumMappings: {
|
||||
[ImageDetail.low]: 'com_ui_low',
|
||||
[ImageDetail.auto]: 'com_ui_auto',
|
||||
[ImageDetail.high]: 'com_ui_high',
|
||||
},
|
||||
optionType: 'conversation',
|
||||
columnSpan: 2,
|
||||
},
|
||||
|
|
@ -83,7 +89,7 @@ const createDefinition = (
|
|||
return { ...base, ...overrides } as SettingDefinition;
|
||||
};
|
||||
|
||||
const librechat: Record<string, SettingDefinition> = {
|
||||
export const librechat = {
|
||||
modelLabel: {
|
||||
key: 'modelLabel',
|
||||
label: 'com_endpoint_custom_name',
|
||||
|
|
@ -94,7 +100,7 @@ const librechat: Record<string, SettingDefinition> = {
|
|||
placeholder: 'com_endpoint_openai_custom_name_placeholder',
|
||||
placeholderCode: true,
|
||||
optionType: 'conversation',
|
||||
},
|
||||
} as const,
|
||||
maxContextTokens: {
|
||||
key: 'maxContextTokens',
|
||||
label: 'com_endpoint_context_tokens',
|
||||
|
|
@ -107,7 +113,7 @@ const librechat: Record<string, SettingDefinition> = {
|
|||
descriptionCode: true,
|
||||
optionType: 'model',
|
||||
columnSpan: 2,
|
||||
},
|
||||
} as const,
|
||||
resendFiles: {
|
||||
key: 'resendFiles',
|
||||
label: 'com_endpoint_plug_resend_files',
|
||||
|
|
@ -120,7 +126,7 @@ const librechat: Record<string, SettingDefinition> = {
|
|||
optionType: 'conversation',
|
||||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
} as const,
|
||||
promptPrefix: {
|
||||
key: 'promptPrefix',
|
||||
label: 'com_endpoint_prompt_prefix',
|
||||
|
|
@ -131,7 +137,7 @@ const librechat: Record<string, SettingDefinition> = {
|
|||
placeholder: 'com_endpoint_openai_prompt_prefix_placeholder',
|
||||
placeholderCode: true,
|
||||
optionType: 'model',
|
||||
},
|
||||
} as const,
|
||||
};
|
||||
|
||||
const openAIParams: Record<string, SettingDefinition> = {
|
||||
|
|
@ -211,9 +217,70 @@ const openAIParams: Record<string, SettingDefinition> = {
|
|||
description: 'com_endpoint_openai_reasoning_effort',
|
||||
descriptionCode: true,
|
||||
type: 'enum',
|
||||
default: ReasoningEffort.medium,
|
||||
default: ReasoningEffort.none,
|
||||
component: 'slider',
|
||||
options: [ReasoningEffort.low, ReasoningEffort.medium, ReasoningEffort.high],
|
||||
options: [
|
||||
ReasoningEffort.none,
|
||||
ReasoningEffort.low,
|
||||
ReasoningEffort.medium,
|
||||
ReasoningEffort.high,
|
||||
],
|
||||
enumMappings: {
|
||||
[ReasoningEffort.none]: 'com_ui_none',
|
||||
[ReasoningEffort.low]: 'com_ui_low',
|
||||
[ReasoningEffort.medium]: 'com_ui_medium',
|
||||
[ReasoningEffort.high]: 'com_ui_high',
|
||||
},
|
||||
optionType: 'model',
|
||||
columnSpan: 4,
|
||||
},
|
||||
useResponsesApi: {
|
||||
key: 'useResponsesApi',
|
||||
label: 'com_endpoint_use_responses_api',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_use_responses_api',
|
||||
descriptionCode: true,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
component: 'switch',
|
||||
optionType: 'model',
|
||||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
web_search: {
|
||||
key: 'web_search',
|
||||
label: 'com_ui_web_search',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_use_web_search',
|
||||
descriptionCode: true,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
component: 'switch',
|
||||
optionType: 'model',
|
||||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
reasoning_summary: {
|
||||
key: 'reasoning_summary',
|
||||
label: 'com_endpoint_reasoning_summary',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_openai_reasoning_summary',
|
||||
descriptionCode: true,
|
||||
type: 'enum',
|
||||
default: ReasoningSummary.none,
|
||||
component: 'slider',
|
||||
options: [
|
||||
ReasoningSummary.none,
|
||||
ReasoningSummary.auto,
|
||||
ReasoningSummary.concise,
|
||||
ReasoningSummary.detailed,
|
||||
],
|
||||
enumMappings: {
|
||||
[ReasoningSummary.none]: 'com_ui_none',
|
||||
[ReasoningSummary.auto]: 'com_ui_auto',
|
||||
[ReasoningSummary.concise]: 'com_ui_concise',
|
||||
[ReasoningSummary.detailed]: 'com_ui_detailed',
|
||||
},
|
||||
optionType: 'model',
|
||||
columnSpan: 4,
|
||||
},
|
||||
|
|
@ -347,7 +414,9 @@ const bedrock: Record<string, SettingDefinition> = {
|
|||
labelCode: true,
|
||||
type: 'number',
|
||||
component: 'input',
|
||||
placeholder: 'com_endpoint_anthropic_maxoutputtokens',
|
||||
description: 'com_endpoint_anthropic_maxoutputtokens',
|
||||
descriptionCode: true,
|
||||
placeholder: 'com_nav_theme_system',
|
||||
placeholderCode: true,
|
||||
optionType: 'model',
|
||||
columnSpan: 2,
|
||||
|
|
@ -450,6 +519,50 @@ const google: Record<string, SettingDefinition> = {
|
|||
optionType: 'model',
|
||||
columnSpan: 2,
|
||||
},
|
||||
thinking: {
|
||||
key: 'thinking',
|
||||
label: 'com_endpoint_thinking',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_google_thinking',
|
||||
descriptionCode: true,
|
||||
type: 'boolean',
|
||||
default: googleSettings.thinking.default,
|
||||
component: 'switch',
|
||||
optionType: 'conversation',
|
||||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
thinkingBudget: {
|
||||
key: 'thinkingBudget',
|
||||
label: 'com_endpoint_thinking_budget',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_google_thinking_budget',
|
||||
descriptionCode: true,
|
||||
placeholder: 'com_ui_auto',
|
||||
placeholderCode: true,
|
||||
type: 'number',
|
||||
component: 'input',
|
||||
range: {
|
||||
min: googleSettings.thinkingBudget.min,
|
||||
max: googleSettings.thinkingBudget.max,
|
||||
step: googleSettings.thinkingBudget.step,
|
||||
},
|
||||
optionType: 'conversation',
|
||||
columnSpan: 2,
|
||||
},
|
||||
grounding: {
|
||||
key: 'grounding',
|
||||
label: 'com_endpoint_use_search_grounding',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_google_use_search_grounding',
|
||||
descriptionCode: true,
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
component: 'switch',
|
||||
optionType: 'model',
|
||||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
};
|
||||
|
||||
const googleConfig: SettingsConfiguration = [
|
||||
|
|
@ -461,6 +574,9 @@ const googleConfig: SettingsConfiguration = [
|
|||
google.topP,
|
||||
google.topK,
|
||||
librechat.resendFiles,
|
||||
google.thinking,
|
||||
google.thinkingBudget,
|
||||
google.grounding,
|
||||
];
|
||||
|
||||
const googleCol1: SettingsConfiguration = [
|
||||
|
|
@ -476,6 +592,9 @@ const googleCol2: SettingsConfiguration = [
|
|||
google.topP,
|
||||
google.topK,
|
||||
librechat.resendFiles,
|
||||
google.thinking,
|
||||
google.thinkingBudget,
|
||||
google.grounding,
|
||||
];
|
||||
|
||||
const openAI: SettingsConfiguration = [
|
||||
|
|
@ -490,7 +609,10 @@ const openAI: SettingsConfiguration = [
|
|||
baseDefinitions.stop,
|
||||
librechat.resendFiles,
|
||||
baseDefinitions.imageDetail,
|
||||
openAIParams.web_search,
|
||||
openAIParams.reasoning_effort,
|
||||
openAIParams.useResponsesApi,
|
||||
openAIParams.reasoning_summary,
|
||||
];
|
||||
|
||||
const openAICol1: SettingsConfiguration = [
|
||||
|
|
@ -507,9 +629,12 @@ const openAICol2: SettingsConfiguration = [
|
|||
openAIParams.frequency_penalty,
|
||||
openAIParams.presence_penalty,
|
||||
baseDefinitions.stop,
|
||||
openAIParams.reasoning_effort,
|
||||
librechat.resendFiles,
|
||||
baseDefinitions.imageDetail,
|
||||
openAIParams.reasoning_effort,
|
||||
openAIParams.reasoning_summary,
|
||||
openAIParams.useResponsesApi,
|
||||
openAIParams.web_search,
|
||||
];
|
||||
|
||||
const anthropicConfig: SettingsConfiguration = [
|
||||
|
|
|
|||
|
|
@ -122,19 +122,6 @@ export function errorsToString(errors: ZodIssue[]) {
|
|||
.join(' ');
|
||||
}
|
||||
|
||||
/** Resolves header values to env variables if detected */
|
||||
export function resolveHeaders(headers: Record<string, string> | undefined) {
|
||||
const resolvedHeaders = { ...(headers ?? {}) };
|
||||
|
||||
if (headers && typeof headers === 'object' && !Array.isArray(headers)) {
|
||||
Object.keys(headers).forEach((key) => {
|
||||
resolvedHeaders[key] = extractEnvVariable(headers[key]);
|
||||
});
|
||||
}
|
||||
|
||||
return resolvedHeaders;
|
||||
}
|
||||
|
||||
export function getFirstDefinedValue(possibleValues: string[]) {
|
||||
let returnValue;
|
||||
for (const value of possibleValues) {
|
||||
|
|
@ -225,13 +212,15 @@ const extractOmniVersion = (modelStr: string): string => {
|
|||
export const getResponseSender = (endpointOption: t.TEndpointOption): string => {
|
||||
const {
|
||||
model: _m,
|
||||
endpoint,
|
||||
endpoint: _e,
|
||||
endpointType,
|
||||
modelDisplayLabel: _mdl,
|
||||
chatGptLabel: _cgl,
|
||||
modelLabel: _ml,
|
||||
} = endpointOption;
|
||||
|
||||
const endpoint = _e as EModelEndpoint;
|
||||
|
||||
const model = _m ?? '';
|
||||
const modelDisplayLabel = _mdl ?? '';
|
||||
const chatGptLabel = _cgl ?? '';
|
||||
|
|
@ -273,15 +262,11 @@ export const getResponseSender = (endpointOption: t.TEndpointOption): string =>
|
|||
if (endpoint === EModelEndpoint.google) {
|
||||
if (modelLabel) {
|
||||
return modelLabel;
|
||||
} else if (model && (model.includes('gemini') || model.includes('learnlm'))) {
|
||||
return 'Gemini';
|
||||
} else if (model?.toLowerCase().includes('gemma') === true) {
|
||||
return 'Gemma';
|
||||
} else if (model && model.includes('code')) {
|
||||
return 'Codey';
|
||||
}
|
||||
|
||||
return 'PaLM2';
|
||||
return 'Gemini';
|
||||
}
|
||||
|
||||
if (endpoint === EModelEndpoint.custom || endpointType === EModelEndpoint.custom) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ export enum PermissionTypes {
|
|||
* Type for Agent Permissions
|
||||
*/
|
||||
AGENTS = 'AGENTS',
|
||||
/**
|
||||
* Type for Memory Permissions
|
||||
*/
|
||||
MEMORIES = 'MEMORIES',
|
||||
/**
|
||||
* Type for Multi-Conversation Permissions
|
||||
*/
|
||||
|
|
@ -45,6 +49,8 @@ export enum Permissions {
|
|||
READ = 'READ',
|
||||
READ_AUTHOR = 'READ_AUTHOR',
|
||||
SHARE = 'SHARE',
|
||||
/** Can disable if desired */
|
||||
OPT_OUT = 'OPT_OUT',
|
||||
}
|
||||
|
||||
export const promptPermissionsSchema = z.object({
|
||||
|
|
@ -60,6 +66,15 @@ export const bookmarkPermissionsSchema = z.object({
|
|||
});
|
||||
export type TBookmarkPermissions = z.infer<typeof bookmarkPermissionsSchema>;
|
||||
|
||||
export const memoryPermissionsSchema = z.object({
|
||||
[Permissions.USE]: z.boolean().default(true),
|
||||
[Permissions.CREATE]: z.boolean().default(true),
|
||||
[Permissions.UPDATE]: z.boolean().default(true),
|
||||
[Permissions.READ]: z.boolean().default(true),
|
||||
[Permissions.OPT_OUT]: z.boolean().default(true),
|
||||
});
|
||||
export type TMemoryPermissions = z.infer<typeof memoryPermissionsSchema>;
|
||||
|
||||
export const agentPermissionsSchema = z.object({
|
||||
[Permissions.SHARED_GLOBAL]: z.boolean().default(false),
|
||||
[Permissions.USE]: z.boolean().default(true),
|
||||
|
|
@ -92,6 +107,7 @@ export type TWebSearchPermissions = z.infer<typeof webSearchPermissionsSchema>;
|
|||
export const permissionsSchema = z.object({
|
||||
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||
[PermissionTypes.MEMORIES]: memoryPermissionsSchema,
|
||||
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
||||
[PermissionTypes.MULTI_CONVO]: multiConvoPermissionsSchema,
|
||||
[PermissionTypes.TEMPORARY_CHAT]: temporaryChatPermissionsSchema,
|
||||
|
|
|
|||
|
|
@ -12,23 +12,6 @@ import { QueryKeys } from '../keys';
|
|||
import * as s from '../schemas';
|
||||
import * as t from '../types';
|
||||
|
||||
export const useAbortRequestWithMessage = (): UseMutationResult<
|
||||
void,
|
||||
Error,
|
||||
{ endpoint: string; abortKey: string; message: string }
|
||||
> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
({ endpoint, abortKey, message }) =>
|
||||
dataService.abortRequestWithMessage(endpoint, abortKey, message),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.balance]);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useGetSharedMessages = (
|
||||
shareId: string,
|
||||
config?: UseQueryOptions<t.TSharedMessagesResponse>,
|
||||
|
|
@ -347,3 +330,19 @@ export const useGetCustomConfigSpeechQuery = (
|
|||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const useUpdateFeedbackMutation = (
|
||||
conversationId: string,
|
||||
messageId: string,
|
||||
): UseMutationResult<t.TUpdateFeedbackResponse, Error, t.TUpdateFeedbackRequest> => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation(
|
||||
(payload: t.TUpdateFeedbackRequest) =>
|
||||
dataService.updateFeedback(conversationId, messageId, payload),
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([QueryKeys.messages, messageId]);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
permissionsSchema,
|
||||
agentPermissionsSchema,
|
||||
promptPermissionsSchema,
|
||||
memoryPermissionsSchema,
|
||||
runCodePermissionsSchema,
|
||||
webSearchPermissionsSchema,
|
||||
bookmarkPermissionsSchema,
|
||||
|
|
@ -48,6 +49,13 @@ const defaultRolesSchema = z.object({
|
|||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema.extend({
|
||||
[Permissions.USE]: z.boolean().default(true),
|
||||
}),
|
||||
[PermissionTypes.MEMORIES]: memoryPermissionsSchema.extend({
|
||||
[Permissions.USE]: z.boolean().default(true),
|
||||
[Permissions.CREATE]: z.boolean().default(true),
|
||||
[Permissions.UPDATE]: z.boolean().default(true),
|
||||
[Permissions.READ]: z.boolean().default(true),
|
||||
[Permissions.OPT_OUT]: z.boolean().default(true),
|
||||
}),
|
||||
[PermissionTypes.AGENTS]: agentPermissionsSchema.extend({
|
||||
[Permissions.SHARED_GLOBAL]: z.boolean().default(true),
|
||||
[Permissions.USE]: z.boolean().default(true),
|
||||
|
|
@ -86,6 +94,13 @@ export const roleDefaults = defaultRolesSchema.parse({
|
|||
[PermissionTypes.BOOKMARKS]: {
|
||||
[Permissions.USE]: true,
|
||||
},
|
||||
[PermissionTypes.MEMORIES]: {
|
||||
[Permissions.USE]: true,
|
||||
[Permissions.CREATE]: true,
|
||||
[Permissions.UPDATE]: true,
|
||||
[Permissions.READ]: true,
|
||||
[Permissions.OPT_OUT]: true,
|
||||
},
|
||||
[PermissionTypes.AGENTS]: {
|
||||
[Permissions.SHARED_GLOBAL]: true,
|
||||
[Permissions.USE]: true,
|
||||
|
|
@ -110,6 +125,7 @@ export const roleDefaults = defaultRolesSchema.parse({
|
|||
permissions: {
|
||||
[PermissionTypes.PROMPTS]: {},
|
||||
[PermissionTypes.BOOKMARKS]: {},
|
||||
[PermissionTypes.MEMORIES]: {},
|
||||
[PermissionTypes.AGENTS]: {},
|
||||
[PermissionTypes.MULTI_CONVO]: {},
|
||||
[PermissionTypes.TEMPORARY_CHAT]: {},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { z } from 'zod';
|
||||
import { Tools } from './types/assistants';
|
||||
import type { TMessageContentParts, FunctionTool, FunctionToolCall } from './types/assistants';
|
||||
import { TFeedback, feedbackSchema } from './feedback';
|
||||
import type { SearchResultData } from './types/web';
|
||||
import type { TEphemeralAgent } from './types';
|
||||
import type { TFile } from './types/files';
|
||||
|
||||
export const isUUID = z.string().uuid();
|
||||
|
|
@ -90,22 +90,6 @@ export const isAgentsEndpoint = (_endpoint?: EModelEndpoint.agents | null | stri
|
|||
return endpoint === EModelEndpoint.agents;
|
||||
};
|
||||
|
||||
export const isEphemeralAgent = (
|
||||
endpoint?: EModelEndpoint.agents | null | string,
|
||||
ephemeralAgent?: TEphemeralAgent | null,
|
||||
) => {
|
||||
if (!ephemeralAgent) {
|
||||
return false;
|
||||
}
|
||||
if (isAgentsEndpoint(endpoint)) {
|
||||
return false;
|
||||
}
|
||||
const hasMCPSelected = (ephemeralAgent?.mcp?.length ?? 0) > 0;
|
||||
const hasCodeSelected = (ephemeralAgent?.execute_code ?? false) === true;
|
||||
const hasSearchSelected = (ephemeralAgent?.web_search ?? false) === true;
|
||||
return hasMCPSelected || hasCodeSelected || hasSearchSelected;
|
||||
};
|
||||
|
||||
export const isParamEndpoint = (
|
||||
endpoint: EModelEndpoint | string,
|
||||
endpointType?: EModelEndpoint | string,
|
||||
|
|
@ -128,11 +112,19 @@ export enum ImageDetail {
|
|||
}
|
||||
|
||||
export enum ReasoningEffort {
|
||||
none = '',
|
||||
low = 'low',
|
||||
medium = 'medium',
|
||||
high = 'high',
|
||||
}
|
||||
|
||||
export enum ReasoningSummary {
|
||||
none = '',
|
||||
auto = 'auto',
|
||||
concise = 'concise',
|
||||
detailed = 'detailed',
|
||||
}
|
||||
|
||||
export const imageDetailNumeric = {
|
||||
[ImageDetail.low]: 0,
|
||||
[ImageDetail.auto]: 1,
|
||||
|
|
@ -147,6 +139,7 @@ export const imageDetailValue = {
|
|||
|
||||
export const eImageDetailSchema = z.nativeEnum(ImageDetail);
|
||||
export const eReasoningEffortSchema = z.nativeEnum(ReasoningEffort);
|
||||
export const eReasoningSummarySchema = z.nativeEnum(ReasoningSummary);
|
||||
|
||||
export const defaultAssistantFormValues = {
|
||||
assistant: '',
|
||||
|
|
@ -271,6 +264,18 @@ export const googleSettings = {
|
|||
step: 1 as const,
|
||||
default: 40 as const,
|
||||
},
|
||||
thinking: {
|
||||
default: true as const,
|
||||
},
|
||||
thinkingBudget: {
|
||||
min: -1 as const,
|
||||
max: 32768 as const,
|
||||
step: 1 as const,
|
||||
/** `-1` = Dynamic Thinking, meaning the model will adjust
|
||||
* the budget based on the complexity of the request.
|
||||
*/
|
||||
default: -1 as const,
|
||||
},
|
||||
};
|
||||
|
||||
const ANTHROPIC_MAX_OUTPUT = 128000 as const;
|
||||
|
|
@ -416,7 +421,7 @@ export type TPluginAuthConfig = z.infer<typeof tPluginAuthConfigSchema>;
|
|||
export const tPluginSchema = z.object({
|
||||
name: z.string(),
|
||||
pluginKey: z.string(),
|
||||
description: z.string(),
|
||||
description: z.string().optional(),
|
||||
icon: z.string().optional(),
|
||||
authConfig: z.array(tPluginAuthConfigSchema).optional(),
|
||||
authenticated: z.boolean().optional(),
|
||||
|
|
@ -498,6 +503,7 @@ export const tMessageSchema = z.object({
|
|||
title: z.string().nullable().or(z.literal('New Chat')).default('New Chat'),
|
||||
sender: z.string().optional(),
|
||||
text: z.string(),
|
||||
/** @deprecated */
|
||||
generation: z.string().nullable().optional(),
|
||||
isCreatedByUser: z.boolean(),
|
||||
error: z.boolean().optional(),
|
||||
|
|
@ -518,13 +524,22 @@ export const tMessageSchema = z.object({
|
|||
thread_id: z.string().optional(),
|
||||
/* frontend components */
|
||||
iconURL: z.string().nullable().optional(),
|
||||
feedback: feedbackSchema.optional(),
|
||||
});
|
||||
|
||||
export type MemoryArtifact = {
|
||||
key: string;
|
||||
value?: string;
|
||||
tokenCount?: number;
|
||||
type: 'update' | 'delete';
|
||||
};
|
||||
|
||||
export type TAttachmentMetadata = {
|
||||
type?: Tools;
|
||||
messageId: string;
|
||||
toolCallId: string;
|
||||
[Tools.web_search]?: SearchResultData;
|
||||
[Tools.memory]?: MemoryArtifact;
|
||||
};
|
||||
|
||||
export type TAttachment =
|
||||
|
|
@ -543,6 +558,7 @@ export type TMessage = z.input<typeof tMessageSchema> & {
|
|||
siblingIndex?: number;
|
||||
attachments?: TAttachment[];
|
||||
clientTimestamp?: string;
|
||||
feedback?: TFeedback;
|
||||
};
|
||||
|
||||
export const coerceNumber = z.union([z.number(), z.string()]).transform((val) => {
|
||||
|
|
@ -613,8 +629,15 @@ export const tConversationSchema = z.object({
|
|||
file_ids: z.array(z.string()).optional(),
|
||||
/* vision */
|
||||
imageDetail: eImageDetailSchema.optional(),
|
||||
/* OpenAI: o1 only */
|
||||
reasoning_effort: eReasoningEffortSchema.optional(),
|
||||
/* OpenAI: Reasoning models only */
|
||||
reasoning_effort: eReasoningEffortSchema.optional().nullable(),
|
||||
reasoning_summary: eReasoningSummarySchema.optional().nullable(),
|
||||
/* OpenAI: use Responses API */
|
||||
useResponsesApi: z.boolean().optional(),
|
||||
/* OpenAI: use Responses API with Web Search */
|
||||
web_search: z.boolean().optional(),
|
||||
/* Google: use Search Grounding */
|
||||
grounding: z.boolean().optional(),
|
||||
/* assistant */
|
||||
assistant_id: z.string().optional(),
|
||||
/* agents */
|
||||
|
|
@ -711,6 +734,14 @@ export const tQueryParamsSchema = tConversationSchema
|
|||
top_p: true,
|
||||
/** @endpoints openAI, custom, azureOpenAI */
|
||||
max_tokens: true,
|
||||
/** @endpoints openAI, custom, azureOpenAI */
|
||||
reasoning_effort: true,
|
||||
/** @endpoints openAI, custom, azureOpenAI */
|
||||
reasoning_summary: true,
|
||||
/** @endpoints openAI, custom, azureOpenAI */
|
||||
useResponsesApi: true,
|
||||
/** @endpoints google */
|
||||
grounding: true,
|
||||
/** @endpoints google, anthropic, bedrock */
|
||||
topP: true,
|
||||
/** @endpoints google, anthropic */
|
||||
|
|
@ -791,6 +822,9 @@ export const googleBaseSchema = tConversationSchema.pick({
|
|||
artifacts: true,
|
||||
topP: true,
|
||||
topK: true,
|
||||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
grounding: true,
|
||||
iconURL: true,
|
||||
greeting: true,
|
||||
spec: true,
|
||||
|
|
@ -816,6 +850,13 @@ export const googleGenConfigSchema = z
|
|||
presencePenalty: coerceNumber.optional(),
|
||||
frequencyPenalty: coerceNumber.optional(),
|
||||
stopSequences: z.array(z.string()).optional(),
|
||||
thinkingConfig: z
|
||||
.object({
|
||||
includeThoughts: z.boolean().optional(),
|
||||
thinkingBudget: coerceNumber.optional(),
|
||||
})
|
||||
.optional(),
|
||||
grounding: z.boolean().optional(),
|
||||
})
|
||||
.strip()
|
||||
.optional();
|
||||
|
|
@ -1030,10 +1071,13 @@ export const openAIBaseSchema = tConversationSchema.pick({
|
|||
maxContextTokens: true,
|
||||
max_tokens: true,
|
||||
reasoning_effort: true,
|
||||
reasoning_summary: true,
|
||||
useResponsesApi: true,
|
||||
web_search: true,
|
||||
});
|
||||
|
||||
export const openAISchema = openAIBaseSchema
|
||||
.transform((obj: Partial<TConversation>) => removeNullishValues(obj))
|
||||
.transform((obj: Partial<TConversation>) => removeNullishValues(obj, true))
|
||||
.catch(() => ({}));
|
||||
|
||||
export const compactGoogleSchema = googleBaseSchema
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
import type OpenAI from 'openai';
|
||||
import type { InfiniteData } from '@tanstack/react-query';
|
||||
import type {
|
||||
TBanner,
|
||||
TMessage,
|
||||
TResPlugin,
|
||||
ImageDetail,
|
||||
TSharedLink,
|
||||
TConversation,
|
||||
EModelEndpoint,
|
||||
TConversationTag,
|
||||
TBanner,
|
||||
TAttachment,
|
||||
} from './schemas';
|
||||
import { SettingDefinition } from './generate';
|
||||
import type { SettingDefinition } from './generate';
|
||||
import type { TMinimalFeedback } from './feedback';
|
||||
import type { Agent } from './types/assistants';
|
||||
|
||||
export type TOpenAIMessage = OpenAI.Chat.ChatCompletionMessageParam;
|
||||
|
||||
export * from './schemas';
|
||||
|
|
@ -18,33 +21,84 @@ export * from './schemas';
|
|||
export type TMessages = TMessage[];
|
||||
|
||||
/* TODO: Cleanup EndpointOption types */
|
||||
export type TEndpointOption = {
|
||||
spec?: string | null;
|
||||
iconURL?: string | null;
|
||||
endpoint: EModelEndpoint;
|
||||
endpointType?: EModelEndpoint;
|
||||
export type TEndpointOption = Pick<
|
||||
TConversation,
|
||||
// Core conversation fields
|
||||
| 'endpoint'
|
||||
| 'endpointType'
|
||||
| 'model'
|
||||
| 'modelLabel'
|
||||
| 'chatGptLabel'
|
||||
| 'promptPrefix'
|
||||
| 'temperature'
|
||||
| 'topP'
|
||||
| 'topK'
|
||||
| 'top_p'
|
||||
| 'frequency_penalty'
|
||||
| 'presence_penalty'
|
||||
| 'maxOutputTokens'
|
||||
| 'maxContextTokens'
|
||||
| 'max_tokens'
|
||||
| 'maxTokens'
|
||||
| 'resendFiles'
|
||||
| 'imageDetail'
|
||||
| 'reasoning_effort'
|
||||
| 'instructions'
|
||||
| 'additional_instructions'
|
||||
| 'append_current_datetime'
|
||||
| 'tools'
|
||||
| 'stop'
|
||||
| 'region'
|
||||
| 'additionalModelRequestFields'
|
||||
// Anthropic-specific
|
||||
| 'promptCache'
|
||||
| 'thinking'
|
||||
| 'thinkingBudget'
|
||||
// Assistant/Agent fields
|
||||
| 'assistant_id'
|
||||
| 'agent_id'
|
||||
// UI/Display fields
|
||||
| 'iconURL'
|
||||
| 'greeting'
|
||||
| 'spec'
|
||||
// Artifacts
|
||||
| 'artifacts'
|
||||
// Files
|
||||
| 'file_ids'
|
||||
// System field
|
||||
| 'system'
|
||||
// Google examples
|
||||
| 'examples'
|
||||
// Context
|
||||
| 'context'
|
||||
> & {
|
||||
// Fields specific to endpoint options that don't exist on TConversation
|
||||
modelDisplayLabel?: string;
|
||||
resendFiles?: boolean;
|
||||
promptCache?: boolean;
|
||||
maxContextTokens?: number;
|
||||
imageDetail?: ImageDetail;
|
||||
model?: string | null;
|
||||
promptPrefix?: string;
|
||||
temperature?: number;
|
||||
chatGptLabel?: string | null;
|
||||
modelLabel?: string | null;
|
||||
jailbreak?: boolean;
|
||||
key?: string | null;
|
||||
/* assistant */
|
||||
/** @deprecated Assistants API */
|
||||
thread_id?: string;
|
||||
/* multi-response stream */
|
||||
// Conversation identifiers for multi-response streams
|
||||
overrideConvoId?: string;
|
||||
overrideUserMessageId?: string;
|
||||
// Model parameters (used by different endpoints)
|
||||
modelOptions?: Record<string, unknown>;
|
||||
model_parameters?: Record<string, unknown>;
|
||||
// Configuration data (added by middleware)
|
||||
modelsConfig?: TModelsConfig;
|
||||
// File attachments (processed by middleware)
|
||||
attachments?: TAttachment[];
|
||||
// Generated prompts
|
||||
artifactsPrompt?: string;
|
||||
// Agent-specific fields
|
||||
agent?: Promise<Agent>;
|
||||
// Client-specific options
|
||||
clientOptions?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type TEphemeralAgent = {
|
||||
mcp?: string[];
|
||||
web_search?: boolean;
|
||||
file_search?: boolean;
|
||||
execute_code?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -55,6 +109,11 @@ export type TPayload = Partial<TMessage> &
|
|||
messages?: TMessages;
|
||||
isTemporary: boolean;
|
||||
ephemeralAgent?: TEphemeralAgent | null;
|
||||
editedContent?: {
|
||||
index: number;
|
||||
text: string;
|
||||
type: 'text' | 'think';
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type TSubmission = {
|
||||
|
|
@ -73,6 +132,11 @@ export type TSubmission = {
|
|||
endpointOption: TEndpointOption;
|
||||
clientTimestamp?: string;
|
||||
ephemeralAgent?: TEphemeralAgent | null;
|
||||
editedContent?: {
|
||||
index: number;
|
||||
text: string;
|
||||
type: 'text' | 'think';
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type EventSubmission = Omit<TSubmission, 'initialResponse'> & { initialResponse: TMessage };
|
||||
|
|
@ -80,7 +144,7 @@ export type EventSubmission = Omit<TSubmission, 'initialResponse'> & { initialRe
|
|||
export type TPluginAction = {
|
||||
pluginKey: string;
|
||||
action: 'install' | 'uninstall';
|
||||
auth?: Partial<Record<string, string>>;
|
||||
auth?: Partial<Record<string, string>> | null;
|
||||
isEntityTool?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -90,7 +154,7 @@ export type TUpdateUserPlugins = {
|
|||
isEntityTool?: boolean;
|
||||
pluginKey: string;
|
||||
action: string;
|
||||
auth?: Partial<Record<string, string | null>>;
|
||||
auth?: Partial<Record<string, string | null>> | null;
|
||||
};
|
||||
|
||||
// TODO `label` needs to be changed to the proper `TranslationKeys`
|
||||
|
|
@ -128,6 +192,9 @@ export type TUser = {
|
|||
plugins?: string[];
|
||||
twoFactorEnabled?: boolean;
|
||||
backupCodes?: TBackupCode[];
|
||||
personalization?: {
|
||||
memories?: boolean;
|
||||
};
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
|
|
@ -547,6 +614,16 @@ export type TAcceptTermsResponse = {
|
|||
|
||||
export type TBannerResponse = TBanner | null;
|
||||
|
||||
export type TUpdateFeedbackRequest = {
|
||||
feedback?: TMinimalFeedback;
|
||||
};
|
||||
|
||||
export type TUpdateFeedbackResponse = {
|
||||
messageId: string;
|
||||
conversationId: string;
|
||||
feedback?: TMinimalFeedback;
|
||||
};
|
||||
|
||||
export type TBalanceResponse = {
|
||||
tokenCredits: number;
|
||||
// Automatic refill settings
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
import { StepTypes, ContentTypes, ToolCallTypes } from './runs';
|
||||
import type { TAttachment, TPlugin } from 'src/schemas';
|
||||
import type { FunctionToolCall } from './assistants';
|
||||
import type { TAttachment } from 'src/schemas';
|
||||
|
||||
export namespace Agents {
|
||||
export type MessageType = 'human' | 'ai' | 'generic' | 'system' | 'function' | 'tool' | 'remove';
|
||||
|
|
@ -279,3 +279,79 @@ export type ToolCallResult = {
|
|||
conversationId: string;
|
||||
attachments?: TAttachment[];
|
||||
};
|
||||
|
||||
export enum AuthTypeEnum {
|
||||
ServiceHttp = 'service_http',
|
||||
OAuth = 'oauth',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export enum AuthorizationTypeEnum {
|
||||
Bearer = 'bearer',
|
||||
Basic = 'basic',
|
||||
Custom = 'custom',
|
||||
}
|
||||
|
||||
export enum TokenExchangeMethodEnum {
|
||||
DefaultPost = 'default_post',
|
||||
BasicAuthHeader = 'basic_auth_header',
|
||||
}
|
||||
|
||||
export type Action = {
|
||||
action_id: string;
|
||||
type?: string;
|
||||
settings?: Record<string, unknown>;
|
||||
metadata: ActionMetadata;
|
||||
version: number | string;
|
||||
} & ({ assistant_id: string; agent_id?: never } | { assistant_id?: never; agent_id: string });
|
||||
|
||||
export type ActionMetadata = {
|
||||
api_key?: string;
|
||||
auth?: ActionAuth;
|
||||
domain?: string;
|
||||
privacy_policy_url?: string;
|
||||
raw_spec?: string;
|
||||
oauth_client_id?: string;
|
||||
oauth_client_secret?: string;
|
||||
};
|
||||
|
||||
export type ActionAuth = {
|
||||
authorization_type?: AuthorizationTypeEnum;
|
||||
custom_auth_header?: string;
|
||||
type?: AuthTypeEnum;
|
||||
authorization_content_type?: string;
|
||||
authorization_url?: string;
|
||||
client_url?: string;
|
||||
scope?: string;
|
||||
token_exchange_method?: TokenExchangeMethodEnum;
|
||||
};
|
||||
|
||||
export type ActionMetadataRuntime = ActionMetadata & {
|
||||
oauth_access_token?: string;
|
||||
oauth_refresh_token?: string;
|
||||
oauth_token_expires_at?: Date;
|
||||
};
|
||||
|
||||
export type MCP = {
|
||||
mcp_id: string;
|
||||
metadata: MCPMetadata;
|
||||
} & ({ assistant_id: string; agent_id?: never } | { assistant_id?: never; agent_id: string });
|
||||
|
||||
export type MCPMetadata = Omit<ActionMetadata, 'auth'> & {
|
||||
name?: string;
|
||||
description?: string;
|
||||
url?: string;
|
||||
tools?: string[];
|
||||
auth?: MCPAuth;
|
||||
icon?: string;
|
||||
trust?: boolean;
|
||||
};
|
||||
|
||||
export type MCPAuth = ActionAuth;
|
||||
|
||||
export type AgentToolType = {
|
||||
tool_id: string;
|
||||
metadata: ToolMetadata;
|
||||
} & ({ assistant_id: string; agent_id?: never } | { assistant_id?: never; agent_id: string });
|
||||
|
||||
export type ToolMetadata = TPlugin;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export enum Tools {
|
|||
web_search = 'web_search',
|
||||
retrieval = 'retrieval',
|
||||
function = 'function',
|
||||
memory = 'memory',
|
||||
}
|
||||
|
||||
export enum EToolResources {
|
||||
|
|
@ -486,60 +487,6 @@ export const actionDomainSeparator = '---';
|
|||
export const hostImageIdSuffix = '_host_copy';
|
||||
export const hostImageNamePrefix = 'host_copy_';
|
||||
|
||||
export enum AuthTypeEnum {
|
||||
ServiceHttp = 'service_http',
|
||||
OAuth = 'oauth',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export enum AuthorizationTypeEnum {
|
||||
Bearer = 'bearer',
|
||||
Basic = 'basic',
|
||||
Custom = 'custom',
|
||||
}
|
||||
|
||||
export enum TokenExchangeMethodEnum {
|
||||
DefaultPost = 'default_post',
|
||||
BasicAuthHeader = 'basic_auth_header',
|
||||
}
|
||||
|
||||
export type ActionAuth = {
|
||||
authorization_type?: AuthorizationTypeEnum;
|
||||
custom_auth_header?: string;
|
||||
type?: AuthTypeEnum;
|
||||
authorization_content_type?: string;
|
||||
authorization_url?: string;
|
||||
client_url?: string;
|
||||
scope?: string;
|
||||
token_exchange_method?: TokenExchangeMethodEnum;
|
||||
};
|
||||
|
||||
export type ActionMetadata = {
|
||||
api_key?: string;
|
||||
auth?: ActionAuth;
|
||||
domain?: string;
|
||||
privacy_policy_url?: string;
|
||||
raw_spec?: string;
|
||||
oauth_client_id?: string;
|
||||
oauth_client_secret?: string;
|
||||
};
|
||||
|
||||
export type ActionMetadataRuntime = ActionMetadata & {
|
||||
oauth_access_token?: string;
|
||||
oauth_refresh_token?: string;
|
||||
oauth_token_expires_at?: Date;
|
||||
};
|
||||
|
||||
/* Assistant types */
|
||||
|
||||
export type Action = {
|
||||
action_id: string;
|
||||
type?: string;
|
||||
settings?: Record<string, unknown>;
|
||||
metadata: ActionMetadata;
|
||||
version: number | string;
|
||||
} & ({ assistant_id: string; agent_id?: never } | { assistant_id?: never; agent_id: string });
|
||||
|
||||
export type AssistantAvatar = {
|
||||
filepath: string;
|
||||
source: string;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export enum FileSources {
|
|||
vectordb = 'vectordb',
|
||||
execute_code = 'execute_code',
|
||||
mistral_ocr = 'mistral_ocr',
|
||||
azure_mistral_ocr = 'azure_mistral_ocr',
|
||||
vertexai_mistral_ocr = 'vertexai_mistral_ocr',
|
||||
text = 'text',
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +49,12 @@ export type FileConfig = {
|
|||
};
|
||||
serverFileSizeLimit?: number;
|
||||
avatarSizeLimit?: number;
|
||||
clientImageResize?: {
|
||||
enabled?: boolean;
|
||||
maxWidth?: number;
|
||||
maxHeight?: number;
|
||||
quality?: number;
|
||||
};
|
||||
checkType?: (fileType: string, supportedTypes: RegExp[]) => boolean;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@ import {
|
|||
Assistant,
|
||||
AssistantCreateParams,
|
||||
AssistantUpdateParams,
|
||||
ActionMetadata,
|
||||
FunctionTool,
|
||||
AssistantDocument,
|
||||
Action,
|
||||
Agent,
|
||||
AgentCreateParams,
|
||||
AgentUpdateParams,
|
||||
} from './assistants';
|
||||
import { Action, ActionMetadata } from './agents';
|
||||
|
||||
export type MutationOptions<
|
||||
Response,
|
||||
|
|
@ -278,7 +277,7 @@ export type UpdatePermVars<T> = {
|
|||
};
|
||||
|
||||
export type UpdatePromptPermVars = UpdatePermVars<p.TPromptPermissions>;
|
||||
|
||||
export type UpdateMemoryPermVars = UpdatePermVars<p.TMemoryPermissions>;
|
||||
export type UpdateAgentPermVars = UpdatePermVars<p.TAgentPermissions>;
|
||||
|
||||
export type UpdatePermResponse = r.TRole;
|
||||
|
|
@ -290,6 +289,13 @@ export type UpdatePromptPermOptions = MutationOptions<
|
|||
types.TError | null | undefined
|
||||
>;
|
||||
|
||||
export type UpdateMemoryPermOptions = MutationOptions<
|
||||
UpdatePermResponse,
|
||||
UpdateMemoryPermVars,
|
||||
unknown,
|
||||
types.TError | null | undefined
|
||||
>;
|
||||
|
||||
export type UpdateAgentPermOptions = MutationOptions<
|
||||
UpdatePermResponse,
|
||||
UpdateAgentPermVars,
|
||||
|
|
|
|||
|
|
@ -109,3 +109,18 @@ export type VerifyToolAuthResponse = {
|
|||
|
||||
export type GetToolCallParams = { conversationId: string };
|
||||
export type ToolCallResults = a.ToolCallResult[];
|
||||
|
||||
/* Memories */
|
||||
export type TUserMemory = {
|
||||
key: string;
|
||||
value: string;
|
||||
updated_at: string;
|
||||
tokenCount?: number;
|
||||
};
|
||||
|
||||
export type MemoriesResponse = {
|
||||
memories: TUserMemory[];
|
||||
totalTokens: number;
|
||||
tokenLimit: number | null;
|
||||
usagePercentage: number | null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ import type {
|
|||
SearchProviders,
|
||||
TWebSearchConfig,
|
||||
} from './config';
|
||||
import { extractVariableName } from './utils';
|
||||
import { SearchCategories, SafeSearchTypes } from './config';
|
||||
import { extractVariableName } from './utils';
|
||||
import { AuthType } from './schemas';
|
||||
|
||||
export function loadWebSearchConfig(
|
||||
|
|
@ -64,23 +64,29 @@ export const webSearchAuth = {
|
|||
/**
|
||||
* Extracts all API keys from the webSearchAuth configuration object
|
||||
*/
|
||||
export const webSearchKeys: TWebSearchKeys[] = [];
|
||||
export function getWebSearchKeys(): TWebSearchKeys[] {
|
||||
const keys: TWebSearchKeys[] = [];
|
||||
|
||||
// Iterate through each category (providers, scrapers, rerankers)
|
||||
for (const category of Object.keys(webSearchAuth)) {
|
||||
const categoryObj = webSearchAuth[category as TWebSearchCategories];
|
||||
// Iterate through each category (providers, scrapers, rerankers)
|
||||
for (const category of Object.keys(webSearchAuth)) {
|
||||
const categoryObj = webSearchAuth[category as TWebSearchCategories];
|
||||
|
||||
// Iterate through each service within the category
|
||||
for (const service of Object.keys(categoryObj)) {
|
||||
const serviceObj = categoryObj[service as keyof typeof categoryObj];
|
||||
// Iterate through each service within the category
|
||||
for (const service of Object.keys(categoryObj)) {
|
||||
const serviceObj = categoryObj[service as keyof typeof categoryObj];
|
||||
|
||||
// Extract the API keys from the service
|
||||
for (const key of Object.keys(serviceObj)) {
|
||||
webSearchKeys.push(key as TWebSearchKeys);
|
||||
// Extract the API keys from the service
|
||||
for (const key of Object.keys(serviceObj)) {
|
||||
keys.push(key as TWebSearchKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
export const webSearchKeys: TWebSearchKeys[] = getWebSearchKeys();
|
||||
|
||||
export function extractWebSearchEnvVars({
|
||||
keys,
|
||||
config,
|
||||
|
|
|
|||
|
|
@ -264,19 +264,19 @@ describe('convertJsonSchemaToZod', () => {
|
|||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'The user\'s name',
|
||||
description: "The user's name",
|
||||
},
|
||||
age: {
|
||||
type: 'number',
|
||||
description: 'The user\'s age',
|
||||
description: "The user's age",
|
||||
},
|
||||
},
|
||||
};
|
||||
const zodSchema = convertJsonSchemaToZod(schema);
|
||||
|
||||
const shape = (zodSchema as z.ZodObject<any>).shape;
|
||||
expect(shape.name.description).toBe('The user\'s name');
|
||||
expect(shape.age.description).toBe('The user\'s age');
|
||||
expect(shape.name.description).toBe("The user's name");
|
||||
expect(shape.age.description).toBe("The user's age");
|
||||
});
|
||||
|
||||
it('should preserve descriptions in nested objects', () => {
|
||||
|
|
@ -290,7 +290,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'The user\'s name',
|
||||
description: "The user's name",
|
||||
},
|
||||
settings: {
|
||||
type: 'object',
|
||||
|
|
@ -318,7 +318,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
|
||||
const userShape = shape.user instanceof z.ZodObject ? shape.user.shape : {};
|
||||
if ('name' in userShape && 'settings' in userShape) {
|
||||
expect(userShape.name.description).toBe('The user\'s name');
|
||||
expect(userShape.name.description).toBe("The user's name");
|
||||
expect(userShape.settings.description).toBe('User preferences');
|
||||
|
||||
const settingsShape =
|
||||
|
|
@ -682,10 +682,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
name: { type: 'string' },
|
||||
age: { type: 'number' },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['name'] },
|
||||
{ required: ['age'] },
|
||||
],
|
||||
anyOf: [{ required: ['name'] }, { required: ['age'] }],
|
||||
oneOf: [
|
||||
{ properties: { role: { type: 'string', enum: ['admin'] } } },
|
||||
{ properties: { role: { type: 'string', enum: ['user'] } } },
|
||||
|
|
@ -708,7 +705,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
it('should drop fields from nested schemas', () => {
|
||||
// Create a schema with nested fields that should be dropped
|
||||
const schema: JsonSchemaType & {
|
||||
properties?: Record<string, JsonSchemaType & { anyOf?: any; oneOf?: any }>
|
||||
properties?: Record<string, JsonSchemaType & { anyOf?: any; oneOf?: any }>;
|
||||
} = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
|
@ -718,10 +715,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
name: { type: 'string' },
|
||||
role: { type: 'string' },
|
||||
},
|
||||
anyOf: [
|
||||
{ required: ['name'] },
|
||||
{ required: ['role'] },
|
||||
],
|
||||
anyOf: [{ required: ['name'] }, { required: ['role'] }],
|
||||
},
|
||||
settings: {
|
||||
type: 'object',
|
||||
|
|
@ -742,20 +736,24 @@ describe('convertJsonSchemaToZod', () => {
|
|||
});
|
||||
|
||||
// The schema should still validate normal properties
|
||||
expect(zodSchema?.parse({
|
||||
user: { name: 'John', role: 'admin' },
|
||||
settings: { theme: 'custom' }, // This would fail if oneOf was still present
|
||||
})).toEqual({
|
||||
expect(
|
||||
zodSchema?.parse({
|
||||
user: { name: 'John', role: 'admin' },
|
||||
settings: { theme: 'custom' }, // This would fail if oneOf was still present
|
||||
}),
|
||||
).toEqual({
|
||||
user: { name: 'John', role: 'admin' },
|
||||
settings: { theme: 'custom' },
|
||||
});
|
||||
|
||||
// But the anyOf constraint should be gone from user
|
||||
// (If it was present, this would fail because neither name nor role is required)
|
||||
expect(zodSchema?.parse({
|
||||
user: {},
|
||||
settings: { theme: 'light' },
|
||||
})).toEqual({
|
||||
expect(
|
||||
zodSchema?.parse({
|
||||
user: {},
|
||||
settings: { theme: 'light' },
|
||||
}),
|
||||
).toEqual({
|
||||
user: {},
|
||||
settings: { theme: 'light' },
|
||||
});
|
||||
|
|
@ -803,10 +801,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
anyOf: [{ minItems: 1 }],
|
||||
},
|
||||
},
|
||||
oneOf: [
|
||||
{ required: ['name', 'permissions'] },
|
||||
{ required: ['name'] },
|
||||
],
|
||||
oneOf: [{ required: ['name', 'permissions'] }, { required: ['name'] }],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -871,10 +866,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
const schema = {
|
||||
type: 'object', // Add a type to satisfy JsonSchemaType
|
||||
properties: {}, // Empty properties
|
||||
oneOf: [
|
||||
{ type: 'string' },
|
||||
{ type: 'number' },
|
||||
],
|
||||
oneOf: [{ type: 'string' }, { type: 'number' }],
|
||||
} as JsonSchemaType & { oneOf?: any };
|
||||
|
||||
// Convert with transformOneOfAnyOf option
|
||||
|
|
@ -893,10 +885,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
const schema = {
|
||||
type: 'object', // Add a type to satisfy JsonSchemaType
|
||||
properties: {}, // Empty properties
|
||||
anyOf: [
|
||||
{ type: 'string' },
|
||||
{ type: 'number' },
|
||||
],
|
||||
anyOf: [{ type: 'string' }, { type: 'number' }],
|
||||
} as JsonSchemaType & { anyOf?: any };
|
||||
|
||||
// Convert with transformOneOfAnyOf option
|
||||
|
|
@ -956,10 +945,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
properties: {
|
||||
value: { type: 'string' },
|
||||
},
|
||||
oneOf: [
|
||||
{ required: ['value'] },
|
||||
{ properties: { optional: { type: 'boolean' } } },
|
||||
],
|
||||
oneOf: [{ required: ['value'] }, { properties: { optional: { type: 'boolean' } } }],
|
||||
} as JsonSchemaType & { oneOf?: any };
|
||||
|
||||
// Convert with transformOneOfAnyOf option
|
||||
|
|
@ -1013,9 +999,12 @@ describe('convertJsonSchemaToZod', () => {
|
|||
},
|
||||
},
|
||||
} as JsonSchemaType & {
|
||||
properties?: Record<string, JsonSchemaType & {
|
||||
properties?: Record<string, JsonSchemaType & { oneOf?: any }>
|
||||
}>
|
||||
properties?: Record<
|
||||
string,
|
||||
JsonSchemaType & {
|
||||
properties?: Record<string, JsonSchemaType & { oneOf?: any }>;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
// Convert with transformOneOfAnyOf option
|
||||
|
|
@ -1024,14 +1013,16 @@ describe('convertJsonSchemaToZod', () => {
|
|||
});
|
||||
|
||||
// The schema should validate nested unions
|
||||
expect(zodSchema?.parse({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'email',
|
||||
email: 'test@example.com',
|
||||
expect(
|
||||
zodSchema?.parse({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'email',
|
||||
email: 'test@example.com',
|
||||
},
|
||||
},
|
||||
},
|
||||
})).toEqual({
|
||||
}),
|
||||
).toEqual({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'email',
|
||||
|
|
@ -1040,14 +1031,16 @@ describe('convertJsonSchemaToZod', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(zodSchema?.parse({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'phone',
|
||||
phone: '123-456-7890',
|
||||
expect(
|
||||
zodSchema?.parse({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'phone',
|
||||
phone: '123-456-7890',
|
||||
},
|
||||
},
|
||||
},
|
||||
})).toEqual({
|
||||
}),
|
||||
).toEqual({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'phone',
|
||||
|
|
@ -1057,14 +1050,16 @@ describe('convertJsonSchemaToZod', () => {
|
|||
});
|
||||
|
||||
// Should reject invalid contact types
|
||||
expect(() => zodSchema?.parse({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'email',
|
||||
phone: '123-456-7890', // Missing email, has phone instead
|
||||
expect(() =>
|
||||
zodSchema?.parse({
|
||||
user: {
|
||||
contact: {
|
||||
type: 'email',
|
||||
phone: '123-456-7890', // Missing email, has phone instead
|
||||
},
|
||||
},
|
||||
},
|
||||
})).toThrow();
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should work with dropFields option', () => {
|
||||
|
|
@ -1072,10 +1067,7 @@ describe('convertJsonSchemaToZod', () => {
|
|||
const schema = {
|
||||
type: 'object', // Add a type to satisfy JsonSchemaType
|
||||
properties: {}, // Empty properties
|
||||
oneOf: [
|
||||
{ type: 'string' },
|
||||
{ type: 'number' },
|
||||
],
|
||||
oneOf: [{ type: 'string' }, { type: 'number' }],
|
||||
deprecated: true, // Field to drop
|
||||
} as JsonSchemaType & { oneOf?: any; deprecated?: boolean };
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue