📦 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

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

@ -0,0 +1,125 @@
import { Schema, Document, Types } from 'mongoose';
// @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: {
type: String,
index: true,
unique: true,
required: true,
},
name: {
type: String,
},
description: {
type: String,
},
instructions: {
type: String,
},
avatar: {
type: {
filepath: String,
source: String,
},
default: undefined,
},
provider: {
type: String,
required: true,
},
model: {
type: String,
required: true,
},
model_parameters: {
type: Object,
},
artifacts: {
type: String,
},
access_level: {
type: Number,
},
tools: {
type: [String],
default: undefined,
},
tool_kwargs: {
type: [{ type: Schema.Types.Mixed }],
},
actions: {
type: [String],
default: undefined,
},
author: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
authorName: {
type: String,
default: undefined,
},
hide_sequential_outputs: {
type: Boolean,
},
end_after_tools: {
type: Boolean,
},
agent_ids: {
type: [String],
},
isCollaborative: {
type: Boolean,
default: undefined,
},
conversation_starters: {
type: [String],
default: [],
},
tool_resources: {
type: Schema.Types.Mixed,
default: {},
},
projectIds: {
type: [Schema.Types.ObjectId],
ref: 'Project',
index: true,
},
},
{
timestamps: true,
},
);
export default agentSchema;

View file

@ -0,0 +1,55 @@
import { Schema, Document, Types } from 'mongoose';
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: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true,
},
assistant_id: {
type: String,
index: true,
required: true,
},
avatar: {
type: {
filepath: String,
source: String,
},
default: undefined,
},
conversation_starters: {
type: [String],
default: [],
},
access_level: {
type: Number,
},
file_ids: { type: [String], default: undefined },
actions: { type: [String], default: undefined },
append_current_datetime: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
},
);
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

@ -0,0 +1,43 @@
import { Schema, Document } from 'mongoose';
export interface IBanner extends Document {
bannerId: string;
message: string;
displayFrom: Date;
displayTo?: Date;
type: 'banner' | 'popup';
isPublic: boolean;
}
const bannerSchema = new Schema<IBanner>(
{
bannerId: {
type: String,
required: true,
},
message: {
type: String,
required: true,
},
displayFrom: {
type: Date,
required: true,
default: Date.now,
},
displayTo: {
type: Date,
},
type: {
type: String,
enum: ['banner', 'popup'],
default: 'banner',
},
isPublic: {
type: Boolean,
default: false,
},
},
{ timestamps: true },
);
export default bannerSchema;

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

@ -0,0 +1,138 @@
import { Schema } from 'mongoose';
// @ts-ignore
export const conversationPreset = {
// endpoint: [azureOpenAI, openAI, anthropic, chatGPTBrowser]
endpoint: {
type: String,
default: null,
required: true,
},
endpointType: {
type: String,
},
// for azureOpenAI, openAI, chatGPTBrowser only
model: {
type: String,
required: false,
},
// for bedrock only
region: {
type: String,
required: false,
},
// for azureOpenAI, openAI only
chatGptLabel: {
type: String,
required: false,
},
// for google only
examples: { type: [{ type: Schema.Types.Mixed }], default: undefined },
modelLabel: {
type: String,
required: false,
},
promptPrefix: {
type: String,
required: false,
},
temperature: {
type: Number,
required: false,
},
top_p: {
type: Number,
required: false,
},
// for google only
topP: {
type: Number,
required: false,
},
topK: {
type: Number,
required: false,
},
maxOutputTokens: {
type: Number,
required: false,
},
maxTokens: {
type: Number,
required: false,
},
presence_penalty: {
type: Number,
required: false,
},
frequency_penalty: {
type: Number,
required: false,
},
file_ids: { type: [{ type: String }], default: undefined },
// deprecated
resendImages: {
type: Boolean,
},
/* Anthropic only */
promptCache: {
type: Boolean,
},
thinking: {
type: Boolean,
},
thinkingBudget: {
type: Number,
},
system: {
type: String,
},
// files
resendFiles: {
type: Boolean,
},
imageDetail: {
type: String,
},
/* agents */
agent_id: {
type: String,
},
/* assistants */
assistant_id: {
type: String,
},
instructions: {
type: String,
},
stop: { type: [{ type: String }], default: undefined },
isArchived: {
type: Boolean,
default: false,
},
/* UI Components */
iconURL: {
type: String,
},
greeting: {
type: String,
},
spec: {
type: String,
},
tags: {
type: [String],
default: [],
},
tools: { type: [{ type: String }], default: undefined },
maxContextTokens: {
type: Number,
},
max_tokens: {
type: Number,
},
/** omni models only */
reasoning_effort: {
type: String,
},
};

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

