📦 refactor: Move DB Models to @librechat/data-schemas (#6210)

* 🚀 feat: Introduce data schemas and refactor models to use @librechat/data-schemas

* 🚀 feat: Add installation step for Data Schemas Package in backend review workflow

* chore: Add `data-schemas` package to update/rebuild packages scripts

* chore: Update Dockerfile to include data-schemas package build process

* fix: add missing @rollup/plugin-typescript package

* chore: Add GitHub Actions workflow for publishing data-schemas package

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
This commit is contained in:
Ruben Talstra 2025-03-07 17:55:44 +01:00 committed by GitHub
parent 4d04904af3
commit b51cd21b3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 1781 additions and 984 deletions

View file

@ -39,6 +39,9 @@ jobs:
- name: Install MCP Package - name: Install MCP Package
run: npm run build:mcp run: npm run build:mcp
- name: Install Data Schemas Package
run: npm run build:data-schemas
- name: Create empty auth.json file - name: Create empty auth.json file
run: | run: |
mkdir -p api/data mkdir -p api/data

34
.github/workflows/data-schemas.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: Node.js Package
on:
push:
branches:
- main
paths:
- 'packages/data-schemas/package.json'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
- run: cd packages/data-schemas && npm ci
- run: cd packages/data-schemas && npm run build
publish-npm:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 16
registry-url: 'https://registry.npmjs.org'
- run: cd packages/data-schemas && npm ci
- run: cd packages/data-schemas && npm run build
- run: cd packages/data-schemas && npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

View file

@ -84,11 +84,11 @@ jobs:
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
sign-commits: true sign-commits: true
commit-message: "chore: update CHANGELOG for release ${GITHUB_REF##*/}" commit-message: "chore: update CHANGELOG for release ${{ github.ref_name }}"
base: main base: main
branch: "changelog/${GITHUB_REF##*/}" branch: "changelog/${{ github.ref_name }}"
reviewers: danny-avila reviewers: danny-avila
title: "chore: update CHANGELOG for release ${GITHUB_REF##*/}" title: "chore: update CHANGELOG for release ${{ github.ref_name }}"
body: | body: |
**Description**: **Description**:
- This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${GITHUB_REF##*/} above previous releases. - This PR updates the CHANGELOG.md by removing the "Unreleased" section and adding new release notes for release ${{ github.ref_name }} above previous releases.

View file

@ -11,6 +11,7 @@ RUN npm config set fetch-retry-maxtimeout 600000 && \
COPY package*.json ./ COPY package*.json ./
COPY packages/data-provider/package*.json ./packages/data-provider/ COPY packages/data-provider/package*.json ./packages/data-provider/
COPY packages/mcp/package*.json ./packages/mcp/ COPY packages/mcp/package*.json ./packages/mcp/
COPY packages/data-schemas/package*.json ./packages/data-schemas/
COPY client/package*.json ./client/ COPY client/package*.json ./client/
COPY api/package*.json ./api/ COPY api/package*.json ./api/
@ -32,6 +33,13 @@ COPY packages/mcp ./
COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist
RUN npm run build RUN npm run build
# Build data-schemas
FROM base AS data-schemas-build
WORKDIR /app/packages/data-schemas
COPY packages/data-schemas ./
COPY --from=data-provider-build /app/packages/data-provider/dist /app/packages/data-provider/dist
RUN npm run build
# Client build # Client build
FROM base AS client-build FROM base AS client-build
WORKDIR /app/client WORKDIR /app/client
@ -49,8 +57,9 @@ COPY api ./api
COPY config ./config COPY config ./config
COPY --from=data-provider-build /app/packages/data-provider/dist ./packages/data-provider/dist COPY --from=data-provider-build /app/packages/data-provider/dist ./packages/data-provider/dist
COPY --from=mcp-build /app/packages/mcp/dist ./packages/mcp/dist COPY --from=mcp-build /app/packages/mcp/dist ./packages/mcp/dist
COPY --from=data-schemas-build /app/packages/data-schemas/dist ./packages/data-schemas/dist
COPY --from=client-build /app/client/dist ./client/dist COPY --from=client-build /app/client/dist ./client/dist
WORKDIR /app/api WORKDIR /app/api
EXPOSE 3080 EXPOSE 3080
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
CMD ["node", "server/index.js"] CMD ["node", "server/index.js"]

View file

@ -1,5 +1,5 @@
const { MeiliSearch } = require('meilisearch'); const { MeiliSearch } = require('meilisearch');
const Conversation = require('~/models/schema/convoSchema'); const Conversation = require('~/models/Conversation');
const Message = require('~/models/schema/messageSchema'); const Message = require('~/models/schema/messageSchema');
const { isEnabled } = require('~/server/utils'); const { isEnabled } = require('~/server/utils');
const { logger } = require('~/config'); const { logger } = require('~/config');

View file

@ -1,5 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const actionSchema = require('./schema/action'); const { actionSchema } = require('@librechat/data-schemas');
const Action = mongoose.model('action', actionSchema); const Action = mongoose.model('action', actionSchema);

View file

@ -9,7 +9,7 @@ const {
removeAgentFromAllProjects, removeAgentFromAllProjects,
} = require('./Project'); } = require('./Project');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
const agentSchema = require('./schema/agent'); const { agentSchema } = require('@librechat/data-schemas');
const Agent = mongoose.model('agent', agentSchema); const Agent = mongoose.model('agent', agentSchema);

View file

@ -1,5 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const assistantSchema = require('./schema/assistant'); const { assistantSchema } = require('@librechat/data-schemas');
const Assistant = mongoose.model('assistant', assistantSchema); const Assistant = mongoose.model('assistant', assistantSchema);

View file

@ -1,5 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const balanceSchema = require('./schema/balance'); const { balanceSchema } = require('@librechat/data-schemas');
const { getMultiplier } = require('./tx'); const { getMultiplier } = require('./tx');
const { logger } = require('~/config'); const { logger } = require('~/config');

View file

@ -1,5 +1,9 @@
const Banner = require('./schema/banner'); const mongoose = require('mongoose');
const logger = require('~/config/winston'); const logger = require('~/config/winston');
const { bannerSchema } = require('@librechat/data-schemas');
const Banner = mongoose.model('Banner', bannerSchema);
/** /**
* Retrieves the current active banner. * Retrieves the current active banner.
* @returns {Promise<Object|null>} The active banner object or null if no active banner is found. * @returns {Promise<Object|null>} The active banner object or null if no active banner is found.

View file

@ -1,5 +1,4 @@
const { logger } = require('~/config'); const { logger } = require('~/config');
// const { Categories } = require('./schema/categories');
const options = [ const options = [
{ {

View file

@ -1,7 +1,11 @@
const ConversationTag = require('./schema/conversationTagSchema'); const mongoose = require('mongoose');
const Conversation = require('./schema/convoSchema'); const Conversation = require('./schema/convoSchema');
const logger = require('~/config/winston'); const logger = require('~/config/winston');
const { conversationTagSchema } = require('@librechat/data-schemas');
const ConversationTag = mongoose.model('ConversationTag', conversationTagSchema);
/** /**
* Retrieves all conversation tags for a user. * Retrieves all conversation tags for a user.
* @param {string} user - The user ID. * @param {string} user - The user ID.

View file

@ -1,5 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const fileSchema = require('./schema/fileSchema'); const { fileSchema } = require('@librechat/data-schemas');
const File = mongoose.model('File', fileSchema); const File = mongoose.model('File', fileSchema);
@ -7,7 +7,7 @@ const File = mongoose.model('File', fileSchema);
* Finds a file by its file_id with additional query options. * Finds a file by its file_id with additional query options.
* @param {string} file_id - The unique identifier of the file. * @param {string} file_id - The unique identifier of the file.
* @param {object} options - Query options for filtering, projection, etc. * @param {object} options - Query options for filtering, projection, etc.
* @returns {Promise<MongoFile>} A promise that resolves to the file document or null. * @returns {Promise<IMongoFile>} A promise that resolves to the file document or null.
*/ */
const findFileById = async (file_id, options = {}) => { const findFileById = async (file_id, options = {}) => {
return await File.findOne({ file_id, ...options }).lean(); return await File.findOne({ file_id, ...options }).lean();
@ -17,7 +17,7 @@ const findFileById = async (file_id, options = {}) => {
* Retrieves files matching a given filter, sorted by the most recently updated. * Retrieves files matching a given filter, sorted by the most recently updated.
* @param {Object} filter - The filter criteria to apply. * @param {Object} filter - The filter criteria to apply.
* @param {Object} [_sortOptions] - Optional sort parameters. * @param {Object} [_sortOptions] - Optional sort parameters.
* @returns {Promise<Array<MongoFile>>} A promise that resolves to an array of file documents. * @returns {Promise<Array<IMongoFile>>} A promise that resolves to an array of file documents.
*/ */
const getFiles = async (filter, _sortOptions) => { const getFiles = async (filter, _sortOptions) => {
const sortOptions = { updatedAt: -1, ..._sortOptions }; const sortOptions = { updatedAt: -1, ..._sortOptions };
@ -26,9 +26,9 @@ const getFiles = async (filter, _sortOptions) => {
/** /**
* Creates a new file with a TTL of 1 hour. * Creates a new file with a TTL of 1 hour.
* @param {MongoFile} data - The file data to be created, must contain file_id. * @param {IMongoFile} data - The file data to be created, must contain file_id.
* @param {boolean} disableTTL - Whether to disable the TTL. * @param {boolean} disableTTL - Whether to disable the TTL.
* @returns {Promise<MongoFile>} A promise that resolves to the created file document. * @returns {Promise<IMongoFile>} A promise that resolves to the created file document.
*/ */
const createFile = async (data, disableTTL) => { const createFile = async (data, disableTTL) => {
const fileData = { const fileData = {
@ -48,8 +48,8 @@ const createFile = async (data, disableTTL) => {
/** /**
* Updates a file identified by file_id with new data and removes the TTL. * Updates a file identified by file_id with new data and removes the TTL.
* @param {MongoFile} data - The data to update, must contain file_id. * @param {IMongoFile} data - The data to update, must contain file_id.
* @returns {Promise<MongoFile>} A promise that resolves to the updated file document. * @returns {Promise<IMongoFile>} A promise that resolves to the updated file document.
*/ */
const updateFile = async (data) => { const updateFile = async (data) => {
const { file_id, ...update } = data; const { file_id, ...update } = data;
@ -62,8 +62,8 @@ const updateFile = async (data) => {
/** /**
* Increments the usage of a file identified by file_id. * Increments the usage of a file identified by file_id.
* @param {MongoFile} data - The data to update, must contain file_id and the increment value for usage. * @param {IMongoFile} data - The data to update, must contain file_id and the increment value for usage.
* @returns {Promise<MongoFile>} A promise that resolves to the updated file document. * @returns {Promise<IMongoFile>} A promise that resolves to the updated file document.
*/ */
const updateFileUsage = async (data) => { const updateFileUsage = async (data) => {
const { file_id, inc = 1 } = data; const { file_id, inc = 1 } = data;
@ -77,7 +77,7 @@ const updateFileUsage = async (data) => {
/** /**
* Deletes a file identified by file_id. * Deletes a file identified by file_id.
* @param {string} file_id - The unique identifier of the file to delete. * @param {string} file_id - The unique identifier of the file to delete.
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null. * @returns {Promise<IMongoFile>} A promise that resolves to the deleted file document or null.
*/ */
const deleteFile = async (file_id) => { const deleteFile = async (file_id) => {
return await File.findOneAndDelete({ file_id }).lean(); return await File.findOneAndDelete({ file_id }).lean();
@ -86,7 +86,7 @@ const deleteFile = async (file_id) => {
/** /**
* Deletes a file identified by a filter. * Deletes a file identified by a filter.
* @param {object} filter - The filter criteria to apply. * @param {object} filter - The filter criteria to apply.
* @returns {Promise<MongoFile>} A promise that resolves to the deleted file document or null. * @returns {Promise<IMongoFile>} A promise that resolves to the deleted file document or null.
*/ */
const deleteFileByFilter = async (filter) => { const deleteFileByFilter = async (filter) => {
return await File.findOneAndDelete(filter).lean(); return await File.findOneAndDelete(filter).lean();

View file

@ -1,4 +1,4 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const keySchema = require('./schema/key'); const { keySchema } = require('@librechat/data-schemas');
module.exports = mongoose.model('Key', keySchema); module.exports = mongoose.model('Key', keySchema);

View file

@ -1,6 +1,6 @@
const { model } = require('mongoose'); const { model } = require('mongoose');
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants; const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
const projectSchema = require('~/models/schema/projectSchema'); const { projectSchema } = require('@librechat/data-schemas');
const Project = model('Project', projectSchema); const Project = model('Project', projectSchema);
@ -9,7 +9,7 @@ const Project = model('Project', projectSchema);
* *
* @param {string} projectId - The ID of the project to find and return as a plain object. * @param {string} projectId - The ID of the project to find and return as a plain object.
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document. * @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
* @returns {Promise<MongoProject>} A plain object representing the project document, or `null` if no project is found. * @returns {Promise<IMongoProject>} A plain object representing the project document, or `null` if no project is found.
*/ */
const getProjectById = async function (projectId, fieldsToSelect = null) { const getProjectById = async function (projectId, fieldsToSelect = null) {
const query = Project.findById(projectId); const query = Project.findById(projectId);
@ -27,7 +27,7 @@ const getProjectById = async function (projectId, fieldsToSelect = null) {
* *
* @param {string} projectName - The name of the project to find or create. * @param {string} projectName - The name of the project to find or create.
* @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document. * @param {string|string[]} [fieldsToSelect] - The fields to include or exclude in the returned document.
* @returns {Promise<MongoProject>} A plain object representing the project document. * @returns {Promise<IMongoProject>} A plain object representing the project document.
*/ */
const getProjectByName = async function (projectName, fieldsToSelect = null) { const getProjectByName = async function (projectName, fieldsToSelect = null) {
const query = { name: projectName }; const query = { name: projectName };
@ -47,7 +47,7 @@ const getProjectByName = async function (projectName, fieldsToSelect = null) {
* *
* @param {string} projectId - The ID of the project to update. * @param {string} projectId - The ID of the project to update.
* @param {string[]} promptGroupIds - The array of prompt group IDs to add to the project. * @param {string[]} promptGroupIds - The array of prompt group IDs to add to the project.
* @returns {Promise<MongoProject>} The updated project document. * @returns {Promise<IMongoProject>} The updated project document.
*/ */
const addGroupIdsToProject = async function (projectId, promptGroupIds) { const addGroupIdsToProject = async function (projectId, promptGroupIds) {
return await Project.findByIdAndUpdate( return await Project.findByIdAndUpdate(
@ -62,7 +62,7 @@ const addGroupIdsToProject = async function (projectId, promptGroupIds) {
* *
* @param {string} projectId - The ID of the project to update. * @param {string} projectId - The ID of the project to update.
* @param {string[]} promptGroupIds - The array of prompt group IDs to remove from the project. * @param {string[]} promptGroupIds - The array of prompt group IDs to remove from the project.
* @returns {Promise<MongoProject>} The updated project document. * @returns {Promise<IMongoProject>} The updated project document.
*/ */
const removeGroupIdsFromProject = async function (projectId, promptGroupIds) { const removeGroupIdsFromProject = async function (projectId, promptGroupIds) {
return await Project.findByIdAndUpdate( return await Project.findByIdAndUpdate(
@ -87,7 +87,7 @@ const removeGroupFromAllProjects = async (promptGroupId) => {
* *
* @param {string} projectId - The ID of the project to update. * @param {string} projectId - The ID of the project to update.
* @param {string[]} agentIds - The array of agent IDs to add to the project. * @param {string[]} agentIds - The array of agent IDs to add to the project.
* @returns {Promise<MongoProject>} The updated project document. * @returns {Promise<IMongoProject>} The updated project document.
*/ */
const addAgentIdsToProject = async function (projectId, agentIds) { const addAgentIdsToProject = async function (projectId, agentIds) {
return await Project.findByIdAndUpdate( return await Project.findByIdAndUpdate(
@ -102,7 +102,7 @@ const addAgentIdsToProject = async function (projectId, agentIds) {
* *
* @param {string} projectId - The ID of the project to update. * @param {string} projectId - The ID of the project to update.
* @param {string[]} agentIds - The array of agent IDs to remove from the project. * @param {string[]} agentIds - The array of agent IDs to remove from the project.
* @returns {Promise<MongoProject>} The updated project document. * @returns {Promise<IMongoProject>} The updated project document.
*/ */
const removeAgentIdsFromProject = async function (projectId, agentIds) { const removeAgentIdsFromProject = async function (projectId, agentIds) {
return await Project.findByIdAndUpdate( return await Project.findByIdAndUpdate(

View file

@ -1,3 +1,4 @@
const mongoose = require('mongoose');
const { ObjectId } = require('mongodb'); const { ObjectId } = require('mongodb');
const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider'); const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider');
const { const {
@ -6,10 +7,13 @@ const {
removeGroupIdsFromProject, removeGroupIdsFromProject,
removeGroupFromAllProjects, removeGroupFromAllProjects,
} = require('./Project'); } = require('./Project');
const { Prompt, PromptGroup } = require('./schema/promptSchema'); const { promptGroupSchema, promptSchema } = require('@librechat/data-schemas');
const { escapeRegExp } = require('~/server/utils'); const { escapeRegExp } = require('~/server/utils');
const { logger } = require('~/config'); const { logger } = require('~/config');
const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema);
const Prompt = mongoose.model('Prompt', promptSchema);
/** /**
* Create a pipeline for the aggregation to get prompt groups * Create a pipeline for the aggregation to get prompt groups
* @param {Object} query * @param {Object} query

View file

@ -1,3 +1,4 @@
const mongoose = require('mongoose');
const { const {
CacheKeys, CacheKeys,
SystemRoles, SystemRoles,
@ -12,9 +13,11 @@ const {
temporaryChatPermissionsSchema, temporaryChatPermissionsSchema,
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
const Role = require('~/models/schema/roleSchema'); const { roleSchema } = require('@librechat/data-schemas');
const { logger } = require('~/config'); const { logger } = require('~/config');
const Role = mongoose.model('Role', roleSchema);
/** /**
* Retrieve a role by name and convert the found role document to a plain object. * Retrieve a role by name and convert the found role document to a plain object.
* If the role with the given name doesn't exist and the name is a system defined role, create it and return the lean version. * If the role with the given name doesn't exist and the name is a system defined role, create it and return the lean version.
@ -168,6 +171,7 @@ const initializeRoles = async function () {
} }
}; };
module.exports = { module.exports = {
Role,
getRoleByName, getRoleByName,
initializeRoles, initializeRoles,
updateRoleByName, updateRoleByName,

View file

@ -8,7 +8,7 @@ const {
} = require('librechat-data-provider'); } = require('librechat-data-provider');
const { updateAccessPermissions, initializeRoles } = require('~/models/Role'); const { updateAccessPermissions, initializeRoles } = require('~/models/Role');
const getLogStores = require('~/cache/getLogStores'); const getLogStores = require('~/cache/getLogStores');
const Role = require('~/models/schema/roleSchema'); const { Role } = require('~/models/Role');
// Mock the cache // Mock the cache
jest.mock('~/cache/getLogStores', () => { jest.mock('~/cache/getLogStores', () => {

View file

@ -1,7 +1,7 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const signPayload = require('~/server/services/signPayload'); const signPayload = require('~/server/services/signPayload');
const { hashToken } = require('~/server/utils/crypto'); const { hashToken } = require('~/server/utils/crypto');
const sessionSchema = require('./schema/session'); const { sessionSchema } = require('@librechat/data-schemas');
const { logger } = require('~/config'); const { logger } = require('~/config');
const Session = mongoose.model('Session', sessionSchema); const Session = mongoose.model('Session', sessionSchema);

View file

@ -1,7 +1,9 @@
const mongoose = require('mongoose');
const { nanoid } = require('nanoid'); const { nanoid } = require('nanoid');
const { Constants } = require('librechat-data-provider'); const { Constants } = require('librechat-data-provider');
const { Conversation } = require('~/models/Conversation'); const { Conversation } = require('~/models/Conversation');
const SharedLink = require('./schema/shareSchema'); const { shareSchema } = require('@librechat/data-schemas');
const SharedLink = mongoose.model('SharedLink', shareSchema);
const { getMessages } = require('./Message'); const { getMessages } = require('./Message');
const logger = require('~/config/winston'); const logger = require('~/config/winston');

View file

@ -1,6 +1,6 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const { encryptV2 } = require('~/server/utils/crypto'); const { encryptV2 } = require('~/server/utils/crypto');
const tokenSchema = require('./schema/tokenSchema'); const { tokenSchema } = require('@librechat/data-schemas');
const { logger } = require('~/config'); const { logger } = require('~/config');
/** /**

View file

@ -1,9 +1,11 @@
const ToolCall = require('./schema/toolCallSchema'); const mongoose = require('mongoose');
const { toolCallSchema } = require('@librechat/data-schemas');
const ToolCall = mongoose.model('ToolCall', toolCallSchema);
/** /**
* Create a new tool call * Create a new tool call
* @param {ToolCallData} toolCallData - The tool call data * @param {IToolCallData} toolCallData - The tool call data
* @returns {Promise<ToolCallData>} The created tool call document * @returns {Promise<IToolCallData>} The created tool call document
*/ */
async function createToolCall(toolCallData) { async function createToolCall(toolCallData) {
try { try {
@ -16,7 +18,7 @@ async function createToolCall(toolCallData) {
/** /**
* Get a tool call by ID * Get a tool call by ID
* @param {string} id - The tool call document ID * @param {string} id - The tool call document ID
* @returns {Promise<ToolCallData|null>} The tool call document or null if not found * @returns {Promise<IToolCallData|null>} The tool call document or null if not found
*/ */
async function getToolCallById(id) { async function getToolCallById(id) {
try { try {
@ -44,7 +46,7 @@ async function getToolCallsByMessage(messageId, userId) {
* Get tool calls by conversation ID and user * Get tool calls by conversation ID and user
* @param {string} conversationId - The conversation ID * @param {string} conversationId - The conversation ID
* @param {string} userId - The user's ObjectId * @param {string} userId - The user's ObjectId
* @returns {Promise<ToolCallData[]>} Array of tool call documents * @returns {Promise<IToolCallData[]>} Array of tool call documents
*/ */
async function getToolCallsByConvo(conversationId, userId) { async function getToolCallsByConvo(conversationId, userId) {
try { try {
@ -57,8 +59,8 @@ async function getToolCallsByConvo(conversationId, userId) {
/** /**
* Update a tool call * Update a tool call
* @param {string} id - The tool call document ID * @param {string} id - The tool call document ID
* @param {Partial<ToolCallData>} updateData - The data to update * @param {Partial<IToolCallData>} updateData - The data to update
* @returns {Promise<ToolCallData|null>} The updated tool call document or null if not found * @returns {Promise<IToolCallData|null>} The updated tool call document or null if not found
*/ */
async function updateToolCall(id, updateData) { async function updateToolCall(id, updateData) {
try { try {

View file

@ -1,6 +1,6 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const { isEnabled } = require('~/server/utils/handleText'); const { isEnabled } = require('~/server/utils/handleText');
const transactionSchema = require('./schema/transaction'); const { transactionSchema } = require('@librechat/data-schemas');
const { getMultiplier, getCacheMultiplier } = require('./tx'); const { getMultiplier, getCacheMultiplier } = require('./tx');
const { logger } = require('~/config'); const { logger } = require('~/config');
const Balance = require('./Balance'); const Balance = require('./Balance');

View file

@ -1,5 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const userSchema = require('~/models/schema/userSchema'); const { userSchema } = require('@librechat/data-schemas');
const User = mongoose.model('User', userSchema); const User = mongoose.model('User', userSchema);

View file

@ -1,60 +0,0 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;
const AuthSchema = new Schema(
{
authorization_type: String,
custom_auth_header: String,
type: {
type: String,
enum: ['service_http', 'oauth', 'none'],
},
authorization_content_type: String,
authorization_url: String,
client_url: String,
scope: String,
token_exchange_method: {
type: String,
enum: ['default_post', 'basic_auth_header', null],
},
},
{ _id: false },
);
const actionSchema = new Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
action_id: {
type: String,
index: true,
required: true,
},
type: {
type: String,
default: 'action_prototype',
},
settings: Schema.Types.Mixed,
agent_id: String,
assistant_id: String,
metadata: {
api_key: String, // private, encrypted
auth: AuthSchema,
domain: {
type: String,
required: true,
},
// json_schema: Schema.Types.Mixed,
privacy_policy_url: String,
raw_spec: String,
oauth_client_id: String, // private, encrypted
oauth_client_secret: String, // private, encrypted
},
});
// }, { minimize: false }); // Prevent removal of empty objects
module.exports = actionSchema;

View file

@ -1,17 +0,0 @@
const mongoose = require('mongoose');
const balanceSchema = mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
// 1000 tokenCredits = 1 mill ($0.001 USD)
tokenCredits: {
type: Number,
default: 0,
},
});
module.exports = balanceSchema;

View file

@ -1,19 +0,0 @@
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const categoriesSchema = new Schema({
label: {
type: String,
required: true,
unique: true,
},
value: {
type: String,
required: true,
unique: true,
},
});
const categories = mongoose.model('categories', categoriesSchema);
module.exports = { Categories: categories };

View file

@ -1,32 +0,0 @@
const mongoose = require('mongoose');
const conversationTagSchema = mongoose.Schema(
{
tag: {
type: String,
index: true,
},
user: {
type: String,
index: true,
},
description: {
type: String,
index: true,
},
count: {
type: Number,
default: 0,
},
position: {
type: Number,
default: 0,
index: true,
},
},
{ timestamps: true },
);
conversationTagSchema.index({ tag: 1, user: 1 }, { unique: true });
module.exports = mongoose.model('ConversationTag', conversationTagSchema);

View file

@ -1,46 +1,7 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const mongoMeili = require('../plugins/mongoMeili'); const mongoMeili = require('../plugins/mongoMeili');
const { conversationPreset } = require('./defaults');
const convoSchema = mongoose.Schema( const { convoSchema } = require('@librechat/data-schemas');
{
conversationId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true,
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true,
},
user: {
type: String,
index: true,
},
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
agentOptions: {
type: mongoose.Schema.Types.Mixed,
},
...conversationPreset,
agent_id: {
type: String,
},
tags: {
type: [String],
default: [],
meiliIndex: true,
},
files: {
type: [String],
},
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
convoSchema.plugin(mongoMeili, { convoSchema.plugin(mongoMeili, {
@ -52,10 +13,6 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
}); });
} }
convoSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 });
convoSchema.index({ createdAt: 1, updatedAt: 1 });
convoSchema.index({ conversationId: 1, user: 1 }, { unique: true });
const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema); const Conversation = mongoose.models.Conversation || mongoose.model('Conversation', convoSchema);
module.exports = Conversation; module.exports = Conversation;

View file

@ -1,111 +0,0 @@
const { FileSources } = require('librechat-data-provider');
const mongoose = require('mongoose');
/**
* @typedef {Object} MongoFile
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {number} [__v] - MongoDB Version Key
* @property {ObjectId} user - User ID
* @property {string} [conversationId] - Optional conversation ID
* @property {string} file_id - File identifier
* @property {string} [temp_file_id] - Temporary File identifier
* @property {number} bytes - Size of the file in bytes
* @property {string} filename - Name of the file
* @property {string} filepath - Location of the file
* @property {'file'} object - Type of object, always 'file'
* @property {string} type - Type of file
* @property {number} [usage=0] - Number of uses of the file
* @property {string} [context] - Context of the file origin
* @property {boolean} [embedded=false] - Whether or not the file is embedded in vector db
* @property {string} [model] - The model to identify the group region of the file (for Azure OpenAI hosting)
* @property {string} [source] - The source of the file (e.g., from FileSources)
* @property {number} [width] - Optional width of the file
* @property {number} [height] - Optional height of the file
* @property {Object} [metadata] - Metadata related to the file
* @property {string} [metadata.fileIdentifier] - Unique identifier for the file in metadata
* @property {Date} [expiresAt] - Optional expiration date of the file
* @property {Date} [createdAt] - Date when the file was created
* @property {Date} [updatedAt] - Date when the file was updated
*/
/** @type {MongooseSchema<MongoFile>} */
const fileSchema = mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
conversationId: {
type: String,
ref: 'Conversation',
index: true,
},
file_id: {
type: String,
// required: true,
index: true,
},
temp_file_id: {
type: String,
// required: true,
},
bytes: {
type: Number,
required: true,
},
filename: {
type: String,
required: true,
},
filepath: {
type: String,
required: true,
},
object: {
type: String,
required: true,
default: 'file',
},
embedded: {
type: Boolean,
},
type: {
type: String,
required: true,
},
context: {
type: String,
// required: true,
},
usage: {
type: Number,
required: true,
default: 0,
},
source: {
type: String,
default: FileSources.local,
},
model: {
type: String,
},
width: Number,
height: Number,
metadata: {
fileIdentifier: String,
},
expiresAt: {
type: Date,
expires: 3600, // 1 hour in seconds
},
},
{
timestamps: true,
},
);
fileSchema.index({ createdAt: 1, updatedAt: 1 });
module.exports = fileSchema;

View file

@ -1,145 +1,6 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const mongoMeili = require('~/models/plugins/mongoMeili'); const mongoMeili = require('~/models/plugins/mongoMeili');
const messageSchema = mongoose.Schema( const { messageSchema } = require('@librechat/data-schemas');
{
messageId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true,
},
conversationId: {
type: String,
index: true,
required: true,
meiliIndex: true,
},
user: {
type: String,
index: true,
required: true,
default: null,
},
model: {
type: String,
default: null,
},
endpoint: {
type: String,
},
conversationSignature: {
type: String,
},
clientId: {
type: String,
},
invocationId: {
type: Number,
},
parentMessageId: {
type: String,
},
tokenCount: {
type: Number,
},
summaryTokenCount: {
type: Number,
},
sender: {
type: String,
meiliIndex: true,
},
text: {
type: String,
meiliIndex: true,
},
summary: {
type: String,
},
isCreatedByUser: {
type: Boolean,
required: true,
default: false,
},
unfinished: {
type: Boolean,
default: false,
},
error: {
type: Boolean,
default: false,
},
finish_reason: {
type: String,
},
_meiliIndex: {
type: Boolean,
required: false,
select: false,
default: false,
},
files: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
plugin: {
type: {
latest: {
type: String,
required: false,
},
inputs: {
type: [mongoose.Schema.Types.Mixed],
required: false,
default: undefined,
},
outputs: {
type: String,
required: false,
},
},
default: undefined,
},
plugins: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
content: {
type: [{ type: mongoose.Schema.Types.Mixed }],
default: undefined,
meiliIndex: true,
},
thread_id: {
type: String,
},
/* frontend components */
iconURL: {
type: String,
},
attachments: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
/*
attachments: {
type: [
{
file_id: String,
filename: String,
filepath: String,
expiresAt: Date,
width: Number,
height: Number,
type: String,
conversationId: String,
messageId: {
type: String,
required: true,
},
toolCallId: String,
},
],
default: undefined,
},
*/
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) { if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
messageSchema.plugin(mongoMeili, { messageSchema.plugin(mongoMeili, {
@ -149,11 +10,7 @@ if (process.env.MEILI_HOST && process.env.MEILI_MASTER_KEY) {
primaryKey: 'messageId', primaryKey: 'messageId',
}); });
} }
messageSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 });
messageSchema.index({ createdAt: 1 });
messageSchema.index({ messageId: 1, user: 1 }, { unique: true });
/** @type {mongoose.Model<TMessage>} */
const Message = mongoose.models.Message || mongoose.model('Message', messageSchema); const Message = mongoose.models.Message || mongoose.model('Message', messageSchema);
module.exports = Message; module.exports = Message;

View file

@ -1,25 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const { pluginAuthSchema } = require('@librechat/data-schemas');
const pluginAuthSchema = mongoose.Schema(
{
authField: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
userId: {
type: String,
required: true,
},
pluginKey: {
type: String,
},
},
{ timestamps: true },
);
const PluginAuth = mongoose.models.Plugin || mongoose.model('PluginAuth', pluginAuthSchema); const PluginAuth = mongoose.models.Plugin || mongoose.model('PluginAuth', pluginAuthSchema);

View file

@ -1,36 +1,5 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const { conversationPreset } = require('./defaults'); const { presetSchema } = require('@librechat/data-schemas');
const presetSchema = mongoose.Schema(
{
presetId: {
type: String,
unique: true,
required: true,
index: true,
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true,
},
user: {
type: String,
default: null,
},
defaultPreset: {
type: Boolean,
},
order: {
type: Number,
},
...conversationPreset,
agentOptions: {
type: mongoose.Schema.Types.Mixed,
default: null,
},
},
{ timestamps: true },
);
const Preset = mongoose.models.Preset || mongoose.model('Preset', presetSchema); const Preset = mongoose.models.Preset || mongoose.model('Preset', presetSchema);

View file

@ -1,35 +0,0 @@
const { Schema } = require('mongoose');
/**
* @typedef {Object} MongoProject
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {string} name - The name of the project
* @property {ObjectId[]} promptGroupIds - Array of PromptGroup IDs associated with the project
* @property {Date} [createdAt] - Date when the project was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the project was last updated (added by timestamps)
*/
const projectSchema = new Schema(
{
name: {
type: String,
required: true,
index: true,
},
promptGroupIds: {
type: [Schema.Types.ObjectId],
ref: 'PromptGroup',
default: [],
},
agentIds: {
type: [String],
ref: 'Agent',
default: [],
},
},
{
timestamps: true,
},
);
module.exports = projectSchema;

View file

@ -1,118 +0,0 @@
const mongoose = require('mongoose');
const { Constants } = require('librechat-data-provider');
const Schema = mongoose.Schema;
/**
* @typedef {Object} MongoPromptGroup
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {string} name - The name of the prompt group
* @property {ObjectId} author - The author of the prompt group
* @property {ObjectId} [projectId=null] - The project ID of the prompt group
* @property {ObjectId} [productionId=null] - The project ID of the prompt group
* @property {string} authorName - The name of the author of the prompt group
* @property {number} [numberOfGenerations=0] - Number of generations the prompt group has
* @property {string} [oneliner=''] - Oneliner description of the prompt group
* @property {string} [category=''] - Category of the prompt group
* @property {string} [command] - Command for the prompt group
* @property {Date} [createdAt] - Date when the prompt group was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the prompt group was last updated (added by timestamps)
*/
const promptGroupSchema = new Schema(
{
name: {
type: String,
required: true,
index: true,
},
numberOfGenerations: {
type: Number,
default: 0,
},
oneliner: {
type: String,
default: '',
},
category: {
type: String,
default: '',
index: true,
},
projectIds: {
type: [Schema.Types.ObjectId],
ref: 'Project',
index: true,
},
productionId: {
type: Schema.Types.ObjectId,
ref: 'Prompt',
required: true,
index: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true,
},
authorName: {
type: String,
required: true,
},
command: {
type: String,
index: true,
validate: {
validator: function (v) {
return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v);
},
message: (props) =>
`${props.value} is not a valid command. Only lowercase alphanumeric characters and highfins (') are allowed.`,
},
maxlength: [
Constants.COMMANDS_MAX_LENGTH,
`Command cannot be longer than ${Constants.COMMANDS_MAX_LENGTH} characters`,
],
},
},
{
timestamps: true,
},
);
const PromptGroup = mongoose.model('PromptGroup', promptGroupSchema);
const promptSchema = new Schema(
{
groupId: {
type: Schema.Types.ObjectId,
ref: 'PromptGroup',
required: true,
index: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
prompt: {
type: String,
required: true,
},
type: {
type: String,
enum: ['text', 'chat'],
required: true,
},
},
{
timestamps: true,
},
);
const Prompt = mongoose.model('Prompt', promptSchema);
promptSchema.index({ createdAt: 1, updatedAt: 1 });
promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
module.exports = { Prompt, PromptGroup };

View file

@ -1,20 +0,0 @@
const mongoose = require('mongoose');
const sessionSchema = mongoose.Schema({
refreshTokenHash: {
type: String,
required: true,
},
expiration: {
type: Date,
required: true,
expires: 0,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
});
module.exports = sessionSchema;

View file

@ -1,54 +0,0 @@
const mongoose = require('mongoose');
/**
* @typedef {Object} ToolCallData
* @property {string} conversationId - The ID of the conversation
* @property {string} messageId - The ID of the message
* @property {string} toolId - The ID of the tool
* @property {string | ObjectId} user - The user's ObjectId
* @property {unknown} [result] - Optional result data
* @property {TAttachment[]} [attachments] - Optional attachments data
* @property {number} [blockIndex] - Optional code block index
* @property {number} [partIndex] - Optional part index
*/
/** @type {MongooseSchema<ToolCallData>} */
const toolCallSchema = mongoose.Schema(
{
conversationId: {
type: String,
required: true,
},
messageId: {
type: String,
required: true,
},
toolId: {
type: String,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
result: {
type: mongoose.Schema.Types.Mixed,
},
attachments: {
type: mongoose.Schema.Types.Mixed,
},
blockIndex: {
type: Number,
},
partIndex: {
type: Number,
},
},
{ timestamps: true },
);
toolCallSchema.index({ messageId: 1, user: 1 });
toolCallSchema.index({ conversationId: 1, user: 1 });
module.exports = mongoose.model('ToolCall', toolCallSchema);

View file

@ -1,151 +0,0 @@
const mongoose = require('mongoose');
const { SystemRoles } = require('librechat-data-provider');
/**
* @typedef {Object} MongoSession
* @property {string} [refreshToken] - The refresh token
*/
/**
* @typedef {Object} MongoUser
* @property {ObjectId} [_id] - MongoDB Document ID
* @property {string} [name] - The user's name
* @property {string} [username] - The user's username, in lowercase
* @property {string} email - The user's email address
* @property {boolean} emailVerified - Whether the user's email is verified
* @property {string} [password] - The user's password, trimmed with 8-128 characters
* @property {string} [avatar] - The URL of the user's avatar
* @property {string} provider - The provider of the user's account (e.g., 'local', 'google')
* @property {string} [role='USER'] - The role of the user
* @property {string} [googleId] - Optional Google ID for the user
* @property {string} [facebookId] - Optional Facebook ID for the user
* @property {string} [openidId] - Optional OpenID ID for the user
* @property {string} [ldapId] - Optional LDAP ID for the user
* @property {string} [githubId] - Optional GitHub ID for the user
* @property {string} [discordId] - Optional Discord ID for the user
* @property {string} [appleId] - Optional Apple ID for the user
* @property {Array} [plugins=[]] - List of plugins used by the user
* @property {Array.<MongoSession>} [refreshToken] - List of sessions with refresh tokens
* @property {Date} [expiresAt] - Optional expiration date of the file
* @property {Date} [createdAt] - Date when the user was created (added by timestamps)
* @property {Date} [updatedAt] - Date when the user was last updated (added by timestamps)
*/
/** @type {MongooseSchema<MongoSession>} */
const Session = mongoose.Schema({
refreshToken: {
type: String,
default: '',
},
});
const backupCodeSchema = mongoose.Schema({
codeHash: { type: String, required: true },
used: { type: Boolean, default: false },
usedAt: { type: Date, default: null },
});
/** @type {MongooseSchema<MongoUser>} */
const userSchema = mongoose.Schema(
{
name: {
type: String,
},
username: {
type: String,
lowercase: true,
default: '',
},
email: {
type: String,
required: [true, 'can\'t be blank'],
lowercase: true,
unique: true,
match: [/\S+@\S+\.\S+/, 'is invalid'],
index: true,
},
emailVerified: {
type: Boolean,
required: true,
default: false,
},
password: {
type: String,
trim: true,
minlength: 8,
maxlength: 128,
},
avatar: {
type: String,
required: false,
},
provider: {
type: String,
required: true,
default: 'local',
},
role: {
type: String,
default: SystemRoles.USER,
},
googleId: {
type: String,
unique: true,
sparse: true,
},
facebookId: {
type: String,
unique: true,
sparse: true,
},
openidId: {
type: String,
unique: true,
sparse: true,
},
ldapId: {
type: String,
unique: true,
sparse: true,
},
githubId: {
type: String,
unique: true,
sparse: true,
},
discordId: {
type: String,
unique: true,
sparse: true,
},
appleId: {
type: String,
unique: true,
sparse: true,
},
plugins: {
type: Array,
},
totpSecret: {
type: String,
},
backupCodes: {
type: [backupCodeSchema],
},
refreshToken: {
type: [Session],
},
expiresAt: {
type: Date,
expires: 604800, // 7 days in seconds
},
termsAccepted: {
type: Boolean,
default: false,
},
},
{ timestamps: true },
);
module.exports = userSchema;

View file

@ -76,6 +76,7 @@
"klona": "^2.0.6", "klona": "^2.0.6",
"langchain": "^0.2.19", "langchain": "^0.2.19",
"librechat-data-provider": "*", "librechat-data-provider": "*",
"@librechat/data-schemas": "*",
"librechat-mcp": "*", "librechat-mcp": "*",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"meilisearch": "^0.38.0", "meilisearch": "^0.38.0",

View file

@ -766,36 +766,6 @@
* @memberof typedefs * @memberof typedefs
*/ */
/**
* @exports MongoFile
* @typedef {import('~/models/schema/fileSchema.js').MongoFile} MongoFile
* @memberof typedefs
*/
/**
* @exports ToolCallData
* @typedef {import('~/models/schema/toolCallSchema.js').ToolCallData} ToolCallData
* @memberof typedefs
*/
/**
* @exports MongoUser
* @typedef {import('~/models/schema/userSchema.js').MongoUser} MongoUser
* @memberof typedefs
*/
/**
* @exports MongoProject
* @typedef {import('~/models/schema/projectSchema.js').MongoProject} MongoProject
* @memberof typedefs
*/
/**
* @exports MongoPromptGroup
* @typedef {import('~/models/schema/promptSchema.js').MongoPromptGroup} MongoPromptGroup
* @memberof typedefs
*/
/** /**
* @exports uploadImageBuffer * @exports uploadImageBuffer
* @typedef {import('~/server/services/Files/process').uploadImageBuffer} uploadImageBuffer * @typedef {import('~/server/services/Files/process').uploadImageBuffer} uploadImageBuffer

View file

@ -9,6 +9,7 @@ const rootDir = path.resolve(__dirname, '..');
const directories = [ const directories = [
rootDir, rootDir,
path.resolve(rootDir, 'packages', 'data-provider'), path.resolve(rootDir, 'packages', 'data-provider'),
path.resolve(rootDir, 'packages', 'data-schemas'),
path.resolve(rootDir, 'packages', 'mcp'), path.resolve(rootDir, 'packages', 'mcp'),
path.resolve(rootDir, 'client'), path.resolve(rootDir, 'client'),
path.resolve(rootDir, 'api'), path.resolve(rootDir, 'api'),

View file

@ -16,6 +16,7 @@ const rootDir = path.resolve(__dirname, '..');
const directories = [ const directories = [
rootDir, rootDir,
path.resolve(rootDir, 'packages', 'data-provider'), path.resolve(rootDir, 'packages', 'data-provider'),
path.resolve(rootDir, 'packages', 'data-schemas'),
path.resolve(rootDir, 'packages', 'mcp'), path.resolve(rootDir, 'packages', 'mcp'),
path.resolve(rootDir, 'client'), path.resolve(rootDir, 'client'),
path.resolve(rootDir, 'api'), path.resolve(rootDir, 'api'),

View file

@ -220,9 +220,6 @@ export default [
'jsx-a11y/interactive-supports-focus': 'off', 'jsx-a11y/interactive-supports-focus': 'off',
'jsx-a11y/no-noninteractive-tabindex': 'off', 'jsx-a11y/no-noninteractive-tabindex': 'off',
'jsx-a11y/img-redundant-alt': 'off', 'jsx-a11y/img-redundant-alt': 'off',
'jsx-a11y/media-has-caption': 'off',
'jsx-a11y/no-autofocus': 'off',
'jsx-a11y/alt-text': 'off',
}, },
}, },
{ {
@ -369,4 +366,16 @@ export default [
}, },
}, },
}, },
{
// **New Data-schemas configuration block**
files: ['./packages/data-schemas/**/*.ts'],
languageOptions: {
parser: tsParser,
ecmaVersion: 'latest',
sourceType: 'module',
parserOptions: {
project: './packages/data-schemas/tsconfig.json',
},
},
},
]; ];

204
package-lock.json generated
View file

@ -62,6 +62,7 @@
"@langchain/google-vertexai": "^0.2.0", "@langchain/google-vertexai": "^0.2.0",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@librechat/agents": "^2.2.0", "@librechat/agents": "^2.2.0",
"@librechat/data-schemas": "*",
"@waylaidwanderer/fetch-event-source": "^3.0.1", "@waylaidwanderer/fetch-event-source": "^3.0.1",
"axios": "1.7.8", "axios": "1.7.8",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
@ -17927,6 +17928,10 @@
"resolved": "api", "resolved": "api",
"link": true "link": true
}, },
"node_modules/@librechat/data-schemas": {
"resolved": "packages/data-schemas",
"link": true
},
"node_modules/@librechat/frontend": { "node_modules/@librechat/frontend": {
"resolved": "client", "resolved": "client",
"link": true "link": true
@ -43691,6 +43696,205 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"packages/data-schemas": {
"name": "@librechat/data-schemas",
"version": "0.0.1",
"license": "ISC",
"dependencies": {
"mongoose": "^8.9.5"
},
"devDependencies": {
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^25.0.2",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.1.0",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"@types/diff": "^6.0.0",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.0",
"jest": "^29.5.0",
"jest-junit": "^16.0.0",
"rimraf": "^5.0.1",
"rollup": "^4.22.4",
"rollup-plugin-generate-package-json": "^3.2.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-typescript2": "^0.35.0",
"ts-node": "^10.9.2",
"typescript": "^5.0.4"
},
"peerDependencies": {
"keyv": "^4.5.4"
}
},
"packages/data-schemas/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"packages/data-schemas/node_modules/bson": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz",
"integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==",
"license": "Apache-2.0",
"engines": {
"node": ">=14.20.1"
}
},
"packages/data-schemas/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"packages/data-schemas/node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"packages/data-schemas/node_modules/kareem": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
"integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
"license": "Apache-2.0",
"engines": {
"node": ">=12.0.0"
}
},
"packages/data-schemas/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"packages/data-schemas/node_modules/mongodb": {
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz",
"integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==",
"license": "Apache-2.0",
"dependencies": {
"bson": "^5.5.0",
"mongodb-connection-string-url": "^2.6.0",
"socks": "^2.7.1"
},
"engines": {
"node": ">=14.20.1"
},
"optionalDependencies": {
"@mongodb-js/saslprep": "^1.1.0"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.0.0",
"kerberos": "^1.0.0 || ^2.0.0",
"mongodb-client-encryption": ">=2.3.0 <3",
"snappy": "^7.2.2"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
}
}
},
"packages/data-schemas/node_modules/mongoose": {
"version": "7.8.6",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.8.6.tgz",
"integrity": "sha512-1oVPRHvcmPVwk/zeSTEzayzQEVeYQM1D5zrkLsttfNNB7pPRUmkKeFu6gpbvyEswOuZLrWJjqB8kSTY+k2AZOA==",
"license": "MIT",
"dependencies": {
"bson": "^5.5.0",
"kareem": "2.5.1",
"mongodb": "5.9.2",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "16.0.1"
},
"engines": {
"node": ">=14.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"packages/data-schemas/node_modules/rimraf": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
"integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"glob": "^10.3.7"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"packages/data-schemas/node_modules/sift": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
"integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==",
"license": "MIT"
},
"packages/mcp": { "packages/mcp": {
"name": "librechat-mcp", "name": "librechat-mcp",
"version": "1.1.0", "version": "1.1.0",

View file

@ -39,7 +39,8 @@
"backend:stop": "node config/stop-backend.js", "backend:stop": "node config/stop-backend.js",
"build:data-provider": "cd packages/data-provider && npm run build", "build:data-provider": "cd packages/data-provider && npm run build",
"build:mcp": "cd packages/mcp && npm run build", "build:mcp": "cd packages/mcp && npm run build",
"frontend": "npm run build:data-provider && npm run build:mcp && cd client && npm run build", "build:data-schemas": "cd packages/data-schemas && npm run build",
"frontend": "npm run build:data-provider && npm run build:mcp && npm run build:data-schemas && cd client && npm run build",
"frontend:ci": "npm run build:data-provider && cd client && npm run build:ci", "frontend:ci": "npm run build:data-provider && cd client && npm run build:ci",
"frontend:dev": "cd client && npm run dev", "frontend:dev": "cd client && npm run dev",
"e2e": "playwright test --config=e2e/playwright.config.local.ts", "e2e": "playwright test --config=e2e/playwright.config.local.ts",
@ -61,7 +62,9 @@
"b:api-inspect": "NODE_ENV=production bun --inspect run api/server/index.js", "b:api-inspect": "NODE_ENV=production bun --inspect run api/server/index.js",
"b:api:dev": "NODE_ENV=production bun run --watch api/server/index.js", "b:api:dev": "NODE_ENV=production bun run --watch api/server/index.js",
"b:data": "cd packages/data-provider && bun run b:build", "b:data": "cd packages/data-provider && bun run b:build",
"b:client": "bun --bun run b:data && cd client && bun --bun run b:build", "b:mcp": "cd packages/mcp && bun run b:build",
"b:data-schemas": "cd packages/data-schemas && bun run b:build",
"b:client": "bun --bun run b:data && bun --bun run b:mcp && bun --bun run b:data-schemas && cd client && bun --bun run b:build",
"b:client:dev": "cd client && bun run b:dev", "b:client:dev": "cd client && bun run b:dev",
"b:test:client": "cd client && bun run b:test", "b:test:client": "cd client && bun run b:test",
"b:test:api": "cd api && bun run b:test", "b:test:api": "cd api && bun run b:test",

2
packages/data-schemas/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
test_bundle/

View file

@ -0,0 +1,4 @@
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }], '@babel/preset-typescript'],
plugins: ['babel-plugin-replace-ts-export-assignment'],
};

View file

@ -0,0 +1,19 @@
export default {
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!<rootDir>/node_modules/'],
coveragePathIgnorePatterns: ['/node_modules/', '/dist/'],
coverageReporters: ['text', 'cobertura'],
testResultsProcessor: 'jest-junit',
moduleNameMapper: {
'^@src/(.*)$': '<rootDir>/src/$1',
},
// coverageThreshold: {
// global: {
// statements: 58,
// branches: 49,
// functions: 50,
// lines: 57,
// },
// },
restoreMocks: true,
testTimeout: 15000,
};

View file

@ -0,0 +1,67 @@
{
"name": "@librechat/data-schemas",
"version": "0.0.1",
"type": "module",
"description": "Mongoose schemas and models for LibreChat",
"main": "dist/index.cjs",
"module": "dist/index.es.js",
"types": "./dist/types/index.d.ts",
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.cjs",
"types": "./dist/types/index.d.ts"
}
},
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && rollup -c --silent --bundleConfigAsCjs",
"build:watch": "rollup -c -w",
"test": "jest --coverage --watch",
"test:ci": "jest --coverage --ci",
"verify": "npm run test:ci",
"b:clean": "bun run rimraf dist",
"b:build": "bun run b:clean && bun run rollup -c --silent --bundleConfigAsCjs"
},
"repository": {
"type": "git",
"url": "git+https://github.com/danny-avila/LibreChat.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/danny-avila/LibreChat/issues"
},
"homepage": "https://librechat.ai",
"devDependencies": {
"@rollup/plugin-alias": "^5.1.0",
"@rollup/plugin-commonjs": "^25.0.2",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.1.0",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^12.1.2",
"@types/diff": "^6.0.0",
"@types/express": "^5.0.0",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.0",
"jest": "^29.5.0",
"jest-junit": "^16.0.0",
"rimraf": "^5.0.1",
"rollup": "^4.22.4",
"rollup-plugin-generate-package-json": "^3.2.0",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-typescript2": "^0.35.0",
"ts-node": "^10.9.2",
"typescript": "^5.0.4"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"dependencies": {
"mongoose": "^8.9.5"
},
"peerDependencies": {
"keyv": "^4.5.4"
}
}

View file

@ -0,0 +1,25 @@
import json from '@rollup/plugin-json';
import typescript from '@rollup/plugin-typescript';
import commonjs from '@rollup/plugin-commonjs';
export default {
input: 'src/index.ts',
output: [
{
file: 'dist/index.cjs', // Changed from index.js to index.cjs
format: 'cjs',
sourcemap: true,
exports: 'named',
},
{
file: 'dist/index.es.js',
format: 'esm',
sourcemap: true,
exports: 'named',
},
],
plugins: [json(), commonjs(), typescript({ tsconfig: './tsconfig.json' })],
external: [
// list your external dependencies
],
};

View file

@ -0,0 +1,50 @@
// index.ts
import actionSchema from './schema/action';
import agentSchema from './schema/agent';
import assistantSchema from './schema/assistant';
import balanceSchema from './schema/balance';
import bannerSchema from './schema/banner';
import categoriesSchema from './schema/categories';
import conversationTagSchema from './schema/conversationTag';
import convoSchema from './schema/convo';
import fileSchema from './schema/file';
import keySchema from './schema/key';
import messageSchema from './schema/message';
import pluginAuthSchema from './schema/pluginAuth';
import presetSchema from './schema/preset';
import projectSchema from './schema/project';
import promptSchema from './schema/prompt';
import promptGroupSchema from './schema/promptGroup';
import roleSchema from './schema/role';
import sessionSchema from './schema/session';
import shareSchema from './schema/share';
import tokenSchema from './schema/token';
import toolCallSchema from './schema/toolCall';
import transactionSchema from './schema/transaction';
import userSchema from './schema/user';
export {
actionSchema,
agentSchema,
assistantSchema,
balanceSchema,
bannerSchema,
categoriesSchema,
conversationTagSchema,
convoSchema,
fileSchema,
keySchema,
messageSchema,
pluginAuthSchema,
presetSchema,
projectSchema,
promptSchema,
promptGroupSchema,
roleSchema,
sessionSchema,
shareSchema,
tokenSchema,
toolCallSchema,
transactionSchema,
userSchema,
};

View file

@ -0,0 +1,78 @@
import mongoose, { Schema, Document } from 'mongoose';
export interface IAction extends Document {
user: mongoose.Types.ObjectId;
action_id: string;
type: string;
settings?: unknown;
agent_id?: string;
assistant_id?: string;
metadata: {
api_key?: string;
auth: {
authorization_type?: string;
custom_auth_header?: string;
type: 'service_http' | 'oauth' | 'none';
authorization_content_type?: string;
authorization_url?: string;
client_url?: string;
scope?: string;
token_exchange_method: 'default_post' | 'basic_auth_header' | null;
};
domain: string;
privacy_policy_url?: string;
raw_spec?: string;
oauth_client_id?: string;
oauth_client_secret?: string;
};
}
// Define the Auth sub-schema with type-safety.
const AuthSchema = new Schema(
{
authorization_type: { type: String },
custom_auth_header: { type: String },
type: { type: String, enum: ['service_http', 'oauth', 'none'] },
authorization_content_type: { type: String },
authorization_url: { type: String },
client_url: { type: String },
scope: { type: String },
token_exchange_method: { type: String, enum: ['default_post', 'basic_auth_header', null] },
},
{ _id: false },
);
const Action = new Schema<IAction>({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
action_id: {
type: String,
index: true,
required: true,
},
type: {
type: String,
default: 'action_prototype',
},
settings: Schema.Types.Mixed,
agent_id: String,
assistant_id: String,
metadata: {
api_key: String,
auth: AuthSchema,
domain: {
type: String,
required: true,
},
privacy_policy_url: String,
raw_spec: String,
oauth_client_id: String,
oauth_client_secret: String,
},
});
export default Action;

View file

@ -1,6 +1,35 @@
const mongoose = require('mongoose'); import { Schema, Document, Types } from 'mongoose';
const agentSchema = mongoose.Schema( // @ts-ignore
export interface IAgent extends Omit<Document, 'model'> {
id: string;
name?: string;
description?: string;
instructions?: string;
avatar?: {
filepath: string;
source: string;
};
provider: string;
model: string;
model_parameters?: Record<string, unknown>;
artifacts?: string;
access_level?: number;
tools?: string[];
tool_kwargs?: Array<unknown>;
actions?: string[];
author: Types.ObjectId;
authorName?: string;
hide_sequential_outputs?: boolean;
end_after_tools?: boolean;
agent_ids?: string[];
isCollaborative?: boolean;
conversation_starters?: string[];
tool_resources?: unknown;
projectIds?: Types.ObjectId[];
}
const agentSchema = new Schema<IAgent>(
{ {
id: { id: {
type: String, type: String,
@ -46,14 +75,14 @@ const agentSchema = mongoose.Schema(
default: undefined, default: undefined,
}, },
tool_kwargs: { tool_kwargs: {
type: [{ type: mongoose.Schema.Types.Mixed }], type: [{ type: Schema.Types.Mixed }],
}, },
actions: { actions: {
type: [String], type: [String],
default: undefined, default: undefined,
}, },
author: { author: {
type: mongoose.Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'User', ref: 'User',
required: true, required: true,
}, },
@ -79,11 +108,11 @@ const agentSchema = mongoose.Schema(
default: [], default: [],
}, },
tool_resources: { tool_resources: {
type: mongoose.Schema.Types.Mixed, type: Schema.Types.Mixed,
default: {}, default: {},
}, },
projectIds: { projectIds: {
type: [mongoose.Schema.Types.ObjectId], type: [Schema.Types.ObjectId],
ref: 'Project', ref: 'Project',
index: true, index: true,
}, },
@ -93,4 +122,4 @@ const agentSchema = mongoose.Schema(
}, },
); );
module.exports = agentSchema; export default agentSchema;

View file

@ -1,9 +1,23 @@
const mongoose = require('mongoose'); import { Schema, Document, Types } from 'mongoose';
const assistantSchema = mongoose.Schema( export interface IAssistant extends Document {
user: Types.ObjectId;
assistant_id: string;
avatar?: {
filepath: string;
source: string;
};
conversation_starters?: string[];
access_level?: number;
file_ids?: string[];
actions?: string[];
append_current_datetime?: boolean;
}
const assistantSchema = new Schema<IAssistant>(
{ {
user: { user: {
type: mongoose.Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'User', ref: 'User',
required: true, required: true,
}, },
@ -38,4 +52,4 @@ const assistantSchema = mongoose.Schema(
}, },
); );
module.exports = assistantSchema; export default assistantSchema;

View file

@ -0,0 +1,22 @@
import { Schema, Document, Types } from 'mongoose';
export interface IBalance extends Document {
user: Types.ObjectId;
tokenCredits: number;
}
const balanceSchema = new Schema<IBalance>({
user: {
type: Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
// 1000 tokenCredits = 1 mill ($0.001 USD)
tokenCredits: {
type: Number,
default: 0,
},
});
export default balanceSchema;

View file

@ -1,6 +1,15 @@
const mongoose = require('mongoose'); import { Schema, Document } from 'mongoose';
const bannerSchema = mongoose.Schema( export interface IBanner extends Document {
bannerId: string;
message: string;
displayFrom: Date;
displayTo?: Date;
type: 'banner' | 'popup';
isPublic: boolean;
}
const bannerSchema = new Schema<IBanner>(
{ {
bannerId: { bannerId: {
type: String, type: String,
@ -28,9 +37,7 @@ const bannerSchema = mongoose.Schema(
default: false, default: false,
}, },
}, },
{ timestamps: true }, { timestamps: true },
); );
const Banner = mongoose.model('Banner', bannerSchema); export default bannerSchema;
module.exports = Banner;

View file

@ -0,0 +1,21 @@
import { Schema, Document } from 'mongoose';
export interface ICategory extends Document {
label: string;
value: string;
}
const categoriesSchema = new Schema<ICategory>({
label: {
type: String,
required: true,
unique: true,
},
value: {
type: String,
required: true,
unique: true,
},
});
export default categoriesSchema;

View file

@ -0,0 +1,41 @@
import { Schema, Document } from 'mongoose';
export interface IConversationTag extends Document {
tag?: string;
user?: string;
description?: string;
count?: number;
position?: number;
}
const conversationTag = new Schema<IConversationTag>(
{
tag: {
type: String,
index: true,
},
user: {
type: String,
index: true,
},
description: {
type: String,
index: true,
},
count: {
type: Number,
default: 0,
},
position: {
type: Number,
default: 0,
index: true,
},
},
{ timestamps: true },
);
// Create a compound index on tag and user with unique constraint.
conversationTag.index({ tag: 1, user: 1 }, { unique: true });
export default conversationTag;

View file

@ -0,0 +1,101 @@
import mongoose, { Schema, Document, Types } from 'mongoose';
import { conversationPreset } from './defaults';
// @ts-ignore
export interface IConversation extends Document {
conversationId: string;
title?: string;
user?: string;
messages?: Types.ObjectId[];
agentOptions?: unknown;
// Fields provided by conversationPreset (adjust types as needed)
endpoint?: string;
endpointType?: string;
model?: string;
region?: string;
chatGptLabel?: string;
examples?: unknown[];
modelLabel?: string;
promptPrefix?: string;
temperature?: number;
top_p?: number;
topP?: number;
topK?: number;
maxOutputTokens?: number;
maxTokens?: number;
presence_penalty?: number;
frequency_penalty?: number;
file_ids?: string[];
resendImages?: boolean;
promptCache?: boolean;
thinking?: boolean;
thinkingBudget?: number;
system?: string;
resendFiles?: boolean;
imageDetail?: string;
agent_id?: string;
assistant_id?: string;
instructions?: string;
stop?: string[];
isArchived?: boolean;
iconURL?: string;
greeting?: string;
spec?: string;
tags?: string[];
tools?: string[];
maxContextTokens?: number;
max_tokens?: number;
reasoning_effort?: string;
// Additional fields
files?: string[];
expiredAt?: Date;
createdAt?: Date;
updatedAt?: Date;
}
const convoSchema: Schema<IConversation> = new Schema(
{
conversationId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true,
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true,
},
user: {
type: String,
index: true,
},
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
agentOptions: {
type: mongoose.Schema.Types.Mixed,
},
...conversationPreset,
agent_id: {
type: String,
},
tags: {
type: [String],
default: [],
meiliIndex: true,
},
files: {
type: [String],
},
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
convoSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 });
convoSchema.index({ createdAt: 1, updatedAt: 1 });
convoSchema.index({ conversationId: 1, user: 1 }, { unique: true });
export default convoSchema;

View file

@ -1,6 +1,7 @@
const mongoose = require('mongoose'); import { Schema } from 'mongoose';
const conversationPreset = { // @ts-ignore
export const conversationPreset = {
// endpoint: [azureOpenAI, openAI, anthropic, chatGPTBrowser] // endpoint: [azureOpenAI, openAI, anthropic, chatGPTBrowser]
endpoint: { endpoint: {
type: String, type: String,
@ -26,7 +27,7 @@ const conversationPreset = {
required: false, required: false,
}, },
// for google only // for google only
examples: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined }, examples: { type: [{ type: Schema.Types.Mixed }], default: undefined },
modelLabel: { modelLabel: {
type: String, type: String,
required: false, required: false,
@ -135,7 +136,3 @@ const conversationPreset = {
type: String, type: String,
}, },
}; };
module.exports = {
conversationPreset,
};

View file

@ -0,0 +1,107 @@
import mongoose, { Schema, Document, Types } from 'mongoose';
import { FileSources } from 'librechat-data-provider';
// @ts-ignore
export interface IMongoFile extends Document {
user: Types.ObjectId;
conversationId?: string;
file_id: string;
temp_file_id?: string;
bytes: number;
filename: string;
filepath: string;
object: 'file';
embedded?: boolean;
type: string;
context?: string;
usage: number;
source: string;
model?: string;
width?: number;
height?: number;
metadata?: {
fileIdentifier?: string;
};
expiresAt?: Date;
createdAt?: Date;
updatedAt?: Date;
}
const file: Schema<IMongoFile> = new Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
conversationId: {
type: String,
ref: 'Conversation',
index: true,
},
file_id: {
type: String,
index: true,
required: true,
},
temp_file_id: {
type: String,
},
bytes: {
type: Number,
required: true,
},
filename: {
type: String,
required: true,
},
filepath: {
type: String,
required: true,
},
object: {
type: String,
required: true,
default: 'file',
},
embedded: {
type: Boolean,
},
type: {
type: String,
required: true,
},
context: {
type: String,
},
usage: {
type: Number,
required: true,
default: 0,
},
source: {
type: String,
default: FileSources.local,
},
model: {
type: String,
},
width: Number,
height: Number,
metadata: {
fileIdentifier: String,
},
expiresAt: {
type: Date,
expires: 3600, // 1 hour in seconds
},
},
{
timestamps: true,
},
);
file.index({ createdAt: 1, updatedAt: 1 });
export default file;

View file

@ -1,6 +1,13 @@
const mongoose = require('mongoose'); import mongoose, { Schema, Document, Types } from 'mongoose';
const keySchema = mongoose.Schema({ export interface IKey extends Document {
userId: Types.ObjectId;
name: string;
value: string;
expiresAt?: Date;
}
const keySchema: Schema<IKey> = new Schema({
userId: { userId: {
type: mongoose.Schema.Types.ObjectId, type: mongoose.Schema.Types.ObjectId,
ref: 'User', ref: 'User',
@ -21,4 +28,4 @@ const keySchema = mongoose.Schema({
keySchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); keySchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
module.exports = keySchema; export default keySchema;

View file

@ -0,0 +1,185 @@
import mongoose, { Schema, Document } from 'mongoose';
// @ts-ignore
export interface IMessage extends Document {
messageId: string;
conversationId: string;
user: string;
model?: string;
endpoint?: string;
conversationSignature?: string;
clientId?: string;
invocationId?: number;
parentMessageId?: string;
tokenCount?: number;
summaryTokenCount?: number;
sender?: string;
text?: string;
summary?: string;
isCreatedByUser: boolean;
unfinished?: boolean;
error?: boolean;
finish_reason?: string;
_meiliIndex?: boolean;
files?: unknown[];
plugin?: {
latest?: string;
inputs?: unknown[];
outputs?: string;
};
plugins?: unknown[];
content?: unknown[];
thread_id?: string;
iconURL?: string;
attachments?: unknown[];
expiredAt?: Date;
createdAt?: Date;
updatedAt?: Date;
}
const messageSchema: Schema<IMessage> = new Schema(
{
messageId: {
type: String,
unique: true,
required: true,
index: true,
meiliIndex: true,
},
conversationId: {
type: String,
index: true,
required: true,
meiliIndex: true,
},
user: {
type: String,
index: true,
required: true,
default: null,
},
model: {
type: String,
default: null,
},
endpoint: {
type: String,
},
conversationSignature: {
type: String,
},
clientId: {
type: String,
},
invocationId: {
type: Number,
},
parentMessageId: {
type: String,
},
tokenCount: {
type: Number,
},
summaryTokenCount: {
type: Number,
},
sender: {
type: String,
meiliIndex: true,
},
text: {
type: String,
meiliIndex: true,
},
summary: {
type: String,
},
isCreatedByUser: {
type: Boolean,
required: true,
default: false,
},
unfinished: {
type: Boolean,
default: false,
},
error: {
type: Boolean,
default: false,
},
finish_reason: {
type: String,
},
_meiliIndex: {
type: Boolean,
required: false,
select: false,
default: false,
},
files: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
plugin: {
type: {
latest: {
type: String,
required: false,
},
inputs: {
type: [mongoose.Schema.Types.Mixed],
required: false,
default: undefined,
},
outputs: {
type: String,
required: false,
},
},
default: undefined,
},
plugins: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
content: {
type: [{ type: mongoose.Schema.Types.Mixed }],
default: undefined,
meiliIndex: true,
},
thread_id: {
type: String,
},
/* frontend components */
iconURL: {
type: String,
},
attachments: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
/*
attachments: {
type: [
{
file_id: String,
filename: String,
filepath: String,
expiresAt: Date,
width: Number,
height: Number,
type: String,
conversationId: String,
messageId: {
type: String,
required: true,
},
toolCallId: String,
},
],
default: undefined,
},
*/
expiredAt: {
type: Date,
},
},
{ timestamps: true },
);
messageSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 });
messageSchema.index({ createdAt: 1 });
messageSchema.index({ messageId: 1, user: 1 }, { unique: true });
export default messageSchema;

View file

@ -0,0 +1,33 @@
import { Schema, Document } from 'mongoose';
export interface IPluginAuth extends Document {
authField: string;
value: string;
userId: string;
pluginKey?: string;
createdAt?: Date;
updatedAt?: Date;
}
const pluginAuthSchema: Schema<IPluginAuth> = new Schema(
{
authField: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
userId: {
type: String,
required: true,
},
pluginKey: {
type: String,
},
},
{ timestamps: true },
);
export default pluginAuthSchema;

View file

@ -0,0 +1,85 @@
import mongoose, { Schema, Document } from 'mongoose';
import { conversationPreset } from './defaults';
// @ts-ignore
export interface IPreset extends Document {
presetId: string;
title: string;
user: string | null;
defaultPreset?: boolean;
order?: number;
// Additional fields from conversationPreset and others will be available via an index signature.
endpoint?: string;
endpointType?: string;
model?: string;
region?: string;
chatGptLabel?: string;
examples?: unknown[];
modelLabel?: string;
promptPrefix?: string;
temperature?: number;
top_p?: number;
topP?: number;
topK?: number;
maxOutputTokens?: number;
maxTokens?: number;
presence_penalty?: number;
frequency_penalty?: number;
file_ids?: string[];
resendImages?: boolean;
promptCache?: boolean;
thinking?: boolean;
thinkingBudget?: number;
system?: string;
resendFiles?: boolean;
imageDetail?: string;
agent_id?: string;
assistant_id?: string;
instructions?: string;
stop?: string[];
isArchived?: boolean;
iconURL?: string;
greeting?: string;
spec?: string;
tags?: string[];
tools?: string[];
maxContextTokens?: number;
max_tokens?: number;
reasoning_effort?: string;
// end of additional fields
agentOptions?: unknown;
}
const presetSchema: Schema<IPreset> = new Schema(
{
presetId: {
type: String,
unique: true,
required: true,
index: true,
},
title: {
type: String,
default: 'New Chat',
meiliIndex: true,
},
user: {
type: String,
default: null,
},
defaultPreset: {
type: Boolean,
},
order: {
type: Number,
},
...conversationPreset,
agentOptions: {
type: mongoose.Schema.Types.Mixed,
default: null,
},
},
{ timestamps: true },
);
export default presetSchema;

View file

@ -0,0 +1,34 @@
import { Schema, Document, Types } from 'mongoose';
export interface IMongoProject extends Document {
name: string;
promptGroupIds: Types.ObjectId[];
agentIds: string[];
createdAt?: Date;
updatedAt?: Date;
}
const projectSchema = new Schema<IMongoProject>(
{
name: {
type: String,
required: true,
index: true,
},
promptGroupIds: {
type: [Schema.Types.ObjectId],
ref: 'PromptGroup',
default: [],
},
agentIds: {
type: [String],
ref: 'Agent',
default: [],
},
},
{
timestamps: true,
},
);
export default projectSchema;

View file

@ -0,0 +1,42 @@
import { Schema, Document, Types } from 'mongoose';
export interface IPrompt extends Document {
groupId: Types.ObjectId;
author: Types.ObjectId;
prompt: string;
type: 'text' | 'chat';
createdAt?: Date;
updatedAt?: Date;
}
const promptSchema: Schema<IPrompt> = new Schema(
{
groupId: {
type: Schema.Types.ObjectId,
ref: 'PromptGroup',
required: true,
index: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
prompt: {
type: String,
required: true,
},
type: {
type: String,
enum: ['text', 'chat'],
required: true,
},
},
{
timestamps: true,
},
);
promptSchema.index({ createdAt: 1, updatedAt: 1 });
export default promptSchema;

View file

@ -0,0 +1,85 @@
import { Schema, Document, Types } from 'mongoose';
import { Constants } from 'librechat-data-provider';
export interface IPromptGroup {
name: string;
numberOfGenerations: number;
oneliner: string;
category: string;
projectIds: Types.ObjectId[];
productionId: Types.ObjectId;
author: Types.ObjectId;
authorName: string;
command?: string;
createdAt?: Date;
updatedAt?: Date;
}
export interface IPromptGroupDocument extends IPromptGroup, Document {}
const promptGroupSchema = new Schema<IPromptGroupDocument>(
{
name: {
type: String,
required: true,
index: true,
},
numberOfGenerations: {
type: Number,
default: 0,
},
oneliner: {
type: String,
default: '',
},
category: {
type: String,
default: '',
index: true,
},
projectIds: {
type: [Schema.Types.ObjectId],
ref: 'Project',
index: true,
default: [],
},
productionId: {
type: Schema.Types.ObjectId,
ref: 'Prompt',
required: true,
index: true,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true,
},
authorName: {
type: String,
required: true,
},
command: {
type: String,
index: true,
validate: {
validator: function (v: unknown): boolean {
return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v);
},
message: (props: unknown) =>
`${props.value} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`,
},
maxlength: [
Constants.COMMANDS_MAX_LENGTH as number,
`Command cannot be longer than ${Constants.COMMANDS_MAX_LENGTH} characters`,
],
}, // Casting here bypasses the type error for the command field.
},
{
timestamps: true,
},
);
promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
export default promptGroupSchema;

View file

@ -1,7 +1,33 @@
const { PermissionTypes, Permissions } = require('librechat-data-provider'); import { Schema, Document } from 'mongoose';
const mongoose = require('mongoose'); import { PermissionTypes, Permissions } from 'librechat-data-provider';
const roleSchema = new mongoose.Schema({ export interface IRole extends Document {
name: string;
[PermissionTypes.BOOKMARKS]?: {
[Permissions.USE]?: boolean;
};
[PermissionTypes.PROMPTS]?: {
[Permissions.SHARED_GLOBAL]?: boolean;
[Permissions.USE]?: boolean;
[Permissions.CREATE]?: boolean;
};
[PermissionTypes.AGENTS]?: {
[Permissions.SHARED_GLOBAL]?: boolean;
[Permissions.USE]?: boolean;
[Permissions.CREATE]?: boolean;
};
[PermissionTypes.MULTI_CONVO]?: {
[Permissions.USE]?: boolean;
};
[PermissionTypes.TEMPORARY_CHAT]?: {
[Permissions.USE]?: boolean;
};
[PermissionTypes.RUN_CODE]?: {
[Permissions.USE]?: boolean;
};
}
const roleSchema: Schema<IRole> = new Schema({
name: { name: {
type: String, type: String,
required: true, required: true,
@ -62,6 +88,4 @@ const roleSchema = new mongoose.Schema({
}, },
}); });
const Role = mongoose.model('Role', roleSchema); export default roleSchema;
module.exports = Role;

View file

@ -0,0 +1,26 @@
import mongoose, { Schema, Document, Types } from 'mongoose';
export interface ISession extends Document {
refreshTokenHash: string;
expiration: Date;
user: Types.ObjectId;
}
const sessionSchema: Schema<ISession> = new Schema({
refreshTokenHash: {
type: String,
required: true,
},
expiration: {
type: Date,
required: true,
expires: 0,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
});
export default sessionSchema;

View file

@ -1,6 +1,17 @@
const mongoose = require('mongoose'); import mongoose, { Schema, Document, Types } from 'mongoose';
const shareSchema = mongoose.Schema( export interface ISharedLink extends Document {
conversationId: string;
title?: string;
user?: string;
messages?: Types.ObjectId[];
shareId?: string;
isPublic: boolean;
createdAt?: Date;
updatedAt?: Date;
}
const shareSchema: Schema<ISharedLink> = new Schema(
{ {
conversationId: { conversationId: {
type: String, type: String,
@ -27,4 +38,4 @@ const shareSchema = mongoose.Schema(
{ timestamps: true }, { timestamps: true },
); );
module.exports = mongoose.model('SharedLink', shareSchema); export default shareSchema;

View file

@ -1,7 +1,17 @@
const mongoose = require('mongoose'); import { Schema, Document, Types } from 'mongoose';
const Schema = mongoose.Schema;
const tokenSchema = new Schema({ export interface IToken extends Document {
userId: Types.ObjectId;
email?: string;
type?: string;
identifier?: string;
token: string;
createdAt: Date;
expiresAt: Date;
metadata?: Map<string, unknown>;
}
const tokenSchema: Schema<IToken> = new Schema({
userId: { userId: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
required: true, required: true,
@ -10,7 +20,9 @@ const tokenSchema = new Schema({
email: { email: {
type: String, type: String,
}, },
type: String, type: {
type: String,
},
identifier: { identifier: {
type: String, type: String,
}, },
@ -35,4 +47,4 @@ const tokenSchema = new Schema({
tokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 }); tokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
module.exports = tokenSchema; export default tokenSchema;

View file

@ -0,0 +1,55 @@
import mongoose, { Schema, Document, Types } from 'mongoose';
import type { TAttachment } from 'librechat-data-provider';
export interface IToolCallData extends Document {
conversationId: string;
messageId: string;
toolId: string;
user: Types.ObjectId;
result?: unknown;
attachments?: TAttachment[];
blockIndex?: number;
partIndex?: number;
createdAt?: Date;
updatedAt?: Date;
}
const toolCallSchema: Schema<IToolCallData> = new Schema(
{
conversationId: {
type: String,
required: true,
},
messageId: {
type: String,
required: true,
},
toolId: {
type: String,
required: true,
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
result: {
type: mongoose.Schema.Types.Mixed,
},
attachments: {
type: mongoose.Schema.Types.Mixed,
},
blockIndex: {
type: Number,
},
partIndex: {
type: Number,
},
},
{ timestamps: true },
);
toolCallSchema.index({ messageId: 1, user: 1 });
toolCallSchema.index({ conversationId: 1, user: 1 });
export default toolCallSchema;

View file

@ -1,6 +1,24 @@
const mongoose = require('mongoose'); import mongoose, { Schema, Document, Types } from 'mongoose';
const transactionSchema = mongoose.Schema( // @ts-ignore
export interface ITransaction extends Document {
user: Types.ObjectId;
conversationId?: string;
tokenType: 'prompt' | 'completion' | 'credits';
model?: string;
context?: string;
valueKey?: string;
rate?: number;
rawAmount?: number;
tokenValue?: number;
inputTokens?: number;
writeTokens?: number;
readTokens?: number;
createdAt?: Date;
updatedAt?: Date;
}
const transactionSchema: Schema<ITransaction> = new Schema(
{ {
user: { user: {
type: mongoose.Schema.Types.ObjectId, type: mongoose.Schema.Types.ObjectId,
@ -39,4 +57,4 @@ const transactionSchema = mongoose.Schema(
}, },
); );
module.exports = transactionSchema; export default transactionSchema;

View file

@ -0,0 +1,158 @@
import { Schema, Document } from 'mongoose';
import { SystemRoles } from 'librechat-data-provider';
export interface IUser extends Document {
name?: string;
username?: string;
email: string;
emailVerified: boolean;
password?: string;
avatar?: string;
provider: string;
role?: string;
googleId?: string;
facebookId?: string;
openidId?: string;
ldapId?: string;
githubId?: string;
discordId?: string;
appleId?: string;
plugins?: unknown[];
totpSecret?: string;
backupCodes?: Array<{
codeHash: string;
used: boolean;
usedAt?: Date | null;
}>;
refreshToken?: Array<{
refreshToken: string;
}>;
expiresAt?: Date;
termsAccepted?: boolean;
createdAt?: Date;
updatedAt?: Date;
}
// Session sub-schema
const SessionSchema = new Schema(
{
refreshToken: {
type: String,
default: '',
},
},
{ _id: false },
);
// Backup code sub-schema
const BackupCodeSchema = new Schema(
{
codeHash: { type: String, required: true },
used: { type: Boolean, default: false },
usedAt: { type: Date, default: null },
},
{ _id: false },
);
const User = new Schema<IUser>(
{
name: {
type: String,
},
username: {
type: String,
lowercase: true,
default: '',
},
email: {
type: String,
required: [true, 'can\'t be blank'],
lowercase: true,
unique: true,
match: [/\S+@\S+\.\S+/, 'is invalid'],
index: true,
},
emailVerified: {
type: Boolean,
required: true,
default: false,
},
password: {
type: String,
trim: true,
minlength: 8,
maxlength: 128,
},
avatar: {
type: String,
required: false,
},
provider: {
type: String,
required: true,
default: 'local',
},
role: {
type: String,
default: SystemRoles.USER,
},
googleId: {
type: String,
unique: true,
sparse: true,
},
facebookId: {
type: String,
unique: true,
sparse: true,
},
openidId: {
type: String,
unique: true,
sparse: true,
},
ldapId: {
type: String,
unique: true,
sparse: true,
},
githubId: {
type: String,
unique: true,
sparse: true,
},
discordId: {
type: String,
unique: true,
sparse: true,
},
appleId: {
type: String,
unique: true,
sparse: true,
},
plugins: {
type: Array,
},
totpSecret: {
type: String,
},
backupCodes: {
type: [BackupCodeSchema],
},
refreshToken: {
type: [SessionSchema],
},
expiresAt: {
type: Date,
expires: 604800, // 7 days in seconds
},
termsAccepted: {
type: Boolean,
default: false,
},
},
{ timestamps: true },
);
export default User;

View file

@ -0,0 +1,28 @@
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./dist/types",
"module": "esnext",
"noImplicitAny": true,
"outDir": "./dist",
"target": "es2015",
"moduleResolution": "node",
"lib": ["es2017", "dom", "ES2021.String"],
"skipLibCheck": true,
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": false,
"sourceMap": true,
"baseUrl": "."
},
"ts-node": {
"experimentalSpecifierResolution": "node",
"transpileOnly": true,
"esm": true
},
"exclude": ["node_modules", "dist", "types"],
"include": ["src/**/*", "types/index.d.ts", "types/react-query/index.d.ts"]
}

View file

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"outDir": "./dist/tests",
"baseUrl": "."
},
"include": ["specs/**/*", "src/**/*"],
"exclude": ["node_modules", "dist"]
}