@librechat/data-schemas 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +318 -0
  2. package/dist/index.cjs +2668 -1041
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.es.js +2650 -1018
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/types/common/enum.d.ts +13 -0
  7. package/dist/types/common/index.d.ts +1 -0
  8. package/dist/types/index.d.ts +2 -0
  9. package/dist/types/methods/accessRole.d.ts +42 -0
  10. package/dist/types/methods/accessRole.spec.d.ts +1 -0
  11. package/dist/types/methods/aclEntry.d.ts +52 -0
  12. package/dist/types/methods/aclEntry.spec.d.ts +1 -0
  13. package/dist/types/methods/agentCategory.d.ts +48 -0
  14. package/dist/types/methods/group.d.ts +40 -0
  15. package/dist/types/methods/group.spec.d.ts +1 -0
  16. package/dist/types/methods/index.d.ts +116 -6
  17. package/dist/types/methods/pluginAuth.d.ts +1 -1
  18. package/dist/types/methods/user.d.ts +9 -1
  19. package/dist/types/methods/userGroup.d.ts +73 -0
  20. package/dist/types/methods/userGroup.methods.spec.d.ts +1 -0
  21. package/dist/types/methods/userGroup.roles.spec.d.ts +1 -0
  22. package/dist/types/methods/userGroup.spec.d.ts +1 -0
  23. package/dist/types/models/accessRole.d.ts +30 -0
  24. package/dist/types/models/aclEntry.d.ts +30 -0
  25. package/dist/types/models/agentCategory.d.ts +30 -0
  26. package/dist/types/models/group.d.ts +30 -0
  27. package/dist/types/models/index.d.ts +4 -0
  28. package/dist/types/schema/accessRole.d.ts +39 -0
  29. package/dist/types/schema/aclEntry.d.ts +39 -0
  30. package/dist/types/schema/agentCategory.d.ts +39 -0
  31. package/dist/types/schema/defaults.d.ts +4 -0
  32. package/dist/types/schema/group.d.ts +37 -0
  33. package/dist/types/schema/index.d.ts +2 -0
  34. package/dist/types/schema/preset.d.ts +1 -0
  35. package/dist/types/schema/prompt.d.ts +2 -9
  36. package/dist/types/schema/promptGroup.d.ts +4 -18
  37. package/dist/types/types/accessRole.d.ts +40 -0
  38. package/dist/types/types/aclEntry.d.ts +52 -0
  39. package/dist/types/types/agent.d.ts +8 -0
  40. package/dist/types/types/agentCategory.d.ts +41 -0
  41. package/dist/types/types/convo.d.ts +1 -0
  42. package/dist/types/types/group.d.ts +45 -0
  43. package/dist/types/types/index.d.ts +5 -0
  44. package/dist/types/types/pluginAuth.d.ts +1 -0
  45. package/dist/types/types/prompts.d.ts +50 -0
  46. package/dist/types/types/user.d.ts +2 -0
  47. package/dist/types/utils/index.d.ts +1 -0
  48. package/dist/types/utils/object-traverse.d.ts +23 -0
  49. package/dist/types/utils/transactions.d.ts +14 -0
  50. package/package.json +1 -3
  51. package/dist/types/utils/traverse-wrapper.d.ts +0 -9
package/dist/index.cjs CHANGED
@@ -1,29 +1,34 @@
1
1
  'use strict';
2
2
 
3
+ var librechatDataProvider = require('librechat-data-provider');
3
4
  var jwt = require('jsonwebtoken');
4
5
  var node_crypto = require('node:crypto');
5
6
  var mongoose = require('mongoose');
6
- var librechatDataProvider = require('librechat-data-provider');
7
- var _ = require('lodash');
8
- var meilisearch = require('meilisearch');
9
7
  var winston = require('winston');
10
8
  require('winston-daily-rotate-file');
11
- var path = require('path');
12
9
  var klona = require('klona');
13
- var traverseLib = require('traverse');
10
+ var path = require('path');
11
+ var _ = require('lodash');
12
+ var meilisearch = require('meilisearch');
14
13
  var nanoid = require('nanoid');
15
14
 
16
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
17
-
18
- var jwt__default = /*#__PURE__*/_interopDefault(jwt);
19
- var mongoose__default = /*#__PURE__*/_interopDefault(mongoose);
20
- var ___default = /*#__PURE__*/_interopDefault(_);
21
- var winston__default = /*#__PURE__*/_interopDefault(winston);
22
- var path__default = /*#__PURE__*/_interopDefault(path);
23
- var traverseLib__default = /*#__PURE__*/_interopDefault(traverseLib);
15
+ /**
16
+ * Common role combinations
17
+ */
18
+ exports.RoleBits = void 0;
19
+ (function (RoleBits) {
20
+ /** 0001 = 1 */
21
+ RoleBits[RoleBits["VIEWER"] = librechatDataProvider.PermissionBits.VIEW] = "VIEWER";
22
+ /** 0011 = 3 */
23
+ RoleBits[RoleBits["EDITOR"] = librechatDataProvider.PermissionBits.VIEW | librechatDataProvider.PermissionBits.EDIT] = "EDITOR";
24
+ /** 0111 = 7 */
25
+ RoleBits[RoleBits["MANAGER"] = librechatDataProvider.PermissionBits.VIEW | librechatDataProvider.PermissionBits.EDIT | librechatDataProvider.PermissionBits.DELETE] = "MANAGER";
26
+ /** 1111 = 15 */
27
+ RoleBits[RoleBits["OWNER"] = librechatDataProvider.PermissionBits.VIEW | librechatDataProvider.PermissionBits.EDIT | librechatDataProvider.PermissionBits.DELETE | librechatDataProvider.PermissionBits.SHARE] = "OWNER";
28
+ })(exports.RoleBits || (exports.RoleBits = {}));
24
29
 
25
30
  async function signPayload({ payload, secret, expirationTime, }) {
26
- return jwt__default.default.sign(payload, secret, { expiresIn: expirationTime });
31
+ return jwt.sign(payload, secret, { expiresIn: expirationTime });
27
32
  }