@ -0,0 +1,31 @@
import mongoose, { Schema, Document, Types } from 'mongoose';
export interface IKey extends Document {
userId: Types.ObjectId;
name: string;
value: string;
expiresAt?: Date;
}
const keySchema: Schema<IKey> = new Schema({
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
},
name: {
type: String,
required: true,
},
value: {
type: String,
required: true,
},
expiresAt: {
type: Date,
},
});
keySchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
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

@ -0,0 +1,91 @@
import { Schema, Document } from 'mongoose';
import { PermissionTypes, Permissions } from 'librechat-data-provider';
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: {
type: String,
required: true,
unique: true,
index: true,
},
[PermissionTypes.BOOKMARKS]: {
[Permissions.USE]: {
type: Boolean,
default: true,
},
},
[PermissionTypes.PROMPTS]: {
[Permissions.SHARED_GLOBAL]: {
type: Boolean,
default: false,
},
[Permissions.USE]: {
type: Boolean,
default: true,
},
[Permissions.CREATE]: {
type: Boolean,
default: true,
},
},
[PermissionTypes.AGENTS]: {
[Permissions.SHARED_GLOBAL]: {
type: Boolean,
default: false,
},
[Permissions.USE]: {
type: Boolean,
default: true,
},
[Permissions.CREATE]: {
type: Boolean,
default: true,
},
},
[PermissionTypes.MULTI_CONVO]: {
[Permissions.USE]: {
type: Boolean,
default: true,
},
},
[PermissionTypes.TEMPORARY_CHAT]: {
[Permissions.USE]: {
type: Boolean,
default: true,
},
},
[PermissionTypes.RUN_CODE]: {
[Permissions.USE]: {
type: Boolean,
default: true,
},
},
});
export default roleSchema;

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

@ -0,0 +1,41 @@
import mongoose, { Schema, Document, Types } from 'mongoose';
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: {
type: String,
required: true,
},
title: {
type: String,
index: true,
},
user: {
type: String,
index: true,
},
messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
shareId: {
type: String,
index: true,
},
isPublic: {
type: Boolean,
default: true,
},
},
{ timestamps: true },
);
export default shareSchema;

View file

@ -0,0 +1,50 @@
import { Schema, Document, Types } from 'mongoose';
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: {
type: Schema.Types.ObjectId,
required: true,
ref: 'user',
},
email: {
type: String,
},
type: {
type: String,
},
identifier: {
type: String,
},
token: {
type: String,
required: true,
},
createdAt: {
type: Date,
required: true,
default: Date.now,
},
expiresAt: {
type: Date,
required: true,
},
metadata: {
type: Map,
of: Schema.Types.Mixed,
},
});
tokenSchema.index({ expiresAt: 1 }, { expireAfterSeconds: 0 });
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

@ -0,0 +1,60 @@
import mongoose, { Schema, Document, Types } from 'mongoose';
// @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: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
index: true,
required: true,
},
conversationId: {
type: String,
ref: 'Conversation',
index: true,
},
tokenType: {
type: String,
enum: ['prompt', 'completion', 'credits'],
required: true,
},
model: {
type: String,
},
context: {
type: String,
},
valueKey: {
type: String,
},
rate: Number,
rawAmount: Number,
tokenValue: Number,
inputTokens: { type: Number },
writeTokens: { type: Number },
readTokens: { type: Number },
},
{
timestamps: true,
},
);
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"]
}