@librechat/data-schemas 0.0.8 → 0.0.11

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.
package/dist/index.es.js CHANGED
@@ -9,6 +9,7 @@ import winston from 'winston';
9
9
  import 'winston-daily-rotate-file';
10
10
  import { klona } from 'klona';
11
11
  import traverse from 'traverse';
12
+ import { nanoid } from 'nanoid';
12
13
 
13
14
  async function signPayload({ payload, secret, expirationTime, }) {
14
15
  return jwt.sign(payload, secret, { expiresIn: expirationTime });
@@ -424,10 +425,23 @@ const conversationPreset = {
424
425
  max_tokens: {
425
426
  type: Number,
426
427
  },
427
- /** omni models only */
428
+ useResponsesApi: {
429
+ type: Boolean,
430
+ },
431
+ /** OpenAI Responses API / Anthropic API / Google API */
432
+ web_search: {
433
+ type: Boolean,
434
+ },
435
+ disableStreaming: {
436
+ type: Boolean,
437
+ },
438
+ /** Reasoning models only */
428
439
  reasoning_effort: {
429
440
  type: String,
430
441
  },
442
+ reasoning_summary: {
443
+ type: String,
444
+ },
431
445
  };
432
446
 
433
447
  const convoSchema = new Schema({
@@ -868,7 +882,7 @@ const promptGroupSchema = new Schema({
868
882
  validator: function (v) {
869
883
  return v === undefined || v === null || v === '' || /^[a-z0-9-]+$/.test(v);
870
884
  },
871
- message: (props) => `${props.value} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`,
885
+ message: (props) => { var _a; return `${(_a = props === null || props === void 0 ? void 0 : props.value) !== null && _a !== void 0 ? _a : 'Value'} is not a valid command. Only lowercase alphanumeric characters and hyphens are allowed.`; },
872
886
  },
873
887
  maxlength: [
874
888
  Constants.COMMANDS_MAX_LENGTH,
@@ -880,7 +894,9 @@ const promptGroupSchema = new Schema({
880
894
  });
881
895
  promptGroupSchema.index({ createdAt: 1, updatedAt: 1 });
882
896
 
883
- // Create a sub-schema for permissions. Notice we disable _id for this subdocument.
897
+ /**
898
+ * Uses a sub-schema for permissions. Notice we disable `_id` for this subdocument.
899
+ */
884
900
  const rolePermissionsSchema = new Schema({
885
901
  [PermissionTypes.BOOKMARKS]: {
886
902
  [Permissions.USE]: { type: Boolean, default: true },
@@ -890,6 +906,13 @@ const rolePermissionsSchema = new Schema({
890
906
  [Permissions.USE]: { type: Boolean, default: true },
891
907
  [Permissions.CREATE]: { type: Boolean, default: true },
892
908
  },
909
+ [PermissionTypes.MEMORIES]: {
910
+ [Permissions.USE]: { type: Boolean, default: true },
911
+ [Permissions.CREATE]: { type: Boolean, default: true },
912
+ [Permissions.UPDATE]: { type: Boolean, default: true },
913
+ [Permissions.READ]: { type: Boolean, default: true },
914
+ [Permissions.OPT_OUT]: { type: Boolean, default: true },
915
+ },
893
916
  [PermissionTypes.AGENTS]: {
894
917
  [Permissions.SHARED_GLOBAL]: { type: Boolean, default: false },
895
918
  [Permissions.USE]: { type: Boolean, default: true },
@@ -907,6 +930,9 @@ const rolePermissionsSchema = new Schema({
907
930
  [PermissionTypes.WEB_SEARCH]: {
908
931
  [Permissions.USE]: { type: Boolean, default: true },
909
932
  },
933
+ [PermissionTypes.FILE_SEARCH]: {
934
+ [Permissions.USE]: { type: Boolean, default: true },
935
+ },
910
936
  }, { _id: false });
911
937
  const roleSchema = new Schema({
912
938
  name: { type: String, required: true, unique: true, index: true },
@@ -919,6 +945,12 @@ const roleSchema = new Schema({
919
945
  [Permissions.USE]: true,
920
946
  [Permissions.CREATE]: true,
921
947
  },
948
+ [PermissionTypes.MEMORIES]: {
949
+ [Permissions.USE]: true,
950
+ [Permissions.CREATE]: true,
951
+ [Permissions.UPDATE]: true,
952
+ [Permissions.READ]: true,
953
+ },
922
954
  [PermissionTypes.AGENTS]: {
923
955
  [Permissions.SHARED_GLOBAL]: false,
924
956
  [Permissions.USE]: true,
@@ -928,6 +960,7 @@ const roleSchema = new Schema({
928
960
  [PermissionTypes.TEMPORARY_CHAT]: { [Permissions.USE]: true },
929
961
  [PermissionTypes.RUN_CODE]: { [Permissions.USE]: true },
930
962
  [PermissionTypes.WEB_SEARCH]: { [Permissions.USE]: true },
963
+ [PermissionTypes.FILE_SEARCH]: { [Permissions.USE]: true },
931
964
  }),
932
965
  },
933
966
  });
@@ -1196,8 +1229,46 @@ const userSchema = new Schema({
1196
1229
  type: Boolean,
1197
1230
  default: false,
1198
1231
  },
1232
+ personalization: {
1233
+ type: {
1234
+ memories: {
1235
+ type: Boolean,
1236
+ default: true,
1237
+ },
1238
+ },
1239
+ default: {},
1240
+ },
1199
1241
  }, { timestamps: true });
1200
1242
 
1243
+ const MemoryEntrySchema = new Schema({
1244
+ userId: {
1245
+ type: Schema.Types.ObjectId,
1246
+ ref: 'User',
1247
+ index: true,
1248
+ required: true,
1249
+ },
1250
+ key: {
1251
+ type: String,
1252
+ required: true,
1253
+ validate: {
1254
+ validator: (v) => /^[a-z_]+$/.test(v),
1255
+ message: 'Key must only contain lowercase letters and underscores',
1256
+ },
1257
+ },
1258
+ value: {
1259
+ type: String,
1260
+ required: true,
1261
+ },
1262
+ tokenCount: {
1263
+ type: Number,
1264
+ default: 0,
1265
+ },
1266
+ updated_at: {
1267
+ type: Date,
1268
+ default: Date.now,
1269
+ },
1270
+ });
1271
+
1201
1272
  /**
1202
1273
  * Creates or returns the User model using the provided mongoose instance and schema
1203
1274
  */
@@ -1226,7 +1297,7 @@ function createBalanceModel(mongoose) {
1226
1297
  return mongoose.models.Balance || mongoose.model('Balance', balanceSchema);
1227
1298
  }
1228
1299
 
1229
- const logDir$1 = path.join(__dirname, '..', 'logs');
1300
+ const logDir$1 = path.join(__dirname, '..', '..', '..', 'api', 'logs');
1230
1301
  const { NODE_ENV: NODE_ENV$1, DEBUG_LOGGING: DEBUG_LOGGING$1 = 'false' } = process.env;
1231
1302
  const useDebugLogging$1 = (typeof DEBUG_LOGGING$1 === 'string' && DEBUG_LOGGING$1.toLowerCase() === 'true') ||
1232
1303
  DEBUG_LOGGING$1 === 'true';
@@ -1284,6 +1355,13 @@ const searchEnabled = process.env.SEARCH != null && process.env.SEARCH.toLowerCa
1284
1355
  * Flag to indicate if MeiliSearch is enabled based on required environment variables.
1285
1356
  */
1286
1357
  const meiliEnabled = process.env.MEILI_HOST != null && process.env.MEILI_MASTER_KEY != null && searchEnabled;
1358
+ /**
1359
+ * Get sync configuration from environment variables
1360
+ */
1361
+ const getSyncConfig = () => ({
1362
+ batchSize: parseInt(process.env.MEILI_SYNC_BATCH_SIZE || '100', 10),
1363
+ delayMs: parseInt(process.env.MEILI_SYNC_DELAY_MS || '100', 10),
1364
+ });
1287
1365
  /**
1288
1366
  * Local implementation of parseTextParts to avoid dependency on librechat-data-provider
1289
1367
  * Extracts text content from an array of content items
@@ -1315,6 +1393,19 @@ const validateOptions = (options) => {
1315
1393
  }
1316
1394
  });
1317
1395
  };
1396
+ /**
1397
+ * Helper function to process documents in batches with rate limiting
1398
+ */
1399
+ const processBatch = async (items, batchSize, delayMs, processor) => {
1400
+ for (let i = 0; i < items.length; i += batchSize) {
1401
+ const batch = items.slice(i, i + batchSize);
1402
+ await processor(batch);
1403
+ // Add delay between batches to prevent overwhelming resources
1404
+ if (i + batchSize < items.length && delayMs > 0) {
1405
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1406
+ }
1407
+ }
1408
+ };
1318
1409
  /**
1319
1410
  * Factory function to create a MeiliMongooseModel class which extends a Mongoose model.
1320
1411
  * This class contains static and instance methods to synchronize and manage the MeiliSearch index
@@ -1323,97 +1414,148 @@ const validateOptions = (options) => {
1323
1414
  * @param config - Configuration object.
1324
1415
  * @param config.index - The MeiliSearch index object.
1325
1416
  * @param config.attributesToIndex - List of attributes to index.
1417
+ * @param config.syncOptions - Sync configuration options.
1326
1418
  * @returns A class definition that will be loaded into the Mongoose schema.
1327
1419
  */
1328
- const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1420
+ const createMeiliMongooseModel = ({ index, attributesToIndex, syncOptions, }) => {
1329
1421
  const primaryKey = attributesToIndex[0];
1422
+ const syncConfig = { ...getSyncConfig(), ...syncOptions };
1330
1423
  class MeiliMongooseModel {
1424
+ /**
1425
+ * Get the current sync progress
1426
+ */
1427
+ static async getSyncProgress() {
1428
+ const totalDocuments = await this.countDocuments();
1429
+ const indexedDocuments = await this.countDocuments({ _meiliIndex: true });
1430
+ return {
1431
+ totalProcessed: indexedDocuments,
1432
+ totalDocuments,
1433
+ isComplete: indexedDocuments === totalDocuments,
1434
+ };
1435
+ }
1331
1436
  /**
1332
1437
  * Synchronizes the data between the MongoDB collection and the MeiliSearch index.
1333
- *
1334
- * The synchronization process involves:
1335
- * 1. Fetching all documents from the MongoDB collection and MeiliSearch index.
1336
- * 2. Comparing documents from both sources.
1337
- * 3. Deleting documents from MeiliSearch that no longer exist in MongoDB.
1338
- * 4. Adding documents to MeiliSearch that exist in MongoDB but not in the index.
1339
- * 5. Updating documents in MeiliSearch if key fields (such as `text` or `title`) differ.
1340
- * 6. Updating the `_meiliIndex` field in MongoDB to indicate the indexing status.
1341
- *
1342
- * Note: The function processes documents in batches because MeiliSearch's
1343
- * `index.getDocuments` requires an exact limit and `index.addDocuments` does not handle
1344
- * partial failures in a batch.
1345
- *
1346
- * @returns {Promise<void>} Resolves when the synchronization is complete.
1438
+ * Now uses streaming and batching to reduce memory usage.
1347
1439
  */
1348
- static async syncWithMeili() {
1440
+ static async syncWithMeili(options) {
1349
1441
  try {
1350
- let moreDocuments = true;
1351
- const mongoDocuments = await this.find().lean();
1442
+ const startTime = Date.now();
1443
+ const { batchSize, delayMs } = syncConfig;
1444
+ logger$1.info(`[syncWithMeili] Starting sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} with batch size ${batchSize}`);
1445
+ // Build query with resume capability
1446
+ const query = {};
1447
+ if (options === null || options === void 0 ? void 0 : options.resumeFromId) {
1448
+ query._id = { $gt: options.resumeFromId };
1449
+ }
1450
+ // Get total count for progress tracking
1451
+ const totalCount = await this.countDocuments(query);
1452
+ let processedCount = 0;
1453
+ // First, handle documents that need to be removed from Meili
1454
+ await this.cleanupMeiliIndex(index, primaryKey, batchSize, delayMs);
1455
+ // Process MongoDB documents in batches using cursor
1456
+ const cursor = this.find(query)
1457
+ .select(attributesToIndex.join(' ') + ' _meiliIndex')
1458
+ .sort({ _id: 1 })
1459
+ .batchSize(batchSize)
1460
+ .cursor();
1352
1461
  const format = (doc) => _.omitBy(_.pick(doc, attributesToIndex), (v, k) => k.startsWith('$'));
1353
- const mongoMap = new Map(mongoDocuments.map((doc) => {
1354
- const typedDoc = doc;
1355
- return [typedDoc[primaryKey], format(typedDoc)];
1356
- }));
1357
- const indexMap = new Map();
1462
+ let documentBatch = [];
1463
+ let updateOps = [];
1464
+ // Process documents in streaming fashion
1465
+ for await (const doc of cursor) {
1466
+ const typedDoc = doc.toObject();
1467
+ const formatted = format(typedDoc);
1468
+ // Check if document needs indexing
1469
+ if (!typedDoc._meiliIndex) {
1470
+ documentBatch.push(formatted);
1471
+ updateOps.push({
1472
+ updateOne: {
1473
+ filter: { _id: typedDoc._id },
1474
+ update: { $set: { _meiliIndex: true } },
1475
+ },
1476
+ });
1477
+ }
1478
+ processedCount++;
1479
+ // Process batch when it reaches the configured size
1480
+ if (documentBatch.length >= batchSize) {
1481
+ await this.processSyncBatch(index, documentBatch, updateOps);
1482
+ documentBatch = [];
1483
+ updateOps = [];
1484
+ // Log progress
1485
+ const progress = Math.round((processedCount / totalCount) * 100);
1486
+ logger$1.info(`[syncWithMeili] Progress: ${progress}% (${processedCount}/${totalCount})`);
1487
+ // Add delay to prevent overwhelming resources
1488
+ if (delayMs > 0) {
1489
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1490
+ }
1491
+ }
1492
+ }
1493
+ // Process remaining documents
1494
+ if (documentBatch.length > 0) {
1495
+ await this.processSyncBatch(index, documentBatch, updateOps);
1496
+ }
1497
+ const duration = Date.now() - startTime;
1498
+ logger$1.info(`[syncWithMeili] Completed sync for ${primaryKey === 'messageId' ? 'messages' : 'conversations'} in ${duration}ms`);
1499
+ }
1500
+ catch (error) {
1501
+ logger$1.error('[syncWithMeili] Error during sync:', error);
1502
+ throw error;
1503
+ }
1504
+ }
1505
+ /**
1506
+ * Process a batch of documents for syncing
1507
+ */
1508
+ static async processSyncBatch(index, documents, updateOps) {
1509
+ if (documents.length === 0) {
1510
+ return;
1511
+ }
1512
+ try {
1513
+ // Add documents to MeiliSearch
1514
+ await index.addDocuments(documents);
1515
+ // Update MongoDB to mark documents as indexed
1516
+ if (updateOps.length > 0) {
1517
+ await this.collection.bulkWrite(updateOps);
1518
+ }
1519
+ }
1520
+ catch (error) {
1521
+ logger$1.error('[processSyncBatch] Error processing batch:', error);
1522
+ // Don't throw - allow sync to continue with other documents
1523
+ }
1524
+ }
1525
+ /**
1526
+ * Clean up documents in MeiliSearch that no longer exist in MongoDB
1527
+ */
1528
+ static async cleanupMeiliIndex(index, primaryKey, batchSize, delayMs) {
1529
+ try {
1358
1530
  let offset = 0;
1359
- const batchSize = 1000;
1531
+ let moreDocuments = true;
1360
1532
  while (moreDocuments) {
1361
1533
  const batch = await index.getDocuments({ limit: batchSize, offset });
1362
1534
  if (batch.results.length === 0) {
1363
1535
  moreDocuments = false;
1536
+ break;
1364
1537
  }
1365
- for (const doc of batch.results) {
1366
- indexMap.set(doc[primaryKey], format(doc));
1538
+ const meiliIds = batch.results.map((doc) => doc[primaryKey]);
1539
+ const query = {};
1540
+ query[primaryKey] = { $in: meiliIds };
1541
+ // Find which documents exist in MongoDB
1542
+ const existingDocs = await this.find(query).select(primaryKey).lean();
1543
+ const existingIds = new Set(existingDocs.map((doc) => doc[primaryKey]));
1544
+ // Delete documents that don't exist in MongoDB
1545
+ const toDelete = meiliIds.filter((id) => !existingIds.has(id));
1546
+ if (toDelete.length > 0) {
1547
+ await Promise.all(toDelete.map((id) => index.deleteDocument(id)));
1548
+ logger$1.debug(`[cleanupMeiliIndex] Deleted ${toDelete.length} orphaned documents`);
1367
1549
  }
1368
1550
  offset += batchSize;
1369
- }
1370
- logger$1.debug('[syncWithMeili]', { indexMap: indexMap.size, mongoMap: mongoMap.size });
1371
- const updateOps = [];
1372
- // Process documents present in the MeiliSearch index
1373
- for (const [id, doc] of indexMap) {
1374
- const update = {};
1375
- update[primaryKey] = id;
1376
- if (mongoMap.has(id)) {
1377
- const mongoDoc = mongoMap.get(id);
1378
- if ((doc.text && doc.text !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.text)) ||
1379
- (doc.title && doc.title !== (mongoDoc === null || mongoDoc === void 0 ? void 0 : mongoDoc.title))) {
1380
- logger$1.debug(`[syncWithMeili] ${id} had document discrepancy in ${doc.text ? 'text' : 'title'} field`);
1381
- updateOps.push({
1382
- updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
1383
- });
1384
- await index.addDocuments([doc]);
1385
- }
1386
- }
1387
- else {
1388
- await index.deleteDocument(id);
1389
- updateOps.push({
1390
- updateOne: { filter: update, update: { $set: { _meiliIndex: false } } },
1391
- });
1392
- }
1393
- }
1394
- // Process documents present in MongoDB
1395
- for (const [id, doc] of mongoMap) {
1396
- const update = {};
1397
- update[primaryKey] = id;
1398
- if (!indexMap.has(id)) {
1399
- await index.addDocuments([doc]);
1400
- updateOps.push({
1401
- updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
1402
- });
1403
- }
1404
- else if (doc._meiliIndex === false) {
1405
- updateOps.push({
1406
- updateOne: { filter: update, update: { $set: { _meiliIndex: true } } },
1407
- });
1551
+ // Add delay between batches
1552
+ if (delayMs > 0) {
1553
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1408
1554
  }
1409
1555
  }
1410
- if (updateOps.length > 0) {
1411
- await this.collection.bulkWrite(updateOps);
1412
- logger$1.debug(`[syncWithMeili] Finished indexing ${primaryKey === 'messageId' ? 'messages' : 'conversations'}`);
1413
- }
1414
1556
  }
1415
1557
  catch (error) {
1416
- logger$1.error('[syncWithMeili] Error adding document to Meili', error);
1558
+ logger$1.error('[cleanupMeiliIndex] Error during cleanup:', error);
1417
1559
  }
1418
1560
  }
1419
1561
  /**
@@ -1469,32 +1611,64 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1469
1611
  return object;
1470
1612
  }
1471
1613
  /**
1472
- * Adds the current document to the MeiliSearch index
1614
+ * Adds the current document to the MeiliSearch index with retry logic
1473
1615
  */
1474
- async addObjectToMeili() {
1616
+ async addObjectToMeili(next) {
1475
1617
  const object = this.preprocessObjectForIndex();
1618
+ const maxRetries = 3;
1619
+ let retryCount = 0;
1620
+ while (retryCount < maxRetries) {
1621
+ try {
1622
+ await index.addDocuments([object]);
1623
+ break;
1624
+ }
1625
+ catch (error) {
1626
+ retryCount++;
1627
+ if (retryCount >= maxRetries) {
1628
+ logger$1.error('[addObjectToMeili] Error adding document to Meili after retries:', error);
1629
+ return next();
1630
+ }
1631
+ // Exponential backoff
1632
+ await new Promise((resolve) => setTimeout(resolve, Math.pow(2, retryCount) * 1000));
1633
+ }
1634
+ }
1476
1635
  try {
1477
- await index.addDocuments([object]);
1636
+ await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
1478
1637
  }
1479
1638
  catch (error) {
1480
- logger$1.error('[addObjectToMeili] Error adding document to Meili', error);
1639
+ logger$1.error('[addObjectToMeili] Error updating _meiliIndex field:', error);
1640
+ return next();
1481
1641
  }
1482
- await this.collection.updateMany({ _id: this._id }, { $set: { _meiliIndex: true } });
1642
+ next();
1483
1643
  }
1484
1644
  /**
1485
1645
  * Updates the current document in the MeiliSearch index
1486
1646
  */
1487
- async updateObjectToMeili() {
1488
- const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
1489
- await index.updateDocuments([object]);
1647
+ async updateObjectToMeili(next) {
1648
+ try {
1649
+ const object = _.omitBy(_.pick(this.toJSON(), attributesToIndex), (v, k) => k.startsWith('$'));
1650
+ await index.updateDocuments([object]);
1651
+ next();
1652
+ }
1653
+ catch (error) {
1654
+ logger$1.error('[updateObjectToMeili] Error updating document in Meili:', error);
1655
+ return next();
1656
+ }
1490
1657
  }
1491
1658
  /**
1492
1659
  * Deletes the current document from the MeiliSearch index.
1493
1660
  *
1494
1661
  * @returns {Promise<void>}
1495
1662
  */
1496
- async deleteObjectFromMeili() {
1497
- await index.deleteDocument(this._id);
1663
+ async deleteObjectFromMeili(next) {
1664
+ try {
1665
+ await index.deleteDocument(this._id);
1666
+ next();
1667
+ }
1668
+ catch (error) {
1669
+ logger$1.error('[deleteObjectFromMeili] Error deleting document from Meili:', error);
1670
+ return next();
1671
+ }
1498
1672
  }
1499
1673
  /**
1500
1674
  * Post-save hook to synchronize the document with MeiliSearch.
@@ -1502,12 +1676,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1502
1676
  * If the document is already indexed (i.e. `_meiliIndex` is true), it updates it;
1503
1677
  * otherwise, it adds the document to the index.
1504
1678
  */
1505
- postSaveHook() {
1679
+ postSaveHook(next) {
1506
1680
  if (this._meiliIndex) {
1507
- this.updateObjectToMeili();
1681
+ this.updateObjectToMeili(next);
1508
1682
  }
1509
1683
  else {
1510
- this.addObjectToMeili();
1684
+ this.addObjectToMeili(next);
1511
1685
  }
1512
1686
  }
1513
1687
  /**
@@ -1516,9 +1690,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1516
1690
  * This hook is triggered after a document update, ensuring that changes are
1517
1691
  * propagated to the MeiliSearch index if the document is indexed.
1518
1692
  */
1519
- postUpdateHook() {
1693
+ postUpdateHook(next) {
1520
1694
  if (this._meiliIndex) {
1521
- this.updateObjectToMeili();
1695
+ this.updateObjectToMeili(next);
1696
+ }
1697
+ else {
1698
+ next();
1522
1699
  }
1523
1700
  }
1524
1701
  /**
@@ -1527,9 +1704,12 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1527
1704
  * This hook is triggered after a document is removed, ensuring that the document
1528
1705
  * is also removed from the MeiliSearch index if it was previously indexed.
1529
1706
  */
1530
- postRemoveHook() {
1707
+ postRemoveHook(next) {
1531
1708
  if (this._meiliIndex) {
1532
- this.deleteObjectFromMeili();
1709
+ this.deleteObjectFromMeili(next);
1710
+ }
1711
+ else {
1712
+ next();
1533
1713
  }
1534
1714
  }
1535
1715
  }
@@ -1551,6 +1731,8 @@ const createMeiliMongooseModel = ({ index, attributesToIndex, }) => {
1551
1731
  * @param options.apiKey - The MeiliSearch API key.
1552
1732
  * @param options.indexName - The name of the MeiliSearch index.
1553
1733
  * @param options.primaryKey - The primary key field for indexing.
1734
+ * @param options.syncBatchSize - Batch size for sync operations.
1735
+ * @param options.syncDelayMs - Delay between batches in milliseconds.
1554
1736
  */
1555
1737
  function mongoMeili(schema, options) {
1556
1738
  const mongoose = options.mongoose;
@@ -1565,9 +1747,37 @@ function mongoMeili(schema, options) {
1565
1747
  },
1566
1748
  });
1567
1749
  const { host, apiKey, indexName, primaryKey } = options;
1750
+ const syncOptions = {
1751
+ batchSize: options.syncBatchSize || getSyncConfig().batchSize,
1752
+ delayMs: options.syncDelayMs || getSyncConfig().delayMs,
1753
+ };
1568
1754
  const client = new MeiliSearch({ host, apiKey });
1569
- client.createIndex(indexName, { primaryKey });
1755
+ /** Create index only if it doesn't exist */
1570
1756
  const index = client.index(indexName);
1757
+ // Check if index exists and create if needed
1758
+ (async () => {
1759
+ try {
1760
+ await index.getRawInfo();
1761
+ logger$1.debug(`[mongoMeili] Index ${indexName} already exists`);
1762
+ }
1763
+ catch (error) {
1764
+ const errorCode = error === null || error === void 0 ? void 0 : error.code;
1765
+ if (errorCode === 'index_not_found') {
1766
+ try {
1767
+ logger$1.info(`[mongoMeili] Creating new index: ${indexName}`);
1768
+ await client.createIndex(indexName, { primaryKey });
1769
+ logger$1.info(`[mongoMeili] Successfully created index: ${indexName}`);
1770
+ }
1771
+ catch (createError) {
1772
+ // Index might have been created by another instance
1773
+ logger$1.debug(`[mongoMeili] Index ${indexName} may already exist:`, createError);
1774
+ }
1775
+ }
1776
+ else {
1777
+ logger$1.error(`[mongoMeili] Error checking index ${indexName}:`, error);
1778
+ }
1779
+ }
1780
+ })();
1571
1781
  // Collect attributes from the schema that should be indexed
1572
1782
  const attributesToIndex = [
1573
1783
  ...Object.entries(schema.obj).reduce((results, [key, value]) => {
@@ -1575,19 +1785,19 @@ function mongoMeili(schema, options) {
1575
1785
  return schemaValue.meiliIndex ? [...results, key] : results;
1576
1786
  }, []),
1577
1787
  ];
1578
- schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex }));
1788
+ schema.loadClass(createMeiliMongooseModel({ index, attributesToIndex, syncOptions }));
1579
1789
  // Register Mongoose hooks
1580
- schema.post('save', function (doc) {
1790
+ schema.post('save', function (doc, next) {
1581
1791
  var _a;
1582
- (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1792
+ (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1583
1793
  });
1584
- schema.post('updateOne', function (doc) {
1794
+ schema.post('updateOne', function (doc, next) {
1585
1795
  var _a;
1586
- (_a = doc.postUpdateHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1796
+ (_a = doc.postUpdateHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1587
1797
  });
1588
- schema.post('deleteOne', function (doc) {
1798
+ schema.post('deleteOne', function (doc, next) {
1589
1799
  var _a;
1590
- (_a = doc.postRemoveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1800
+ (_a = doc.postRemoveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1591
1801
  });
1592
1802
  // Pre-deleteMany hook: remove corresponding documents from MeiliSearch when multiple documents are deleted.
1593
1803
  schema.pre('deleteMany', async function (next) {
@@ -1596,41 +1806,50 @@ function mongoMeili(schema, options) {
1596
1806
  }
1597
1807
  try {
1598
1808
  const conditions = this.getQuery();
1809
+ const { batchSize, delayMs } = syncOptions;
1599
1810
  if (Object.prototype.hasOwnProperty.call(schema.obj, 'messages')) {
1600
1811
  const convoIndex = client.index('convos');
1601
1812
  const deletedConvos = await mongoose
1602
1813
  .model('Conversation')
1603
1814
  .find(conditions)
1815
+ .select('conversationId')
1604
1816
  .lean();
1605
- const promises = deletedConvos.map((convo) => convoIndex.deleteDocument(convo.conversationId));
1606
- await Promise.all(promises);
1817
+ // Process deletions in batches
1818
+ await processBatch(deletedConvos, batchSize, delayMs, async (batch) => {
1819
+ const promises = batch.map((convo) => convoIndex.deleteDocument(convo.conversationId));
1820
+ await Promise.all(promises);
1821
+ });
1607
1822
  }
1608
1823
  if (Object.prototype.hasOwnProperty.call(schema.obj, 'messageId')) {
1609
1824
  const messageIndex = client.index('messages');
1610
1825
  const deletedMessages = await mongoose
1611
1826
  .model('Message')
1612
1827
  .find(conditions)
1828
+ .select('messageId')
1613
1829
  .lean();
1614
- const promises = deletedMessages.map((message) => messageIndex.deleteDocument(message.messageId));
1615
- await Promise.all(promises);
1830
+ // Process deletions in batches
1831
+ await processBatch(deletedMessages, batchSize, delayMs, async (batch) => {
1832
+ const promises = batch.map((message) => messageIndex.deleteDocument(message.messageId));
1833
+ await Promise.all(promises);
1834
+ });
1616
1835
  }
1617
1836
  return next();
1618
1837
  }
1619
1838
  catch (error) {
1620
1839
  if (meiliEnabled) {
1621
- logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may be slow due to syncing.', error);
1840
+ logger$1.error('[MeiliMongooseModel.deleteMany] There was an issue deleting conversation indexes upon deletion. Next startup may trigger syncing.', error);
1622
1841
  }
1623
1842
  return next();
1624
1843
  }
1625
1844
  });
1626
1845
  // Post-findOneAndUpdate hook
1627
- schema.post('findOneAndUpdate', async function (doc) {
1846
+ schema.post('findOneAndUpdate', async function (doc, next) {
1628
1847
  var _a;
1629
1848
  if (!meiliEnabled) {
1630
- return;
1849
+ return next();
1631
1850
  }
1632
1851
  if (doc.unfinished) {
1633
- return;
1852
+ return next();
1634
1853
  }
1635
1854
  let meiliDoc;
1636
1855
  if (doc.messages) {
@@ -1643,9 +1862,9 @@ function mongoMeili(schema, options) {
1643
1862
  }
1644
1863
  }
1645
1864
  if (meiliDoc && meiliDoc.title === doc.title) {
1646
- return;
1865
+ return next();
1647
1866
  }
1648
- (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc);
1867
+ (_a = doc.postSaveHook) === null || _a === void 0 ? void 0 : _a.call(doc, next);
1649
1868
  });
1650
1869
  }
1651
1870
 
@@ -1796,6 +2015,10 @@ function createToolCallModel(mongoose) {
1796
2015
  return mongoose.models.ToolCall || mongoose.model('ToolCall', toolCallSchema);
1797
2016
  }
1798
2017
 
2018
+ function createMemoryModel(mongoose) {
2019
+ return mongoose.models.MemoryEntry || mongoose.model('MemoryEntry', MemoryEntrySchema);
2020
+ }
2021
+
1799
2022
  /**
1800
2023
  * Creates all database models for all collections
1801
2024
  */
@@ -1823,6 +2046,7 @@ function createModels(mongoose) {
1823
2046
  ConversationTag: createConversationTagModel(mongoose),
1824
2047
  SharedLink: createSharedLinkModel(mongoose),
1825
2048
  ToolCall: createToolCallModel(mongoose),
2049
+ MemoryEntry: createMemoryModel(mongoose),
1826
2050
  };
1827
2051
  }
1828
2052
 
@@ -1958,6 +2182,28 @@ function createUserMethods(mongoose) {
1958
2182
  expirationTime: expires / 1000,
1959
2183
  });
1960
2184
  }
2185
+ /**
2186
+ * Update a user's personalization memories setting.
2187
+ * Handles the edge case where the personalization object doesn't exist.
2188
+ */
2189
+ async function toggleUserMemories(userId, memoriesEnabled) {
2190
+ const User = mongoose.models.User;
2191
+ // First, ensure the personalization object exists
2192
+ const user = await User.findById(userId);
2193
+ if (!user) {
2194
+ return null;
2195
+ }
2196
+ // Use $set to update the nested field, which will create the personalization object if it doesn't exist
2197
+ const updateOperation = {
2198
+ $set: {
2199
+ 'personalization.memories': memoriesEnabled,
2200
+ },
2201
+ };
2202
+ return (await User.findByIdAndUpdate(userId, updateOperation, {
2203
+ new: true,
2204
+ runValidators: true,
2205
+ }).lean());
2206
+ }
1961
2207
  // Return all methods
1962
2208
  return {
1963
2209
  findUser,
@@ -1967,6 +2213,7 @@ function createUserMethods(mongoose) {
1967
2213
  getUserById,
1968
2214
  deleteUserById,
1969
2215
  generateToken,
2216
+ toggleUserMemories,
1970
2217
  };
1971
2218
  }
1972
2219
 
@@ -2171,14 +2418,11 @@ const jsonTruncateFormat = winston.format((info) => {
2171
2418
  return truncateObject(info);
2172
2419
  });
2173
2420
 
2174
- // Define log directory
2175
- const logDir = path.join(__dirname, '..', 'logs');
2176
- // Type-safe environment variables
2421
+ const logDir = path.join(__dirname, '..', '..', '..', 'api', 'logs');
2177
2422
  const { NODE_ENV, DEBUG_LOGGING, CONSOLE_JSON, DEBUG_CONSOLE } = process.env;
2178
2423
  const useConsoleJson = typeof CONSOLE_JSON === 'string' && CONSOLE_JSON.toLowerCase() === 'true';
2179
2424
  const useDebugConsole = typeof DEBUG_CONSOLE === 'string' && DEBUG_CONSOLE.toLowerCase() === 'true';
2180
2425
  const useDebugLogging = typeof DEBUG_LOGGING === 'string' && DEBUG_LOGGING.toLowerCase() === 'true';
2181
- // Define custom log levels
2182
2426
  const levels = {
2183
2427
  error: 0,
2184
2428
  warn: 1,
@@ -2258,7 +2502,7 @@ const logger = winston.createLogger({
2258
2502
  transports,
2259
2503
  });
2260
2504
 
2261
- var _a, _b;
2505
+ var _a;
2262
2506
  class SessionError extends Error {
2263
2507
  constructor(message, code = 'SESSION_ERROR') {
2264
2508
  super(message);
@@ -2267,10 +2511,9 @@ class SessionError extends Error {
2267
2511
  }
2268
2512
  }
2269
2513
  const { REFRESH_TOKEN_EXPIRY } = (_a = process.env) !== null && _a !== void 0 ? _a : {};
2270
- const expires = (_b = eval(REFRESH_TOKEN_EXPIRY !== null && REFRESH_TOKEN_EXPIRY !== void 0 ? REFRESH_TOKEN_EXPIRY : '0')) !== null && _b !== void 0 ? _b : 1000 * 60 * 60 * 24 * 7; // 7 days default
2514
+ const expires = REFRESH_TOKEN_EXPIRY ? eval(REFRESH_TOKEN_EXPIRY) : 1000 * 60 * 60 * 24 * 7; // 7 days default
2271
2515
  // Factory function that takes mongoose instance and returns the methods
2272
2516
  function createSessionMethods(mongoose) {
2273
- const Session = mongoose.models.Session;
2274
2517
  /**
2275
2518
  * Creates a new session for a user
2276
2519
  */
@@ -2279,12 +2522,13 @@ function createSessionMethods(mongoose) {
2279
2522
  throw new SessionError('User ID is required', 'INVALID_USER_ID');
2280
2523
  }
2281
2524
  try {
2282
- const session = new Session({
2525
+ const Session = mongoose.models.Session;
2526
+ const currentSession = new Session({
2283
2527
  user: userId,
2284
2528
  expiration: options.expiration || new Date(Date.now() + expires),
2285
2529
  });
2286
- const refreshToken = await generateRefreshToken(session);
2287
- return { session, refreshToken };
2530
+ const refreshToken = await generateRefreshToken(currentSession);
2531
+ return { session: currentSession, refreshToken };
2288
2532
  }
2289
2533
  catch (error) {
2290
2534
  logger.error('[createSession] Error creating session:', error);
@@ -2296,6 +2540,7 @@ function createSessionMethods(mongoose) {
2296
2540
  */
2297
2541
  async function findSession(params, options = { lean: true }) {
2298
2542
  try {
2543
+ const Session = mongoose.models.Session;
2299
2544
  const query = {};
2300
2545
  if (!params.refreshToken && !params.userId && !params.sessionId) {
2301
2546
  throw new SessionError('At least one search parameter is required', 'INVALID_SEARCH_PARAMS');
@@ -2336,6 +2581,7 @@ function createSessionMethods(mongoose) {
2336
2581
  */
2337
2582
  async function updateExpiration(session, newExpiration) {
2338
2583
  try {
2584
+ const Session = mongoose.models.Session;
2339
2585
  const sessionDoc = typeof session === 'string' ? await Session.findById(session) : session;
2340
2586
  if (!sessionDoc) {
2341
2587
  throw new SessionError('Session not found', 'SESSION_NOT_FOUND');
@@ -2353,6 +2599,7 @@ function createSessionMethods(mongoose) {
2353
2599
  */
2354
2600
  async function deleteSession(params) {
2355
2601
  try {
2602
+ const Session = mongoose.models.Session;
2356
2603
  if (!params.refreshToken && !params.sessionId) {
2357
2604
  throw new SessionError('Either refreshToken or sessionId is required', 'INVALID_DELETE_PARAMS');
2358
2605
  }
@@ -2379,6 +2626,7 @@ function createSessionMethods(mongoose) {
2379
2626
  */
2380
2627
  async function deleteAllUserSessions(userId, options = {}) {
2381
2628
  try {
2629
+ const Session = mongoose.models.Session;
2382
2630
  if (!userId) {
2383
2631
  throw new SessionError('User ID is required', 'INVALID_USER_ID');
2384
2632
  }
@@ -2435,6 +2683,7 @@ function createSessionMethods(mongoose) {
2435
2683
  */
2436
2684
  async function countActiveSessions(userId) {
2437
2685
  try {
2686
+ const Session = mongoose.models.Session;
2438
2687
  if (!userId) {
2439
2688
  throw new SessionError('User ID is required', 'INVALID_USER_ID');
2440
2689
  }
@@ -2597,6 +2846,591 @@ function createRoleMethods(mongoose) {
2597
2846
  };
2598
2847
  }
2599
2848
 
2849
+ /**
2850
+ * Formats a date in YYYY-MM-DD format
2851
+ */
2852
+ const formatDate = (date) => {
2853
+ return date.toISOString().split('T')[0];
2854
+ };
2855
+ // Factory function that takes mongoose instance and returns the methods
2856
+ function createMemoryMethods(mongoose) {
2857
+ /**
2858
+ * Creates a new memory entry for a user
2859
+ * Throws an error if a memory with the same key already exists
2860
+ */
2861
+ async function createMemory({ userId, key, value, tokenCount = 0, }) {
2862
+ try {
2863
+ if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
2864
+ return { ok: false };
2865
+ }
2866
+ const MemoryEntry = mongoose.models.MemoryEntry;
2867
+ const existingMemory = await MemoryEntry.findOne({ userId, key });
2868
+ if (existingMemory) {
2869
+ throw new Error('Memory with this key already exists');
2870
+ }
2871
+ await MemoryEntry.create({
2872
+ userId,
2873
+ key,
2874
+ value,
2875
+ tokenCount,
2876
+ updated_at: new Date(),
2877
+ });
2878
+ return { ok: true };
2879
+ }
2880
+ catch (error) {
2881
+ throw new Error(`Failed to create memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2882
+ }
2883
+ }
2884
+ /**
2885
+ * Sets or updates a memory entry for a user
2886
+ */
2887
+ async function setMemory({ userId, key, value, tokenCount = 0, }) {
2888
+ try {
2889
+ if ((key === null || key === void 0 ? void 0 : key.toLowerCase()) === 'nothing') {
2890
+ return { ok: false };
2891
+ }
2892
+ const MemoryEntry = mongoose.models.MemoryEntry;
2893
+ await MemoryEntry.findOneAndUpdate({ userId, key }, {
2894
+ value,
2895
+ tokenCount,
2896
+ updated_at: new Date(),
2897
+ }, {
2898
+ upsert: true,
2899
+ new: true,
2900
+ });
2901
+ return { ok: true };
2902
+ }
2903
+ catch (error) {
2904
+ throw new Error(`Failed to set memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2905
+ }
2906
+ }
2907
+ /**
2908
+ * Deletes a specific memory entry for a user
2909
+ */
2910
+ async function deleteMemory({ userId, key }) {
2911
+ try {
2912
+ const MemoryEntry = mongoose.models.MemoryEntry;
2913
+ const result = await MemoryEntry.findOneAndDelete({ userId, key });
2914
+ return { ok: !!result };
2915
+ }
2916
+ catch (error) {
2917
+ throw new Error(`Failed to delete memory: ${error instanceof Error ? error.message : 'Unknown error'}`);
2918
+ }
2919
+ }
2920
+ /**
2921
+ * Gets all memory entries for a user
2922
+ */
2923
+ async function getAllUserMemories(userId) {
2924
+ try {
2925
+ const MemoryEntry = mongoose.models.MemoryEntry;
2926
+ return (await MemoryEntry.find({ userId }).lean());
2927
+ }
2928
+ catch (error) {
2929
+ throw new Error(`Failed to get all memories: ${error instanceof Error ? error.message : 'Unknown error'}`);
2930
+ }
2931
+ }
2932
+ /**
2933
+ * Gets and formats all memories for a user in two different formats
2934
+ */
2935
+ async function getFormattedMemories({ userId, }) {
2936
+ try {
2937
+ const memories = await getAllUserMemories(userId);
2938
+ if (!memories || memories.length === 0) {
2939
+ return { withKeys: '', withoutKeys: '', totalTokens: 0 };
2940
+ }
2941
+ const sortedMemories = memories.sort((a, b) => new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime());
2942
+ const totalTokens = sortedMemories.reduce((sum, memory) => {
2943
+ return sum + (memory.tokenCount || 0);
2944
+ }, 0);
2945
+ const withKeys = sortedMemories
2946
+ .map((memory, index) => {
2947
+ const date = formatDate(new Date(memory.updated_at));
2948
+ const tokenInfo = memory.tokenCount ? ` [${memory.tokenCount} tokens]` : '';
2949
+ return `${index + 1}. [${date}]. ["key": "${memory.key}"]${tokenInfo}. ["value": "${memory.value}"]`;
2950
+ })
2951
+ .join('\n\n');
2952
+ const withoutKeys = sortedMemories
2953
+ .map((memory, index) => {
2954
+ const date = formatDate(new Date(memory.updated_at));
2955
+ return `${index + 1}. [${date}]. ${memory.value}`;
2956
+ })
2957
+ .join('\n\n');
2958
+ return { withKeys, withoutKeys, totalTokens };
2959
+ }
2960
+ catch (error) {
2961
+ logger.error('Failed to get formatted memories:', error);
2962
+ return { withKeys: '', withoutKeys: '', totalTokens: 0 };
2963
+ }
2964
+ }
2965
+ return {
2966
+ setMemory,
2967
+ createMemory,
2968
+ deleteMemory,
2969
+ getAllUserMemories,
2970
+ getFormattedMemories,
2971
+ };
2972
+ }
2973
+
2974
+ class ShareServiceError extends Error {
2975
+ constructor(message, code) {
2976
+ super(message);
2977
+ this.name = 'ShareServiceError';
2978
+ this.code = code;
2979
+ }
2980
+ }
2981
+ function memoizedAnonymizeId(prefix) {
2982
+ const memo = new Map();
2983
+ return (id) => {
2984
+ if (!memo.has(id)) {
2985
+ memo.set(id, `${prefix}_${nanoid()}`);
2986
+ }
2987
+ return memo.get(id);
2988
+ };
2989
+ }
2990
+ const anonymizeConvoId = memoizedAnonymizeId('convo');
2991
+ const anonymizeAssistantId = memoizedAnonymizeId('a');
2992
+ const anonymizeMessageId = (id) => id === Constants.NO_PARENT ? id : memoizedAnonymizeId('msg')(id);
2993
+ function anonymizeConvo(conversation) {
2994
+ if (!conversation) {
2995
+ return null;
2996
+ }
2997
+ const newConvo = { ...conversation };
2998
+ if (newConvo.assistant_id) {
2999
+ newConvo.assistant_id = anonymizeAssistantId(newConvo.assistant_id);
3000
+ }
3001
+ return newConvo;
3002
+ }
3003
+ function anonymizeMessages(messages, newConvoId) {
3004
+ if (!Array.isArray(messages)) {
3005
+ return [];
3006
+ }
3007
+ const idMap = new Map();
3008
+ return messages.map((message) => {
3009
+ var _a, _b;
3010
+ const newMessageId = anonymizeMessageId(message.messageId);
3011
+ idMap.set(message.messageId, newMessageId);
3012
+ const anonymizedAttachments = (_a = message.attachments) === null || _a === void 0 ? void 0 : _a.map((attachment) => {
3013
+ return {
3014
+ ...attachment,
3015
+ messageId: newMessageId,
3016
+ conversationId: newConvoId,
3017
+ };
3018
+ });
3019
+ return {
3020
+ ...message,
3021
+ messageId: newMessageId,
3022
+ parentMessageId: idMap.get(message.parentMessageId || '') ||
3023
+ anonymizeMessageId(message.parentMessageId || ''),
3024
+ conversationId: newConvoId,
3025
+ model: ((_b = message.model) === null || _b === void 0 ? void 0 : _b.startsWith('asst_'))
3026
+ ? anonymizeAssistantId(message.model)
3027
+ : message.model,
3028
+ attachments: anonymizedAttachments,
3029
+ };
3030
+ });
3031
+ }
3032
+ /** Factory function that takes mongoose instance and returns the methods */
3033
+ function createShareMethods(mongoose) {
3034
+ /**
3035
+ * Get shared messages for a public share link
3036
+ */
3037
+ async function getSharedMessages(shareId) {
3038
+ try {
3039
+ const SharedLink = mongoose.models.SharedLink;
3040
+ const share = (await SharedLink.findOne({ shareId, isPublic: true })
3041
+ .populate({
3042
+ path: 'messages',
3043
+ select: '-_id -__v -user',
3044
+ })
3045
+ .select('-_id -__v -user')
3046
+ .lean());
3047
+ if (!(share === null || share === void 0 ? void 0 : share.conversationId) || !share.isPublic) {
3048
+ return null;
3049
+ }
3050
+ const newConvoId = anonymizeConvoId(share.conversationId);
3051
+ const result = {
3052
+ shareId: share.shareId || shareId,
3053
+ title: share.title,
3054
+ isPublic: share.isPublic,
3055
+ createdAt: share.createdAt,
3056
+ updatedAt: share.updatedAt,
3057
+ conversationId: newConvoId,
3058
+ messages: anonymizeMessages(share.messages, newConvoId),
3059
+ };
3060
+ return result;
3061
+ }
3062
+ catch (error) {
3063
+ logger.error('[getSharedMessages] Error getting share link', {
3064
+ error: error instanceof Error ? error.message : 'Unknown error',
3065
+ shareId,
3066
+ });
3067
+ throw new ShareServiceError('Error getting share link', 'SHARE_FETCH_ERROR');
3068
+ }
3069
+ }
3070
+ /**
3071
+ * Get shared links for a specific user with pagination and search
3072
+ */
3073
+ async function getSharedLinks(user, pageParam, pageSize = 10, isPublic = true, sortBy = 'createdAt', sortDirection = 'desc', search) {
3074
+ var _a;
3075
+ try {
3076
+ const SharedLink = mongoose.models.SharedLink;
3077
+ const Conversation = mongoose.models.Conversation;
3078
+ const query = { user, isPublic };
3079
+ if (pageParam) {
3080
+ if (sortDirection === 'desc') {
3081
+ query[sortBy] = { $lt: pageParam };
3082
+ }
3083
+ else {
3084
+ query[sortBy] = { $gt: pageParam };
3085
+ }
3086
+ }
3087
+ if (search && search.trim()) {
3088
+ try {
3089
+ const searchResults = await Conversation.meiliSearch(search);
3090
+ if (!((_a = searchResults === null || searchResults === void 0 ? void 0 : searchResults.hits) === null || _a === void 0 ? void 0 : _a.length)) {
3091
+ return {
3092
+ links: [],
3093
+ nextCursor: undefined,
3094
+ hasNextPage: false,
3095
+ };
3096
+ }
3097
+ const conversationIds = searchResults.hits.map((hit) => hit.conversationId);
3098
+ query['conversationId'] = { $in: conversationIds };
3099
+ }
3100
+ catch (searchError) {
3101
+ logger.error('[getSharedLinks] Meilisearch error', {
3102
+ error: searchError instanceof Error ? searchError.message : 'Unknown error',
3103
+ user,
3104
+ });
3105
+ return {
3106
+ links: [],
3107
+ nextCursor: undefined,
3108
+ hasNextPage: false,
3109
+ };
3110
+ }
3111
+ }
3112
+ const sort = {};
3113
+ sort[sortBy] = sortDirection === 'desc' ? -1 : 1;
3114
+ const sharedLinks = await SharedLink.find(query)
3115
+ .sort(sort)
3116
+ .limit(pageSize + 1)
3117
+ .select('-__v -user')
3118
+ .lean();
3119
+ const hasNextPage = sharedLinks.length > pageSize;
3120
+ const links = sharedLinks.slice(0, pageSize);
3121
+ const nextCursor = hasNextPage
3122
+ ? links[links.length - 1][sortBy]
3123
+ : undefined;
3124
+ return {
3125
+ links: links.map((link) => ({
3126
+ shareId: link.shareId || '',
3127
+ title: (link === null || link === void 0 ? void 0 : link.title) || 'Untitled',
3128
+ isPublic: link.isPublic,
3129
+ createdAt: link.createdAt || new Date(),
3130
+ conversationId: link.conversationId,
3131
+ })),
3132
+ nextCursor,
3133
+ hasNextPage,
3134
+ };
3135
+ }
3136
+ catch (error) {
3137
+ logger.error('[getSharedLinks] Error getting shares', {
3138
+ error: error instanceof Error ? error.message : 'Unknown error',
3139
+ user,
3140
+ });
3141
+ throw new ShareServiceError('Error getting shares', 'SHARES_FETCH_ERROR');
3142
+ }
3143
+ }
3144
+ /**
3145
+ * Delete all shared links for a user
3146
+ */
3147
+ async function deleteAllSharedLinks(user) {
3148
+ try {
3149
+ const SharedLink = mongoose.models.SharedLink;
3150
+ const result = await SharedLink.deleteMany({ user });
3151
+ return {
3152
+ message: 'All shared links deleted successfully',
3153
+ deletedCount: result.deletedCount,
3154
+ };
3155
+ }
3156
+ catch (error) {
3157
+ logger.error('[deleteAllSharedLinks] Error deleting shared links', {
3158
+ error: error instanceof Error ? error.message : 'Unknown error',
3159
+ user,
3160
+ });
3161
+ throw new ShareServiceError('Error deleting shared links', 'BULK_DELETE_ERROR');
3162
+ }
3163
+ }
3164
+ /**
3165
+ * Create a new shared link for a conversation
3166
+ */
3167
+ async function createSharedLink(user, conversationId) {
3168
+ if (!user || !conversationId) {
3169
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3170
+ }
3171
+ try {
3172
+ const Message = mongoose.models.Message;
3173
+ const SharedLink = mongoose.models.SharedLink;
3174
+ const Conversation = mongoose.models.Conversation;
3175
+ const [existingShare, conversationMessages] = await Promise.all([
3176
+ SharedLink.findOne({ conversationId, user, isPublic: true })
3177
+ .select('-_id -__v -user')
3178
+ .lean(),
3179
+ Message.find({ conversationId, user }).sort({ createdAt: 1 }).lean(),
3180
+ ]);
3181
+ if (existingShare && existingShare.isPublic) {
3182
+ logger.error('[createSharedLink] Share already exists', {
3183
+ user,
3184
+ conversationId,
3185
+ });
3186
+ throw new ShareServiceError('Share already exists', 'SHARE_EXISTS');
3187
+ }
3188
+ else if (existingShare) {
3189
+ await SharedLink.deleteOne({ conversationId, user });
3190
+ }
3191
+ const conversation = (await Conversation.findOne({ conversationId, user }).lean());
3192
+ // Check if user owns the conversation
3193
+ if (!conversation) {
3194
+ throw new ShareServiceError('Conversation not found or access denied', 'CONVERSATION_NOT_FOUND');
3195
+ }
3196
+ // Check if there are any messages to share
3197
+ if (!conversationMessages || conversationMessages.length === 0) {
3198
+ throw new ShareServiceError('No messages to share', 'NO_MESSAGES');
3199
+ }
3200
+ const title = conversation.title || 'Untitled';
3201
+ const shareId = nanoid();
3202
+ await SharedLink.create({
3203
+ shareId,
3204
+ conversationId,
3205
+ messages: conversationMessages,
3206
+ title,
3207
+ user,
3208
+ });
3209
+ return { shareId, conversationId };
3210
+ }
3211
+ catch (error) {
3212
+ if (error instanceof ShareServiceError) {
3213
+ throw error;
3214
+ }
3215
+ logger.error('[createSharedLink] Error creating shared link', {
3216
+ error: error instanceof Error ? error.message : 'Unknown error',
3217
+ user,
3218
+ conversationId,
3219
+ });
3220
+ throw new ShareServiceError('Error creating shared link', 'SHARE_CREATE_ERROR');
3221
+ }
3222
+ }
3223
+ /**
3224
+ * Get a shared link for a conversation
3225
+ */
3226
+ async function getSharedLink(user, conversationId) {
3227
+ if (!user || !conversationId) {
3228
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3229
+ }
3230
+ try {
3231
+ const SharedLink = mongoose.models.SharedLink;
3232
+ const share = (await SharedLink.findOne({ conversationId, user, isPublic: true })
3233
+ .select('shareId -_id')
3234
+ .lean());
3235
+ if (!share) {
3236
+ return { shareId: null, success: false };
3237
+ }
3238
+ return { shareId: share.shareId || null, success: true };
3239
+ }
3240
+ catch (error) {
3241
+ logger.error('[getSharedLink] Error getting shared link', {
3242
+ error: error instanceof Error ? error.message : 'Unknown error',
3243
+ user,
3244
+ conversationId,
3245
+ });
3246
+ throw new ShareServiceError('Error getting shared link', 'SHARE_FETCH_ERROR');
3247
+ }
3248
+ }
3249
+ /**
3250
+ * Update a shared link with new messages
3251
+ */
3252
+ async function updateSharedLink(user, shareId) {
3253
+ if (!user || !shareId) {
3254
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3255
+ }
3256
+ try {
3257
+ const SharedLink = mongoose.models.SharedLink;
3258
+ const Message = mongoose.models.Message;
3259
+ const share = (await SharedLink.findOne({ shareId, user })
3260
+ .select('-_id -__v -user')
3261
+ .lean());
3262
+ if (!share) {
3263
+ throw new ShareServiceError('Share not found', 'SHARE_NOT_FOUND');
3264
+ }
3265
+ const updatedMessages = await Message.find({ conversationId: share.conversationId, user })
3266
+ .sort({ createdAt: 1 })
3267
+ .lean();
3268
+ const newShareId = nanoid();
3269
+ const update = {
3270
+ messages: updatedMessages,
3271
+ user,
3272
+ shareId: newShareId,
3273
+ };
3274
+ const updatedShare = (await SharedLink.findOneAndUpdate({ shareId, user }, update, {
3275
+ new: true,
3276
+ upsert: false,
3277
+ runValidators: true,
3278
+ }).lean());
3279
+ if (!updatedShare) {
3280
+ throw new ShareServiceError('Share update failed', 'SHARE_UPDATE_ERROR');
3281
+ }
3282
+ anonymizeConvo(updatedShare);
3283
+ return { shareId: newShareId, conversationId: updatedShare.conversationId };
3284
+ }
3285
+ catch (error) {
3286
+ logger.error('[updateSharedLink] Error updating shared link', {
3287
+ error: error instanceof Error ? error.message : 'Unknown error',
3288
+ user,
3289
+ shareId,
3290
+ });
3291
+ throw new ShareServiceError(error instanceof ShareServiceError ? error.message : 'Error updating shared link', error instanceof ShareServiceError ? error.code : 'SHARE_UPDATE_ERROR');
3292
+ }
3293
+ }
3294
+ /**
3295
+ * Delete a shared link
3296
+ */
3297
+ async function deleteSharedLink(user, shareId) {
3298
+ if (!user || !shareId) {
3299
+ throw new ShareServiceError('Missing required parameters', 'INVALID_PARAMS');
3300
+ }
3301
+ try {
3302
+ const SharedLink = mongoose.models.SharedLink;
3303
+ const result = await SharedLink.findOneAndDelete({ shareId, user }).lean();
3304
+ if (!result) {
3305
+ return null;
3306
+ }
3307
+ return {
3308
+ success: true,
3309
+ shareId,
3310
+ message: 'Share deleted successfully',
3311
+ };
3312
+ }
3313
+ catch (error) {
3314
+ logger.error('[deleteSharedLink] Error deleting shared link', {
3315
+ error: error instanceof Error ? error.message : 'Unknown error',
3316
+ user,
3317
+ shareId,
3318
+ });
3319
+ throw new ShareServiceError('Error deleting shared link', 'SHARE_DELETE_ERROR');
3320
+ }
3321
+ }
3322
+ // Return all methods
3323
+ return {
3324
+ getSharedLink,
3325
+ getSharedLinks,
3326
+ createSharedLink,
3327
+ updateSharedLink,
3328
+ deleteSharedLink,
3329
+ getSharedMessages,
3330
+ deleteAllSharedLinks,
3331
+ };
3332
+ }
3333
+
3334
+ // Factory function that takes mongoose instance and returns the methods
3335
+ function createPluginAuthMethods(mongoose) {
3336
+ /**
3337
+ * Finds a single plugin auth entry by userId and authField
3338
+ */
3339
+ async function findOnePluginAuth({ userId, authField, }) {
3340
+ try {
3341
+ const PluginAuth = mongoose.models.PluginAuth;
3342
+ return await PluginAuth.findOne({ userId, authField }).lean();
3343
+ }
3344
+ catch (error) {
3345
+ throw new Error(`Failed to find plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3346
+ }
3347
+ }
3348
+ /**
3349
+ * Finds multiple plugin auth entries by userId and pluginKeys
3350
+ */
3351
+ async function findPluginAuthsByKeys({ userId, pluginKeys, }) {
3352
+ try {
3353
+ if (!pluginKeys || pluginKeys.length === 0) {
3354
+ return [];
3355
+ }
3356
+ const PluginAuth = mongoose.models.PluginAuth;
3357
+ return await PluginAuth.find({
3358
+ userId,
3359
+ pluginKey: { $in: pluginKeys },
3360
+ }).lean();
3361
+ }
3362
+ catch (error) {
3363
+ throw new Error(`Failed to find plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3364
+ }
3365
+ }
3366
+ /**
3367
+ * Updates or creates a plugin auth entry
3368
+ */
3369
+ async function updatePluginAuth({ userId, authField, pluginKey, value, }) {
3370
+ try {
3371
+ const PluginAuth = mongoose.models.PluginAuth;
3372
+ const existingAuth = await PluginAuth.findOne({ userId, pluginKey, authField }).lean();
3373
+ if (existingAuth) {
3374
+ return await PluginAuth.findOneAndUpdate({ userId, pluginKey, authField }, { $set: { value } }, { new: true, upsert: true }).lean();
3375
+ }
3376
+ else {
3377
+ const newPluginAuth = await new PluginAuth({
3378
+ userId,
3379
+ authField,
3380
+ value,
3381
+ pluginKey,
3382
+ });
3383
+ await newPluginAuth.save();
3384
+ return newPluginAuth.toObject();
3385
+ }
3386
+ }
3387
+ catch (error) {
3388
+ throw new Error(`Failed to update plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3389
+ }
3390
+ }
3391
+ /**
3392
+ * Deletes plugin auth entries based on provided parameters
3393
+ */
3394
+ async function deletePluginAuth({ userId, authField, pluginKey, all = false, }) {
3395
+ try {
3396
+ const PluginAuth = mongoose.models.PluginAuth;
3397
+ if (all) {
3398
+ const filter = { userId };
3399
+ if (pluginKey) {
3400
+ filter.pluginKey = pluginKey;
3401
+ }
3402
+ return await PluginAuth.deleteMany(filter);
3403
+ }
3404
+ if (!authField) {
3405
+ throw new Error('authField is required when all is false');
3406
+ }
3407
+ return await PluginAuth.deleteOne({ userId, authField });
3408
+ }
3409
+ catch (error) {
3410
+ throw new Error(`Failed to delete plugin auth: ${error instanceof Error ? error.message : 'Unknown error'}`);
3411
+ }
3412
+ }
3413
+ /**
3414
+ * Deletes all plugin auth entries for a user
3415
+ */
3416
+ async function deleteAllUserPluginAuths(userId) {
3417
+ try {
3418
+ const PluginAuth = mongoose.models.PluginAuth;
3419
+ return await PluginAuth.deleteMany({ userId });
3420
+ }
3421
+ catch (error) {
3422
+ throw new Error(`Failed to delete all user plugin auths: ${error instanceof Error ? error.message : 'Unknown error'}`);
3423
+ }
3424
+ }
3425
+ return {
3426
+ findOnePluginAuth,
3427
+ findPluginAuthsByKeys,
3428
+ updatePluginAuth,
3429
+ deletePluginAuth,
3430
+ deleteAllUserPluginAuths,
3431
+ };
3432
+ }
3433
+
2600
3434
  /**
2601
3435
  * Creates all database methods for all collections
2602
3436
  */
@@ -2606,8 +3440,11 @@ function createMethods(mongoose) {
2606
3440
  ...createSessionMethods(mongoose),
2607
3441
  ...createTokenMethods(mongoose),
2608
3442
  ...createRoleMethods(mongoose),
3443
+ ...createMemoryMethods(mongoose),
3444
+ ...createShareMethods(mongoose),
3445
+ ...createPluginAuthMethods(mongoose),
2609
3446
  };
2610
3447
  }
2611
3448
 
2612
- export { Action as actionSchema, agentSchema, assistantSchema, balanceSchema, bannerSchema, categoriesSchema, conversationTag as conversationTagSchema, convoSchema, createMethods, createModels, file as fileSchema, hashToken, keySchema, logger, logger$1 as meiliLogger, messageSchema, pluginAuthSchema, presetSchema, projectSchema, promptGroupSchema, promptSchema, roleSchema, sessionSchema, shareSchema, signPayload, tokenSchema, toolCallSchema, transactionSchema, userSchema };
3449
+ export { Action as actionSchema, agentSchema, assistantSchema, balanceSchema, bannerSchema, categoriesSchema, conversationTag as conversationTagSchema, convoSchema, createMethods, createModels, file as fileSchema, hashToken, keySchema, logger, logger$1 as meiliLogger, MemoryEntrySchema as memorySchema, messageSchema, pluginAuthSchema, presetSchema, projectSchema, promptGroupSchema, promptSchema, roleSchema, sessionSchema, shareSchema, signPayload, tokenSchema, toolCallSchema, transactionSchema, userSchema };
2613
3450
  //# sourceMappingURL=index.es.js.map