28
33
  async function hashToken(str) {
29
34
  const data = new TextEncoder().encode(str);
@@ -44,7 +49,7 @@ const AuthSchema = new mongoose.Schema({
44
49
  }, { _id: false });
45
50
  const Action = new mongoose.Schema({
46
51
  user: {
47
- type: mongoose__default.default.Schema.Types.ObjectId,
52
+ type: mongoose.Schema.Types.ObjectId,
48
53
  ref: 'User',
49
54
  index: true,
50
55
  required: true,
@@ -165,9 +170,59 @@ const agentSchema = new mongoose.Schema({
165
170
  type: [mongoose.Schema.Types.Mixed],
166
171
  default: [],
167
172
  },
173
+ category: {
174
+ type: String,
175
+ trim: true,
176
+ index: true,
177
+ default: 'general',
178
+ },
179
+ support_contact: {
180
+ type: mongoose.Schema.Types.Mixed,
181
+ default: undefined,
182
+ },
183
+ is_promoted: {
184
+ type: Boolean,
185
+ default: false,
186
+ index: true,
187
+ },
188
+ }, {
189
+ timestamps: true,
190
+ });
191
+ agentSchema.index({ updatedAt: -1, _id: 1 });
192
+
193
+ const agentCategorySchema = new mongoose.Schema({
194
+ value: {
195
+ type: String,
196
+ required: true,
197
+ unique: true,
198
+ trim: true,
199
+ lowercase: true,
200
+ index: true,
201
+ },
202
+ label: {
203
+ type: String,
204
+ required: true,
205
+ trim: true,
206
+ },
207
+ description: {
208
+ type: String,
209
+ trim: true,
210
+ default: '',
211
+ },
212
+ order: {
213
+ type: Number,
214
+ default: 0,
215
+ index: true,
216
+ },
217
+ isActive: {
218
+ type: Boolean,
219
+ default: true,
220
+ index: true,
221
+ },
168
222
  }, {
169
223
  timestamps: true,
170
224
  });
225
+ agentCategorySchema.index({ isActive: 1, order: 1 });
171
226
 
172
227
  const assistantSchema = new mongoose.Schema({
173
228
  user: {
@@ -453,6 +508,10 @@ const conversationPreset = {
453
508
  reasoning_summary: {
454
509
  type: String,
455
510
  },
511
+ /** Verbosity control */
512
+ verbosity: {
513
+ type: String,
514
+ },
456
515
  };
457
516
 
458
517
  const convoSchema = new mongoose.Schema({
@@ -498,7 +557,7 @@ convoSchema.index({ conversationId: 1, user: 1 }, { unique: true });
498
557
 
499
558
  const file = new mongoose.Schema({
500
559
  user: {
501
- type: mongoose__default.default.Schema.Types.ObjectId,
560
+ type: mongoose.Schema.Types.ObjectId,
502
561
  ref: 'User',
503
562
  index: true,
504
563
  required: true,
@@ -574,7 +633,7 @@ file.index({ createdAt: 1, updatedAt: 1 });
574
633
 
575
634
  const keySchema = new mongoose.Schema({
576
635
  userId: {
577
- type: mongoose__default.default.Schema.Types.ObjectId,
636
+ type: mongoose.Schema.Types.ObjectId,
578
637
  ref: 'User',
579
638
  required: true,
580
639
  },
@@ -672,7 +731,7 @@ const messageSchema = new mongoose.Schema({
672
731
  required: true,
673
732
  },
674
733
  tag: {
675
- type: mongoose__default.default.Schema.Types.Mixed,
734
+ type: mongoose.Schema.Types.Mixed,
676
735
  required: false,
677
736
  },
678
737
  text: {
@@ -689,7 +748,7 @@ const messageSchema = new mongoose.Schema({
689
748
  select: false,
690
749
  default: false,
691
750
  },
692
- files: { type: [{ type: mongoose__default.default.Schema.Types.Mixed }], default: undefined },
751
+ files: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
693
752
  plugin: {
694
753
  type: {
695
754
  latest: {
@@ -697,7 +756,7 @@ const messageSchema = new mongoose.Schema({
697
756
  required: false,
698
757
  },
699
758
  inputs: {
700
- type: [mongoose__default.default.Schema.Types.Mixed],
759
+ type: [mongoose.Schema.Types.Mixed],
701
760
  required: false,
702
761
  default: undefined,
703
762
  },
@@ -708,9 +767,9 @@ const messageSchema = new mongoose.Schema({
708
767
  },
709
768
  default: undefined,
710
769
  },
711
- plugins: { type: [{ type: mongoose__default.default.Schema.Types.Mixed }], default: undefined },
770
+ plugins: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
712
771
  content: {
713
- type: [{ type: mongoose__default.default.Schema.Types.Mixed }],
772
+ type: [{ type: mongoose.Schema.Types.Mixed }],
714
773
  default: undefined,
715
774
  meiliIndex: true,
716
775
  },
@@ -721,7 +780,7 @@ const messageSchema = new mongoose.Schema({
721
780
  iconURL: {
722
781
  type: String,
723
782
  },
724
- attachments: { type: [{ type: mongoose__default.default.Schema.Types.Mixed }], default: undefined },
783
+ attachments: { type: [{ type: mongoose.Schema.Types.Mixed }], default: undefined },
725
784
  /*
726
785
  attachments: {
727
786
  type: [
@@ -794,7 +853,7 @@ const presetSchema = new mongoose.Schema({
794
853
  },
795
854
  ...conversationPreset,
796
855
  agentOptions: {
797
- type: mongoose__default.default.Schema.Types.Mixed,
856
+ type: mongoose.Schema.Types.Mixed,
798
857
  default: null,
799
858
  },
800
859
  }, { timestamps: true });
@@ -910,69 +969,56 @@ promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
910
969
  */
911
970
  const rolePermissionsSchema = new mongoose.Schema({
912
971
  [librechatDataProvider.PermissionTypes.BOOKMARKS]: {
913
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
972
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
914
973
  },
915
974
  [librechatDataProvider.PermissionTypes.PROMPTS]: {
916
- [librechatDataProvider.Permissions.SHARED_GLOBAL]: { type: Boolean, default: false },
917
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
918
- [librechatDataProvider.Permissions.CREATE]: { type: Boolean, default: true },
975
+ [librechatDataProvider.Permissions.SHARED_GLOBAL]: { type: Boolean },
976
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
977
+ [librechatDataProvider.Permissions.CREATE]: { type: Boolean },
919
978
  },
920
979
  [librechatDataProvider.PermissionTypes.MEMORIES]: {
921
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
922
- [librechatDataProvider.Permissions.CREATE]: { type: Boolean, default: true },
923
- [librechatDataProvider.Permissions.UPDATE]: { type: Boolean, default: true },
924
- [librechatDataProvider.Permissions.READ]: { type: Boolean, default: true },
925
- [librechatDataProvider.Permissions.OPT_OUT]: { type: Boolean, default: true },
980
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
981
+ [librechatDataProvider.Permissions.CREATE]: { type: Boolean },
982
+ [librechatDataProvider.Permissions.UPDATE]: { type: Boolean },
983
+ [librechatDataProvider.Permissions.READ]: { type: Boolean },
984
+ [librechatDataProvider.Permissions.OPT_OUT]: { type: Boolean },
926
985
  },
927
986
  [librechatDataProvider.PermissionTypes.AGENTS]: {
928
- [librechatDataProvider.Permissions.SHARED_GLOBAL]: { type: Boolean, default: false },
929
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
930
- [librechatDataProvider.Permissions.CREATE]: { type: Boolean, default: true },
987
+ [librechatDataProvider.Permissions.SHARED_GLOBAL]: { type: Boolean },
988
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
989
+ [librechatDataProvider.Permissions.CREATE]: { type: Boolean },
931
990
  },
932
991
  [librechatDataProvider.PermissionTypes.MULTI_CONVO]: {
933
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
992
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
934
993
  },
935
994
  [librechatDataProvider.PermissionTypes.TEMPORARY_CHAT]: {
936
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
995
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
937
996
  },
938
997
  [librechatDataProvider.PermissionTypes.RUN_CODE]: {
939
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
998
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
940
999
  },
941
1000
  [librechatDataProvider.PermissionTypes.WEB_SEARCH]: {
942
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
1001
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
1002
+ },
1003
+ [librechatDataProvider.PermissionTypes.PEOPLE_PICKER]: {
1004
+ [librechatDataProvider.Permissions.VIEW_USERS]: { type: Boolean },
1005
+ [librechatDataProvider.Permissions.VIEW_GROUPS]: { type: Boolean },
1006
+ [librechatDataProvider.Permissions.VIEW_ROLES]: { type: Boolean },
1007
+ },
1008
+ [librechatDataProvider.PermissionTypes.MARKETPLACE]: {
1009
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
943
1010
  },
944
1011
  [librechatDataProvider.PermissionTypes.FILE_SEARCH]: {
945
- [librechatDataProvider.Permissions.USE]: { type: Boolean, default: true },
1012
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
1013
+ },
1014
+ [librechatDataProvider.PermissionTypes.FILE_CITATIONS]: {
1015
+ [librechatDataProvider.Permissions.USE]: { type: Boolean },
946
1016
  },
947
1017
  }, { _id: false });
948
1018
  const roleSchema = new mongoose.Schema({
949
1019
  name: { type: String, required: true, unique: true, index: true },
950
1020
  permissions: {
951
1021
  type: rolePermissionsSchema,
952
- default: () => ({
953
- [librechatDataProvider.PermissionTypes.BOOKMARKS]: { [librechatDataProvider.Permissions.USE]: true },
954
- [librechatDataProvider.PermissionTypes.PROMPTS]: {
955
- [librechatDataProvider.Permissions.SHARED_GLOBAL]: false,
956
- [librechatDataProvider.Permissions.USE]: true,
957
- [librechatDataProvider.Permissions.CREATE]: true,
958
- },
959
- [librechatDataProvider.PermissionTypes.MEMORIES]: {
960
- [librechatDataProvider.Permissions.USE]: true,
961
- [librechatDataProvider.Permissions.CREATE]: true,
962
- [librechatDataProvider.Permissions.UPDATE]: true,
963
- [librechatDataProvider.Permissions.READ]: true,
964
- },
965
- [librechatDataProvider.PermissionTypes.AGENTS]: {
966
- [librechatDataProvider.Permissions.SHARED_GLOBAL]: false,
967
- [librechatDataProvider.Permissions.USE]: true,
968
- [librechatDataProvider.Permissions.CREATE]: true,
969
- },
970
- [librechatDataProvider.PermissionTypes.MULTI_CONVO]: { [librechatDataProvider.Permissions.USE]: true },
971
- [librechatDataProvider.PermissionTypes.TEMPORARY_CHAT]: { [librechatDataProvider.Permissions.USE]: true },
972
- [librechatDataProvider.PermissionTypes.RUN_CODE]: { [librechatDataProvider.Permissions.USE]: true },
973
- [librechatDataProvider.PermissionTypes.WEB_SEARCH]: { [librechatDataProvider.Permissions.USE]: true },
974
- [librechatDataProvider.PermissionTypes.FILE_SEARCH]: { [librechatDataProvider.Permissions.USE]: true },
975
- }),
976
1022
  },
977
1023
  });
978
1024
 
@@ -987,7 +1033,7 @@ const sessionSchema = new mongoose.Schema({
987
1033
  expires: 0,
988
1034
  },
989
1035
  user: {
990
- type: mongoose__default.default.Schema.Types.ObjectId,
1036
+ type: mongoose.Schema.Types.ObjectId,
991
1037
  ref: 'User',
992
1038
  required: true,
993
1039
  },
@@ -1006,7 +1052,7 @@ const shareSchema = new mongoose.Schema({
1006
1052
  type: String,
1007
1053
  index: true,
1008
1054
  },
1009
- messages: [{ type: mongoose__default.default.Schema.Types.ObjectId, ref: 'Message' }],
1055
+ messages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }],
1010
1056
  shareId: {
1011
1057
  type: String,
1012
1058
  index: true,
@@ -1066,15 +1112,15 @@ const toolCallSchema = new mongoose.Schema({
1066
1112
  required: true,
1067
1113
  },
1068
1114
  user: {
1069
- type: mongoose__default.default.Schema.Types.ObjectId,
1115
+ type: mongoose.Schema.Types.ObjectId,
1070
1116
  ref: 'User',
1071
1117
  required: true,
1072
1118
  },
1073
1119
  result: {
1074
- type: mongoose__default.default.Schema.Types.Mixed,
1120
+ type: mongoose.Schema.Types.Mixed,
1075
1121
  },
1076
1122
  attachments: {
1077
- type: mongoose__default.default.Schema.Types.Mixed,
1123
+ type: mongoose.Schema.Types.Mixed,
1078
1124
  },
1079
1125
  blockIndex: {
1080
1126
  type: Number,
@@ -1088,7 +1134,7 @@ toolCallSchema.index({ conversationId: 1, user: 1 });
1088
1134
 
1089
1135
  const transactionSchema = new mongoose.Schema({
1090
1136
  user: {
1091
- type: mongoose__default.default.Schema.Types.ObjectId,
1137
+ type: mongoose.Schema.Types.ObjectId,
1092
1138
  ref: 'User',
1093
1139
  index: true,
1094
1140
  required: true,
@@ -1162,6 +1208,7 @@ const userSchema = new mongoose.Schema({
1162
1208
  trim: true,
1163
1209
  minlength: 8,
1164
1210
  maxlength: 128,
1211
+ select: false,
1165
1212
  },
1166
1213
  avatar: {
1167
1214
  type: String,
@@ -1225,6 +1272,7 @@ const userSchema = new mongoose.Schema({
1225
1272
  },
1226
1273
  totpSecret: {
1227
1274
  type: String,
1275
+ select: false,
1228
1276
  },
1229
1277
  backupCodes: {
1230
1278
  type: [BackupCodeSchema],
@@ -1249,6 +1297,11 @@ const userSchema = new mongoose.Schema({
1249
1297
  },
1250
1298
  default: {},
1251
1299
  },
1300
+ /** Field for external source identification (for consistency with TPrincipal schema) */
1301
+ idOnTheSource: {
1302
+ type: String,
1303
+ sparse: true,
1304
+ },
1252
1305
  }, { timestamps: true });
1253
1306
 
1254
1307
  const MemoryEntrySchema = new mongoose.Schema({
@@ -1280,80 +1333,464 @@ const MemoryEntrySchema = new mongoose.Schema({
1280
1333
  },
1281
1334
  });
1282
1335
 
1336
+ const groupSchema = new mongoose.Schema({
1337
+ name: {
1338
+ type: String,
1339
+ required: true,
1340
+ index: true,
1341
+ },
1342
+ description: {
1343
+ type: String,
1344
+ required: false,
1345
+ },
1346
+ email: {
1347
+ type: String,
1348
+ required: false,
1349
+ index: true,
1350
+ },
1351
+ avatar: {
1352
+ type: String,
1353
+ required: false,
1354
+ },
1355
+ memberIds: [
1356
+ {
1357
+ type: String,
1358
+ },
1359
+ ],
1360
+ source: {
1361
+ type: String,
1362
+ enum: ['local', 'entra'],
1363
+ default: 'local',
1364
+ },
1365
+ /** External ID (e.g., Entra ID) */
1366
+ idOnTheSource: {
1367
+ type: String,
1368
+ sparse: true,
1369
+ index: true,
1370
+ required: function () {
1371
+ return this.source !== 'local';
1372
+ },
1373
+ },
1374
+ }, { timestamps: true });
1375
+ groupSchema.index({ idOnTheSource: 1, source: 1 }, {
1376
+ unique: true,
1377
+ partialFilterExpression: { idOnTheSource: { $exists: true } },
1378
+ });
1379
+ groupSchema.index({ memberIds: 1 });
1380
+
1283
1381
  /**
1284
- * Creates or returns the User model using the provided mongoose instance and schema
1382
+ * ESM-native object traversal utility
1383
+ * Simplified implementation focused on the forEach use case
1285
1384
  */
1286
- function createUserModel(mongoose) {
1287
- return mongoose.models.User || mongoose.model('User', userSchema);
1385
+ function isObject(value) {
1386
+ if (value === null || typeof value !== 'object') {
1387
+ return false;
1388
+ }
1389
+ // Treat these built-in types as leaf nodes, not objects to traverse
1390
+ if (value instanceof Date)
1391
+ return false;
1392
+ if (value instanceof RegExp)
1393
+ return false;
1394
+ if (value instanceof Error)
1395
+ return false;
1396
+ if (value instanceof URL)
1397
+ return false;
1398
+ // Check for Buffer (Node.js)
1399
+ if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value))
1400
+ return false;
1401
+ // Check for TypedArrays and ArrayBuffer
1402
+ if (ArrayBuffer.isView(value))
1403
+ return false;
1404
+ if (value instanceof ArrayBuffer)
1405
+ return false;
1406
+ if (value instanceof SharedArrayBuffer)
1407
+ return false;
1408
+ // Check for other built-in types that shouldn't be traversed
1409
+ if (value instanceof Promise)
1410
+ return false;
1411
+ if (value instanceof WeakMap)
1412
+ return false;
1413
+ if (value instanceof WeakSet)
1414
+ return false;
1415
+ if (value instanceof Map)
1416
+ return false;
1417
+ if (value instanceof Set)
1418
+ return false;
1419
+ // Check if it's a primitive wrapper object
1420
+ const stringTag = Object.prototype.toString.call(value);
1421
+ if (stringTag === '[object Boolean]' ||
1422
+ stringTag === '[object Number]' ||
1423
+ stringTag === '[object String]') {
1424
+ return false;
1425
+ }
1426
+ return true;
1427
+ }
1428
+ // Helper to safely set a property on an object or array
1429
+ function setProperty(obj, key, value) {
1430
+ if (Array.isArray(obj) && typeof key === 'number') {
1431
+ obj[key] = value;
1432
+ }
1433
+ else if (!Array.isArray(obj) && typeof key === 'string') {
1434
+ obj[key] = value;
1435
+ }
1436
+ else if (!Array.isArray(obj) && typeof key === 'number') {
1437
+ // Handle numeric keys on objects
1438
+ obj[key] = value;
1439
+ }
1440
+ }
1441
+ // Helper to safely delete a property from an object
1442
+ function deleteProperty(obj, key) {
1443
+ if (Array.isArray(obj) && typeof key === 'number') {
1444
+ // For arrays, we should use splice, but this is handled in remove()
1445
+ // This function is only called for non-array deletion
1446
+ return;
1447
+ }
1448
+ if (!Array.isArray(obj)) {
1449
+ delete obj[key];
1450
+ }
1451
+ }
1452
+ function forEach(obj, callback) {
1453
+ const visited = new WeakSet();
1454
+ function walk(node, path = [], parent) {
1455
+ // Check for circular references
1456
+ let circular = null;
1457
+ if (isObject(node)) {
1458
+ if (visited.has(node)) {
1459
+ // Find the circular reference in the parent chain
1460
+ let p = parent;
1461
+ while (p) {
1462
+ if (p.node === node) {
1463
+ circular = p;
1464
+ break;
1465
+ }
1466
+ p = p.parent;
1467
+ }
1468
+ return; // Skip circular references
1469
+ }
1470
+ visited.add(node);
1471
+ }
1472
+ const key = path.length > 0 ? path[path.length - 1] : undefined;
1473
+ const isRoot = path.length === 0;
1474
+ const level = path.length;
1475
+ // Determine if this is a leaf node
1476
+ const isLeaf = !isObject(node) ||
1477
+ (Array.isArray(node) && node.length === 0) ||
1478
+ Object.keys(node).length === 0;
1479
+ // Create context
1480
+ const context = {
1481
+ node,
1482
+ path: [...path],
1483
+ parent,
1484
+ key,
1485
+ isLeaf,
1486
+ notLeaf: !isLeaf,
1487
+ isRoot,
1488
+ notRoot: !isRoot,
1489
+ level,
1490
+ circular,
1491
+ update(value) {
1492
+ if (!isRoot && parent && key !== undefined && isObject(parent.node)) {
1493
+ setProperty(parent.node, key, value);
1494
+ }
1495
+ this.node = value;
1496
+ },
1497
+ remove() {
1498
+ if (!isRoot && parent && key !== undefined && isObject(parent.node)) {
1499
+ if (Array.isArray(parent.node) && typeof key === 'number') {
1500
+ parent.node.splice(key, 1);
1501
+ }
1502
+ else {
1503
+ deleteProperty(parent.node, key);
1504
+ }
1505
+ }
1506
+ },
1507
+ };
1508
+ // Call the callback with the context
1509
+ callback.call(context, node);
1510
+ // Traverse children if not circular and is an object
1511
+ if (!circular && isObject(node) && !isLeaf) {
1512
+ if (Array.isArray(node)) {
1513
+ for (let i = 0; i < node.length; i++) {
1514
+ walk(node[i], [...path, i], context);
1515
+ }
1516
+ }
1517
+ else {
1518
+ for (const [childKey, childValue] of Object.entries(node)) {
1519
+ walk(childValue, [...path, childKey], context);
1520
+ }
1521
+ }
1522
+ }
1523
+ }
1524
+ walk(obj);
1525
+ }
1526
+ // Main traverse function that returns an object with forEach method
1527
+ function traverse(obj) {
1528
+ return {
1529
+ forEach(callback) {
1530
+ forEach(obj, callback);
1531
+ },
1532
+ };
1288
1533
  }
1289
1534
 
1535
+ const SPLAT_SYMBOL = Symbol.for('splat');
1536
+ const MESSAGE_SYMBOL = Symbol.for('message');
1537
+ const CONSOLE_JSON_STRING_LENGTH = parseInt(process.env.CONSOLE_JSON_STRING_LENGTH || '', 10) || 255;
1538
+ const sensitiveKeys = [
1539
+ /^(sk-)[^\s]+/, // OpenAI API key pattern
1540
+ /(Bearer )[^\s]+/, // Header: Bearer token pattern
1541
+ /(api-key:? )[^\s]+/, // Header: API key pattern
1542
+ /(key=)[^\s]+/, // URL query param: sensitive key pattern (Google)
1543
+ ];
1290
1544
  /**
1291
- * Creates or returns the Token model using the provided mongoose instance and schema
1545
+ * Determines if a given value string is sensitive and returns matching regex patterns.
1546
+ *
1547
+ * @param valueStr - The value string to check.
1548
+ * @returns An array of regex patterns that match the value string.
1292
1549
  */
1293
- function createTokenModel(mongoose) {
1294
- return mongoose.models.Token || mongoose.model('Token', tokenSchema);
1550
+ function getMatchingSensitivePatterns(valueStr) {
1551
+ if (valueStr) {
1552
+ // Filter and return all regex patterns that match the value string
1553
+ return sensitiveKeys.filter((regex) => regex.test(valueStr));
1554
+ }
1555
+ return [];
1295
1556
  }
1296
-
1297
1557
  /**
1298
- * Creates or returns the Session model using the provided mongoose instance and schema
1558
+ * Redacts sensitive information from a console message and trims it to a specified length if provided.
1559
+ * @param str - The console message to be redacted.
1560
+ * @param trimLength - The optional length at which to trim the redacted message.
1561
+ * @returns The redacted and optionally trimmed console message.
1299
1562
  */
1300
- function createSessionModel(mongoose) {
1301
- return mongoose.models.Session || mongoose.model('Session', sessionSchema);
1563
+ function redactMessage(str, trimLength) {
1564
+ if (!str) {
1565
+ return '';
1566
+ }
1567
+ const patterns = getMatchingSensitivePatterns(str);
1568
+ patterns.forEach((pattern) => {
1569
+ str = str.replace(pattern, '$1[REDACTED]');
1570
+ });
1571
+ return str;
1302
1572
  }
1303
-
1304
1573
  /**
1305
- * Creates or returns the Balance model using the provided mongoose instance and schema
1574
+ * Redacts sensitive information from log messages if the log level is 'error'.
1575
+ * Note: Intentionally mutates the object.
1576
+ * @param info - The log information object.
1577
+ * @returns The modified log information object.
1306
1578
  */
1307
- function createBalanceModel(mongoose) {
1308
- return mongoose.models.Balance || mongoose.model('Balance', balanceSchema);
1309
- }
1310
-
1579
+ const redactFormat = winston.format((info) => {
1580
+ if (info.level === 'error') {
1581
+ // Type guard to ensure message is a string
1582
+ if (typeof info.message === 'string') {
1583
+ info.message = redactMessage(info.message);
1584
+ }
1585
+ // Handle MESSAGE_SYMBOL with type safety
1586
+ const symbolValue = info[MESSAGE_SYMBOL];
1587
+ if (typeof symbolValue === 'string') {
1588
+ info[MESSAGE_SYMBOL] = redactMessage(symbolValue);
1589
+ }
1590
+ }
1591
+ return info;
1592
+ });
1311
1593
  /**
1312
- * Determine the log directory in a cross-compatible way.
1313
- * Priority:
1314
- * 1. LIBRECHAT_LOG_DIR environment variable
1315
- * 2. If running within LibreChat monorepo (when cwd ends with /api), use api/logs
1316
- * 3. If api/logs exists relative to cwd, use that (for running from project root)
1317
- * 4. Otherwise, use logs directory relative to process.cwd()
1594
+ * Truncates long strings, especially base64 image data, within log messages.
1318
1595
  *
1319
- * This avoids using __dirname which is not available in ESM modules
1596
+ * @param value - The value to be inspected and potentially truncated.
1597
+ * @param length - The length at which to truncate the value. Default: 100.
1598
+ * @returns The truncated or original value.
1320
1599
  */
1321
- const getLogDirectory = () => {
1322
- if (process.env.LIBRECHAT_LOG_DIR) {
1323
- return process.env.LIBRECHAT_LOG_DIR;
1600
+ const truncateLongStrings = (value, length = 100) => {
1601
+ if (typeof value === 'string') {
1602
+ return value.length > length ? value.substring(0, length) + '... [truncated]' : value;
1324
1603
  }
1325
- const cwd = process.cwd();
1326
- // Check if we're running from within the api directory
1327
- if (cwd.endsWith('/api') || cwd.endsWith('\\api')) {
1328
- return path__default.default.join(cwd, 'logs');
1604
+ return value;
1605
+ };
1606
+ /**
1607
+ * An array mapping function that truncates long strings (objects converted to JSON strings).
1608
+ * @param item - The item to be condensed.
1609
+ * @returns The condensed item.
1610
+ */
1611
+ const condenseArray = (item) => {
1612
+ if (typeof item === 'string') {
1613
+ return truncateLongStrings(JSON.stringify(item));
1329
1614
  }
1330
- // Check if api/logs exists relative to current directory (running from project root)
1331
- // We'll just use the path and let the file system create it if needed
1332
- const apiLogsPath = path__default.default.join(cwd, 'api', 'logs');
1333
- // For LibreChat project structure, use api/logs
1334
- // For external consumers, they should set LIBRECHAT_LOG_DIR
1335
- if (cwd.includes('LibreChat')) {
1336
- return apiLogsPath;
1615
+ else if (typeof item === 'object') {
1616
+ return truncateLongStrings(JSON.stringify(item));
1337
1617
  }
1338
- // Default to logs directory relative to current working directory
1339
- return path__default.default.join(cwd, 'logs');
1618
+ return item;
1340
1619
  };
1341
-
1342
- const logDir$1 = getLogDirectory();
1343
- const { NODE_ENV: NODE_ENV$1, DEBUG_LOGGING: DEBUG_LOGGING$1 = 'false' } = process.env;
1344
- const useDebugLogging$1 = (typeof DEBUG_LOGGING$1 === 'string' && DEBUG_LOGGING$1.toLowerCase() === 'true') ||
1345
- DEBUG_LOGGING$1 === 'true';
1346
- const levels$1 = {
1347
- error: 0,
1348
- warn: 1,
1349
- info: 2,
1350
- http: 3,
1620
+ /**
1621
+ * Formats log messages for debugging purposes.
1622
+ * - Truncates long strings within log messages.
1623
+ * - Condenses arrays by truncating long strings and objects as strings within array items.
1624
+ * - Redacts sensitive information from log messages if the log level is 'error'.
1625
+ * - Converts log information object to a formatted string.
1626
+ *
1627
+ * @param options - The options for formatting log messages.
1628
+ * @returns The formatted log message.
1629
+ */
1630
+ const debugTraverse = winston.format.printf(({ level, message, timestamp, ...metadata }) => {
1631
+ if (!message) {
1632
+ return `${timestamp} ${level}`;
1633
+ }
1634
+ // Type-safe version of the CJS logic: !message?.trim || typeof message !== 'string'
1635
+ if (typeof message !== 'string' || !message.trim) {
1636
+ return `${timestamp} ${level}: ${JSON.stringify(message)}`;
1637
+ }
1638
+ const msgParts = [
1639
+ `${timestamp} ${level}: ${truncateLongStrings(message.trim(), 150)}`,
1640
+ ];
1641
+ try {
1642
+ if (level !== 'debug') {
1643
+ return msgParts[0];
1644
+ }
1645
+ if (!metadata) {
1646
+ return msgParts[0];
1647
+ }
1648
+ // Type-safe access to SPLAT_SYMBOL using bracket notation
1649
+ const metadataRecord = metadata;
1650
+ const splatArray = metadataRecord[SPLAT_SYMBOL];
1651
+ const debugValue = Array.isArray(splatArray) ? splatArray[0] : undefined;
1652
+ if (!debugValue) {
1653
+ return msgParts[0];
1654
+ }
1655
+ if (debugValue && Array.isArray(debugValue)) {
1656
+ msgParts.push(`\n${JSON.stringify(debugValue.map(condenseArray))}`);
1657
+ return msgParts.join('');
1658
+ }
1659
+ if (typeof debugValue !== 'object') {
1660
+ msgParts.push(` ${debugValue}`);
1661
+ return msgParts.join('');
1662
+ }
1663
+ msgParts.push('\n{');
1664
+ const copy = klona.klona(metadata);
1665
+ try {
1666
+ const traversal = traverse(copy);
1667
+ traversal.forEach(function (value) {
1668
+ var _a;
1669
+ if (typeof (this === null || this === void 0 ? void 0 : this.key) === 'symbol') {
1670
+ return;
1671
+ }
1672
+ let _parentKey = '';
1673
+ const parent = this.parent;
1674
+ if (typeof (parent === null || parent === void 0 ? void 0 : parent.key) !== 'symbol' && (parent === null || parent === void 0 ? void 0 : parent.key) !== undefined) {
1675
+ _parentKey = String(parent.key);
1676
+ }
1677
+ const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`;
1678
+ const tabs = `${parent && parent.notRoot ? ' ' : ' '}`;
1679
+ const currentKey = (_a = this === null || this === void 0 ? void 0 : this.key) !== null && _a !== void 0 ? _a : 'unknown';
1680
+ if (this.isLeaf && typeof value === 'string') {
1681
+ const truncatedText = truncateLongStrings(value);
1682
+ msgParts.push(`\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`);
1683
+ }
1684
+ else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
1685
+ const currentMessage = `\n${tabs}// ${value.length} ${String(currentKey).replace(/s$/, '')}(s)`;
1686
+ this.update(currentMessage);
1687
+ msgParts.push(currentMessage);
1688
+ const stringifiedArray = value.map(condenseArray);
1689
+ msgParts.push(`\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`);
1690
+ }
1691
+ else if (this.isLeaf && typeof value === 'function') {
1692
+ msgParts.push(`\n${tabs}${parentKey}${currentKey}: function,`);
1693
+ }
1694
+ else if (this.isLeaf) {
1695
+ msgParts.push(`\n${tabs}${parentKey}${currentKey}: ${value},`);
1696
+ }
1697
+ });
1698
+ }
1699
+ catch (e) {
1700
+ const errorMessage = e instanceof Error ? e.message : 'Unknown error';
1701
+ msgParts.push(`\n[LOGGER TRAVERSAL ERROR] ${errorMessage}`);
1702
+ }
1703
+ msgParts.push('\n}');
1704
+ return msgParts.join('');
1705
+ }
1706
+ catch (e) {
1707
+ const errorMessage = e instanceof Error ? e.message : 'Unknown error';
1708
+ msgParts.push(`\n[LOGGER PARSING ERROR] ${errorMessage}`);
1709
+ return msgParts.join('');
1710
+ }
1711
+ });
1712
+ /**
1713
+ * Truncates long string values in JSON log objects.
1714
+ * Prevents outputting extremely long values (e.g., base64, blobs).
1715
+ */
1716
+ const jsonTruncateFormat = winston.format((info) => {
1717
+ const truncateLongStrings = (str, maxLength) => str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
1718
+ const seen = new WeakSet();
1719
+ const truncateObject = (obj) => {
1720
+ if (typeof obj !== 'object' || obj === null) {
1721
+ return obj;
1722
+ }
1723
+ // Handle circular references - now with proper object type
1724
+ if (seen.has(obj)) {
1725
+ return '[Circular]';
1726
+ }
1727
+ seen.add(obj);
1728
+ if (Array.isArray(obj)) {
1729
+ return obj.map((item) => truncateObject(item));
1730
+ }
1731
+ // We know this is an object at this point
1732
+ const objectRecord = obj;
1733
+ const newObj = {};
1734
+ Object.entries(objectRecord).forEach(([key, value]) => {
1735
+ if (typeof value === 'string') {
1736
+ newObj[key] = truncateLongStrings(value, CONSOLE_JSON_STRING_LENGTH);
1737
+ }
1738
+ else {
1739
+ newObj[key] = truncateObject(value);
1740
+ }
1741
+ });
1742
+ return newObj;
1743
+ };
1744
+ return truncateObject(info);
1745
+ });
1746
+
1747
+ /**
1748
+ * Determine the log directory in a cross-compatible way.
1749
+ * Priority:
1750
+ * 1. LIBRECHAT_LOG_DIR environment variable
1751
+ * 2. If running within LibreChat monorepo (when cwd ends with /api), use api/logs
1752
+ * 3. If api/logs exists relative to cwd, use that (for running from project root)
1753
+ * 4. Otherwise, use logs directory relative to process.cwd()
1754
+ *
1755
+ * This avoids using __dirname which is not available in ESM modules
1756
+ */
1757
+ const getLogDirectory = () => {
1758
+ if (process.env.LIBRECHAT_LOG_DIR) {
1759
+ return process.env.LIBRECHAT_LOG_DIR;
1760
+ }
1761
+ const cwd = process.cwd();
1762
+ // Check if we're running from within the api directory
1763
+ if (cwd.endsWith('/api') || cwd.endsWith('\\api')) {
1764
+ return path.join(cwd, 'logs');
1765
+ }
1766
+ // Check if api/logs exists relative to current directory (running from project root)
1767
+ // We'll just use the path and let the file system create it if needed
1768
+ const apiLogsPath = path.join(cwd, 'api', 'logs');
1769
+ // For LibreChat project structure, use api/logs
1770
+ // For external consumers, they should set LIBRECHAT_LOG_DIR
1771
+ if (cwd.includes('LibreChat')) {
1772
+ return apiLogsPath;
1773
+ }
1774
+ // Default to logs directory relative to current working directory
1775
+ return path.join(cwd, 'logs');
1776
+ };
1777
+
1778
+ const logDir$1 = getLogDirectory();
1779
+ const { NODE_ENV: NODE_ENV$1, DEBUG_LOGGING: DEBUG_LOGGING$1, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
1780
+ const useConsoleJson = typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true';
1781
+ const useDebugConsole = typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true';
1782
+ const useDebugLogging$1 = typeof DEBUG_LOGGING$1 === 'string' && DEBUG_LOGGING$1.toLowerCase() === 'true';
1783
+ const levels$1 = {
1784
+ error: 0,
1785
+ warn: 1,
1786
+ info: 2,
1787
+ http: 3,
1351
1788
  verbose: 4,
1352
1789
  debug: 5,
1353
1790
  activity: 6,
1354
1791
  silly: 7,
1355
1792
  };
1356
- winston__default.default.addColors({
1793
+ winston.addColors({
1357
1794
  info: 'green',
1358
1795
  warn: 'italic yellow',
1359
1796
  error: 'red',
@@ -1361,15 +1798,13 @@ winston__default.default.addColors({
1361
1798
  });
1362
1799
  const level$1 = () => {
1363
1800
  const env = NODE_ENV$1 || 'development';
1364
- const isDevelopment = env === 'development';
1365
- return isDevelopment ? 'debug' : 'warn';
1801
+ return env === 'development' ? 'debug' : 'warn';
1366
1802
  };
1367
- const fileFormat$1 = winston__default.default.format.combine(winston__default.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston__default.default.format.errors({ stack: true }), winston__default.default.format.splat());
1368
- const logLevel = useDebugLogging$1 ? 'debug' : 'error';
1803
+ const fileFormat$1 = winston.format.combine(redactFormat(), winston.format.timestamp({ format: () => new Date().toISOString() }), winston.format.errors({ stack: true }), winston.format.splat());
1369
1804
  const transports$1 = [
1370
- new winston__default.default.transports.DailyRotateFile({
1371
- level: logLevel,
1372
- filename: `${logDir$1}/meiliSync-%DATE%.log`,
1805
+ new winston.transports.DailyRotateFile({
1806
+ level: 'error',
1807
+ filename: `${logDir$1}/error-%DATE%.log`,
1373
1808
  datePattern: 'YYYY-MM-DD',
1374
1809
  zippedArchive: true,
1375
1810
  maxSize: '20m',
@@ -1377,17 +1812,174 @@ const transports$1 = [
1377
1812
  format: fileFormat$1,
1378
1813
  }),
1379
1814
  ];
1380
- const consoleFormat$1 = winston__default.default.format.combine(winston__default.default.format.colorize({ all: true }), winston__default.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston__default.default.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`));
1381
- transports$1.push(new winston__default.default.transports.Console({
1382
- level: 'info',
1383
- format: consoleFormat$1,
1815
+ if (useDebugLogging$1) {
1816
+ transports$1.push(new winston.transports.DailyRotateFile({
1817
+ level: 'debug',
1818
+ filename: `${logDir$1}/debug-%DATE%.log`,
1819
+ datePattern: 'YYYY-MM-DD',
1820
+ zippedArchive: true,
1821
+ maxSize: '20m',
1822
+ maxFiles: '14d',
1823
+ format: winston.format.combine(fileFormat$1, debugTraverse),
1824
+ }));
1825
+ }
1826
+ const consoleFormat$1 = winston.format.combine(redactFormat(), winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => {
1827
+ const message = `${info.timestamp} ${info.level}: ${info.message}`;
1828
+ return info.level.includes('error') ? redactMessage(message) : message;
1384
1829
  }));
1385
- const logger$1 = winston__default.default.createLogger({
1830
+ let consoleLogLevel = 'info';
1831
+ if (useDebugConsole) {
1832
+ consoleLogLevel = 'debug';
1833
+ }
1834
+ // Add console transport
1835
+ if (useDebugConsole) {
1836
+ transports$1.push(new winston.transports.Console({
1837
+ level: consoleLogLevel,
1838
+ format: useConsoleJson
1839
+ ? winston.format.combine(fileFormat$1, jsonTruncateFormat(), winston.format.json())
1840
+ : winston.format.combine(fileFormat$1, debugTraverse),
1841
+ }));
1842
+ }
1843
+ else if (useConsoleJson) {
1844
+ transports$1.push(new winston.transports.Console({
1845
+ level: consoleLogLevel,
1846
+ format: winston.format.combine(fileFormat$1, jsonTruncateFormat(), winston.format.json()),
1847
+ }));
1848
+ }
1849
+ else {
1850
+ transports$1.push(new winston.transports.Console({
1851
+ level: consoleLogLevel,
1852
+ format: consoleFormat$1,
1853
+ }));
1854
+ }
1855
+ // Create logger
1856
+ const logger$1 = winston.createLogger({
1386
1857
  level: level$1(),
1387
1858
  levels: levels$1,
1388
1859
  transports: transports$1,
1389
1860
  });
1390
1861
 
1862
+ /**
1863
+ * Checks if the connected MongoDB deployment supports transactions
1864
+ * This requires a MongoDB replica set configuration
1865
+ *
1866
+ * @returns True if transactions are supported, false otherwise
1867
+ */
1868
+ const supportsTransactions = async (mongoose) => {
1869
+ var _a;
1870
+ try {
1871
+ const session = await mongoose.startSession();
1872
+ try {
1873
+ session.startTransaction();
1874
+ await ((_a = mongoose.connection.db) === null || _a === void 0 ? void 0 : _a.collection('__transaction_test__').findOne({}, { session }));
1875
+ await session.abortTransaction();
1876
+ logger$1.debug('MongoDB transactions are supported');
1877
+ return true;
1878
+ }
1879
+ catch (transactionError) {
1880
+ logger$1.debug('MongoDB transactions not supported (transaction error):', (transactionError === null || transactionError === void 0 ? void 0 : transactionError.message) || 'Unknown error');
1881
+ return false;
1882
+ }
1883
+ finally {
1884
+ await session.endSession();
1885
+ }
1886
+ }
1887
+ catch (error) {
1888
+ logger$1.debug('MongoDB transactions not supported (session error):', (error === null || error === void 0 ? void 0 : error.message) || 'Unknown error');
1889
+ return false;
1890
+ }
1891
+ };
1892
+ /**
1893
+ * Gets whether the current MongoDB deployment supports transactions
1894
+ * Caches the result for performance
1895
+ *
1896
+ * @returns True if transactions are supported, false otherwise
1897
+ */
1898
+ const getTransactionSupport = async (mongoose, transactionSupportCache) => {
1899
+ let transactionsSupported = false;
1900
+ if (transactionSupportCache === null) {
1901
+ transactionsSupported = await supportsTransactions(mongoose);
1902
+ }
1903
+ return transactionsSupported;
1904
+ };
1905
+
1906
+ /**
1907
+ * Creates or returns the User model using the provided mongoose instance and schema
1908
+ */
1909
+ function createUserModel(mongoose) {
1910
+ return mongoose.models.User || mongoose.model('User', userSchema);
1911
+ }
1912
+
1913
+ /**
1914
+ * Creates or returns the Token model using the provided mongoose instance and schema
1915
+ */
1916
+ function createTokenModel(mongoose) {
1917
+ return mongoose.models.Token || mongoose.model('Token', tokenSchema);
1918
+ }
1919
+
1920
+ /**
1921
+ * Creates or returns the Session model using the provided mongoose instance and schema
1922
+ */
1923
+ function createSessionModel(mongoose) {
1924
+ return mongoose.models.Session || mongoose.model('Session', sessionSchema);
1925
+ }
1926
+
1927
+ /**
1928
+ * Creates or returns the Balance model using the provided mongoose instance and schema
1929
+ */
1930
+ function createBalanceModel(mongoose) {
1931
+ return mongoose.models.Balance || mongoose.model('Balance', balanceSchema);
1932
+ }
1933
+
1934
+ const logDir = getLogDirectory();
1935
+ const { NODE_ENV, DEBUG_LOGGING = 'false' } = process.env;
1936
+ const useDebugLogging = (typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true') ||
1937
+ DEBUG_LOGGING === 'true';
1938
+ const levels = {
1939
+ error: 0,
1940
+ warn: 1,
1941
+ info: 2,
1942
+ http: 3,
1943
+ verbose: 4,
1944
+ debug: 5,
1945
+ activity: 6,
1946
+ silly: 7,
1947
+ };
1948
+ winston.addColors({
1949
+ info: 'green',
1950
+ warn: 'italic yellow',
1951
+ error: 'red',
1952
+ debug: 'blue',
1953
+ });
1954
+ const level = () => {
1955
+ const env = NODE_ENV || 'development';
1956
+ const isDevelopment = env === 'development';
1957
+ return isDevelopment ? 'debug' : 'warn';
1958
+ };
1959
+ const fileFormat = winston.format.combine(winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.errors({ stack: true }), winston.format.splat());
1960
+ const logLevel = useDebugLogging ? 'debug' : 'error';
1961
+ const transports = [
1962
+ new winston.transports.DailyRotateFile({
1963
+ level: logLevel,
1964
+ filename: `${logDir}/meiliSync-%DATE%.log`,
1965
+ datePattern: 'YYYY-MM-DD',
1966
+ zippedArchive: true,
1967
+ maxSize: '20m',
1968
+ maxFiles: '14d',
1969
+ format: fileFormat,
1970
+ }),
1971
+ ];
1972
+ const consoleFormat = winston.format.combine(winston.format.colorize({ all: true }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`));
1973
+ transports.push(new winston.transports.Console({
1974
+ level: 'info',
1975
+ format: consoleFormat,
1976
+ }));
1977
+ const logger = winston.createLogger({
1978
+ level: level(),
1979
+ levels,
1980
+ transports,
1981
+ });
1982
+
1391
1983
  // Environment flags
1392
1984
  /**
1393
1985
  * Flag to indicate if search is enabled based on environment variables.
@@ -1483,7 +2075,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1483
2075
  try {
1484
2076
  const startTime = Date.now();
1485
2077
  const { batchSize, delayMs } = syncConfig;
1486
- logger$1.info(`[syncWithMeili] Starting sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} with batch size ${batchSize}`);
2078
+ logger.info(`[syncWithMeili] Starting sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} with batch size ${batchSize}`);
1487
2079
  // Build query with resume capability
1488
2080
  const query = {};
1489
2081
  if (options === null || options === void 0 ? void 0 : options.resumeFromId) {
@@ -1500,7 +2092,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1500
2092
  .sort({ _id: 1 })
1501
2093
  .batchSize(batchSize)
1502
2094
  .cursor();
1503
- const format = (doc) => ___default.default.omitBy(___default.default.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
2095
+ const format = (doc) => _.omitBy(_.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
1504
2096
  let documentBatch = [];
1505
2097
  let updateOps = [];
1506
2098
  // Process documents in streaming fashion
@@ -1525,7 +2117,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1525
2117
  updateOps = [];
1526
2118
  // Log progress
1527
2119
  const progress = Math.round((processedCount / totalCount) * 100);
1528
- logger$1.info(`[syncWithMeili] Progress: ${progress}% (${processedCount}/${totalCount})`);
2120
+ logger.info(`[syncWithMeili] Progress: ${progress}% (${processedCount}/${totalCount})`);
1529
2121
  // Add delay to prevent overwhelming resources
1530
2122
  if (delayMs > 0) {
1531
2123
  await new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -1537,10 +2129,10 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1537
2129
  await this.processSyncBatch(index, documentBatch, updateOps);
1538
2130
  }
1539
2131
  const duration = Date.now() - startTime;
1540
- logger$1.info(`[syncWithMeili] Completed sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} in ${duration}ms`);
2132
+ logger.info(`[syncWithMeili] Completed sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} in ${duration}ms`);
1541
2133
  }
1542
2134
  catch (error) {
1543
- logger$1.error('[syncWithMeili] Error during sync:', error);
2135
+ logger.error('[syncWithMeili] Error during sync:', error);
1544
2136
  throw error;
1545
2137
  }
1546
2138
  }
@@ -1560,7 +2152,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1560
2152
  }
1561
2153
  }
1562
2154
  catch (error) {
1563
- logger$1.error('[processSyncBatch] Error processing batch:', error);
2155
+ logger.error('[processSyncBatch] Error processing batch:', error);
1564
2156
  // Don't throw - allow sync to continue with other documents
1565
2157
  }
1566
2158
  }
@@ -1587,7 +2179,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1587
2179
  const toDelete = meiliIds.filter((id) => !existingIds.has(id));
1588
2180
  if (toDelete.length > 0) {
1589
2181
  await Promise.all(toDelete.map((id) => index.deleteDocument(id)));
1590
- logger$1.debug(`[cleanupMeiliIndex] Deleted ${toDelete.length} orphaned documents`);
2182
+ logger.debug(`[cleanupMeiliIndex] Deleted ${toDelete.length} orphaned documents`);
1591
2183
  }
1592
2184
  offset += batchSize;
1593
2185
  // Add delay between batches
@@ -1597,7 +2189,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1597
2189
  }
1598
2190
  }
1599
2191
  catch (error) {
1600
- logger$1.error('[cleanupMeiliIndex] Error during cleanup:', error);
2192
+ logger.error('[cleanupMeiliIndex] Error during cleanup:', error);
1601
2193
  }
1602
2194
  }
1603
2195
  /**
@@ -1613,7 +2205,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1613
2205
  const data = await index.search(q, params);
1614
2206
  if (populate) {
1615
2207
  const query = {};
1616
- query[primaryKey] = ___default.default.map(data.hits, (hit) => cleanUpPrimaryKeyValue(hit[primaryKey]));
2208
+ query[primaryKey] = _.map(data.hits, (hit) => cleanUpPrimaryKeyValue(hit[primaryKey]));
1617
2209
  const projection = Object.keys(this.schema.obj).reduce((results, key) => {
1618
2210
  if (!key.startsWith('$')) {
1619
2211
  results[key] = 1;
@@ -1623,7 +2215,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1623
2215
  const hitsFromMongoose = await this.find(query, projection).lean();
1624
2216
  const populatedHits = data.hits.map((hit) => {
1625
2217
  hit[primaryKey];
1626
- const originalHit = ___default.default.find(hitsFromMongoose, (item) => {
2218
+ const originalHit = _.find(hitsFromMongoose, (item) => {
1627
2219
  const typedItem = item;
1628
2220
  return typedItem[primaryKey] === hit[primaryKey];
1629
2221
  });
@@ -1640,7 +2232,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1640
2232
  * Preprocesses the current document for indexing
1641
2233
  */
1642
2234
  preprocessObjectForIndex() {
1643
- const object = ___default.default.omitBy(___default.default.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
2235
+ const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
1644
2236
  if (object.conversationId &&
1645
2237
  typeof object.conversationId === 'string' &&
1646
2238
  object.conversationId.includes('|')) {
@@ -1667,7 +2259,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1667
2259
  catch (error) {
1668
2260
  retryCount++;
1669
2261
  if (retryCount >= maxRetries) {
1670
- logger$1.error('[addObjectToMeili] Error adding document to Meili after retries:', error);
2262
+ logger.error('[addObjectToMeili] Error adding document to Meili after retries:', error);
1671
2263
  return next();
1672
2264
  }
1673
2265
  // Exponential backoff
@@ -1678,7 +2270,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1678
2270
  await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
1679
2271
  }
1680
2272
  catch (error) {
1681
- logger$1.error('[addObjectToMeili] Error updating _meiliIndex field:', error);
2273
+ logger.error('[addObjectToMeili] Error updating _meiliIndex field:', error);
1682
2274
  return next();
1683
2275
  }
1684
2276
  next();
@@ -1688,12 +2280,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1688
2280
  */
1689
2281
  async updateObjectToMeili(next) {
1690
2282
  try {
1691
- const object = ___default.default.omitBy(___default.default.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
2283
+ const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
1692
2284
  await index.updateDocuments([object]);
1693
2285
  next();
1694
2286
  }
1695
2287
  catch (error) {
1696
- logger$1.error('[updateObjectToMeili] Error updating document in Meili:', error);
2288
+ logger.error('[updateObjectToMeili] Error updating document in Meili:', error);
1697
2289
  return next();
1698
2290
  }
1699
2291
  }
@@ -1708,7 +2300,7 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) =>
1708
2300
  next();
1709
2301
  }
1710
2302
  catch (error) {
1711
- logger$1.error('[deleteObjectFromMeili] Error deleting document from Meili:', error);
2303
+ logger.error('[deleteObjectFromMeili] Error deleting document from Meili:', error);
1712
2304
  return next();
1713
2305
  }
1714
2306
  }
@@ -1800,23 +2392,23 @@ function mongoMeili(schema, options) {
1800
2392
  (async () => {
1801
2393
  try {
1802
2394
  await index.getRawInfo();
1803
- logger$1.debug(`[mongoMeili] Index ${indexName} already exists`);
2395
+ logger.debug(`[mongoMeili] Index ${indexName} already exists`);
1804
2396
  }
1805
2397
  catch (error) {
1806
2398
  const errorCode = error === null || error === void 0 ? void 0 : error.code;
1807
2399
  if (errorCode === 'index_not_found') {
1808
2400
  try {
1809
- logger$1.info(`[mongoMeili] Creating new index: ${indexName}`);
2401
+ logger.info(`[mongoMeili] Creating new index: ${indexName}`);
1810
2402
  await client.createIndex(indexName, { primaryKey });
1811
- logger$1.info(`[mongoMeili] Successfully created index: ${indexName}`);
2403
+ logger.info(`[mongoMeili] Successfully created index: ${indexName}`);
1812
2404
  }
1813
2405
  catch (createError) {
1814
2406
  // Index might have been created by another instance
1815
- logger$1.debug(`[mongoMeili] Index ${indexName} may already exist:`, createError);
2407
+ logger.debug(`[mongoMeili] Index ${indexName} may already exist:`, createError);
1816
2408
  }
1817
2409
  }
1818
2410
  else {
1819
- logger$1.error(`[mongoMeili] Error checking index ${indexName}:`, error);
2411
+ logger.error(`[mongoMeili] Error checking index ${indexName}:`, error);
1820
2412
  }
1821
2413
  }
1822
2414
  })();
@@ -1879,7 +2471,7 @@ function mongoMeili(schema, options) {
1879
2471
  }
1880
2472
  catch (error) {
1881
2473
  if (meiliEnabled) {
1882
- logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may trigger syncing.', error);
2474
+ logger.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may trigger syncing.', error);
1883
2475
  }
1884
2476
  return next();
1885
2477
  }
@@ -1899,7 +2491,7 @@ function mongoMeili(schema, options) {
1899
2491
  meiliDoc = await client.index('convos').getDocument(doc.conversationId);
1900
2492
  }
1901
2493
  catch (error) {
1902
- logger$1.debug('[MeiliMongooseModel.findOneAndUpdate] Convo not found in MeiliSearch and will index ' +
2494
+ logger.debug('[MeiliMongooseModel.findOneAndUpdate] Convo not found in MeiliSearch and will index ' +
1903
2495
  doc.conversationId, error);
1904
2496
  }
1905
2497
  }
@@ -1950,6 +2542,13 @@ function createAgentModel(mongoose) {
1950
2542
  return mongoose.models.Agent || mongoose.model('Agent', agentSchema);
1951
2543
  }
1952
2544
 
2545
+ /**
2546
+ * Creates or returns the AgentCategory model using the provided mongoose instance and schema
2547
+ */
2548
+ function createAgentCategoryModel(mongoose) {
2549
+ return mongoose.models.AgentCategory || mongoose.model('AgentCategory', agentCategorySchema);
2550
+ }
2551
+
1953
2552
  /**
1954
2553
  * Creates or returns the Role model using the provided mongoose instance and schema
1955
2554
  */
@@ -2061,6 +2660,108 @@ function createMemoryModel(mongoose) {
2061
2660
  return mongoose.models.MemoryEntry || mongoose.model('MemoryEntry', MemoryEntrySchema);
2062
2661
  }
2063
2662
 
2663
+ const accessRoleSchema = new mongoose.Schema({
2664
+ accessRoleId: {
2665
+ type: String,
2666
+ required: true,
2667
+ index: true,
2668
+ unique: true,
2669
+ },
2670
+ name: {
2671
+ type: String,
2672
+ required: true,
2673
+ },
2674
+ description: String,
2675
+ resourceType: {
2676
+ type: String,
2677
+ enum: ['agent', 'project', 'file', 'promptGroup'],
2678
+ required: true,
2679
+ default: 'agent',
2680
+ },
2681
+ permBits: {
2682
+ type: Number,
2683
+ required: true,
2684
+ },
2685
+ }, { timestamps: true });
2686
+
2687
+ /**
2688
+ * Creates or returns the AccessRole model using the provided mongoose instance and schema
2689
+ */
2690
+ function createAccessRoleModel(mongoose) {
2691
+ return (mongoose.models.AccessRole || mongoose.model('AccessRole', accessRoleSchema));
2692
+ }
2693
+
2694
+ const aclEntrySchema = new mongoose.Schema({
2695
+ principalType: {
2696
+ type: String,
2697
+ enum: Object.values(librechatDataProvider.PrincipalType),
2698
+ required: true,
2699
+ },
2700
+ principalId: {
2701
+ type: mongoose.Schema.Types.Mixed, // Can be ObjectId for users/groups or String for roles
2702
+ refPath: 'principalModel',
2703
+ required: function () {
2704
+ return this.principalType !== librechatDataProvider.PrincipalType.PUBLIC;
2705
+ },
2706
+ index: true,
2707
+ },
2708
+ principalModel: {
2709
+ type: String,
2710
+ enum: Object.values(librechatDataProvider.PrincipalModel),
2711
+ required: function () {
2712
+ return this.principalType !== librechatDataProvider.PrincipalType.PUBLIC;
2713
+ },
2714
+ },
2715
+ resourceType: {
2716
+ type: String,
2717
+ enum: Object.values(librechatDataProvider.ResourceType),
2718
+ required: true,
2719
+ },
2720
+ resourceId: {
2721
+ type: mongoose.Schema.Types.ObjectId,
2722
+ required: true,
2723
+ index: true,
2724
+ },
2725
+ permBits: {
2726
+ type: Number,
2727
+ default: 1,
2728
+ },
2729
+ roleId: {
2730
+ type: mongoose.Schema.Types.ObjectId,
2731
+ ref: 'AccessRole',
2732
+ },
2733
+ inheritedFrom: {
2734
+ type: mongoose.Schema.Types.ObjectId,
2735
+ sparse: true,
2736
+ index: true,
2737
+ },
2738
+ grantedBy: {
2739
+ type: mongoose.Schema.Types.ObjectId,
2740
+ ref: 'User',
2741
+ },
2742
+ grantedAt: {
2743
+ type: Date,
2744
+ default: Date.now,
2745
+ },
2746
+ }, { timestamps: true });
2747
+ aclEntrySchema.index({ principalId: 1, principalType: 1, resourceType: 1, resourceId: 1 });
2748
+ aclEntrySchema.index({ resourceId: 1, principalType: 1, principalId: 1 });
2749
+ aclEntrySchema.index({ principalId: 1, permBits: 1, resourceType: 1 });
2750
+
2751
+ /**
2752
+ * Creates or returns the AclEntry model using the provided mongoose instance and schema
2753
+ */
2754
+ function createAclEntryModel(mongoose) {
2755
+ return mongoose.models.AclEntry || mongoose.model('AclEntry', aclEntrySchema);
2756
+ }
2757
+
2758
+ /**
2759
+ * Creates or returns the Group model using the provided mongoose instance and schema
2760
+ */
2761
+ function createGroupModel(mongoose) {
2762
+ return mongoose.models.Group || mongoose.model('Group', groupSchema);
2763
+ }
2764
+
2064
2765
  /**
2065
2766
  * Creates all database models for all collections
2066
2767
  */
@@ -2073,6 +2774,7 @@ function createModels(mongoose) {
2073
2774
  Conversation: createConversationModel(mongoose),
2074
2775
  Message: createMessageModel(mongoose),
2075
2776
  Agent: createAgentModel(mongoose),
2777
+ AgentCategory: createAgentCategoryModel(mongoose),
2076
2778
  Role: createRoleModel(mongoose),
2077
2779
  Action: createActionModel(mongoose),
2078
2780
  Assistant: createAssistantModel(mongoose),
@@ -2089,935 +2791,1951 @@ function createModels(mongoose) {
2089
2791
  SharedLink: createSharedLinkModel(mongoose),
2090
2792
  ToolCall: createToolCallModel(mongoose),
2091
2793
  MemoryEntry: createMemoryModel(mongoose),
2794
+ AccessRole: createAccessRoleModel(mongoose),
2795
+ AclEntry: createAclEntryModel(mongoose),
2796
+ Group: createGroupModel(mongoose),
2092
2797
  };
2093
2798
  }
2094
2799
 
2095
- /** Factory function that takes mongoose instance and returns the methods */
2096
- function createUserMethods(mongoose) {
2097
- /**
2098
- * Search for a single user based on partial data and return matching user document as plain object.
2099
- */
2100
- async function findUser(searchCriteria, fieldsToSelect) {
2101
- const User = mongoose.models.User;
2102
- const query = User.findOne(searchCriteria);
2103
- if (fieldsToSelect) {
2104
- query.select(fieldsToSelect);
2105
- }
2106
- return (await query.lean());
2800
+ var _a;
2801
+ class SessionError extends Error {
2802
+ constructor(message, code = 'SESSION_ERROR') {
2803
+ super(message);
2804
+ this.name = 'SessionError';
2805
+ this.code = code;
2107
2806
  }
2807
+ }
2808
+ const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
2809
+ const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
2810
+ // Factory function that takes mongoose instance and returns the methods
2811
+ function createSessionMethods(mongoose) {
2108
2812
  /**
2109
- * Count the number of user documents in the collection based on the provided filter.
2813
+ * Creates a new session for a user
2110
2814
  */
2111
- async function countUsers(filter = {}) {
2112
- const User = mongoose.models.User;
2113
- return await User.countDocuments(filter);
2815
+ async function createSession(userId, options = {}) {
2816
+ if (!userId) {
2817
+ throw new SessionError('User ID is required', 'INVALID_USER_ID');
2818
+ }
2819
+ try {
2820
+ const Session = mongoose.models.Session;
2821
+ const currentSession = new Session({
2822
+ user: userId,
2823
+ expiration: options.expiration || new Date(Date.now() + expires),
2824
+ });
2825
+ const refreshToken = await generateRefreshToken(currentSession);
2826
+ return { session: currentSession, refreshToken };
2827
+ }
2828
+ catch (error) {
2829
+ logger$1.error('[createSession] Error creating session:', error);
2830
+ throw new SessionError('Failed to create session', 'CREATE_SESSION_FAILED');
2831
+ }
2114
2832
  }
2115
2833
  /**
2116
- * Creates a new user, optionally with a TTL of 1 week.
2834
+ * Finds a session by various parameters
2117
2835
  */
2118
- async function createUser(data, balanceConfig, disableTTL = true, returnUser = false) {
2119
- const User = mongoose.models.User;
2120
- const Balance = mongoose.models.Balance;
2121
- const userData = {
2122
- ...data,
2123
- expiresAt: disableTTL ? undefined : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
2124
- };
2125
- if (disableTTL) {
2126
- delete userData.expiresAt;
2127
- }
2128
- const user = await User.create(userData);
2129
- // If balance is enabled, create or update a balance record for the user
2130
- if ((balanceConfig === null || balanceConfig === void 0 ? void 0 : balanceConfig.enabled) && (balanceConfig === null || balanceConfig === void 0 ? void 0 : balanceConfig.startBalance)) {
2131
- const update = {
2132
- $inc: { tokenCredits: balanceConfig.startBalance },
2133
- };
2134
- if (balanceConfig.autoRefillEnabled &&
2135
- balanceConfig.refillIntervalValue != null &&
2136
- balanceConfig.refillIntervalUnit != null &&
2137
- balanceConfig.refillAmount != null) {
2138
- update.$set = {
2139
- autoRefillEnabled: true,
2140
- refillIntervalValue: balanceConfig.refillIntervalValue,
2141
- refillIntervalUnit: balanceConfig.refillIntervalUnit,
2142
- refillAmount: balanceConfig.refillAmount,
2143
- };
2836
+ async function findSession(params, options = { lean: true }) {
2837
+ try {
2838
+ const Session = mongoose.models.Session;
2839
+ const query = {};
2840
+ if (!params.refreshToken && !params.userId && !params.sessionId) {
2841
+ throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
2144
2842
  }
2145
- await Balance.findOneAndUpdate({ user: user._id }, update, {
2146
- upsert: true,
2147
- new: true,
2148
- }).lean();
2843
+ if (params.refreshToken) {
2844
+ const tokenHash = await hashToken(params.refreshToken);
2845
+ query.refreshTokenHash = tokenHash;
2846
+ }
2847
+ if (params.userId) {
2848
+ query.user = params.userId;
2849
+ }
2850
+ if (params.sessionId) {
2851
+ const sessionId = typeof params.sessionId === 'object' &&
2852
+ params.sessionId !== null &&
2853
+ 'sessionId' in params.sessionId
2854
+ ? params.sessionId.sessionId
2855
+ : params.sessionId;
2856
+ if (!mongoose.Types.ObjectId.isValid(sessionId)) {
2857
+ throw new SessionError('Invalid session ID format', 'INVALID_SESSION_ID');
2858
+ }
2859
+ query._id = sessionId;
2860
+ }
2861
+ // Add expiration check to only return valid sessions
2862
+ query.expiration = { $gt: new Date() };
2863
+ const sessionQuery = Session.findOne(query);
2864
+ if (options.lean) {
2865
+ return (await sessionQuery.lean());
2866
+ }
2867
+ return await sessionQuery.exec();
2149
2868
  }
2150
- if (returnUser) {
2151
- return user.toObject();
2869
+ catch (error) {
2870
+ logger$1.error('[findSession] Error finding session:', error);
2871
+ throw new SessionError('Failed to find session', 'FIND_SESSION_FAILED');
2152
2872
  }
2153
- return user._id;
2154
2873
  }
2155
2874
  /**
2156
- * Update a user with new data without overwriting existing properties.
2875
+ * Updates session expiration
2157
2876
  */
2158
- async function updateUser(userId, updateData) {
2159
- const User = mongoose.models.User;
2160
- const updateOperation = {
2161
- $set: updateData,
2162
- $unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
2163
- };
2164
- return (await User.findByIdAndUpdate(userId, updateOperation, {
2165
- new: true,
2166
- runValidators: true,
2167
- }).lean());
2877
+ async function updateExpiration(session, newExpiration) {
2878
+ try {
2879
+ const Session = mongoose.models.Session;
2880
+ const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
2881
+ if (!sessionDoc) {
2882
+ throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
2883
+ }
2884
+ sessionDoc.expiration = newExpiration || new Date(Date.now() + expires);
2885
+ return await sessionDoc.save();
2886
+ }
2887
+ catch (error) {
2888
+ logger$1.error('[updateExpiration] Error updating session:', error);
2889
+ throw new SessionError('Failed to update session expiration', 'UPDATE_EXPIRATION_FAILED');
2890
+ }
2168
2891
  }
2169
2892
  /**
2170
- * Retrieve a user by ID and convert the found user document to a plain object.
2893
+ * Deletes a session by refresh token or session ID
2171
2894
  */
2172
- async function getUserById(userId, fieldsToSelect) {
2173
- const User = mongoose.models.User;
2174
- const query = User.findById(userId);
2175
- if (fieldsToSelect) {
2176
- query.select(fieldsToSelect);
2895
+ async function deleteSession(params) {
2896
+ try {
2897
+ const Session = mongoose.models.Session;
2898
+ if (!params.refreshToken && !params.sessionId) {
2899
+ throw new SessionError('Either refreshToken or sessionId is required', 'INVALID_DELETE_PARAMS');
2900
+ }
2901
+ const query = {};
2902
+ if (params.refreshToken) {
2903
+ query.refreshTokenHash = await hashToken(params.refreshToken);
2904
+ }
2905
+ if (params.sessionId) {
2906
+ query._id = params.sessionId;
2907
+ }
2908
+ const result = await Session.deleteOne(query);
2909
+ if (result.deletedCount === 0) {
2910
+ logger$1.warn('[deleteSession] No session found to delete');
2911
+ }
2912
+ return result;
2913
+ }
2914
+ catch (error) {
2915
+ logger$1.error('[deleteSession] Error deleting session:', error);
2916
+ throw new SessionError('Failed to delete session', 'DELETE_SESSION_FAILED');
2177
2917
  }
2178
- return (await query.lean());
2179
2918
  }
2180
2919
  /**
2181
- * Delete a user by their unique ID.
2920
+ * Deletes all sessions for a user
2182
2921
  */
2183
- async function deleteUserById(userId) {
2922
+ async function deleteAllUserSessions(userId, options = {}) {
2184
2923
  try {
2185
- const User = mongoose.models.User;
2186
- const result = await User.deleteOne({ _id: userId });
2187
- if (result.deletedCount === 0) {
2188
- return { deletedCount: 0, message: 'No user found with that ID.' };
2924
+ const Session = mongoose.models.Session;
2925
+ if (!userId) {
2926
+ throw new SessionError('User ID is required', 'INVALID_USER_ID');
2189
2927
  }
2190
- return { deletedCount: result.deletedCount, message: 'User was deleted successfully.' };
2928
+ const userIdString = typeof userId === 'object' && userId !== null ? userId.userId : userId;
2929
+ if (!mongoose.Types.ObjectId.isValid(userIdString)) {
2930
+ throw new SessionError('Invalid user ID format', 'INVALID_USER_ID_FORMAT');
2931
+ }
2932
+ const query = { user: userIdString };
2933
+ if (options.excludeCurrentSession && options.currentSessionId) {
2934
+ query._id = { $ne: options.currentSessionId };
2935
+ }
2936
+ const result = await Session.deleteMany(query);
2937
+ if (result.deletedCount && result.deletedCount > 0) {
2938
+ logger$1.debug(`[deleteAllUserSessions] Deleted ${result.deletedCount} sessions for user ${userIdString}.`);
2939
+ }
2940
+ return result;
2191
2941
  }
2192
2942
  catch (error) {
2193
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
2194
- throw new Error('Error deleting user: ' + errorMessage);
2943
+ logger$1.error('[deleteAllUserSessions] Error deleting user sessions:', error);
2944
+ throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
2195
2945
  }
2196
2946
  }
2197
2947
  /**
2198
- * Generates a JWT token for a given user.
2948
+ * Generates a refresh token for a session
2199
2949
  */
2200
- async function generateToken(user) {
2201
- if (!user) {
2202
- throw new Error('No user provided');
2950
+ async function generateRefreshToken(session) {
2951
+ if (!session || !session.user) {
2952
+ throw new SessionError('Invalid session object', 'INVALID_SESSION');
2203
2953
  }
2204
- let expires = 1000 * 60 * 15;
2205
- if (process.env.SESSION_EXPIRY !== undefined && process.env.SESSION_EXPIRY !== '') {
2206
- try {
2207
- const evaluated = eval(process.env.SESSION_EXPIRY);
2208
- if (evaluated) {
2209
- expires = evaluated;
2210
- }
2211
- }
2212
- catch (error) {
2213
- console.warn('Invalid SESSION_EXPIRY expression, using default:', error);
2954
+ try {
2955
+ const expiresIn = session.expiration ? session.expiration.getTime() : Date.now() + expires;
2956
+ if (!session.expiration) {
2957
+ session.expiration = new Date(expiresIn);
2214
2958
  }
2959
+ const refreshToken = await signPayload({
2960
+ payload: {
2961
+ id: session.user,
2962
+ sessionId: session._id,
2963
+ },
2964
+ secret: process.env.JWT_REFRESH_SECRET,
2965
+ expirationTime: Math.floor((expiresIn - Date.now()) / 1000),
2966
+ });
2967
+ session.refreshTokenHash = await hashToken(refreshToken);
2968
+ await session.save();
2969
+ return refreshToken;
2970
+ }
2971
+ catch (error) {
2972
+ logger$1.error('[generateRefreshToken] Error generating refresh token:', error);
2973
+ throw new SessionError('Failed to generate refresh token', 'GENERATE_TOKEN_FAILED');
2215
2974
  }
2216
- return await signPayload({
2217
- payload: {
2218
- id: user._id,
2219
- username: user.username,
2220
- provider: user.provider,
2221
- email: user.email,
2222
- },
2223
- secret: process.env.JWT_SECRET,
2224
- expirationTime: expires / 1000,
2225
- });
2226
2975
  }
2227
2976
  /**
2228
- * Update a user's personalization memories setting.
2229
- * Handles the edge case where the personalization object doesn't exist.
2977
+ * Counts active sessions for a user
2230
2978
  */
2231
- async function toggleUserMemories(userId, memoriesEnabled) {
2232
- const User = mongoose.models.User;
2233
- // First, ensure the personalization object exists
2234
- const user = await User.findById(userId);
2235
- if (!user) {
2236
- return null;
2979
+ async function countActiveSessions(userId) {
2980
+ try {
2981
+ const Session = mongoose.models.Session;
2982
+ if (!userId) {
2983
+ throw new SessionError('User ID is required', 'INVALID_USER_ID');
2984
+ }
2985
+ return await Session.countDocuments({
2986
+ user: userId,
2987
+ expiration: { $gt: new Date() },
2988
+ });
2989
+ }
2990
+ catch (error) {
2991
+ logger$1.error('[countActiveSessions] Error counting active sessions:', error);
2992
+ throw new SessionError('Failed to count active sessions', 'COUNT_SESSIONS_FAILED');
2237
2993
  }
2238
- // Use $set to update the nested field, which will create the personalization object if it doesn't exist
2239
- const updateOperation = {
2240
- $set: {
2241
- 'personalization.memories': memoriesEnabled,
2242
- },
2243
- };
2244
- return (await User.findByIdAndUpdate(userId, updateOperation, {
2245
- new: true,
2246
- runValidators: true,
2247
- }).lean());
2248
2994
  }
2249
- // Return all methods
2250
2995
  return {
2251
- findUser,
2252
- countUsers,
2253
- createUser,
2254
- updateUser,
2255
- getUserById,
2256
- deleteUserById,
2257
- generateToken,
2258
- toggleUserMemories,
2996
+ findSession,
2997
+ SessionError,
2998
+ deleteSession,
2999
+ createSession,
3000
+ updateExpiration,
3001
+ countActiveSessions,
3002
+ generateRefreshToken,
3003
+ deleteAllUserSessions,
2259
3004
  };
2260
3005
  }
2261
3006
 
2262
- /**
2263
- * Wrapper for the traverse module to handle CommonJS imports in ESM context
2264
- */
2265
- // @ts-ignore - traverse doesn't have proper ESM exports
2266
- // Handle both default export and named exports
2267
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2268
- const traverse = (traverseLib__default.default.default || traverseLib__default.default);
2269
-
2270
- const SPLAT_SYMBOL = Symbol.for('splat');
2271
- const MESSAGE_SYMBOL = Symbol.for('message');
2272
- const CONSOLE_JSON_STRING_LENGTH = parseInt(process.env.CONSOLE_JSON_STRING_LENGTH || '', 10) || 255;
2273
- const sensitiveKeys = [
2274
- /^(sk-)[^\s]+/, // OpenAI API key pattern
2275
- /(Bearer )[^\s]+/, // Header: Bearer token pattern
2276
- /(api-key:? )[^\s]+/, // Header: API key pattern
2277
- /(key=)[^\s]+/, // URL query param: sensitive key pattern (Google)
2278
- ];
2279
- /**
2280
- * Determines if a given value string is sensitive and returns matching regex patterns.
2281
- *
2282
- * @param valueStr - The value string to check.
2283
- * @returns An array of regex patterns that match the value string.
2284
- */
2285
- function getMatchingSensitivePatterns(valueStr) {
2286
- if (valueStr) {
2287
- // Filter and return all regex patterns that match the value string
2288
- return sensitiveKeys.filter((regex) => regex.test(valueStr));
2289
- }
2290
- return [];
2291
- }
2292
- /**
2293
- * Redacts sensitive information from a console message and trims it to a specified length if provided.
2294
- * @param str - The console message to be redacted.
2295
- * @param trimLength - The optional length at which to trim the redacted message.
2296
- * @returns The redacted and optionally trimmed console message.
2297
- */
2298
- function redactMessage(str, trimLength) {
2299
- if (!str) {
2300
- return '';
2301
- }
2302
- const patterns = getMatchingSensitivePatterns(str);
2303
- patterns.forEach((pattern) => {
2304
- str = str.replace(pattern, '$1[REDACTED]');
2305
- });
2306
- return str;
2307
- }
2308
- /**
2309
- * Redacts sensitive information from log messages if the log level is 'error'.
2310
- * Note: Intentionally mutates the object.
2311
- * @param info - The log information object.
2312
- * @returns The modified log information object.
2313
- */
2314
- const redactFormat = winston__default.default.format((info) => {
2315
- if (info.level === 'error') {
2316
- // Type guard to ensure message is a string
2317
- if (typeof info.message === 'string') {
2318
- info.message = redactMessage(info.message);
3007
+ // Factory function that takes mongoose instance and returns the methods
3008
+ function createTokenMethods(mongoose) {
3009
+ /**
3010
+ * Creates a new Token instance.
3011
+ */
3012
+ async function createToken(tokenData) {
3013
+ try {
3014
+ const Token = mongoose.models.Token;
3015
+ const currentTime = new Date();
3016
+ const expiresAt = new Date(currentTime.getTime() + tokenData.expiresIn * 1000);
3017
+ const newTokenData = {
3018
+ ...tokenData,
3019
+ createdAt: currentTime,
3020
+ expiresAt,
3021
+ };
3022
+ return await Token.create(newTokenData);
2319
3023
  }
2320
- // Handle MESSAGE_SYMBOL with type safety
2321
- const symbolValue = info[MESSAGE_SYMBOL];
2322
- if (typeof symbolValue === 'string') {
2323
- info[MESSAGE_SYMBOL] = redactMessage(symbolValue);
3024
+ catch (error) {
3025
+ logger$1.debug('An error occurred while creating token:', error);
3026
+ throw error;
2324
3027
  }
2325
3028
  }
2326
- return info;
2327
- });
2328
- /**
2329
- * Truncates long strings, especially base64 image data, within log messages.
2330
- *
2331
- * @param value - The value to be inspected and potentially truncated.
2332
- * @param length - The length at which to truncate the value. Default: 100.
2333
- * @returns The truncated or original value.
2334
- */
2335
- const truncateLongStrings = (value, length = 100) => {
2336
- if (typeof value === 'string') {
2337
- return value.length > length ? value.substring(0, length) + '... [truncated]' : value;
2338
- }
2339
- return value;
2340
- };
2341
- /**
2342
- * An array mapping function that truncates long strings (objects converted to JSON strings).
2343
- * @param item - The item to be condensed.
2344
- * @returns The condensed item.
2345
- */
2346
- const condenseArray = (item) => {
2347
- if (typeof item === 'string') {
2348
- return truncateLongStrings(JSON.stringify(item));
2349
- }
2350
- else if (typeof item === 'object') {
2351
- return truncateLongStrings(JSON.stringify(item));
2352
- }
2353
- return item;
2354
- };
2355
- /**
2356
- * Formats log messages for debugging purposes.
2357
- * - Truncates long strings within log messages.
2358
- * - Condenses arrays by truncating long strings and objects as strings within array items.
2359
- * - Redacts sensitive information from log messages if the log level is 'error'.
2360
- * - Converts log information object to a formatted string.
2361
- *
2362
- * @param options - The options for formatting log messages.
2363
- * @returns The formatted log message.
2364
- */
2365
- const debugTraverse = winston__default.default.format.printf(({ level, message, timestamp, ...metadata }) => {
2366
- if (!message) {
2367
- return `${timestamp} ${level}`;
2368
- }
2369
- // Type-safe version of the CJS logic: !message?.trim || typeof message !== 'string'
2370
- if (typeof message !== 'string' || !message.trim) {
2371
- return `${timestamp} ${level}: ${JSON.stringify(message)}`;
2372
- }
2373
- let msg = `${timestamp} ${level}: ${truncateLongStrings(message.trim(), 150)}`;
2374
- try {
2375
- if (level !== 'debug') {
2376
- return msg;
2377
- }
2378
- if (!metadata) {
2379
- return msg;
3029
+ /**
3030
+ * Updates a Token document that matches the provided query.
3031
+ */
3032
+ async function updateToken(query, updateData) {
3033
+ try {
3034
+ const Token = mongoose.models.Token;
3035
+ return await Token.findOneAndUpdate(query, updateData, { new: true });
2380
3036
  }
2381
- // Type-safe access to SPLAT_SYMBOL using bracket notation
2382
- const metadataRecord = metadata;
2383
- const splatArray = metadataRecord[SPLAT_SYMBOL];
2384
- const debugValue = Array.isArray(splatArray) ? splatArray[0] : undefined;
2385
- if (!debugValue) {
2386
- return msg;
3037
+ catch (error) {
3038
+ logger$1.debug('An error occurred while updating token:', error);
3039
+ throw error;
2387
3040
  }
2388
- if (debugValue && Array.isArray(debugValue)) {
2389
- msg += `\n${JSON.stringify(debugValue.map(condenseArray))}`;
2390
- return msg;
3041
+ }
3042
+ /**
3043
+ * Deletes all Token documents that match the provided token, user ID, or email.
3044
+ */
3045
+ async function deleteTokens(query) {
3046
+ try {
3047
+ const Token = mongoose.models.Token;
3048
+ return await Token.deleteMany({
3049
+ $or: [
3050
+ { userId: query.userId },
3051
+ { token: query.token },
3052
+ { email: query.email },
3053
+ { identifier: query.identifier },
3054
+ ],
3055
+ });
2391
3056
  }
2392
- if (typeof debugValue !== 'object') {
2393
- return (msg += ` ${debugValue}`);
3057
+ catch (error) {
3058
+ logger$1.debug('An error occurred while deleting tokens:', error);
3059
+ throw error;
2394
3060
  }
2395
- msg += '\n{';
2396
- const copy = klona.klona(metadata);
2397
- traverse(copy).forEach(function (value) {
2398
- var _a;
2399
- if (typeof (this === null || this === void 0 ? void 0 : this.key) === 'symbol') {
2400
- return;
2401
- }
2402
- let _parentKey = '';
2403
- const parent = this.parent;
2404
- if (typeof (parent === null || parent === void 0 ? void 0 : parent.key) !== 'symbol' && (parent === null || parent === void 0 ? void 0 : parent.key)) {
2405
- _parentKey = parent.key;
2406
- }
2407
- const parentKey = `${parent && parent.notRoot ? _parentKey + '.' : ''}`;
2408
- const tabs = `${parent && parent.notRoot ? ' ' : ' '}`;
2409
- const currentKey = (_a = this === null || this === void 0 ? void 0 : this.key) !== null && _a !== void 0 ? _a : 'unknown';
2410
- if (this.isLeaf && typeof value === 'string') {
2411
- const truncatedText = truncateLongStrings(value);
2412
- msg += `\n${tabs}${parentKey}${currentKey}: ${JSON.stringify(truncatedText)},`;
3061
+ }
3062
+ /**
3063
+ * Finds a Token document that matches the provided query.
3064
+ */
3065
+ async function findToken(query) {
3066
+ try {
3067
+ const Token = mongoose.models.Token;
3068
+ const conditions = [];
3069
+ if (query.userId) {
3070
+ conditions.push({ userId: query.userId });
2413
3071
  }
2414
- else if (this.notLeaf && Array.isArray(value) && value.length > 0) {
2415
- const currentMessage = `\n${tabs}// ${value.length} ${currentKey.replace(/s$/, '')}(s)`;
2416
- this.update(currentMessage, true);
2417
- msg += currentMessage;
2418
- const stringifiedArray = value.map(condenseArray);
2419
- msg += `\n${tabs}${parentKey}${currentKey}: [${stringifiedArray}],`;
3072
+ if (query.token) {
3073
+ conditions.push({ token: query.token });
2420
3074
  }
2421
- else if (this.isLeaf && typeof value === 'function') {
2422
- msg += `\n${tabs}${parentKey}${currentKey}: function,`;
3075
+ if (query.email) {
3076
+ conditions.push({ email: query.email });
2423
3077
  }
2424
- else if (this.isLeaf) {
2425
- msg += `\n${tabs}${parentKey}${currentKey}: ${value},`;
3078
+ if (query.identifier) {
3079
+ conditions.push({ identifier: query.identifier });
2426
3080
  }
2427
- });
2428
- msg += '\n}';
2429
- return msg;
2430
- }
2431
- catch (e) {
2432
- const errorMessage = e instanceof Error ? e.message : 'Unknown error';
2433
- return (msg += `\n[LOGGER PARSING ERROR] ${errorMessage}`);
2434
- }
2435
- });
3081
+ const token = await Token.findOne({
3082
+ $and: conditions,
3083
+ }).lean();
3084
+ return token;
3085
+ }
3086
+ catch (error) {
3087
+ logger$1.debug('An error occurred while finding token:', error);
3088
+ throw error;
3089
+ }
3090
+ }
3091
+ // Return all methods
3092
+ return {
3093
+ findToken,
3094
+ createToken,
3095
+ updateToken,
3096
+ deleteTokens,
3097
+ };
3098
+ }
3099
+
3100
+ // Factory function that takes mongoose instance and returns the methods
3101
+ function createRoleMethods(mongoose) {
3102
+ /**
3103
+ * Initialize default roles in the system.
3104
+ * Creates the default roles (ADMIN, USER) if they don't exist in the database.
3105
+ * Updates existing roles with new permission types if they're missing.
3106
+ */
3107
+ async function initializeRoles() {
3108
+ var _a, _b;
3109
+ const Role = mongoose.models.Role;
3110
+ for (const roleName of [librechatDataProvider.SystemRoles.ADMIN, librechatDataProvider.SystemRoles.USER]) {
3111
+ let role = await Role.findOne({ name: roleName });
3112
+ const defaultPerms = librechatDataProvider.roleDefaults[roleName].permissions;
3113
+ if (!role) {
3114
+ role = new Role(librechatDataProvider.roleDefaults[roleName]);
3115
+ }
3116
+ else {
3117
+ const permissions = (_b = (_a = role.toObject()) === null || _a === void 0 ? void 0 : _a.permissions) !== null && _b !== void 0 ? _b : {};
3118
+ role.permissions = role.permissions || {};
3119
+ for (const permType of Object.keys(defaultPerms)) {
3120
+ if (permissions[permType] == null || Object.keys(permissions[permType]).length === 0) {
3121
+ role.permissions[permType] = defaultPerms[permType];
3122
+ }
3123
+ }
3124
+ }
3125
+ await role.save();
3126
+ }
3127
+ }
3128
+ /**
3129
+ * List all roles in the system (for testing purposes)
3130
+ * Returns an array of all roles with their names and permissions
3131
+ */
3132
+ async function listRoles() {
3133
+ const Role = mongoose.models.Role;
3134
+ return await Role.find({}).select('name permissions').lean();
3135
+ }
3136
+ // Return all methods you want to expose
3137
+ return {
3138
+ listRoles,
3139
+ initializeRoles,
3140
+ };
3141
+ }
3142
+
3143
+ /** Factory function that takes mongoose instance and returns the methods */
3144
+ function createUserMethods(mongoose) {
3145
+ /**
3146
+ * Search for a single user based on partial data and return matching user document as plain object.
3147
+ */
3148
+ async function findUser(searchCriteria, fieldsToSelect) {
3149
+ const User = mongoose.models.User;
3150
+ const query = User.findOne(searchCriteria);
3151
+ if (fieldsToSelect) {
3152
+ query.select(fieldsToSelect);
3153
+ }
3154
+ return (await query.lean());
3155
+ }
3156
+ /**
3157
+ * Count the number of user documents in the collection based on the provided filter.
3158
+ */
3159
+ async function countUsers(filter = {}) {
3160
+ const User = mongoose.models.User;
3161
+ return await User.countDocuments(filter);
3162
+ }
3163
+ /**
3164
+ * Creates a new user, optionally with a TTL of 1 week.
3165
+ */
3166
+ async function createUser(data, balanceConfig, disableTTL = true, returnUser = false) {
3167
+ const User = mongoose.models.User;
3168
+ const Balance = mongoose.models.Balance;
3169
+ const userData = {
3170
+ ...data,
3171
+ expiresAt: disableTTL ? undefined : new Date(Date.now() + 604800 * 1000), // 1 week in milliseconds
3172
+ };
3173
+ if (disableTTL) {
3174
+ delete userData.expiresAt;
3175
+ }
3176
+ const user = await User.create(userData);
3177
+ // If balance is enabled, create or update a balance record for the user
3178
+ if ((balanceConfig === null || balanceConfig === void 0 ? void 0 : balanceConfig.enabled) && (balanceConfig === null || balanceConfig === void 0 ? void 0 : balanceConfig.startBalance)) {
3179
+ const update = {
3180
+ $inc: { tokenCredits: balanceConfig.startBalance },
3181
+ };
3182
+ if (balanceConfig.autoRefillEnabled &&
3183
+ balanceConfig.refillIntervalValue != null &&
3184
+ balanceConfig.refillIntervalUnit != null &&
3185
+ balanceConfig.refillAmount != null) {
3186
+ update.$set = {
3187
+ autoRefillEnabled: true,
3188
+ refillIntervalValue: balanceConfig.refillIntervalValue,
3189
+ refillIntervalUnit: balanceConfig.refillIntervalUnit,
3190
+ refillAmount: balanceConfig.refillAmount,
3191
+ };
3192
+ }
3193
+ await Balance.findOneAndUpdate({ user: user._id }, update, {
3194
+ upsert: true,
3195
+ new: true,
3196
+ }).lean();
3197
+ }
3198
+ if (returnUser) {
3199
+ return user.toObject();
3200
+ }
3201
+ return user._id;
3202
+ }
3203
+ /**
3204
+ * Update a user with new data without overwriting existing properties.
3205
+ */
3206
+ async function updateUser(userId, updateData) {
3207
+ const User = mongoose.models.User;
3208
+ const updateOperation = {
3209
+ $set: updateData,
3210
+ $unset: { expiresAt: '' }, // Remove the expiresAt field to prevent TTL
3211
+ };
3212
+ return (await User.findByIdAndUpdate(userId, updateOperation, {
3213
+ new: true,
3214
+ runValidators: true,
3215
+ }).lean());
3216
+ }
3217
+ /**
3218
+ * Retrieve a user by ID and convert the found user document to a plain object.
3219
+ */
3220
+ async function getUserById(userId, fieldsToSelect) {
3221
+ const User = mongoose.models.User;
3222
+ const query = User.findById(userId);
3223
+ if (fieldsToSelect) {
3224
+ query.select(fieldsToSelect);
3225
+ }
3226
+ return (await query.lean());
3227
+ }
3228
+ /**
3229
+ * Delete a user by their unique ID.
3230
+ */
3231
+ async function deleteUserById(userId) {
3232
+ try {
3233
+ const User = mongoose.models.User;
3234
+ const result = await User.deleteOne({ _id: userId });
3235
+ if (result.deletedCount === 0) {
3236
+ return { deletedCount: 0, message: 'No user found with that ID.' };
3237
+ }
3238
+ return { deletedCount: result.deletedCount, message: 'User was deleted successfully.' };
3239
+ }
3240
+ catch (error) {
3241
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
3242
+ throw new Error('Error deleting user: ' + errorMessage);
3243
+ }
3244
+ }
3245
+ /**
3246
+ * Generates a JWT token for a given user.
3247
+ */
3248
+ async function generateToken(user) {
3249
+ if (!user) {
3250
+ throw new Error('No user provided');
3251
+ }
3252
+ let expires = 1000 * 60 * 15;
3253
+ if (process.env.SESSION_EXPIRY !== undefined && process.env.SESSION_EXPIRY !== '') {
3254
+ try {
3255
+ const evaluated = eval(process.env.SESSION_EXPIRY);
3256
+ if (evaluated) {
3257
+ expires = evaluated;
3258
+ }
3259
+ }
3260
+ catch (error) {
3261
+ console.warn('Invalid SESSION_EXPIRY expression, using default:', error);
3262
+ }
3263
+ }
3264
+ return await signPayload({
3265
+ payload: {
3266
+ id: user._id,
3267
+ username: user.username,
3268
+ provider: user.provider,
3269
+ email: user.email,
3270
+ },
3271
+ secret: process.env.JWT_SECRET,
3272
+ expirationTime: expires / 1000,
3273
+ });
3274
+ }
3275
+ /**
3276
+ * Update a user's personalization memories setting.
3277
+ * Handles the edge case where the personalization object doesn't exist.
3278
+ */
3279
+ async function toggleUserMemories(userId, memoriesEnabled) {
3280
+ const User = mongoose.models.User;
3281
+ // First, ensure the personalization object exists
3282
+ const user = await User.findById(userId);
3283
+ if (!user) {
3284
+ return null;
3285
+ }
3286
+ // Use $set to update the nested field, which will create the personalization object if it doesn't exist
3287
+ const updateOperation = {
3288
+ $set: {
3289
+ 'personalization.memories': memoriesEnabled,
3290
+ },
3291
+ };
3292
+ return (await User.findByIdAndUpdate(userId, updateOperation, {
3293
+ new: true,
3294
+ runValidators: true,
3295
+ }).lean());
3296
+ }
3297
+ /**
3298
+ * Search for users by pattern matching on name, email, or username (case-insensitive)
3299
+ * @param searchPattern - The pattern to search for
3300
+ * @param limit - Maximum number of results to return
3301
+ * @param fieldsToSelect - The fields to include or exclude in the returned documents
3302
+ * @returns Array of matching user documents
3303
+ */
3304
+ const searchUsers = async function ({ searchPattern, limit = 20, fieldsToSelect = null, }) {
3305
+ if (!searchPattern || searchPattern.trim().length === 0) {
3306
+ return [];
3307
+ }
3308
+ const regex = new RegExp(searchPattern.trim(), 'i');
3309
+ const User = mongoose.models.User;
3310
+ const query = User.find({
3311
+ $or: [{ email: regex }, { name: regex }, { username: regex }],
3312
+ }).limit(limit * 2); // Get more results to allow for relevance sorting
3313
+ if (fieldsToSelect) {
3314
+ query.select(fieldsToSelect);
3315
+ }
3316
+ const users = await query.lean();
3317
+ // Score results by relevance
3318
+ const exactRegex = new RegExp(`^${searchPattern.trim()}$`, 'i');
3319
+ const startsWithPattern = searchPattern.trim().toLowerCase();
3320
+ const scoredUsers = users.map((user) => {
3321
+ const searchableFields = [user.name, user.email, user.username].filter(Boolean);
3322
+ let maxScore = 0;
3323
+ for (const field of searchableFields) {
3324
+ const fieldLower = field.toLowerCase();
3325
+ let score = 0;
3326
+ // Exact match gets highest score
3327
+ if (exactRegex.test(field)) {
3328
+ score = 100;
3329
+ }
3330
+ // Starts with query gets high score
3331
+ else if (fieldLower.startsWith(startsWithPattern)) {
3332
+ score = 80;
3333
+ }
3334
+ // Contains query gets medium score
3335
+ else if (fieldLower.includes(startsWithPattern)) {
3336
+ score = 50;
3337
+ }
3338
+ // Default score for regex match
3339
+ else {
3340
+ score = 10;
3341
+ }
3342
+ maxScore = Math.max(maxScore, score);
3343
+ }
3344
+ return { ...user, _searchScore: maxScore };
3345
+ });
3346
+ /** Top results sorted by relevance */
3347
+ return scoredUsers
3348
+ .sort((a, b) => b._searchScore - a._searchScore)
3349
+ .slice(0, limit)
3350
+ .map((user) => {
3351
+ // Remove the search score from final results
3352
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
3353
+ const { _searchScore, ...userWithoutScore } = user;
3354
+ return userWithoutScore;
3355
+ });
3356
+ };
3357
+ return {
3358
+ findUser,
3359
+ countUsers,
3360
+ createUser,
3361
+ updateUser,
3362
+ searchUsers,
3363
+ getUserById,
3364
+ generateToken,
3365
+ deleteUserById,
3366
+ toggleUserMemories,
3367
+ };
3368
+ }
3369
+
2436
3370
  /**
2437
- * Truncates long string values in JSON log objects.
2438
- * Prevents outputting extremely long values (e.g., base64, blobs).
3371
+ * Formats a date in YYYY-MM-DD format
2439
3372
  */
2440
- const jsonTruncateFormat = winston__default.default.format((info) => {
2441
- const truncateLongStrings = (str, maxLength) => str.length > maxLength ? str.substring(0, maxLength) + '...' : str;
2442
- const seen = new WeakSet();
2443
- const truncateObject = (obj) => {
2444
- if (typeof obj !== 'object' || obj === null) {
2445
- return obj;
3373
+ const formatDate = (date) => {
3374
+ return date.toISOString().split('T')[0];
3375
+ };
3376
+ // Factory function that takes mongoose instance and returns the methods
3377
+ function createMemoryMethods(mongoose) {
3378
+ /**
3379
+ * Creates a new memory entry for a user
3380
+ * Throws an error if a memory with the same key already exists
3381
+ */
3382
+ async function createMemory({ userId, key, value, tokenCount = 0, }) {
3383
+ try {
3384
+ if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
3385
+ return { ok: false };
3386
+ }
3387
+ const MemoryEntry = mongoose.models.MemoryEntry;
3388
+ const existingMemory = await MemoryEntry.findOne({ userId, key });
3389
+ if (existingMemory) {
3390
+ throw new Error('Memory with this key already exists');
3391
+ }
3392
+ await MemoryEntry.create({
3393
+ userId,
3394
+ key,
3395
+ value,
3396
+ tokenCount,
3397
+ updated_at: new Date(),
3398
+ });
3399
+ return { ok: true };
3400
+ }
3401
+ catch (error) {
3402
+ throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
3403
+ }
3404
+ }
3405
+ /**
3406
+ * Sets or updates a memory entry for a user
3407
+ */
3408
+ async function setMemory({ userId, key, value, tokenCount = 0, }) {
3409
+ try {
3410
+ if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
3411
+ return { ok: false };
3412
+ }
3413
+ const MemoryEntry = mongoose.models.MemoryEntry;
3414
+ await MemoryEntry.findOneAndUpdate({ userId, key }, {
3415
+ value,
3416
+ tokenCount,
3417
+ updated_at: new Date(),
3418
+ }, {
3419
+ upsert: true,
3420
+ new: true,
3421
+ });
3422
+ return { ok: true };
3423
+ }
3424
+ catch (error) {
3425
+ throw new Error(`Failed to set memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
3426
+ }
3427
+ }
3428
+ /**
3429
+ * Deletes a specific memory entry for a user
3430
+ */
3431
+ async function deleteMemory({ userId, key }) {
3432
+ try {
3433
+ const MemoryEntry = mongoose.models.MemoryEntry;
3434
+ const result = await MemoryEntry.findOneAndDelete({ userId, key });
3435
+ return { ok: !!result };
3436
+ }
3437
+ catch (error) {
3438
+ throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
3439
+ }
3440
+ }
3441
+ /**
3442
+ * Gets all memory entries for a user
3443
+ */
3444
+ async function getAllUserMemories(userId) {
3445
+ try {
3446
+ const MemoryEntry = mongoose.models.MemoryEntry;
3447
+ return (await MemoryEntry.find({ userId }).lean());
3448
+ }
3449
+ catch (error) {
3450
+ throw new Error(`Failed to get all memories: ${error instanceof Error ? error.message : 'Unknown error'}`);
3451
+ }
3452
+ }
3453
+ /**
3454
+ * Gets and formats all memories for a user in two different formats
3455
+ */
3456
+ async function getFormattedMemories({ userId, }) {
3457
+ try {
3458
+ const memories = await getAllUserMemories(userId);
3459
+ if (!memories || memories.length === 0) {
3460
+ return { withKeys: '', withoutKeys: '', totalTokens: 0 };
3461
+ }
3462
+ const sortedMemories = memories.sort((a, b) => new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime());
3463
+ const totalTokens = sortedMemories.reduce((sum, memory) => {
3464
+ return sum + (memory.tokenCount || 0);
3465
+ }, 0);
3466
+ const withKeys = sortedMemories
3467
+ .map((memory, index) => {
3468
+ const date = formatDate(new Date(memory.updated_at));
3469
+ const tokenInfo = memory.tokenCount ? ` [${memory.tokenCount} tokens]` : '';
3470
+ return `${index + 1}. [${date}]. ["key": "${memory.key}"]${tokenInfo}. ["value": "${memory.value}"]`;
3471
+ })
3472
+ .join('\n\n');
3473
+ const withoutKeys = sortedMemories
3474
+ .map((memory, index) => {
3475
+ const date = formatDate(new Date(memory.updated_at));
3476
+ return `${index + 1}. [${date}]. ${memory.value}`;
3477
+ })
3478
+ .join('\n\n');
3479
+ return { withKeys, withoutKeys, totalTokens };
3480
+ }
3481
+ catch (error) {
3482
+ logger$1.error('Failed to get formatted memories:', error);
3483
+ return { withKeys: '', withoutKeys: '', totalTokens: 0 };
3484
+ }
3485
+ }
3486
+ return {
3487
+ setMemory,
3488
+ createMemory,
3489
+ deleteMemory,
3490
+ getAllUserMemories,
3491
+ getFormattedMemories,
3492
+ };
3493
+ }
3494
+
3495
+ function createAgentCategoryMethods(mongoose) {
3496
+ /**
3497
+ * Get all active categories sorted by order
3498
+ * @returns Array of active categories
3499
+ */
3500
+ async function getActiveCategories() {
3501
+ const AgentCategory = mongoose.models.AgentCategory;
3502
+ return await AgentCategory.find({ isActive: true }).sort({ order: 1, label: 1 }).lean();
3503
+ }
3504
+ /**
3505
+ * Get categories with agent counts
3506
+ * @returns Categories with agent counts
3507
+ */
3508
+ async function getCategoriesWithCounts() {
3509
+ const Agent = mongoose.models.Agent;
3510
+ const categoryCounts = await Agent.aggregate([
3511
+ { $match: { category: { $exists: true, $ne: null } } },
3512
+ { $group: { _id: '$category', count: { $sum: 1 } } },
3513
+ ]);
3514
+ const countMap = new Map(categoryCounts.map((c) => [c._id, c.count]));
3515
+ const categories = await getActiveCategories();
3516
+ return categories.map((category) => ({
3517
+ ...category,
3518
+ agentCount: countMap.get(category.value) || 0,
3519
+ }));
3520
+ }
3521
+ /**
3522
+ * Get valid category values for Agent model validation
3523
+ * @returns Array of valid category values
3524
+ */
3525
+ async function getValidCategoryValues() {
3526
+ const AgentCategory = mongoose.models.AgentCategory;
3527
+ return await AgentCategory.find({ isActive: true }).distinct('value').lean();
3528
+ }
3529
+ /**
3530
+ * Seed initial categories from existing constants
3531
+ * @param categories - Array of category data to seed
3532
+ * @returns Bulk write result
3533
+ */
3534
+ async function seedCategories(categories) {
3535
+ const AgentCategory = mongoose.models.AgentCategory;
3536
+ const operations = categories.map((category, index) => ({
3537
+ updateOne: {
3538
+ filter: { value: category.value },
3539
+ update: {
3540
+ $setOnInsert: {
3541
+ value: category.value,
3542
+ label: category.label || category.value,
3543
+ description: category.description || '',
3544
+ order: category.order || index,
3545
+ isActive: true,
3546
+ },
3547
+ },
3548
+ upsert: true,
3549
+ },
3550
+ }));
3551
+ return await AgentCategory.bulkWrite(operations);
3552
+ }
3553
+ /**
3554
+ * Find a category by value
3555
+ * @param value - The category value to search for
3556
+ * @returns The category document or null
3557
+ */
3558
+ async function findCategoryByValue(value) {
3559
+ const AgentCategory = mongoose.models.AgentCategory;
3560
+ return await AgentCategory.findOne({ value }).lean();
3561
+ }
3562
+ /**
3563
+ * Create a new category
3564
+ * @param categoryData - The category data to create
3565
+ * @returns The created category
3566
+ */
3567
+ async function createCategory(categoryData) {
3568
+ const AgentCategory = mongoose.models.AgentCategory;
3569
+ const category = await AgentCategory.create(categoryData);
3570
+ return category.toObject();
3571
+ }
3572
+ /**
3573
+ * Update a category by value
3574
+ * @param value - The category value to update
3575
+ * @param updateData - The data to update
3576
+ * @returns The updated category or null
3577
+ */
3578
+ async function updateCategory(value, updateData) {
3579
+ const AgentCategory = mongoose.models.AgentCategory;
3580
+ return await AgentCategory.findOneAndUpdate({ value }, { $set: updateData }, { new: true, runValidators: true }).lean();
3581
+ }
3582
+ /**
3583
+ * Delete a category by value
3584
+ * @param value - The category value to delete
3585
+ * @returns Whether the deletion was successful
3586
+ */
3587
+ async function deleteCategory(value) {
3588
+ const AgentCategory = mongoose.models.AgentCategory;
3589
+ const result = await AgentCategory.deleteOne({ value });
3590
+ return result.deletedCount > 0;
3591
+ }
3592
+ /**
3593
+ * Find a category by ID
3594
+ * @param id - The category ID to search for
3595
+ * @returns The category document or null
3596
+ */
3597
+ async function findCategoryById(id) {
3598
+ const AgentCategory = mongoose.models.AgentCategory;
3599
+ return await AgentCategory.findById(id).lean();
3600
+ }
3601
+ /**
3602
+ * Get all categories (active and inactive)
3603
+ * @returns Array of all categories
3604
+ */
3605
+ async function getAllCategories() {
3606
+ const AgentCategory = mongoose.models.AgentCategory;
3607
+ return await AgentCategory.find({}).sort({ order: 1, label: 1 }).lean();
3608
+ }
3609
+ /**
3610
+ * Ensure default categories exist, seed them if none are present
3611
+ * @returns Promise<boolean> - true if categories were seeded, false if they already existed
3612
+ */
3613
+ async function ensureDefaultCategories() {
3614
+ const existingCategories = await getAllCategories();
3615
+ if (existingCategories.length > 0) {
3616
+ return false; // Categories already exist
3617
+ }
3618
+ const defaultCategories = [
3619
+ {
3620
+ value: 'general',
3621
+ label: 'General',
3622
+ description: 'General purpose agents for common tasks and inquiries',
3623
+ order: 0,
3624
+ },
3625
+ {
3626
+ value: 'hr',
3627
+ label: 'Human Resources',
3628
+ description: 'Agents specialized in HR processes, policies, and employee support',
3629
+ order: 1,
3630
+ },
3631
+ {
3632
+ value: 'rd',
3633
+ label: 'Research & Development',
3634
+ description: 'Agents focused on R&D processes, innovation, and technical research',
3635
+ order: 2,
3636
+ },
3637
+ {
3638
+ value: 'finance',
3639
+ label: 'Finance',
3640
+ description: 'Agents specialized in financial analysis, budgeting, and accounting',
3641
+ order: 3,
3642
+ },
3643
+ {
3644
+ value: 'it',
3645
+ label: 'IT',
3646
+ description: 'Agents for IT support, technical troubleshooting, and system administration',
3647
+ order: 4,
3648
+ },
3649
+ {
3650
+ value: 'sales',
3651
+ label: 'Sales',
3652
+ description: 'Agents focused on sales processes, customer relations.',
3653
+ order: 5,
3654
+ },
3655
+ {
3656
+ value: 'aftersales',
3657
+ label: 'After Sales',
3658
+ description: 'Agents specialized in post-sale support, maintenance, and customer service',
3659
+ order: 6,
3660
+ },
3661
+ ];
3662
+ await seedCategories(defaultCategories);
3663
+ return true; // Categories were seeded
3664
+ }
3665
+ return {
3666
+ getActiveCategories,
3667
+ getCategoriesWithCounts,
3668
+ getValidCategoryValues,
3669
+ seedCategories,
3670
+ findCategoryByValue,
3671
+ createCategory,
3672
+ updateCategory,
3673
+ deleteCategory,
3674
+ findCategoryById,
3675
+ getAllCategories,
3676
+ ensureDefaultCategories,
3677
+ };
3678
+ }
3679
+
3680
+ // Factory function that takes mongoose instance and returns the methods
3681
+ function createPluginAuthMethods(mongoose) {
3682
+ /**
3683
+ * Finds a single plugin auth entry by userId and authField (and optionally pluginKey)
3684
+ */
3685
+ async function findOnePluginAuth({ userId, authField, pluginKey, }) {
3686
+ try {
3687
+ const PluginAuth = mongoose.models.PluginAuth;
3688
+ return await PluginAuth.findOne({
3689
+ userId,
3690
+ authField,
3691
+ ...(pluginKey && { pluginKey }),
3692
+ }).lean();
2446
3693
  }
2447
- // Handle circular references - now with proper object type
2448
- if (seen.has(obj)) {
2449
- return '[Circular]';
3694
+ catch (error) {
3695
+ throw new Error(`Failed to find plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
2450
3696
  }
2451
- seen.add(obj);
2452
- if (Array.isArray(obj)) {
2453
- return obj.map((item) => truncateObject(item));
3697
+ }
3698
+ /**
3699
+ * Finds multiple plugin auth entries by userId and pluginKeys
3700
+ */
3701
+ async function findPluginAuthsByKeys({ userId, pluginKeys, }) {
3702
+ try {
3703
+ if (!pluginKeys || pluginKeys.length === 0) {
3704
+ return [];
3705
+ }
3706
+ const PluginAuth = mongoose.models.PluginAuth;
3707
+ return await PluginAuth.find({
3708
+ userId,
3709
+ pluginKey: { $in: pluginKeys },
3710
+ }).lean();
2454
3711
  }
2455
- // We know this is an object at this point
2456
- const objectRecord = obj;
2457
- const newObj = {};
2458
- Object.entries(objectRecord).forEach(([key, value]) => {
2459
- if (typeof value === 'string') {
2460
- newObj[key] = truncateLongStrings(value, CONSOLE_JSON_STRING_LENGTH);
3712
+ catch (error) {
3713
+ throw new Error(`Failed to find plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3714
+ }
3715
+ }
3716
+ /**
3717
+ * Updates or creates a plugin auth entry
3718
+ */
3719
+ async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
3720
+ try {
3721
+ const PluginAuth = mongoose.models.PluginAuth;
3722
+ const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
3723
+ if (existingAuth) {
3724
+ return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
2461
3725
  }
2462
3726
  else {
2463
- newObj[key] = truncateObject(value);
3727
+ const newPluginAuth = await new PluginAuth({
3728
+ userId,
3729
+ authField,
3730
+ value,
3731
+ pluginKey,
3732
+ });
3733
+ await newPluginAuth.save();
3734
+ return newPluginAuth.toObject();
2464
3735
  }
2465
- });
2466
- return newObj;
3736
+ }
3737
+ catch (error) {
3738
+ throw new Error(`Failed to update plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3739
+ }
3740
+ }
3741
+ /**
3742
+ * Deletes plugin auth entries based on provided parameters
3743
+ */
3744
+ async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
3745
+ try {
3746
+ const PluginAuth = mongoose.models.PluginAuth;
3747
+ if (all) {
3748
+ const filter = { userId };
3749
+ if (pluginKey) {
3750
+ filter.pluginKey = pluginKey;
3751
+ }
3752
+ return await PluginAuth.deleteMany(filter);
3753
+ }
3754
+ if (!authField) {
3755
+ throw new Error('authField is required when all is false');
3756
+ }
3757
+ return await PluginAuth.deleteOne({ userId, authField });
3758
+ }
3759
+ catch (error) {
3760
+ throw new Error(`Failed to delete plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3761
+ }
3762
+ }
3763
+ /**
3764
+ * Deletes all plugin auth entries for a user
3765
+ */
3766
+ async function deleteAllUserPluginAuths(userId) {
3767
+ try {
3768
+ const PluginAuth = mongoose.models.PluginAuth;
3769
+ return await PluginAuth.deleteMany({ userId });
3770
+ }
3771
+ catch (error) {
3772
+ throw new Error(`Failed to delete all user plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3773
+ }
3774
+ }
3775
+ return {
3776
+ findOnePluginAuth,
3777
+ findPluginAuthsByKeys,
3778
+ updatePluginAuth,
3779
+ deletePluginAuth,
3780
+ deleteAllUserPluginAuths,
2467
3781
  };
2468
- return truncateObject(info);
2469
- });
2470
-
2471
- const logDir = getLogDirectory();
2472
- const { NODE_ENV, DEBUG_LOGGING, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
2473
- const useConsoleJson = typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true';
2474
- const useDebugConsole = typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true';
2475
- const useDebugLogging = typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true';
2476
- const levels = {
2477
- error: 0,
2478
- warn: 1,
2479
- info: 2,
2480
- http: 3,
2481
- verbose: 4,
2482
- debug: 5,
2483
- activity: 6,
2484
- silly: 7,
2485
- };
2486
- winston__default.default.addColors({
2487
- info: 'green',
2488
- warn: 'italic yellow',
2489
- error: 'red',
2490
- debug: 'blue',
2491
- });
2492
- const level = () => {
2493
- const env = NODE_ENV || 'development';
2494
- return env === 'development' ? 'debug' : 'warn';
2495
- };
2496
- const fileFormat = winston__default.default.format.combine(redactFormat(), winston__default.default.format.timestamp({ format: () => new Date().toISOString() }), winston__default.default.format.errors({ stack: true }), winston__default.default.format.splat());
2497
- const transports = [
2498
- new winston__default.default.transports.DailyRotateFile({
2499
- level: 'error',
2500
- filename: `${logDir}/error-%DATE%.log`,
2501
- datePattern: 'YYYY-MM-DD',
2502
- zippedArchive: true,
2503
- maxSize: '20m',
2504
- maxFiles: '14d',
2505
- format: fileFormat,
2506
- }),
2507
- ];
2508
- if (useDebugLogging) {
2509
- transports.push(new winston__default.default.transports.DailyRotateFile({
2510
- level: 'debug',
2511
- filename: `${logDir}/debug-%DATE%.log`,
2512
- datePattern: 'YYYY-MM-DD',
2513
- zippedArchive: true,
2514
- maxSize: '20m',
2515
- maxFiles: '14d',
2516
- format: winston__default.default.format.combine(fileFormat, debugTraverse),
2517
- }));
2518
- }
2519
- const consoleFormat = winston__default.default.format.combine(redactFormat(), winston__default.default.format.colorize({ all: true }), winston__default.default.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston__default.default.format.printf((info) => {
2520
- const message = `${info.timestamp} ${info.level}: ${info.message}`;
2521
- return info.level.includes('error') ? redactMessage(message) : message;
2522
- }));
2523
- let consoleLogLevel = 'info';
2524
- if (useDebugConsole) {
2525
- consoleLogLevel = 'debug';
2526
- }
2527
- // Add console transport
2528
- if (useDebugConsole) {
2529
- transports.push(new winston__default.default.transports.Console({
2530
- level: consoleLogLevel,
2531
- format: useConsoleJson
2532
- ? winston__default.default.format.combine(fileFormat, jsonTruncateFormat(), winston__default.default.format.json())
2533
- : winston__default.default.format.combine(fileFormat, debugTraverse),
2534
- }));
2535
- }
2536
- else if (useConsoleJson) {
2537
- transports.push(new winston__default.default.transports.Console({
2538
- level: consoleLogLevel,
2539
- format: winston__default.default.format.combine(fileFormat, jsonTruncateFormat(), winston__default.default.format.json()),
2540
- }));
2541
- }
2542
- else {
2543
- transports.push(new winston__default.default.transports.Console({
2544
- level: consoleLogLevel,
2545
- format: consoleFormat,
2546
- }));
2547
3782
  }
2548
- // Create logger
2549
- const logger = winston__default.default.createLogger({
2550
- level: level(),
2551
- levels,
2552
- transports,
2553
- });
2554
3783
 
2555
- var _a;
2556
- class SessionError extends Error {
2557
- constructor(message, code = 'SESSION_ERROR') {
2558
- super(message);
2559
- this.name = 'SessionError';
2560
- this.code = code;
3784
+ function createAccessRoleMethods(mongoose) {
3785
+ /**
3786
+ * Find an access role by its ID
3787
+ * @param roleId - The role ID
3788
+ * @returns The role document or null if not found
3789
+ */
3790
+ async function findRoleById(roleId) {
3791
+ const AccessRole = mongoose.models.AccessRole;
3792
+ return await AccessRole.findById(roleId).lean();
3793
+ }
3794
+ /**
3795
+ * Find an access role by its unique identifier
3796
+ * @param accessRoleId - The unique identifier (e.g., "agent_viewer")
3797
+ * @returns The role document or null if not found
3798
+ */
3799
+ async function findRoleByIdentifier(accessRoleId) {
3800
+ const AccessRole = mongoose.models.AccessRole;
3801
+ return await AccessRole.findOne({ accessRoleId }).lean();
3802
+ }
3803
+ /**
3804
+ * Find all access roles for a specific resource type
3805
+ * @param resourceType - The type of resource ('agent', 'project', 'file')
3806
+ * @returns Array of role documents
3807
+ */
3808
+ async function findRolesByResourceType(resourceType) {
3809
+ const AccessRole = mongoose.models.AccessRole;
3810
+ return await AccessRole.find({ resourceType }).lean();
3811
+ }
3812
+ /**
3813
+ * Find an access role by resource type and permission bits
3814
+ * @param resourceType - The type of resource
3815
+ * @param permBits - The permission bits (use PermissionBits or RoleBits enum)
3816
+ * @returns The role document or null if not found
3817
+ */
3818
+ async function findRoleByPermissions(resourceType, permBits) {
3819
+ const AccessRole = mongoose.models.AccessRole;
3820
+ return await AccessRole.findOne({ resourceType, permBits }).lean();
3821
+ }
3822
+ /**
3823
+ * Create a new access role
3824
+ * @param roleData - Role data (accessRoleId, name, description, resourceType, permBits)
3825
+ * @returns The created role document
3826
+ */
3827
+ async function createRole(roleData) {
3828
+ const AccessRole = mongoose.models.AccessRole;
3829
+ return await AccessRole.create(roleData);
3830
+ }
3831
+ /**
3832
+ * Update an existing access role
3833
+ * @param accessRoleId - The unique identifier of the role to update
3834
+ * @param updateData - Data to update
3835
+ * @returns The updated role document or null if not found
3836
+ */
3837
+ async function updateRole(accessRoleId, updateData) {
3838
+ const AccessRole = mongoose.models.AccessRole;
3839
+ return await AccessRole.findOneAndUpdate({ accessRoleId }, { $set: updateData }, { new: true }).lean();
3840
+ }
3841
+ /**
3842
+ * Delete an access role
3843
+ * @param accessRoleId - The unique identifier of the role to delete
3844
+ * @returns The result of the delete operation
3845
+ */
3846
+ async function deleteRole(accessRoleId) {
3847
+ const AccessRole = mongoose.models.AccessRole;
3848
+ return await AccessRole.deleteOne({ accessRoleId });
3849
+ }
3850
+ /**
3851
+ * Get all predefined roles
3852
+ * @returns Array of all role documents
3853
+ */
3854
+ async function getAllRoles() {
3855
+ const AccessRole = mongoose.models.AccessRole;
3856
+ return await AccessRole.find().lean();
3857
+ }
3858
+ /**
3859
+ * Seed default roles if they don't exist
3860
+ * @returns Object containing created roles
3861
+ */
3862
+ async function seedDefaultRoles() {
3863
+ const AccessRole = mongoose.models.AccessRole;
3864
+ const defaultRoles = [
3865
+ {
3866
+ accessRoleId: librechatDataProvider.AccessRoleIds.AGENT_VIEWER,
3867
+ name: 'com_ui_role_viewer',
3868
+ description: 'com_ui_role_viewer_desc',
3869
+ resourceType: librechatDataProvider.ResourceType.AGENT,
3870
+ permBits: exports.RoleBits.VIEWER,
3871
+ },
3872
+ {
3873
+ accessRoleId: librechatDataProvider.AccessRoleIds.AGENT_EDITOR,
3874
+ name: 'com_ui_role_editor',
3875
+ description: 'com_ui_role_editor_desc',
3876
+ resourceType: librechatDataProvider.ResourceType.AGENT,
3877
+ permBits: exports.RoleBits.EDITOR,
3878
+ },
3879
+ {
3880
+ accessRoleId: librechatDataProvider.AccessRoleIds.AGENT_OWNER,
3881
+ name: 'com_ui_role_owner',
3882
+ description: 'com_ui_role_owner_desc',
3883
+ resourceType: librechatDataProvider.ResourceType.AGENT,
3884
+ permBits: exports.RoleBits.OWNER,
3885
+ },
3886
+ {
3887
+ accessRoleId: librechatDataProvider.AccessRoleIds.PROMPTGROUP_VIEWER,
3888
+ name: 'com_ui_role_viewer',
3889
+ description: 'com_ui_role_viewer_desc',
3890
+ resourceType: librechatDataProvider.ResourceType.PROMPTGROUP,
3891
+ permBits: exports.RoleBits.VIEWER,
3892
+ },
3893
+ {
3894
+ accessRoleId: librechatDataProvider.AccessRoleIds.PROMPTGROUP_EDITOR,
3895
+ name: 'com_ui_role_editor',
3896
+ description: 'com_ui_role_editor_desc',
3897
+ resourceType: librechatDataProvider.ResourceType.PROMPTGROUP,
3898
+ permBits: exports.RoleBits.EDITOR,
3899
+ },
3900
+ {
3901
+ accessRoleId: librechatDataProvider.AccessRoleIds.PROMPTGROUP_OWNER,
3902
+ name: 'com_ui_role_owner',
3903
+ description: 'com_ui_role_owner_desc',
3904
+ resourceType: librechatDataProvider.ResourceType.PROMPTGROUP,
3905
+ permBits: exports.RoleBits.OWNER,
3906
+ },
3907
+ ];
3908
+ const result = {};
3909
+ for (const role of defaultRoles) {
3910
+ const upsertedRole = await AccessRole.findOneAndUpdate({ accessRoleId: role.accessRoleId }, { $setOnInsert: role }, { upsert: true, new: true }).lean();
3911
+ result[role.accessRoleId] = upsertedRole;
3912
+ }
3913
+ return result;
3914
+ }
3915
+ /**
3916
+ * Helper to get the appropriate role for a set of permissions
3917
+ * @param resourceType - The type of resource
3918
+ * @param permBits - The permission bits
3919
+ * @returns The matching role or null if none found
3920
+ */
3921
+ async function getRoleForPermissions(resourceType, permBits) {
3922
+ const AccessRole = mongoose.models.AccessRole;
3923
+ const exactMatch = await AccessRole.findOne({ resourceType, permBits }).lean();
3924
+ if (exactMatch) {
3925
+ return exactMatch;
3926
+ }
3927
+ /** If no exact match, the closest role without exceeding permissions */
3928
+ const roles = await AccessRole.find({ resourceType }).sort({ permBits: -1 }).lean();
3929
+ return roles.find((role) => (role.permBits & permBits) === role.permBits) || null;
2561
3930
  }
3931
+ return {
3932
+ createRole,
3933
+ updateRole,
3934
+ deleteRole,
3935
+ getAllRoles,
3936
+ findRoleById,
3937
+ seedDefaultRoles,
3938
+ findRoleByIdentifier,
3939
+ getRoleForPermissions,
3940
+ findRoleByPermissions,
3941
+ findRolesByResourceType,
3942
+ };
2562
3943
  }
2563
- const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
2564
- const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
2565
- // Factory function that takes mongoose instance and returns the methods
2566
- function createSessionMethods(mongoose) {
3944
+
3945
+ function createUserGroupMethods(mongoose$1) {
3946
+ /**
3947
+ * Find a group by its ID
3948
+ * @param groupId - The group ID
3949
+ * @param projection - Optional projection of fields to return
3950
+ * @param session - Optional MongoDB session for transactions
3951
+ * @returns The group document or null if not found
3952
+ */
3953
+ async function findGroupById(groupId, projection = {}, session) {
3954
+ const Group = mongoose$1.models.Group;
3955
+ const query = Group.findOne({ _id: groupId }, projection);
3956
+ if (session) {
3957
+ query.session(session);
3958
+ }
3959
+ return await query.lean();
3960
+ }
3961
+ /**
3962
+ * Find a group by its external ID (e.g., Entra ID)
3963
+ * @param idOnTheSource - The external ID
3964
+ * @param source - The source ('entra' or 'local')
3965
+ * @param projection - Optional projection of fields to return
3966
+ * @param session - Optional MongoDB session for transactions
3967
+ * @returns The group document or null if not found
3968
+ */
3969
+ async function findGroupByExternalId(idOnTheSource, source = 'entra', projection = {}, session) {
3970
+ const Group = mongoose$1.models.Group;
3971
+ const query = Group.findOne({ idOnTheSource, source }, projection);
3972
+ if (session) {
3973
+ query.session(session);
3974
+ }
3975
+ return await query.lean();
3976
+ }
3977
+ /**
3978
+ * Find groups by name pattern (case-insensitive partial match)
3979
+ * @param namePattern - The name pattern to search for
3980
+ * @param source - Optional source filter ('entra', 'local', or null for all)
3981
+ * @param limit - Maximum number of results to return
3982
+ * @param session - Optional MongoDB session for transactions
3983
+ * @returns Array of matching groups
3984
+ */
3985
+ async function findGroupsByNamePattern(namePattern, source = null, limit = 20, session) {
3986
+ const Group = mongoose$1.models.Group;
3987
+ const regex = new RegExp(namePattern, 'i');
3988
+ const query = {
3989
+ $or: [{ name: regex }, { email: regex }, { description: regex }],
3990
+ };
3991
+ if (source) {
3992
+ query.source = source;
3993
+ }
3994
+ const dbQuery = Group.find(query).limit(limit);
3995
+ if (session) {
3996
+ dbQuery.session(session);
3997
+ }
3998
+ return await dbQuery.lean();
3999
+ }
4000
+ /**
4001
+ * Find all groups a user is a member of by their ID or idOnTheSource
4002
+ * @param userId - The user ID
4003
+ * @param session - Optional MongoDB session for transactions
4004
+ * @returns Array of groups the user is a member of
4005
+ */
4006
+ async function findGroupsByMemberId(userId, session) {
4007
+ const User = mongoose$1.models.User;
4008
+ const Group = mongoose$1.models.Group;
4009
+ const userQuery = User.findById(userId, 'idOnTheSource');
4010
+ if (session) {
4011
+ userQuery.session(session);
4012
+ }
4013
+ const user = (await userQuery.lean());
4014
+ if (!user) {
4015
+ return [];
4016
+ }
4017
+ const userIdOnTheSource = user.idOnTheSource || userId.toString();
4018
+ const query = Group.find({ memberIds: userIdOnTheSource });
4019
+ if (session) {
4020
+ query.session(session);
4021
+ }
4022
+ return await query.lean();
4023
+ }
4024
+ /**
4025
+ * Create a new group
4026
+ * @param groupData - Group data including name, source, and optional idOnTheSource
4027
+ * @param session - Optional MongoDB session for transactions
4028
+ * @returns The created group
4029
+ */
4030
+ async function createGroup(groupData, session) {
4031
+ const Group = mongoose$1.models.Group;
4032
+ const options = session ? { session } : {};
4033
+ return await Group.create([groupData], options).then((groups) => groups[0]);
4034
+ }
4035
+ /**
4036
+ * Update or create a group by external ID
4037
+ * @param idOnTheSource - The external ID
4038
+ * @param source - The source ('entra' or 'local')
4039
+ * @param updateData - Data to update or set if creating
4040
+ * @param session - Optional MongoDB session for transactions
4041
+ * @returns The updated or created group
4042
+ */
4043
+ async function upsertGroupByExternalId(idOnTheSource, source, updateData, session) {
4044
+ const Group = mongoose$1.models.Group;
4045
+ const options = {
4046
+ new: true,
4047
+ upsert: true,
4048
+ ...(session ? { session } : {}),
4049
+ };
4050
+ return await Group.findOneAndUpdate({ idOnTheSource, source }, { $set: updateData }, options);
4051
+ }
4052
+ /**
4053
+ * Add a user to a group
4054
+ * Only updates Group.memberIds (one-way relationship)
4055
+ * Note: memberIds stores idOnTheSource values, not ObjectIds
4056
+ *
4057
+ * @param userId - The user ID
4058
+ * @param groupId - The group ID to add
4059
+ * @param session - Optional MongoDB session for transactions
4060
+ * @returns The user and updated group documents
4061
+ */
4062
+ async function addUserToGroup(userId, groupId, session) {
4063
+ const User = mongoose$1.models.User;
4064
+ const Group = mongoose$1.models.Group;
4065
+ const options = { new: true, ...(session ? { session } : {}) };
4066
+ const user = (await User.findById(userId, 'idOnTheSource', options).lean());
4067
+ if (!user) {
4068
+ throw new Error(`User not found: ${userId}`);
4069
+ }
4070
+ const userIdOnTheSource = user.idOnTheSource || userId.toString();
4071
+ const updatedGroup = await Group.findByIdAndUpdate(groupId, { $addToSet: { memberIds: userIdOnTheSource } }, options).lean();
4072
+ return { user: user, group: updatedGroup };
4073
+ }
4074
+ /**
4075
+ * Remove a user from a group
4076
+ * Only updates Group.memberIds (one-way relationship)
4077
+ * Note: memberIds stores idOnTheSource values, not ObjectIds
4078
+ *
4079
+ * @param userId - The user ID
4080
+ * @param groupId - The group ID to remove
4081
+ * @param session - Optional MongoDB session for transactions
4082
+ * @returns The user and updated group documents
4083
+ */
4084
+ async function removeUserFromGroup(userId, groupId, session) {
4085
+ const User = mongoose$1.models.User;
4086
+ const Group = mongoose$1.models.Group;
4087
+ const options = { new: true, ...(session ? { session } : {}) };
4088
+ const user = (await User.findById(userId, 'idOnTheSource', options).lean());
4089
+ if (!user) {
4090
+ throw new Error(`User not found: ${userId}`);
4091
+ }
4092
+ const userIdOnTheSource = user.idOnTheSource || userId.toString();
4093
+ const updatedGroup = await Group.findByIdAndUpdate(groupId, { $pull: { memberIds: userIdOnTheSource } }, options).lean();
4094
+ return { user: user, group: updatedGroup };
4095
+ }
2567
4096
  /**
2568
- * Creates a new session for a user
4097
+ * Get all groups a user is a member of
4098
+ * @param userId - The user ID
4099
+ * @param session - Optional MongoDB session for transactions
4100
+ * @returns Array of group documents
2569
4101
  */
2570
- async function createSession(userId, options = {}) {
2571
- if (!userId) {
2572
- throw new SessionError('User ID is required', 'INVALID_USER_ID');
4102
+ async function getUserGroups(userId, session) {
4103
+ return await findGroupsByMemberId(userId, session);
4104
+ }
4105
+ /**
4106
+ * Get a list of all principal identifiers for a user (user ID + group IDs + public)
4107
+ * For use in permission checks
4108
+ * @param params - Parameters object
4109
+ * @param params.userId - The user ID
4110
+ * @param params.role - Optional user role (if not provided, will query from DB)
4111
+ * @param session - Optional MongoDB session for transactions
4112
+ * @returns Array of principal objects with type and id
4113
+ */
4114
+ async function getUserPrincipals(params, session) {
4115
+ const { userId, role } = params;
4116
+ /** `userId` must be an `ObjectId` for USER principal since ACL entries store `ObjectId`s */
4117
+ const userObjectId = typeof userId === 'string' ? new mongoose.Types.ObjectId(userId) : userId;
4118
+ const principals = [
4119
+ { principalType: librechatDataProvider.PrincipalType.USER, principalId: userObjectId },
4120
+ ];
4121
+ // If role is not provided, query user to get it
4122
+ let userRole = role;
4123
+ if (userRole === undefined) {
4124
+ const User = mongoose$1.models.User;
4125
+ const query = User.findById(userId).select('role');
4126
+ if (session) {
4127
+ query.session(session);
4128
+ }
4129
+ const user = await query.lean();
4130
+ userRole = user === null || user === void 0 ? void 0 : user.role;
2573
4131
  }
2574
- try {
2575
- const Session = mongoose.models.Session;
2576
- const currentSession = new Session({
2577
- user: userId,
2578
- expiration: options.expiration || new Date(Date.now() + expires),
2579
- });
2580
- const refreshToken = await generateRefreshToken(currentSession);
2581
- return { session: currentSession, refreshToken };
4132
+ // Add role as a principal if user has one
4133
+ if (userRole && userRole.trim()) {
4134
+ principals.push({ principalType: librechatDataProvider.PrincipalType.ROLE, principalId: userRole });
2582
4135
  }
2583
- catch (error) {
2584
- logger.error('[createSession] Error creating session:', error);
2585
- throw new SessionError('Failed to create session', 'CREATE_SESSION_FAILED');
4136
+ const userGroups = await getUserGroups(userId, session);
4137
+ if (userGroups && userGroups.length > 0) {
4138
+ userGroups.forEach((group) => {
4139
+ principals.push({ principalType: librechatDataProvider.PrincipalType.GROUP, principalId: group._id });
4140
+ });
2586
4141
  }
4142
+ principals.push({ principalType: librechatDataProvider.PrincipalType.PUBLIC });
4143
+ return principals;
2587
4144
  }
2588
4145
  /**
2589
- * Finds a session by various parameters
4146
+ * Sync a user's Entra ID group memberships
4147
+ * @param userId - The user ID
4148
+ * @param entraGroups - Array of Entra groups with id and name
4149
+ * @param session - Optional MongoDB session for transactions
4150
+ * @returns The updated user with new group memberships
2590
4151
  */
2591
- async function findSession(params, options = { lean: true }) {
2592
- try {
2593
- const Session = mongoose.models.Session;
2594
- const query = {};
2595
- if (!params.refreshToken && !params.userId && !params.sessionId) {
2596
- throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
2597
- }
2598
- if (params.refreshToken) {
2599
- const tokenHash = await hashToken(params.refreshToken);
2600
- query.refreshTokenHash = tokenHash;
2601
- }
2602
- if (params.userId) {
2603
- query.user = params.userId;
4152
+ async function syncUserEntraGroups(userId, entraGroups, session) {
4153
+ var _a;
4154
+ const User = mongoose$1.models.User;
4155
+ const Group = mongoose$1.models.Group;
4156
+ const query = User.findById(userId, { idOnTheSource: 1 });
4157
+ if (session) {
4158
+ query.session(session);
4159
+ }
4160
+ const user = (await query.lean());
4161
+ if (!user) {
4162
+ throw new Error(`User not found: ${userId}`);
4163
+ }
4164
+ /** Get user's idOnTheSource for storing in group.memberIds */
4165
+ const userIdOnTheSource = user.idOnTheSource || userId.toString();
4166
+ const entraIdMap = new Map();
4167
+ const addedGroups = [];
4168
+ const removedGroups = [];
4169
+ for (const entraGroup of entraGroups) {
4170
+ entraIdMap.set(entraGroup.id, true);
4171
+ let group = await findGroupByExternalId(entraGroup.id, 'entra', {}, session);
4172
+ if (!group) {
4173
+ group = await createGroup({
4174
+ name: entraGroup.name,
4175
+ description: entraGroup.description,
4176
+ email: entraGroup.email,
4177
+ idOnTheSource: entraGroup.id,
4178
+ source: 'entra',
4179
+ memberIds: [userIdOnTheSource],
4180
+ }, session);
4181
+ addedGroups.push(group);
2604
4182
  }
2605
- if (params.sessionId) {
2606
- const sessionId = typeof params.sessionId === 'object' &&
2607
- params.sessionId !== null &&
2608
- 'sessionId' in params.sessionId
2609
- ? params.sessionId.sessionId
2610
- : params.sessionId;
2611
- if (!mongoose.Types.ObjectId.isValid(sessionId)) {
2612
- throw new SessionError('Invalid session ID format', 'INVALID_SESSION_ID');
4183
+ else if (!((_a = group.memberIds) === null || _a === void 0 ? void 0 : _a.includes(userIdOnTheSource))) {
4184
+ const { group: updatedGroup } = await addUserToGroup(userId, group._id, session);
4185
+ if (updatedGroup) {
4186
+ addedGroups.push(updatedGroup);
2613
4187
  }
2614
- query._id = sessionId;
2615
- }
2616
- // Add expiration check to only return valid sessions
2617
- query.expiration = { $gt: new Date() };
2618
- const sessionQuery = Session.findOne(query);
2619
- if (options.lean) {
2620
- return (await sessionQuery.lean());
2621
4188
  }
2622
- return await sessionQuery.exec();
2623
4189
  }
2624
- catch (error) {
2625
- logger.error('[findSession] Error finding session:', error);
2626
- throw new SessionError('Failed to find session', 'FIND_SESSION_FAILED');
4190
+ const groupsQuery = Group.find({ source: 'entra', memberIds: userIdOnTheSource }, { _id: 1, idOnTheSource: 1 });
4191
+ if (session) {
4192
+ groupsQuery.session(session);
2627
4193
  }
2628
- }
2629
- /**
2630
- * Updates session expiration
2631
- */
2632
- async function updateExpiration(session, newExpiration) {
2633
- try {
2634
- const Session = mongoose.models.Session;
2635
- const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
2636
- if (!sessionDoc) {
2637
- throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
4194
+ const existingGroups = (await groupsQuery.lean());
4195
+ for (const group of existingGroups) {
4196
+ if (group.idOnTheSource && !entraIdMap.has(group.idOnTheSource)) {
4197
+ const { group: removedGroup } = await removeUserFromGroup(userId, group._id, session);
4198
+ if (removedGroup) {
4199
+ removedGroups.push(removedGroup);
4200
+ }
2638
4201
  }
2639
- sessionDoc.expiration = newExpiration || new Date(Date.now() + expires);
2640
- return await sessionDoc.save();
2641
4202
  }
2642
- catch (error) {
2643
- logger.error('[updateExpiration] Error updating session:', error);
2644
- throw new SessionError('Failed to update session expiration', 'UPDATE_EXPIRATION_FAILED');
4203
+ const userQuery = User.findById(userId);
4204
+ if (session) {
4205
+ userQuery.session(session);
2645
4206
  }
4207
+ const updatedUser = await userQuery.lean();
4208
+ if (!updatedUser) {
4209
+ throw new Error(`User not found after update: ${userId}`);
4210
+ }
4211
+ return {
4212
+ user: updatedUser,
4213
+ addedGroups,
4214
+ removedGroups,
4215
+ };
2646
4216
  }
2647
4217
  /**
2648
- * Deletes a session by refresh token or session ID
4218
+ * Calculate relevance score for a search result
4219
+ * @param item - The search result item
4220
+ * @param searchPattern - The search pattern
4221
+ * @returns Relevance score (0-100)
2649
4222
  */
2650
- async function deleteSession(params) {
2651
- try {
2652
- const Session = mongoose.models.Session;
2653
- if (!params.refreshToken && !params.sessionId) {
2654
- throw new SessionError('Either refreshToken or sessionId is required', 'INVALID_DELETE_PARAMS');
4223
+ function calculateRelevanceScore(item, searchPattern) {
4224
+ const exactRegex = new RegExp(`^${searchPattern}$`, 'i');
4225
+ const startsWithPattern = searchPattern.toLowerCase();
4226
+ /** Get searchable text based on type */
4227
+ const searchableFields = item.type === librechatDataProvider.PrincipalType.USER
4228
+ ? [item.name, item.email, item.username].filter(Boolean)
4229
+ : [item.name, item.email, item.description].filter(Boolean);
4230
+ let maxScore = 0;
4231
+ for (const field of searchableFields) {
4232
+ if (!field)
4233
+ continue;
4234
+ const fieldLower = field.toLowerCase();
4235
+ let score = 0;
4236
+ /** Exact match gets highest score */
4237
+ if (exactRegex.test(field)) {
4238
+ score = 100;
2655
4239
  }
2656
- const query = {};
2657
- if (params.refreshToken) {
2658
- query.refreshTokenHash = await hashToken(params.refreshToken);
4240
+ else if (fieldLower.startsWith(startsWithPattern)) {
4241
+ /** Starts with query gets high score */
4242
+ score = 80;
2659
4243
  }
2660
- if (params.sessionId) {
2661
- query._id = params.sessionId;
4244
+ else if (fieldLower.includes(startsWithPattern)) {
4245
+ /** Contains query gets medium score */
4246
+ score = 50;
2662
4247
  }
2663
- const result = await Session.deleteOne(query);
2664
- if (result.deletedCount === 0) {
2665
- logger.warn('[deleteSession] No session found to delete');
4248
+ else {
4249
+ /** Default score for regex match */
4250
+ score = 10;
2666
4251
  }
2667
- return result;
2668
- }
2669
- catch (error) {
2670
- logger.error('[deleteSession] Error deleting session:', error);
2671
- throw new SessionError('Failed to delete session', 'DELETE_SESSION_FAILED');
4252
+ maxScore = Math.max(maxScore, score);
2672
4253
  }
4254
+ return maxScore;
2673
4255
  }
2674
4256
  /**
2675
- * Deletes all sessions for a user
4257
+ * Sort principals by relevance score and type priority
4258
+ * @param results - Array of results with _searchScore property
4259
+ * @returns Sorted array
2676
4260
  */
2677
- async function deleteAllUserSessions(userId, options = {}) {
2678
- try {
2679
- const Session = mongoose.models.Session;
2680
- if (!userId) {
2681
- throw new SessionError('User ID is required', 'INVALID_USER_ID');
2682
- }
2683
- const userIdString = typeof userId === 'object' && userId !== null ? userId.userId : userId;
2684
- if (!mongoose.Types.ObjectId.isValid(userIdString)) {
2685
- throw new SessionError('Invalid user ID format', 'INVALID_USER_ID_FORMAT');
2686
- }
2687
- const query = { user: userIdString };
2688
- if (options.excludeCurrentSession && options.currentSessionId) {
2689
- query._id = { $ne: options.currentSessionId };
4261
+ function sortPrincipalsByRelevance(results) {
4262
+ return results.sort((a, b) => {
4263
+ if (b._searchScore !== a._searchScore) {
4264
+ return (b._searchScore || 0) - (a._searchScore || 0);
2690
4265
  }
2691
- const result = await Session.deleteMany(query);
2692
- if (result.deletedCount && result.deletedCount > 0) {
2693
- logger.debug(`[deleteAllUserSessions] Deleted ${result.deletedCount} sessions for user ${userIdString}.`);
4266
+ if (a.type !== b.type) {
4267
+ return a.type === librechatDataProvider.PrincipalType.USER ? -1 : 1;
2694
4268
  }
2695
- return result;
2696
- }
2697
- catch (error) {
2698
- logger.error('[deleteAllUserSessions] Error deleting user sessions:', error);
2699
- throw new SessionError('Failed to delete user sessions', 'DELETE_ALL_SESSIONS_FAILED');
2700
- }
4269
+ const aName = a.name || a.email || '';
4270
+ const bName = b.name || b.email || '';
4271
+ return aName.localeCompare(bName);
4272
+ });
2701
4273
  }
2702
4274
  /**
2703
- * Generates a refresh token for a session
4275
+ * Transform user object to TPrincipalSearchResult format
4276
+ * @param user - User object from database
4277
+ * @returns Transformed user result
2704
4278
  */
2705
- async function generateRefreshToken(session) {
2706
- if (!session || !session.user) {
2707
- throw new SessionError('Invalid session object', 'INVALID_SESSION');
4279
+ function transformUserToTPrincipalSearchResult(user) {
4280
+ return {
4281
+ id: user.id,
4282
+ type: librechatDataProvider.PrincipalType.USER,
4283
+ name: user.name || user.email,
4284
+ email: user.email,
4285
+ username: user.username,
4286
+ avatar: user.avatar,
4287
+ provider: user.provider,
4288
+ source: 'local',
4289
+ idOnTheSource: user.idOnTheSource || user.id,
4290
+ };
4291
+ }
4292
+ /**
4293
+ * Transform group object to TPrincipalSearchResult format
4294
+ * @param group - Group object from database
4295
+ * @returns Transformed group result
4296
+ */
4297
+ function transformGroupToTPrincipalSearchResult(group) {
4298
+ var _a, _b;
4299
+ return {
4300
+ id: (_a = group._id) === null || _a === void 0 ? void 0 : _a.toString(),
4301
+ type: librechatDataProvider.PrincipalType.GROUP,
4302
+ name: group.name,
4303
+ email: group.email,
4304
+ avatar: group.avatar,
4305
+ description: group.description,
4306
+ source: group.source || 'local',
4307
+ memberCount: group.memberIds ? group.memberIds.length : 0,
4308
+ idOnTheSource: group.idOnTheSource || ((_b = group._id) === null || _b === void 0 ? void 0 : _b.toString()),
4309
+ };
4310
+ }
4311
+ /**
4312
+ * Search for principals (users and groups) by pattern matching on name/email
4313
+ * Returns combined results in TPrincipalSearchResult format without sorting
4314
+ * @param searchPattern - The pattern to search for
4315
+ * @param limitPerType - Maximum number of results to return
4316
+ * @param typeFilter - Optional array of types to filter by, or null for all types
4317
+ * @param session - Optional MongoDB session for transactions
4318
+ * @returns Array of principals in TPrincipalSearchResult format
4319
+ */
4320
+ async function searchPrincipals(searchPattern, limitPerType = 10, typeFilter = null, session) {
4321
+ if (!searchPattern || searchPattern.trim().length === 0) {
4322
+ return [];
2708
4323
  }
2709
- try {
2710
- const expiresIn = session.expiration ? session.expiration.getTime() : Date.now() + expires;
2711
- if (!session.expiration) {
2712
- session.expiration = new Date(expiresIn);
4324
+ const trimmedPattern = searchPattern.trim();
4325
+ const promises = [];
4326
+ if (!typeFilter || typeFilter.includes(librechatDataProvider.PrincipalType.USER)) {
4327
+ /** Note: searchUsers is imported from ~/models and needs to be passed in or implemented */
4328
+ const userFields = 'name email username avatar provider idOnTheSource';
4329
+ /** For now, we'll use a direct query instead of searchUsers */
4330
+ const User = mongoose$1.models.User;
4331
+ const regex = new RegExp(trimmedPattern, 'i');
4332
+ const userQuery = User.find({
4333
+ $or: [{ name: regex }, { email: regex }, { username: regex }],
4334
+ })
4335
+ .select(userFields)
4336
+ .limit(limitPerType);
4337
+ if (session) {
4338
+ userQuery.session(session);
2713
4339
  }
2714
- const refreshToken = await signPayload({
2715
- payload: {
2716
- id: session.user,
2717
- sessionId: session._id,
2718
- },
2719
- secret: process.env.JWT_REFRESH_SECRET,
2720
- expirationTime: Math.floor((expiresIn - Date.now()) / 1000),
2721
- });
2722
- session.refreshTokenHash = await hashToken(refreshToken);
2723
- await session.save();
2724
- return refreshToken;
4340
+ promises.push(userQuery.lean().then((users) => users.map((user) => {
4341
+ var _a;
4342
+ const userWithId = user;
4343
+ return transformUserToTPrincipalSearchResult({
4344
+ id: ((_a = userWithId._id) === null || _a === void 0 ? void 0 : _a.toString()) || '',
4345
+ name: userWithId.name,
4346
+ email: userWithId.email,
4347
+ username: userWithId.username,
4348
+ avatar: userWithId.avatar,
4349
+ provider: userWithId.provider,
4350
+ });
4351
+ })));
2725
4352
  }
2726
- catch (error) {
2727
- logger.error('[generateRefreshToken] Error generating refresh token:', error);
2728
- throw new SessionError('Failed to generate refresh token', 'GENERATE_TOKEN_FAILED');
4353
+ else {
4354
+ promises.push(Promise.resolve([]));
2729
4355
  }
2730
- }
2731
- /**
2732
- * Counts active sessions for a user
2733
- */
2734
- async function countActiveSessions(userId) {
2735
- try {
2736
- const Session = mongoose.models.Session;
2737
- if (!userId) {
2738
- throw new SessionError('User ID is required', 'INVALID_USER_ID');
4356
+ if (!typeFilter || typeFilter.includes(librechatDataProvider.PrincipalType.GROUP)) {
4357
+ promises.push(findGroupsByNamePattern(trimmedPattern, null, limitPerType, session).then((groups) => groups.map(transformGroupToTPrincipalSearchResult)));
4358
+ }
4359
+ else {
4360
+ promises.push(Promise.resolve([]));
4361
+ }
4362
+ if (!typeFilter || typeFilter.includes(librechatDataProvider.PrincipalType.ROLE)) {
4363
+ const Role = mongoose$1.models.Role;
4364
+ if (Role) {
4365
+ const regex = new RegExp(trimmedPattern, 'i');
4366
+ const roleQuery = Role.find({ name: regex }).select('name').limit(limitPerType);
4367
+ if (session) {
4368
+ roleQuery.session(session);
4369
+ }
4370
+ promises.push(roleQuery.lean().then((roles) => roles.map((role) => ({
4371
+ /** Role name as ID */
4372
+ id: role.name,
4373
+ type: librechatDataProvider.PrincipalType.ROLE,
4374
+ name: role.name,
4375
+ source: 'local',
4376
+ idOnTheSource: role.name,
4377
+ }))));
2739
4378
  }
2740
- return await Session.countDocuments({
2741
- user: userId,
2742
- expiration: { $gt: new Date() },
2743
- });
2744
4379
  }
2745
- catch (error) {
2746
- logger.error('[countActiveSessions] Error counting active sessions:', error);
2747
- throw new SessionError('Failed to count active sessions', 'COUNT_SESSIONS_FAILED');
4380
+ else {
4381
+ promises.push(Promise.resolve([]));
2748
4382
  }
4383
+ const results = await Promise.all(promises);
4384
+ const combined = results.flat();
4385
+ return combined;
2749
4386
  }
2750
4387
  return {
2751
- findSession,
2752
- SessionError,
2753
- deleteSession,
2754
- createSession,
2755
- updateExpiration,
2756
- countActiveSessions,
2757
- generateRefreshToken,
2758
- deleteAllUserSessions,
4388
+ findGroupById,
4389
+ findGroupByExternalId,
4390
+ findGroupsByNamePattern,
4391
+ findGroupsByMemberId,
4392
+ createGroup,
4393
+ upsertGroupByExternalId,
4394
+ addUserToGroup,
4395
+ removeUserFromGroup,
4396
+ getUserGroups,
4397
+ getUserPrincipals,
4398
+ syncUserEntraGroups,
4399
+ searchPrincipals,
4400
+ calculateRelevanceScore,
4401
+ sortPrincipalsByRelevance,
2759
4402
  };
2760
4403
  }
2761
4404
 
2762
- // Factory function that takes mongoose instance and returns the methods
2763
- function createTokenMethods(mongoose) {
4405
+ function createAclEntryMethods(mongoose$1) {
2764
4406
  /**
2765
- * Creates a new Token instance.
4407
+ * Find ACL entries for a specific principal (user or group)
4408
+ * @param principalType - The type of principal ('user', 'group')
4409
+ * @param principalId - The ID of the principal
4410
+ * @param resourceType - Optional filter by resource type
4411
+ * @returns Array of ACL entries
2766
4412
  */
2767
- async function createToken(tokenData) {
2768
- try {
2769
- const Token = mongoose.models.Token;
2770
- const currentTime = new Date();
2771
- const expiresAt = new Date(currentTime.getTime() + tokenData.expiresIn * 1000);
2772
- const newTokenData = {
2773
- ...tokenData,
2774
- createdAt: currentTime,
2775
- expiresAt,
2776
- };
2777
- return await Token.create(newTokenData);
2778
- }
2779
- catch (error) {
2780
- logger.debug('An error occurred while creating token:', error);
2781
- throw error;
4413
+ async function findEntriesByPrincipal(principalType, principalId, resourceType) {
4414
+ const AclEntry = mongoose$1.models.AclEntry;
4415
+ const query = { principalType, principalId };
4416
+ if (resourceType) {
4417
+ query.resourceType = resourceType;
2782
4418
  }
4419
+ return await AclEntry.find(query).lean();
2783
4420
  }
2784
4421
  /**
2785
- * Updates a Token document that matches the provided query.
4422
+ * Find ACL entries for a specific resource
4423
+ * @param resourceType - The type of resource ('agent', 'project', 'file')
4424
+ * @param resourceId - The ID of the resource
4425
+ * @returns Array of ACL entries
2786
4426
  */
2787
- async function updateToken(query, updateData) {
2788
- try {
2789
- const Token = mongoose.models.Token;
2790
- return await Token.findOneAndUpdate(query, updateData, { new: true });
2791
- }
2792
- catch (error) {
2793
- logger.debug('An error occurred while updating token:', error);
2794
- throw error;
2795
- }
4427
+ async function findEntriesByResource(resourceType, resourceId) {
4428
+ const AclEntry = mongoose$1.models.AclEntry;
4429
+ return await AclEntry.find({ resourceType, resourceId }).lean();
2796
4430
  }
2797
4431
  /**
2798
- * Deletes all Token documents that match the provided token, user ID, or email.
4432
+ * Find all ACL entries for a set of principals (including public)
4433
+ * @param principalsList - List of principals, each containing { principalType, principalId }
4434
+ * @param resourceType - The type of resource
4435
+ * @param resourceId - The ID of the resource
4436
+ * @returns Array of matching ACL entries
2799
4437
  */
2800
- async function deleteTokens(query) {
2801
- try {
2802
- const Token = mongoose.models.Token;
2803
- return await Token.deleteMany({
2804
- $or: [
2805
- { userId: query.userId },
2806
- { token: query.token },
2807
- { email: query.email },
2808
- { identifier: query.identifier },
2809
- ],
2810
- });
2811
- }
2812
- catch (error) {
2813
- logger.debug('An error occurred while deleting tokens:', error);
2814
- throw error;
4438
+ async function findEntriesByPrincipalsAndResource(principalsList, resourceType, resourceId) {
4439
+ const AclEntry = mongoose$1.models.AclEntry;
4440
+ const principalsQuery = principalsList.map((p) => ({
4441
+ principalType: p.principalType,
4442
+ ...(p.principalType !== librechatDataProvider.PrincipalType.PUBLIC && { principalId: p.principalId }),
4443
+ }));
4444
+ return await AclEntry.find({
4445
+ $or: principalsQuery,
4446
+ resourceType,
4447
+ resourceId,
4448
+ }).lean();
4449
+ }
4450
+ /**
4451
+ * Check if a set of principals has a specific permission on a resource
4452
+ * @param principalsList - List of principals, each containing { principalType, principalId }
4453
+ * @param resourceType - The type of resource
4454
+ * @param resourceId - The ID of the resource
4455
+ * @param permissionBit - The permission bit to check (use PermissionBits enum)
4456
+ * @returns Whether any of the principals has the permission
4457
+ */
4458
+ async function hasPermission(principalsList, resourceType, resourceId, permissionBit) {
4459
+ const AclEntry = mongoose$1.models.AclEntry;
4460
+ const principalsQuery = principalsList.map((p) => ({
4461
+ principalType: p.principalType,
4462
+ ...(p.principalType !== librechatDataProvider.PrincipalType.PUBLIC && { principalId: p.principalId }),
4463
+ }));
4464
+ const entry = await AclEntry.findOne({
4465
+ $or: principalsQuery,
4466
+ resourceType,
4467
+ resourceId,
4468
+ permBits: { $bitsAllSet: permissionBit },
4469
+ }).lean();
4470
+ return !!entry;
4471
+ }
4472
+ /**
4473
+ * Get the combined effective permissions for a set of principals on a resource
4474
+ * @param principalsList - List of principals, each containing { principalType, principalId }
4475
+ * @param resourceType - The type of resource
4476
+ * @param resourceId - The ID of the resource
4477
+ * @returns {Promise<number>} Effective permission bitmask
4478
+ */
4479
+ async function getEffectivePermissions(principalsList, resourceType, resourceId) {
4480
+ const aclEntries = await findEntriesByPrincipalsAndResource(principalsList, resourceType, resourceId);
4481
+ let effectiveBits = 0;
4482
+ for (const entry of aclEntries) {
4483
+ effectiveBits |= entry.permBits;
2815
4484
  }
4485
+ return effectiveBits;
2816
4486
  }
2817
4487
  /**
2818
- * Finds a Token document that matches the provided query.
4488
+ * Grant permission to a principal for a resource
4489
+ * @param principalType - The type of principal ('user', 'group', 'public')
4490
+ * @param principalId - The ID of the principal (null for 'public')
4491
+ * @param resourceType - The type of resource
4492
+ * @param resourceId - The ID of the resource
4493
+ * @param permBits - The permission bits to grant
4494
+ * @param grantedBy - The ID of the user granting the permission
4495
+ * @param session - Optional MongoDB session for transactions
4496
+ * @param roleId - Optional role ID to associate with this permission
4497
+ * @returns The created or updated ACL entry
2819
4498
  */
2820
- async function findToken(query) {
2821
- try {
2822
- const Token = mongoose.models.Token;
2823
- const conditions = [];
2824
- if (query.userId) {
2825
- conditions.push({ userId: query.userId });
2826
- }
2827
- if (query.token) {
2828
- conditions.push({ token: query.token });
4499
+ async function grantPermission(principalType, principalId, resourceType, resourceId, permBits, grantedBy, session, roleId) {
4500
+ const AclEntry = mongoose$1.models.AclEntry;
4501
+ const query = {
4502
+ principalType,
4503
+ resourceType,
4504
+ resourceId,
4505
+ };
4506
+ if (principalType !== librechatDataProvider.PrincipalType.PUBLIC) {
4507
+ query.principalId =
4508
+ typeof principalId === 'string' && principalType !== librechatDataProvider.PrincipalType.ROLE
4509
+ ? new mongoose.Types.ObjectId(principalId)
4510
+ : principalId;
4511
+ if (principalType === librechatDataProvider.PrincipalType.USER) {
4512
+ query.principalModel = librechatDataProvider.PrincipalModel.USER;
2829
4513
  }
2830
- if (query.email) {
2831
- conditions.push({ email: query.email });
4514
+ else if (principalType === librechatDataProvider.PrincipalType.GROUP) {
4515
+ query.principalModel = librechatDataProvider.PrincipalModel.GROUP;
2832
4516
  }
2833
- if (query.identifier) {
2834
- conditions.push({ identifier: query.identifier });
4517
+ else if (principalType === librechatDataProvider.PrincipalType.ROLE) {
4518
+ query.principalModel = librechatDataProvider.PrincipalModel.ROLE;
2835
4519
  }
2836
- const token = await Token.findOne({
2837
- $and: conditions,
2838
- }).lean();
2839
- return token;
2840
4520
  }
2841
- catch (error) {
2842
- logger.debug('An error occurred while finding token:', error);
2843
- throw error;
4521
+ const update = {
4522
+ $set: {
4523
+ permBits,
4524
+ grantedBy,
4525
+ grantedAt: new Date(),
4526
+ ...(roleId && { roleId }),
4527
+ },
4528
+ };
4529
+ const options = {
4530
+ upsert: true,
4531
+ new: true,
4532
+ ...(session ? { session } : {}),
4533
+ };
4534
+ return await AclEntry.findOneAndUpdate(query, update, options);
4535
+ }
4536
+ /**
4537
+ * Revoke permissions from a principal for a resource
4538
+ * @param principalType - The type of principal ('user', 'group', 'public')
4539
+ * @param principalId - The ID of the principal (null for 'public')
4540
+ * @param resourceType - The type of resource
4541
+ * @param resourceId - The ID of the resource
4542
+ * @param session - Optional MongoDB session for transactions
4543
+ * @returns The result of the delete operation
4544
+ */
4545
+ async function revokePermission(principalType, principalId, resourceType, resourceId, session) {
4546
+ const AclEntry = mongoose$1.models.AclEntry;
4547
+ const query = {
4548
+ principalType,
4549
+ resourceType,
4550
+ resourceId,
4551
+ };
4552
+ if (principalType !== librechatDataProvider.PrincipalType.PUBLIC) {
4553
+ query.principalId =
4554
+ typeof principalId === 'string' && principalType !== librechatDataProvider.PrincipalType.ROLE
4555
+ ? new mongoose.Types.ObjectId(principalId)
4556
+ : principalId;
4557
+ }
4558
+ const options = session ? { session } : {};
4559
+ return await AclEntry.deleteOne(query, options);
4560
+ }
4561
+ /**
4562
+ * Modify existing permission bits for a principal on a resource
4563
+ * @param principalType - The type of principal ('user', 'group', 'public')
4564
+ * @param principalId - The ID of the principal (null for 'public')
4565
+ * @param resourceType - The type of resource
4566
+ * @param resourceId - The ID of the resource
4567
+ * @param addBits - Permission bits to add
4568
+ * @param removeBits - Permission bits to remove
4569
+ * @param session - Optional MongoDB session for transactions
4570
+ * @returns The updated ACL entry
4571
+ */
4572
+ async function modifyPermissionBits(principalType, principalId, resourceType, resourceId, addBits, removeBits, session) {
4573
+ const AclEntry = mongoose$1.models.AclEntry;
4574
+ const query = {
4575
+ principalType,
4576
+ resourceType,
4577
+ resourceId,
4578
+ };
4579
+ if (principalType !== librechatDataProvider.PrincipalType.PUBLIC) {
4580
+ query.principalId =
4581
+ typeof principalId === 'string' && principalType !== librechatDataProvider.PrincipalType.ROLE
4582
+ ? new mongoose.Types.ObjectId(principalId)
4583
+ : principalId;
4584
+ }
4585
+ const update = {};
4586
+ if (addBits) {
4587
+ update.$bit = { permBits: { or: addBits } };
2844
4588
  }
4589
+ if (removeBits) {
4590
+ if (!update.$bit)
4591
+ update.$bit = {};
4592
+ const bitUpdate = update.$bit;
4593
+ bitUpdate.permBits = { ...bitUpdate.permBits, and: ~removeBits };
4594
+ }
4595
+ const options = {
4596
+ new: true,
4597
+ ...(session ? { session } : {}),
4598
+ };
4599
+ return await AclEntry.findOneAndUpdate(query, update, options);
4600
+ }
4601
+ /**
4602
+ * Find all resources of a specific type that a set of principals has access to
4603
+ * @param principalsList - List of principals, each containing { principalType, principalId }
4604
+ * @param resourceType - The type of resource
4605
+ * @param requiredPermBit - Required permission bit (use PermissionBits enum)
4606
+ * @returns Array of resource IDs
4607
+ */
4608
+ async function findAccessibleResources(principalsList, resourceType, requiredPermBit) {
4609
+ const AclEntry = mongoose$1.models.AclEntry;
4610
+ const principalsQuery = principalsList.map((p) => ({
4611
+ principalType: p.principalType,
4612
+ ...(p.principalType !== librechatDataProvider.PrincipalType.PUBLIC && { principalId: p.principalId }),
4613
+ }));
4614
+ const entries = await AclEntry.find({
4615
+ $or: principalsQuery,
4616
+ resourceType,
4617
+ permBits: { $bitsAllSet: requiredPermBit },
4618
+ }).distinct('resourceId');
4619
+ return entries;
2845
4620
  }
2846
- // Return all methods
2847
4621
  return {
2848
- findToken,
2849
- createToken,
2850
- updateToken,
2851
- deleteTokens,
4622
+ findEntriesByPrincipal,
4623
+ findEntriesByResource,
4624
+ findEntriesByPrincipalsAndResource,
4625
+ hasPermission,
4626
+ getEffectivePermissions,
4627
+ grantPermission,
4628
+ revokePermission,
4629
+ modifyPermissionBits,
4630
+ findAccessibleResources,
2852
4631
  };
2853
4632
  }
2854
4633
 
2855
- // Factory function that takes mongoose instance and returns the methods
2856
- function createRoleMethods(mongoose) {
4634
+ function createGroupMethods(mongoose) {
2857
4635
  /**
2858
- * Initialize default roles in the system.
2859
- * Creates the default roles (ADMIN, USER) if they don't exist in the database.
2860
- * Updates existing roles with new permission types if they're missing.
4636
+ * Find a group by its ID
4637
+ * @param groupId - The group ID
4638
+ * @returns The group document or null if not found
2861
4639
  */
2862
- async function initializeRoles() {
2863
- const Role = mongoose.models.Role;
2864
- for (const roleName of [librechatDataProvider.SystemRoles.ADMIN, librechatDataProvider.SystemRoles.USER]) {
2865
- let role = await Role.findOne({ name: roleName });
2866
- const defaultPerms = librechatDataProvider.roleDefaults[roleName].permissions;
2867
- if (!role) {
2868
- // Create new role if it doesn't exist.
2869
- role = new Role(librechatDataProvider.roleDefaults[roleName]);
2870
- }
2871
- else {
2872
- // Ensure role.permissions is defined.
2873
- role.permissions = role.permissions || {};
2874
- // For each permission type in defaults, add it if missing.
2875
- for (const permType of Object.keys(defaultPerms)) {
2876
- if (role.permissions[permType] == null) {
2877
- role.permissions[permType] = defaultPerms[permType];
2878
- }
2879
- }
2880
- }
2881
- await role.save();
2882
- }
4640
+ async function findGroupById(groupId) {
4641
+ const Group = mongoose.models.Group;
4642
+ return await Group.findById(groupId).lean();
2883
4643
  }
2884
4644
  /**
2885
- * List all roles in the system (for testing purposes)
2886
- * Returns an array of all roles with their names and permissions
4645
+ * Create a new group
4646
+ * @param groupData - Group data including name, source, and optional fields
4647
+ * @returns The created group
2887
4648
  */
2888
- async function listRoles() {
2889
- const Role = mongoose.models.Role;
2890
- return await Role.find({}).select('name permissions').lean();
4649
+ async function createGroup(groupData) {
4650
+ const Group = mongoose.models.Group;
4651
+ return await Group.create(groupData);
2891
4652
  }
2892
- // Return all methods you want to expose
2893
- return {
2894
- listRoles,
2895
- initializeRoles,
2896
- };
2897
- }
2898
-
2899
- /**
2900
- * Formats a date in YYYY-MM-DD format
2901
- */
2902
- const formatDate = (date) => {
2903
- return date.toISOString().split('T')[0];
2904
- };
2905
- // Factory function that takes mongoose instance and returns the methods
2906
- function createMemoryMethods(mongoose) {
2907
4653
  /**
2908
- * Creates a new memory entry for a user
2909
- * Throws an error if a memory with the same key already exists
4654
+ * Update an existing group
4655
+ * @param groupId - The ID of the group to update
4656
+ * @param updateData - Data to update
4657
+ * @returns The updated group document or null if not found
2910
4658
  */
2911
- async function createMemory({ userId, key, value, tokenCount = 0, }) {
2912
- try {
2913
- if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
2914
- return { ok: false };
2915
- }
2916
- const MemoryEntry = mongoose.models.MemoryEntry;
2917
- const existingMemory = await MemoryEntry.findOne({ userId, key });
2918
- if (existingMemory) {
2919
- throw new Error('Memory with this key already exists');
2920
- }
2921
- await MemoryEntry.create({
2922
- userId,
2923
- key,
2924
- value,
2925
- tokenCount,
2926
- updated_at: new Date(),
2927
- });
2928
- return { ok: true };
2929
- }
2930
- catch (error) {
2931
- throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2932
- }
4659
+ async function updateGroup(groupId, updateData) {
4660
+ const Group = mongoose.models.Group;
4661
+ return await Group.findByIdAndUpdate(groupId, { $set: updateData }, { new: true }).lean();
2933
4662
  }
2934
4663
  /**
2935
- * Sets or updates a memory entry for a user
4664
+ * Delete a group
4665
+ * @param groupId - The ID of the group to delete
4666
+ * @returns The result of the delete operation
2936
4667
  */
2937
- async function setMemory({ userId, key, value, tokenCount = 0, }) {
2938
- try {
2939
- if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
2940
- return { ok: false };
2941
- }
2942
- const MemoryEntry = mongoose.models.MemoryEntry;
2943
- await MemoryEntry.findOneAndUpdate({ userId, key }, {
2944
- value,
2945
- tokenCount,
2946
- updated_at: new Date(),
2947
- }, {
2948
- upsert: true,
2949
- new: true,
2950
- });
2951
- return { ok: true };
2952
- }
2953
- catch (error) {
2954
- throw new Error(`Failed to set memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2955
- }
4668
+ async function deleteGroup(groupId) {
4669
+ const Group = mongoose.models.Group;
4670
+ return await Group.deleteOne({ _id: groupId });
2956
4671
  }
2957
4672
  /**
2958
- * Deletes a specific memory entry for a user
4673
+ * Find all groups
4674
+ * @returns Array of all group documents
2959
4675
  */
2960
- async function deleteMemory({ userId, key }) {
2961
- try {
2962
- const MemoryEntry = mongoose.models.MemoryEntry;
2963
- const result = await MemoryEntry.findOneAndDelete({ userId, key });
2964
- return { ok: !!result };
2965
- }
2966
- catch (error) {
2967
- throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2968
- }
4676
+ async function getAllGroups() {
4677
+ const Group = mongoose.models.Group;
4678
+ return await Group.find().lean();
2969
4679
  }
2970
4680
  /**
2971
- * Gets all memory entries for a user
4681
+ * Find groups by source
4682
+ * @param source - The source ('local' or 'entra')
4683
+ * @returns Array of group documents
2972
4684
  */
2973
- async function getAllUserMemories(userId) {
2974
- try {
2975
- const MemoryEntry = mongoose.models.MemoryEntry;
2976
- return (await MemoryEntry.find({ userId }).lean());
2977
- }
2978
- catch (error) {
2979
- throw new Error(`Failed to get all memories: ${error instanceof Error ? error.message : 'Unknown error'}`);
2980
- }
4685
+ async function findGroupsBySource(source) {
4686
+ const Group = mongoose.models.Group;
4687
+ return await Group.find({ source }).lean();
2981
4688
  }
2982
4689
  /**
2983
- * Gets and formats all memories for a user in two different formats
4690
+ * Find a group by its external ID
4691
+ * @param idOnTheSource - The external ID
4692
+ * @param source - The source ('entra' or 'local')
4693
+ * @returns The group document or null if not found
2984
4694
  */
2985
- async function getFormattedMemories({ userId, }) {
2986
- try {
2987
- const memories = await getAllUserMemories(userId);
2988
- if (!memories || memories.length === 0) {
2989
- return { withKeys: '', withoutKeys: '', totalTokens: 0 };
2990
- }
2991
- const sortedMemories = memories.sort((a, b) => new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime());
2992
- const totalTokens = sortedMemories.reduce((sum, memory) => {
2993
- return sum + (memory.tokenCount || 0);
2994
- }, 0);
2995
- const withKeys = sortedMemories
2996
- .map((memory, index) => {
2997
- const date = formatDate(new Date(memory.updated_at));
2998
- const tokenInfo = memory.tokenCount ? ` [${memory.tokenCount} tokens]` : '';
2999
- return `${index + 1}. [${date}]. ["key": "${memory.key}"]${tokenInfo}. ["value": "${memory.value}"]`;
3000
- })
3001
- .join('\n\n');
3002
- const withoutKeys = sortedMemories
3003
- .map((memory, index) => {
3004
- const date = formatDate(new Date(memory.updated_at));
3005
- return `${index + 1}. [${date}]. ${memory.value}`;
3006
- })
3007
- .join('\n\n');
3008
- return { withKeys, withoutKeys, totalTokens };
3009
- }
3010
- catch (error) {
3011
- logger.error('Failed to get formatted memories:', error);
3012
- return { withKeys: '', withoutKeys: '', totalTokens: 0 };
3013
- }
4695
+ async function findGroupByExternalId(idOnTheSource, source = 'entra') {
4696
+ const Group = mongoose.models.Group;
4697
+ return await Group.findOne({ idOnTheSource, source }).lean();
4698
+ }
4699
+ /**
4700
+ * Add a member to a group
4701
+ * @param groupId - The group ID
4702
+ * @param memberId - The member ID to add (idOnTheSource value)
4703
+ * @returns The updated group or null if not found
4704
+ */
4705
+ async function addMemberToGroup(groupId, memberId) {
4706
+ const Group = mongoose.models.Group;
4707
+ return await Group.findByIdAndUpdate(groupId, { $addToSet: { memberIds: memberId } }, { new: true }).lean();
4708
+ }
4709
+ /**
4710
+ * Remove a member from a group
4711
+ * @param groupId - The group ID
4712
+ * @param memberId - The member ID to remove (idOnTheSource value)
4713
+ * @returns The updated group or null if not found
4714
+ */
4715
+ async function removeMemberFromGroup(groupId, memberId) {
4716
+ const Group = mongoose.models.Group;
4717
+ return await Group.findByIdAndUpdate(groupId, { $pull: { memberIds: memberId } }, { new: true }).lean();
4718
+ }
4719
+ /**
4720
+ * Find all groups that contain a specific member
4721
+ * @param memberId - The member ID (idOnTheSource value)
4722
+ * @returns Array of groups containing the member
4723
+ */
4724
+ async function findGroupsByMemberId(memberId) {
4725
+ const Group = mongoose.models.Group;
4726
+ return await Group.find({ memberIds: memberId }).lean();
3014
4727
  }
3015
4728
  return {
3016
- setMemory,
3017
- createMemory,
3018
- deleteMemory,
3019
- getAllUserMemories,
3020
- getFormattedMemories,
4729
+ createGroup,
4730
+ updateGroup,
4731
+ deleteGroup,
4732
+ getAllGroups,
4733
+ findGroupById,
4734
+ addMemberToGroup,
4735
+ findGroupsBySource,
4736
+ removeMemberFromGroup,
4737
+ findGroupsByMemberId,
4738
+ findGroupByExternalId,
3021
4739
  };
3022
4740
  }
3023
4741
 
@@ -3110,7 +4828,7 @@ function createShareMethods(mongoose) {
3110
4828
  return result;
3111
4829
  }
3112
4830
  catch (error) {
3113
- logger.error('[getSharedMessages] Error getting share link', {
4831
+ logger$1.error('[getSharedMessages] Error getting share link', {
3114
4832
  error: error instanceof Error ? error.message : 'Unknown error',
3115
4833
  shareId,
3116
4834
  });
@@ -3148,7 +4866,7 @@ function createShareMethods(mongoose) {
3148
4866
  query['conversationId'] = { $in: conversationIds };
3149
4867
  }
3150
4868
  catch (searchError) {
3151
- logger.error('[getSharedLinks] Meilisearch error', {
4869
+ logger$1.error('[getSharedLinks] Meilisearch error', {
3152
4870
  error: searchError instanceof Error ? searchError.message : 'Unknown error',
3153
4871
  user,
3154
4872
  });
@@ -3184,7 +4902,7 @@ function createShareMethods(mongoose) {
3184
4902
  };
3185
4903
  }
3186
4904
  catch (error) {
3187
- logger.error('[getSharedLinks] Error getting shares', {
4905
+ logger$1.error('[getSharedLinks] Error getting shares', {
3188
4906
  error: error instanceof Error ? error.message : 'Unknown error',
3189
4907
  user,
3190
4908
  });
@@ -3204,7 +4922,7 @@ function createShareMethods(mongoose) {
3204
4922
  };
3205
4923
  }
3206
4924
  catch (error) {
3207
- logger.error('[deleteAllSharedLinks] Error deleting shared links', {
4925
+ logger$1.error('[deleteAllSharedLinks] Error deleting shared links', {
3208
4926
  error: error instanceof Error ? error.message : 'Unknown error',
3209
4927
  user,
3210
4928
  });
@@ -3229,7 +4947,7 @@ function createShareMethods(mongoose) {
3229
4947
  Message.find({ conversationId, user }).sort({ createdAt: 1 }).lean(),
3230
4948
  ]);
3231
4949
  if (existingShare && existingShare.isPublic) {
3232
- logger.error('[createSharedLink] Share already exists', {
4950
+ logger$1.error('[createSharedLink] Share already exists', {
3233
4951
  user,
3234
4952
  conversationId,
3235
4953
  });
@@ -3262,7 +4980,7 @@ function createShareMethods(mongoose) {
3262
4980
  if (error instanceof ShareServiceError) {
3263
4981
  throw error;
3264
4982
  }
3265
- logger.error('[createSharedLink] Error creating shared link', {
4983
+ logger$1.error('[createSharedLink] Error creating shared link', {
3266
4984
  error: error instanceof Error ? error.message : 'Unknown error',
3267
4985
  user,
3268
4986
  conversationId,
@@ -3288,7 +5006,7 @@ function createShareMethods(mongoose) {
3288
5006
  return { shareId: share.shareId || null, success: true };
3289
5007
  }
3290
5008
  catch (error) {
3291
- logger.error('[getSharedLink] Error getting shared link', {
5009
+ logger$1.error('[getSharedLink] Error getting shared link', {
3292
5010
  error: error instanceof Error ? error.message : 'Unknown error',
3293
5011
  user,
3294
5012
  conversationId,
@@ -3333,7 +5051,7 @@ function createShareMethods(mongoose) {
3333
5051
  return { shareId: newShareId, conversationId: updatedShare.conversationId };
3334
5052
  }
3335
5053
  catch (error) {
3336
- logger.error('[updateSharedLink] Error updating shared link', {
5054
+ logger$1.error('[updateSharedLink] Error updating shared link', {
3337
5055
  error: error instanceof Error ? error.message : 'Unknown error',
3338
5056
  user,
3339
5057
  shareId,
@@ -3361,7 +5079,7 @@ function createShareMethods(mongoose) {
3361
5079
  };
3362
5080
  }
3363
5081
  catch (error) {
3364
- logger.error('[deleteSharedLink] Error deleting shared link', {
5082
+ logger$1.error('[deleteSharedLink] Error deleting shared link', {
3365
5083
  error: error instanceof Error ? error.message : 'Unknown error',
3366
5084
  user,
3367
5085
  shareId,
@@ -3381,106 +5099,6 @@ function createShareMethods(mongoose) {
3381
5099
  };
3382
5100
  }
3383
5101
 
3384
- // Factory function that takes mongoose instance and returns the methods
3385
- function createPluginAuthMethods(mongoose) {
3386
- /**
3387
- * Finds a single plugin auth entry by userId and authField
3388
- */
3389
- async function findOnePluginAuth({ userId, authField, }) {
3390
- try {
3391
- const PluginAuth = mongoose.models.PluginAuth;
3392
- return await PluginAuth.findOne({ userId, authField }).lean();
3393
- }
3394
- catch (error) {
3395
- throw new Error(`Failed to find plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3396
- }
3397
- }
3398
- /**
3399
- * Finds multiple plugin auth entries by userId and pluginKeys
3400
- */
3401
- async function findPluginAuthsByKeys({ userId, pluginKeys, }) {
3402
- try {
3403
- if (!pluginKeys || pluginKeys.length === 0) {
3404
- return [];
3405
- }
3406
- const PluginAuth = mongoose.models.PluginAuth;
3407
- return await PluginAuth.find({
3408
- userId,
3409
- pluginKey: { $in: pluginKeys },
3410
- }).lean();
3411
- }
3412
- catch (error) {
3413
- throw new Error(`Failed to find plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3414
- }
3415
- }
3416
- /**
3417
- * Updates or creates a plugin auth entry
3418
- */
3419
- async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
3420
- try {
3421
- const PluginAuth = mongoose.models.PluginAuth;
3422
- const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
3423
- if (existingAuth) {
3424
- return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
3425
- }
3426
- else {
3427
- const newPluginAuth = await new PluginAuth({
3428
- userId,
3429
- authField,
3430
- value,
3431
- pluginKey,
3432
- });
3433
- await newPluginAuth.save();
3434
- return newPluginAuth.toObject();
3435
- }
3436
- }
3437
- catch (error) {
3438
- throw new Error(`Failed to update plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3439
- }
3440
- }
3441
- /**
3442
- * Deletes plugin auth entries based on provided parameters
3443
- */
3444
- async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
3445
- try {
3446
- const PluginAuth = mongoose.models.PluginAuth;
3447
- if (all) {
3448
- const filter = { userId };
3449
- if (pluginKey) {
3450
- filter.pluginKey = pluginKey;
3451
- }
3452
- return await PluginAuth.deleteMany(filter);
3453
- }
3454
- if (!authField) {
3455
- throw new Error('authField is required when all is false');
3456
- }
3457
- return await PluginAuth.deleteOne({ userId, authField });
3458
- }
3459
- catch (error) {
3460
- throw new Error(`Failed to delete plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3461
- }
3462
- }
3463
- /**
3464
- * Deletes all plugin auth entries for a user
3465
- */
3466
- async function deleteAllUserPluginAuths(userId) {
3467
- try {
3468
- const PluginAuth = mongoose.models.PluginAuth;
3469
- return await PluginAuth.deleteMany({ userId });
3470
- }
3471
- catch (error) {
3472
- throw new Error(`Failed to delete all user plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3473
- }
3474
- }
3475
- return {
3476
- findOnePluginAuth,
3477
- findPluginAuthsByKeys,
3478
- updatePluginAuth,
3479
- deletePluginAuth,
3480
- deleteAllUserPluginAuths,
3481
- };
3482
- }
3483
-
3484
5102
  /**
3485
5103
  * Creates all database methods for all collections
3486
5104
  */
@@ -3491,12 +5109,18 @@ function createMethods(mongoose) {
3491
5109
  ...createTokenMethods(mongoose),
3492
5110
  ...createRoleMethods(mongoose),
3493
5111
  ...createMemoryMethods(mongoose),
5112
+ ...createAgentCategoryMethods(mongoose),
5113
+ ...createAccessRoleMethods(mongoose),
5114
+ ...createUserGroupMethods(mongoose),
5115
+ ...createAclEntryMethods(mongoose),
5116
+ ...createGroupMethods(mongoose),
3494
5117
  ...createShareMethods(mongoose),
3495
5118
  ...createPluginAuthMethods(mongoose),
3496
5119
  };
3497
5120
  }
3498
5121
 
3499
5122
  exports.actionSchema = Action;
5123
+ exports.agentCategorySchema = agentCategorySchema;
3500
5124
  exports.agentSchema = agentSchema;
3501
5125
  exports.assistantSchema = assistantSchema;
3502
5126
  exports.balanceSchema = balanceSchema;
@@ -3507,10 +5131,12 @@ exports.convoSchema = convoSchema;
3507
5131
  exports.createMethods = createMethods;
3508
5132
  exports.createModels = createModels;
3509
5133
  exports.fileSchema = file;
5134
+ exports.getTransactionSupport = getTransactionSupport;
5135
+ exports.groupSchema = groupSchema;
3510
5136
  exports.hashToken = hashToken;
3511
5137
  exports.keySchema = keySchema;
3512
- exports.logger = logger;
3513
- exports.meiliLogger = logger$1;
5138
+ exports.logger = logger$1;
5139
+ exports.meiliLogger = logger;
3514
5140
  exports.memorySchema = MemoryEntrySchema;
3515
5141
  exports.messageSchema = messageSchema;
3516
5142
  exports.pluginAuthSchema = pluginAuthSchema;
@@ -3522,6 +5148,7 @@ exports.roleSchema = roleSchema;
3522
5148
  exports.sessionSchema = sessionSchema;
3523
5149
  exports.shareSchema = shareSchema;
3524
5150
  exports.signPayload = signPayload;
5151
+ exports.supportsTransactions = supportsTransactions;
3525
5152
  exports.tokenSchema = tokenSchema;
3526
5153
  exports.toolCallSchema = toolCallSchema;
3527
5154
  exports.transactionSchema = transactionSchema;