@exulu/backend 1.18.0 → 1.19.0

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.cjs CHANGED
@@ -531,6 +531,9 @@ var ExuluZodFileType = ({
531
531
  });
532
532
  };
533
533
  var ExuluAgent = class {
534
+ // Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
535
+ // underscores and be a max length of 80 characters and at least 5 characters long.
536
+ // The ID is used for storing references to agents so it is important it does not change.
534
537
  id;
535
538
  name;
536
539
  description = "";
@@ -938,38 +941,10 @@ var ExuluEval = class {
938
941
  }
939
942
  };
940
943
  };
941
- var calculateStatistics = (items, method) => {
942
- let methodProperty = "";
943
- if (method === "l1Distance") {
944
- methodProperty = "l1_distance";
945
- }
946
- if (method === "l2Distance") {
947
- methodProperty = "l2_distance";
948
- }
949
- if (method === "hammingDistance") {
950
- methodProperty = "hamming_distance";
951
- }
952
- if (method === "jaccardDistance") {
953
- methodProperty = "jaccard_distance";
954
- }
955
- if (method === "maxInnerProduct") {
956
- methodProperty = "inner_product";
957
- }
958
- if (method === "cosineDistance") {
959
- methodProperty = "cosine_distance";
960
- }
961
- const average = items.reduce((acc, item) => {
962
- return acc + item[methodProperty];
963
- }, 0) / items.length;
964
- const total = items.reduce((acc, item) => {
965
- return acc + item[methodProperty];
966
- }, 0);
967
- return {
968
- average,
969
- total
970
- };
971
- };
972
944
  var ExuluTool = class {
945
+ // Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
946
+ // underscores and be a max length of 80 characters and at least 5 characters long.
947
+ // The ID is used for storing references to tools so it is important it does not change.
973
948
  id;
974
949
  name;
975
950
  description;
@@ -991,13 +966,16 @@ var ExuluTool = class {
991
966
  });
992
967
  }
993
968
  };
994
- var getTableName = (name) => {
995
- return sanitizeName(name) + "_items";
969
+ var getTableName = (id) => {
970
+ return sanitizeName(id) + "_items";
996
971
  };
997
- var getChunksTableName = (name) => {
998
- return sanitizeName(name) + "_chunks";
972
+ var getChunksTableName = (id) => {
973
+ return sanitizeName(id) + "_chunks";
999
974
  };
1000
975
  var ExuluContext = class {
976
+ // Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
977
+ // underscores and be a max length of 80 characters and at least 5 characters long.
978
+ // The ID is used for the table name in the database, so it is important it does not change.
1001
979
  id;
1002
980
  name;
1003
981
  active;
@@ -1014,7 +992,9 @@ var ExuluContext = class {
1014
992
  this.name = name;
1015
993
  this.fields = fields || [];
1016
994
  this.configuration = configuration || {
1017
- calculateVectors: "manual"
995
+ calculateVectors: "manual",
996
+ language: "english",
997
+ defaultRightsMode: "private"
1018
998
  };
1019
999
  this.description = description;
1020
1000
  this.embedder = embedder;
@@ -1025,8 +1005,8 @@ var ExuluContext = class {
1025
1005
  }
1026
1006
  deleteAll = async () => {
1027
1007
  const { db: db3 } = await postgresClient();
1028
- await db3.from(getTableName(this.name)).delete();
1029
- await db3.from(getChunksTableName(this.name)).delete();
1008
+ await db3.from(getTableName(this.id)).delete();
1009
+ await db3.from(getChunksTableName(this.id)).delete();
1030
1010
  return {
1031
1011
  count: 0,
1032
1012
  results: []
@@ -1034,38 +1014,50 @@ var ExuluContext = class {
1034
1014
  };
1035
1015
  tableExists = async () => {
1036
1016
  const { db: db3 } = await postgresClient();
1037
- const tableExists = await db3.schema.hasTable(getTableName(this.name));
1017
+ const tableName = getTableName(this.id);
1018
+ console.log("[EXULU] checking if table exists.", tableName);
1019
+ const tableExists = await db3.schema.hasTable(tableName);
1038
1020
  return tableExists;
1039
1021
  };
1040
- createAndUpsertEmbeddings = async (item, user, statistics, role) => {
1022
+ chunksTableExists = async () => {
1023
+ const { db: db3 } = await postgresClient();
1024
+ const chunksTableName = getChunksTableName(this.id);
1025
+ const chunksTableExists = await db3.schema.hasTable(chunksTableName);
1026
+ return chunksTableExists;
1027
+ };
1028
+ createAndUpsertEmbeddings = async (item, user, statistics, role, job) => {
1041
1029
  if (!this.embedder) {
1042
1030
  throw new Error("Embedder is not set for this context.");
1043
1031
  }
1032
+ if (!item.id) {
1033
+ throw new Error("Item id is required for generating embeddings.");
1034
+ }
1044
1035
  const { db: db3 } = await postgresClient();
1045
1036
  const { id: source, chunks } = await this.embedder.generateFromDocument({
1046
1037
  ...item,
1047
1038
  id: item.id
1048
1039
  }, {
1049
- label: statistics.label || this.name,
1050
- trigger: statistics.trigger || "agent"
1040
+ label: statistics?.label || this.name,
1041
+ trigger: statistics?.trigger || "agent"
1051
1042
  }, user, role);
1052
- const exists = await db3.schema.hasTable(getChunksTableName(this.name));
1043
+ const exists = await db3.schema.hasTable(getChunksTableName(this.id));
1053
1044
  if (!exists) {
1054
1045
  await this.createChunksTable();
1055
1046
  }
1056
- await db3.from(getChunksTableName(this.name)).where({ source }).delete();
1057
- await db3.from(getChunksTableName(this.name)).insert(chunks.map((chunk) => ({
1047
+ await db3.from(getChunksTableName(this.id)).where({ source }).delete();
1048
+ await db3.from(getChunksTableName(this.id)).insert(chunks.map((chunk) => ({
1058
1049
  source,
1059
1050
  content: chunk.content,
1060
1051
  chunk_index: chunk.index,
1061
1052
  embedding: import_knex4.default.toSql(chunk.vector)
1062
1053
  })));
1063
- await db3.from(getTableName(this.name)).where({ id: item.id }).update({
1054
+ await db3.from(getTableName(this.id)).where({ id: item.id }).update({
1064
1055
  embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1065
1056
  }).returning("id");
1066
1057
  return {
1067
1058
  id: item.id,
1068
- chunks: chunks?.length || 0
1059
+ chunks: chunks?.length || 0,
1060
+ job
1069
1061
  };
1070
1062
  };
1071
1063
  async updateItem(user, item, role, trigger) {
@@ -1091,43 +1083,105 @@ var ExuluContext = class {
1091
1083
  delete payloadWithSanitizedPropertyNames.created_at;
1092
1084
  delete payloadWithSanitizedPropertyNames.upsert;
1093
1085
  payloadWithSanitizedPropertyNames.updated_at = db3.fn.now();
1094
- const result = await db3.from(getTableName(this.name)).where({ id }).update(payloadWithSanitizedPropertyNames).returning("id");
1086
+ const result = await db3.from(getTableName(this.id)).where({ id }).update(payloadWithSanitizedPropertyNames).returning("id");
1095
1087
  if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
1096
- if (this.embedder.queue?.name) {
1097
- console.log("[EXULU] embedder is in queue mode, scheduling job.");
1098
- const job = await bullmqDecorator({
1099
- label: `${this.embedder.name}`,
1100
- embedder: this.embedder.id,
1101
- context: this.id,
1102
- inputs: item,
1103
- item: item.id,
1104
- queue: this.embedder.queue,
1105
- user,
1106
- role,
1107
- trigger: trigger || "agent"
1108
- });
1109
- return {
1110
- id: result[0].id,
1111
- job: job.id
1112
- };
1113
- }
1114
- await this.createAndUpsertEmbeddings(item, user, {
1115
- label: this.embedder.name,
1088
+ const { job } = await this.embeddings.generate.one({
1089
+ item: {
1090
+ ...item,
1091
+ id: result[0].id
1092
+ },
1093
+ user,
1094
+ role,
1116
1095
  trigger: trigger || "agent"
1117
- }, role);
1096
+ });
1097
+ return {
1098
+ id: result[0].id,
1099
+ job
1100
+ };
1118
1101
  }
1119
1102
  return {
1120
1103
  id: result[0].id,
1121
1104
  job: void 0
1122
1105
  };
1123
1106
  }
1107
+ embeddings = {
1108
+ generate: {
1109
+ one: async ({
1110
+ item,
1111
+ user,
1112
+ role,
1113
+ trigger
1114
+ }) => {
1115
+ if (!this.embedder) {
1116
+ throw new Error("Embedder is not set for this context.");
1117
+ }
1118
+ if (!item.id) {
1119
+ throw new Error("Item id is required for generating embeddings.");
1120
+ }
1121
+ if (this.embedder.queue?.name) {
1122
+ console.log("[EXULU] embedder is in queue mode, scheduling job.");
1123
+ const job = await bullmqDecorator({
1124
+ label: `${this.embedder.name}`,
1125
+ embedder: this.embedder.id,
1126
+ context: this.id,
1127
+ inputs: item,
1128
+ item: item.id,
1129
+ queue: this.embedder.queue,
1130
+ user,
1131
+ role,
1132
+ trigger: trigger || "agent"
1133
+ });
1134
+ return {
1135
+ id: item.id,
1136
+ job: job.id,
1137
+ chunks: 0
1138
+ };
1139
+ }
1140
+ return await this.createAndUpsertEmbeddings(item, user, {
1141
+ label: this.embedder.name,
1142
+ trigger: trigger || "agent"
1143
+ }, role, void 0);
1144
+ },
1145
+ all: async (context, userId, roleId) => {
1146
+ const exists = await context.tableExists();
1147
+ if (!exists) {
1148
+ await context.createItemsTable();
1149
+ }
1150
+ const chunksTableExists = await context.chunksTableExists();
1151
+ if (!chunksTableExists && context.embedder) {
1152
+ await context.createChunksTable();
1153
+ }
1154
+ const { db: db3 } = await postgresClient();
1155
+ const items = await db3.from(getTableName(context.id)).select("*");
1156
+ const jobs = [];
1157
+ if (!context.embedder?.queue?.name && items.length > 2e3) {
1158
+ throw new Error(`Embedder is not in queue mode, cannot generate embeddings for more than
1159
+ 2.000 items at once, if you need to generate embeddings for more items please configure
1160
+ the embedder to use a queue. You can configure the embedder to use a queue by setting
1161
+ the queue property in the embedder configuration.`);
1162
+ }
1163
+ for (const item of items) {
1164
+ const { job } = await context.embeddings.generate.one({
1165
+ item,
1166
+ user: userId,
1167
+ role: roleId,
1168
+ trigger: "api"
1169
+ });
1170
+ if (job) {
1171
+ jobs.push(job);
1172
+ }
1173
+ }
1174
+ return jobs || [];
1175
+ }
1176
+ }
1177
+ };
1124
1178
  async insertItem(user, item, upsert = false, role, trigger) {
1125
1179
  if (!item.name) {
1126
1180
  throw new Error("Name field is required.");
1127
1181
  }
1128
1182
  const { db: db3 } = await postgresClient();
1129
1183
  if (item.external_id) {
1130
- const existingItem = await db3.from(getTableName(this.name)).where({ external_id: item.external_id }).first();
1184
+ const existingItem = await db3.from(getTableName(this.id)).where({ external_id: item.external_id }).first();
1131
1185
  if (existingItem && !upsert) {
1132
1186
  throw new Error("Item with external id " + item.external_id + " already exists.");
1133
1187
  }
@@ -1140,7 +1194,7 @@ var ExuluContext = class {
1140
1194
  }
1141
1195
  }
1142
1196
  if (upsert && item.id) {
1143
- const existingItem = await db3.from(getTableName(this.name)).where({ id: item.id }).first();
1197
+ const existingItem = await db3.from(getTableName(this.id)).where({ id: item.id }).first();
1144
1198
  if (existingItem && upsert) {
1145
1199
  await this.updateItem(user, {
1146
1200
  ...item,
@@ -1164,39 +1218,26 @@ var ExuluContext = class {
1164
1218
  });
1165
1219
  delete payloadWithSanitizedPropertyNames.id;
1166
1220
  delete payloadWithSanitizedPropertyNames.upsert;
1167
- const result = await db3.from(getTableName(this.name)).insert({
1221
+ const result = await db3.from(getTableName(this.id)).insert({
1168
1222
  ...payloadWithSanitizedPropertyNames,
1169
1223
  id: db3.fn.uuid(),
1170
1224
  created_at: db3.fn.now(),
1171
1225
  updated_at: db3.fn.now()
1172
1226
  }).returning("id");
1173
1227
  if (this.embedder && (this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always")) {
1174
- if (this.embedder.queue?.name) {
1175
- console.log("[EXULU] embedder is in queue mode, scheduling job.");
1176
- const job = await bullmqDecorator({
1177
- label: `${this.embedder.name}`,
1178
- embedder: this.embedder.id,
1179
- context: this.id,
1180
- inputs: item,
1181
- item: item.id,
1182
- queue: this.embedder.queue,
1183
- user,
1184
- role,
1185
- trigger: trigger || "agent"
1186
- });
1187
- return {
1188
- id: result[0].id,
1189
- job: job.id
1190
- };
1191
- }
1192
- console.log("[EXULU] embedder is not in queue mode, calculating vectors directly.");
1193
- await this.createAndUpsertEmbeddings({
1194
- ...item,
1195
- id: result[0].id
1196
- }, user, {
1197
- label: this.embedder.name,
1228
+ const { job } = await this.embeddings.generate.one({
1229
+ item: {
1230
+ ...item,
1231
+ id: result[0].id
1232
+ },
1233
+ user,
1234
+ role,
1198
1235
  trigger: trigger || "agent"
1199
- }, role);
1236
+ });
1237
+ return {
1238
+ id: result[0].id,
1239
+ job
1240
+ };
1200
1241
  }
1201
1242
  return {
1202
1243
  id: result[0].id,
@@ -1225,7 +1266,7 @@ var ExuluContext = class {
1225
1266
  if (page < 1) page = 1;
1226
1267
  if (limit < 1) limit = 10;
1227
1268
  let offset = (page - 1) * limit;
1228
- const mainTable = getTableName(this.name);
1269
+ const mainTable = getTableName(this.id);
1229
1270
  const { db: db3 } = await postgresClient();
1230
1271
  const columns = await db3(mainTable).columnInfo();
1231
1272
  const totalQuery = db3.count("* as count").from(mainTable).first();
@@ -1288,7 +1329,7 @@ var ExuluContext = class {
1288
1329
  if (this.queryRewriter) {
1289
1330
  query = await this.queryRewriter(query);
1290
1331
  }
1291
- const chunksTable = getChunksTableName(this.name);
1332
+ const chunksTable = getChunksTableName(this.id);
1292
1333
  itemsQuery.leftJoin(chunksTable, function() {
1293
1334
  this.on(chunksTable + ".source", "=", mainTable + ".id");
1294
1335
  });
@@ -1308,52 +1349,133 @@ var ExuluContext = class {
1308
1349
  const vector = chunks[0].vector;
1309
1350
  const vectorStr = `ARRAY[${vector.join(",")}]`;
1310
1351
  const vectorExpr = `${vectorStr}::vector`;
1352
+ const language = this.configuration.language || "english";
1353
+ let items = [];
1311
1354
  switch (method) {
1312
- case "l1Distance":
1313
- itemsQuery.select(db3.raw(`?? <-> ${vectorExpr} as l1_distance`, [`${chunksTable}.embedding`]));
1314
- itemsQuery.orderByRaw(db3.raw(`?? <-> ${vectorExpr} ASC`, [`${chunksTable}.embedding`]));
1315
- break;
1316
- case "l2Distance":
1317
- itemsQuery.select(db3.raw(`?? <-> ${vectorExpr} as l2_distance`, [`${chunksTable}.embedding`]));
1318
- itemsQuery.orderByRaw(db3.raw(`?? <-> ${vectorExpr} ASC`, [`${chunksTable}.embedding`]));
1319
- break;
1320
- case "hammingDistance":
1321
- itemsQuery.select(db3.raw(`?? <#> ${vectorExpr} as hamming_distance`, [`${chunksTable}.embedding`]));
1322
- itemsQuery.orderByRaw(db3.raw(`?? <#> ${vectorExpr} ASC`, [`${chunksTable}.embedding`]));
1323
- break;
1324
- case "jaccardDistance":
1325
- itemsQuery.select(db3.raw(`?? <#> ${vectorExpr} as jaccard_distance`, [`${chunksTable}.embedding`]));
1326
- itemsQuery.orderByRaw(db3.raw(`?? <#> ${vectorExpr} ASC`, [`${chunksTable}.embedding`]));
1327
- break;
1328
- case "maxInnerProduct":
1329
- itemsQuery.select(db3.raw(`?? <#> ${vectorExpr} as inner_product`, [`${chunksTable}.embedding`]));
1330
- itemsQuery.orderByRaw(db3.raw(`?? <#> ${vectorExpr} ASC`, [`${chunksTable}.embedding`]));
1355
+ case "tsvector":
1356
+ itemsQuery.select(db3.raw(
1357
+ `ts_rank(${chunksTable}.fts, websearch_to_tsquery(?, ?)) as fts_rank`,
1358
+ [language, query]
1359
+ )).whereRaw(
1360
+ `${chunksTable}.fts @@ websearch_to_tsquery(?, ?)`,
1361
+ [language, query]
1362
+ ).orderByRaw(`fts_rank DESC`);
1363
+ items = await itemsQuery;
1331
1364
  break;
1332
1365
  case "cosineDistance":
1333
1366
  default:
1334
- itemsQuery.select(db3.raw(`1 - (?? <#> ${vectorExpr}) as cosine_distance`, [`${chunksTable}.embedding`]));
1335
- itemsQuery.orderByRaw(db3.raw(`1 - (?? <#> ${vectorExpr}) DESC`, [`${chunksTable}.embedding`]));
1367
+ itemsQuery.whereNotNull(`${chunksTable}.embedding`);
1368
+ itemsQuery.select(
1369
+ db3.raw(`1 - (${chunksTable}.embedding <=> ${vectorExpr}) AS cosine_distance`)
1370
+ );
1371
+ itemsQuery.orderByRaw(
1372
+ `${chunksTable}.embedding <=> ${vectorExpr} ASC NULLS LAST`
1373
+ );
1374
+ items = await itemsQuery;
1336
1375
  break;
1376
+ case "hybridSearch":
1377
+ const matchCount = Math.min(limit * 5, 30);
1378
+ const fullTextWeight = 1;
1379
+ const semanticWeight = 1;
1380
+ const rrfK = 50;
1381
+ const hybridSQL = `
1382
+ WITH full_text AS (
1383
+ SELECT
1384
+ c.id,
1385
+ c.source,
1386
+ row_number() OVER (
1387
+ ORDER BY ts_rank_cd(c.fts, websearch_to_tsquery(?, ?)) DESC
1388
+ ) AS rank_ix
1389
+ FROM ${chunksTable} c
1390
+ WHERE c.fts @@ websearch_to_tsquery(?, ?)
1391
+ ORDER BY rank_ix
1392
+ LIMIT LEAST(?, 30) * 2
1393
+ ),
1394
+ semantic AS (
1395
+ SELECT
1396
+ c.id,
1397
+ c.source,
1398
+ row_number() OVER (
1399
+ ORDER BY c.embedding <=> ${vectorExpr} ASC
1400
+ ) AS rank_ix
1401
+ FROM ${chunksTable} c
1402
+ WHERE c.embedding IS NOT NULL
1403
+ ORDER BY rank_ix
1404
+ LIMIT LEAST(?, 30) * 2
1405
+ )
1406
+ SELECT
1407
+ m.*,
1408
+ c.id AS chunk_id,
1409
+ c.source,
1410
+ c.content,
1411
+ c.chunk_index,
1412
+ c.created_at AS chunk_created_at,
1413
+ c.updated_at AS chunk_updated_at,
1414
+
1415
+ /* Per-signal scores for introspection */
1416
+ ts_rank(c.fts, websearch_to_tsquery(?, ?)) AS fts_rank,
1417
+ (1 - (c.embedding <=> ${vectorExpr})) AS cosine_distance,
1418
+
1419
+ /* Hybrid RRF score */
1420
+ (
1421
+ COALESCE(1.0 / (? + ft.rank_ix), 0.0) * ?
1422
+ +
1423
+ COALESCE(1.0 / (? + se.rank_ix), 0.0) * ?
1424
+ ) AS hybrid_score
1425
+
1426
+ FROM full_text ft
1427
+ FULL OUTER JOIN semantic se
1428
+ ON ft.id = se.id
1429
+ JOIN ${chunksTable} c
1430
+ ON COALESCE(ft.id, se.id) = c.id
1431
+ JOIN ${mainTable} m
1432
+ ON m.id = c.source
1433
+ ORDER BY hybrid_score DESC
1434
+ LIMIT LEAST(?, 30)
1435
+ OFFSET 0
1436
+ `;
1437
+ const bindings = [
1438
+ // full_text: websearch_to_tsquery(lang, query) in rank and where
1439
+ language,
1440
+ query,
1441
+ language,
1442
+ query,
1443
+ matchCount,
1444
+ // full_text limit
1445
+ matchCount,
1446
+ // semantic limit
1447
+ // fts_rank (ts_rank) call
1448
+ language,
1449
+ query,
1450
+ // RRF fusion parameters
1451
+ rrfK,
1452
+ fullTextWeight,
1453
+ rrfK,
1454
+ semanticWeight,
1455
+ matchCount
1456
+ // final limit
1457
+ ];
1458
+ items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
1337
1459
  }
1338
- let items = await itemsQuery;
1460
+ console.log("items", items.map((item) => item.name + " " + item.id));
1339
1461
  const seenSources = /* @__PURE__ */ new Map();
1340
1462
  items = items.reduce((acc, item) => {
1341
1463
  if (!seenSources.has(item.source)) {
1342
1464
  seenSources.set(item.source, {
1343
1465
  ...Object.fromEntries(
1344
1466
  Object.keys(item).filter(
1345
- (key) => key !== "l1_distance" && key !== "l2_distance" && key !== "hamming_distance" && key !== "jaccard_distance" && key !== "inner_product" && key !== "cosine_distance" && key !== "content" && key !== "source" && key !== "chunk_index" && key !== "chunk_id" && key !== "chunk_created_at" && key !== "chunk_updated_at"
1467
+ (key) => key !== "cosine_distance" && // kept per chunk below
1468
+ key !== "fts_rank" && // kept per chunk below
1469
+ key !== "hybrid_score" && // we will compute per item below
1470
+ key !== "content" && key !== "source" && key !== "chunk_index" && key !== "chunk_id" && key !== "chunk_created_at" && key !== "chunk_updated_at"
1346
1471
  ).map((key) => [key, item[key]])
1347
1472
  ),
1348
1473
  chunks: [{
1349
1474
  content: item.content,
1350
1475
  chunk_index: item.chunk_index,
1351
- ...method === "l1Distance" && { l1_distance: item.l1_distance },
1352
- ...method === "l2Distance" && { l2_distance: item.l2_distance },
1353
- ...method === "hammingDistance" && { hamming_distance: item.hamming_distance },
1354
- ...method === "jaccardDistance" && { jaccard_distance: item.jaccard_distance },
1355
- ...method === "maxInnerProduct" && { inner_product: item.inner_product },
1356
- ...method === "cosineDistance" && { cosine_distance: item.cosine_distance }
1476
+ ...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
1477
+ ...(method === "tsvector" || method === "hybridSearch") && { fts_rank: item.fts_rank },
1478
+ ...method === "hybridSearch" && { hybrid_score: item.hybrid_score }
1357
1479
  }]
1358
1480
  });
1359
1481
  acc.push(seenSources.get(item.source));
@@ -1361,12 +1483,9 @@ var ExuluContext = class {
1361
1483
  seenSources.get(item.source).chunks.push({
1362
1484
  content: item.content,
1363
1485
  chunk_index: item.chunk_index,
1364
- ...method === "l1Distance" && { l1_distance: item.l1_distance },
1365
- ...method === "l2Distance" && { l2_distance: item.l2_distance },
1366
- ...method === "hammingDistance" && { hamming_distance: item.hamming_distance },
1367
- ...method === "jaccardDistance" && { jaccard_distance: item.jaccard_distance },
1368
- ...method === "maxInnerProduct" && { inner_product: item.inner_product },
1369
- ...method === "cosineDistance" && { cosine_distance: item.cosine_distance }
1486
+ ...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
1487
+ ...(method === "tsvector" || method === "hybridSearch") && { fts_rank: item.fts_rank },
1488
+ ...method === "hybridSearch" && { hybrid_score: item.hybrid_score }
1370
1489
  });
1371
1490
  }
1372
1491
  return acc;
@@ -1375,16 +1494,34 @@ var ExuluContext = class {
1375
1494
  if (!item.chunks?.length) {
1376
1495
  return;
1377
1496
  }
1378
- const {
1379
- average,
1380
- total
1381
- } = calculateStatistics(item.chunks, method ?? "cosineDistance");
1382
- item.averageRelevance = average;
1383
- item.totalRelevance = total;
1497
+ if (method === "tsvector") {
1498
+ const ranks = item.chunks.map((c) => typeof c.fts_rank === "number" ? c.fts_rank : 0);
1499
+ const total = ranks.reduce((a, b) => a + b, 0);
1500
+ const average = ranks.length ? total / ranks.length : 0;
1501
+ item.averageRelevance = average;
1502
+ item.totalRelevance = total;
1503
+ } else if (method === "cosineDistance") {
1504
+ let methodProperty = "cosine_distance";
1505
+ const average = item.chunks.reduce((acc, item2) => {
1506
+ return acc + item2[methodProperty];
1507
+ }, 0) / item.chunks.length;
1508
+ const total = item.chunks.reduce((acc, item2) => {
1509
+ return acc + item2[methodProperty];
1510
+ }, 0);
1511
+ item.averageRelevance = average;
1512
+ item.totalRelevance = total;
1513
+ } else if (method === "hybridSearch") {
1514
+ const scores = item.chunks.map((c) => typeof c.hybrid_score === "number" ? c.hybrid_score : 0);
1515
+ const total = scores.reduce((a, b) => a + b, 0);
1516
+ const average = scores.length ? total / scores.length : 0;
1517
+ item.averageRelevance = average;
1518
+ item.totalRelevance = total;
1519
+ }
1384
1520
  });
1385
1521
  if (this.resultReranker && query) {
1386
1522
  items = await this.resultReranker(items);
1387
1523
  }
1524
+ items = items.slice(0, limit);
1388
1525
  return {
1389
1526
  filters: {
1390
1527
  archived,
@@ -1402,7 +1539,7 @@ var ExuluContext = class {
1402
1539
  };
1403
1540
  createItemsTable = async () => {
1404
1541
  const { db: db3 } = await postgresClient();
1405
- const tableName = getTableName(this.name);
1542
+ const tableName = getTableName(this.id);
1406
1543
  console.log("[EXULU] Creating table: " + tableName);
1407
1544
  return await db3.schema.createTable(tableName, (table) => {
1408
1545
  console.log("[EXULU] Creating fields for table.", this.fields);
@@ -1428,19 +1565,33 @@ var ExuluContext = class {
1428
1565
  };
1429
1566
  createChunksTable = async () => {
1430
1567
  const { db: db3 } = await postgresClient();
1431
- const tableName = getChunksTableName(this.name);
1568
+ const tableName = getChunksTableName(this.id);
1432
1569
  console.log("[EXULU] Creating table: " + tableName);
1433
- return await db3.schema.createTable(tableName, (table) => {
1570
+ await db3.schema.createTable(tableName, (table) => {
1434
1571
  if (!this.embedder) {
1435
1572
  throw new Error("Embedder must be set for context " + this.name + " to create chunks table.");
1436
1573
  }
1437
1574
  table.uuid("id").primary().defaultTo(db3.fn.uuid());
1438
- table.uuid("source").references("id").inTable(getTableName(this.name));
1575
+ table.uuid("source").references("id").inTable(getTableName(this.id));
1439
1576
  table.text("content");
1440
1577
  table.integer("chunk_index");
1441
1578
  table.specificType("embedding", `vector(${this.embedder.vectorDimensions})`);
1579
+ table.specificType(
1580
+ "fts",
1581
+ `tsvector GENERATED ALWAYS AS (to_tsvector('${this.configuration.language || "english"}', coalesce(content, ''))) STORED`
1582
+ );
1583
+ table.index(["fts"], `${tableName}_fts_gin_idx`, "gin");
1584
+ table.index(["source"], `${tableName}_source_idx`);
1442
1585
  table.timestamps(true, true);
1443
1586
  });
1587
+ await db3.raw(`
1588
+ CREATE INDEX IF NOT EXISTS ${tableName}_embedding_hnsw_cosine
1589
+ ON ${tableName}
1590
+ USING hnsw (embedding vector_cosine_ops)
1591
+ WHERE embedding IS NOT NULL
1592
+ WITH (m = 16, ef_construction = 64)
1593
+ `);
1594
+ return;
1444
1595
  };
1445
1596
  // Exports the context as a tool that can be used by an agent
1446
1597
  tool = () => {
@@ -1858,9 +2009,6 @@ var requestValidators = {
1858
2009
  }
1859
2010
  };
1860
2011
 
1861
- // src/registry/routes.ts
1862
- var import_zodex = require("zodex");
1863
-
1864
2012
  // src/bullmq/queues.ts
1865
2013
  var import_bullmq4 = require("bullmq");
1866
2014
  var import_bullmq_otel = require("bullmq-otel");
@@ -1897,17 +2045,13 @@ var queues = new ExuluQueues();
1897
2045
  // types/models/vector-methods.ts
1898
2046
  var VectorMethodEnum = {
1899
2047
  "cosineDistance": "cosineDistance",
1900
- "l1Distance": "l1Distance",
1901
- "l2Distance": "l2Distance",
1902
- "hammingDistance": "hammingDistance",
1903
- "jaccardDistance": "jaccardDistance",
1904
- "maxInnerProduct": "maxInnerProduct"
2048
+ "hybridSearch": "hybridSearch",
2049
+ "tsvector": "tsvector"
1905
2050
  };
1906
2051
 
1907
2052
  // src/registry/routes.ts
1908
2053
  var import_express4 = __toESM(require("express"), 1);
1909
2054
  var import_server3 = require("@apollo/server");
1910
- var Papa = require("papaparse");
1911
2055
  var import_cors = __toESM(require("cors"), 1);
1912
2056
  var import_reflect_metadata = require("reflect-metadata");
1913
2057
 
@@ -3149,7 +3293,7 @@ function createSDL(tables, contexts, agents, tools) {
3149
3293
  const contextSchemas = [];
3150
3294
  console.log("============= Agents =============", agents?.length);
3151
3295
  contexts.forEach((context) => {
3152
- const tableName = getTableName(context.name);
3296
+ const tableName = getTableName(context.id);
3153
3297
  const definition = {
3154
3298
  name: {
3155
3299
  singular: tableName,
@@ -3476,30 +3620,6 @@ type StatisticsResult {
3476
3620
  typeDefs: fullSDL,
3477
3621
  resolvers
3478
3622
  });
3479
- console.log("\n\u{1F4CA} GraphQL Schema Overview\n");
3480
- const queriesTable = Object.keys(resolvers.Query).map((query) => ({
3481
- "Operation Type": "Query",
3482
- "Name": query,
3483
- "Description": "Retrieves data"
3484
- }));
3485
- const mutationsTable = Object.keys(resolvers.Mutation).map((mutation) => ({
3486
- "Operation Type": "Mutation",
3487
- "Name": mutation,
3488
- "Description": "Modifies data"
3489
- }));
3490
- const typesTable = tables.flatMap(
3491
- (table) => table.fields.map((field) => ({
3492
- "Type": table.name.singular,
3493
- "Field": field.name,
3494
- "Field Type": field.type,
3495
- "Required": field.required ? "Yes" : "No"
3496
- }))
3497
- );
3498
- console.log("\u{1F50D} Operations:");
3499
- console.table([...queriesTable, ...mutationsTable]);
3500
- console.log("\n\u{1F4DD} Types and Fields:");
3501
- console.table(typesTable);
3502
- console.log("\n");
3503
3623
  return schema;
3504
3624
  }
3505
3625
  var encryptSensitiveFields = (input) => {
@@ -4001,12 +4121,9 @@ var createRecurringJobs = async () => {
4001
4121
  }
4002
4122
  }
4003
4123
  );
4004
- console.log("[EXULU] recurring job schedulers:");
4005
- console.table(recurringJobSchedulersLogs);
4006
4124
  return queue;
4007
4125
  };
4008
4126
  var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
4009
- const routeLogs = [];
4010
4127
  console.log("============= agents =============", agents?.length);
4011
4128
  var corsOptions = {
4012
4129
  origin: "*",
@@ -4029,35 +4146,6 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
4029
4146
  Intelligence Management Platform
4030
4147
 
4031
4148
  `);
4032
- console.log("Agents:");
4033
- console.table(agents.map((agent) => {
4034
- return {
4035
- id: agent.id,
4036
- name: agent.name,
4037
- description: agent.description,
4038
- slug: "/agents/" + agent.id,
4039
- active: true
4040
- };
4041
- }));
4042
- console.log("Contexts:");
4043
- console.table(contexts.map((context) => {
4044
- return {
4045
- id: context.id,
4046
- name: context.name,
4047
- description: context.description,
4048
- embedder: context.embedder?.name || void 0,
4049
- slug: "/contexts/" + context.id,
4050
- active: context.active
4051
- };
4052
- }));
4053
- routeLogs.push(
4054
- { route: "/contexts", method: "GET", note: "List all contexts" },
4055
- { route: "/contexts/:id", method: "GET", note: "Get specific context" },
4056
- { route: "/items/:context", method: "POST", note: "Create new item in context" },
4057
- { route: "/items/:context", method: "GET", note: "Get items from context" },
4058
- { route: "/items/export/:context", method: "GET", note: "Export items from context" },
4059
- { route: "/graphql", method: "POST", note: "GraphQL endpoint" }
4060
- );
4061
4149
  if (redisServer.host?.length && redisServer.port?.length) {
4062
4150
  await createRecurringJobs();
4063
4151
  } else {
@@ -4076,15 +4164,12 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
4076
4164
  statisticsSchema2(),
4077
4165
  rbacSchema2()
4078
4166
  ], contexts, agents, tools);
4079
- console.log("[EXULU] graphql server");
4080
4167
  const server = new import_server3.ApolloServer({
4081
4168
  cache: new import_utils2.InMemoryLRUCache(),
4082
4169
  schema,
4083
4170
  introspection: true
4084
4171
  });
4085
- console.log("[EXULU] starting graphql server");
4086
4172
  await server.start();
4087
- console.log("[EXULU] graphql server started");
4088
4173
  app.use(
4089
4174
  "/graphql",
4090
4175
  (0, import_cors.default)(corsOptions),
@@ -4223,9 +4308,9 @@ Mood: friendly and intelligent.
4223
4308
  }
4224
4309
  const exists = await context.tableExists();
4225
4310
  if (!exists) {
4226
- throw new Error("Table with name " + getTableName(context.name) + " does not exist.");
4311
+ throw new Error("Table with name " + getTableName(context.id) + " does not exist.");
4227
4312
  }
4228
- const query = db3.from(getTableName(context.name)).select("id");
4313
+ const query = db3.from(getTableName(context.id)).select("id");
4229
4314
  if (id) {
4230
4315
  query.where({ id });
4231
4316
  }
@@ -4237,12 +4322,12 @@ Mood: friendly and intelligent.
4237
4322
  return null;
4238
4323
  }
4239
4324
  if (context.embedder) {
4240
- const chunks = await db3.from(getChunksTableName(context.name)).where({ source: item.id }).select("id");
4325
+ const chunks = await db3.from(getChunksTableName(context.id)).where({ source: item.id }).select("id");
4241
4326
  if (chunks.length > 0) {
4242
- await db3.from(getChunksTableName(context.name)).where({ source: item.id }).delete();
4327
+ await db3.from(getChunksTableName(context.id)).where({ source: item.id }).delete();
4243
4328
  }
4244
4329
  }
4245
- const mutation = db3.from(getTableName(context.name)).where({ id: item.id }).delete().returning("id");
4330
+ const mutation = db3.from(getTableName(context.id)).where({ id: item.id }).delete().returning("id");
4246
4331
  const result = await mutation;
4247
4332
  return result;
4248
4333
  };
@@ -4284,6 +4369,47 @@ Mood: friendly and intelligent.
4284
4369
  }
4285
4370
  res.status(200).json(result);
4286
4371
  });
4372
+ app.delete("/items/:context/:id/chunks/delete", async (req, res) => {
4373
+ console.log("[EXULU] deleting chunks for item.", req.params.id);
4374
+ const authenticationResult = await requestValidators.authenticate(req);
4375
+ if (!authenticationResult.user?.id) {
4376
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4377
+ return;
4378
+ }
4379
+ const { db: db3 } = await postgresClient();
4380
+ const context = contexts.find((context2) => context2.id === req.params.context);
4381
+ if (!context) {
4382
+ res.status(400).json({
4383
+ message: "Context not found in registry."
4384
+ });
4385
+ return;
4386
+ }
4387
+ await db3.from(getChunksTableName(context.id)).where({ source: req.params.id }).delete();
4388
+ res.status(200).json({
4389
+ message: "Chunks deleted successfully."
4390
+ });
4391
+ });
4392
+ app.delete("/items/:context/chunks/delete", async (req, res) => {
4393
+ console.log("[EXULU] deleting chunks for context.", req.params.context);
4394
+ const authenticationResult = await requestValidators.authenticate(req);
4395
+ if (!authenticationResult.user?.id) {
4396
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4397
+ return;
4398
+ }
4399
+ const { db: db3 } = await postgresClient();
4400
+ const context = contexts.find((context2) => context2.id === req.params.context);
4401
+ if (!context) {
4402
+ res.status(400).json({
4403
+ message: "Context not found in registry."
4404
+ });
4405
+ return;
4406
+ }
4407
+ console.log("[EXULU] truncating chunks table for context.", context.id);
4408
+ await db3.from(getChunksTableName(context.id)).truncate();
4409
+ res.status(200).json({
4410
+ message: "Chunks deleted successfully."
4411
+ });
4412
+ });
4287
4413
  app.get("/items/:context/:id", async (req, res) => {
4288
4414
  if (!req.params.context) {
4289
4415
  res.status(400).json({
@@ -4306,14 +4432,15 @@ Mood: friendly and intelligent.
4306
4432
  return;
4307
4433
  }
4308
4434
  const itemsTableExists = await context.tableExists();
4435
+ console.log("[EXULU] items table exists.", itemsTableExists);
4309
4436
  if (!itemsTableExists) {
4310
4437
  await context.createItemsTable();
4311
4438
  }
4312
- const chunksTableExists = await db3.schema.hasTable(getChunksTableName(context.name));
4439
+ const chunksTableExists = await context.chunksTableExists();
4313
4440
  if (!chunksTableExists && context.embedder) {
4314
4441
  await context.createChunksTable();
4315
4442
  }
4316
- const item = await db3.from(getTableName(context.name)).where({ id: req.params.id }).select("*").first();
4443
+ const item = await db3.from(getTableName(context.id)).where({ id: req.params.id }).select("*").first();
4317
4444
  if (!item) {
4318
4445
  res.status(404).json({
4319
4446
  message: "Item not found."
@@ -4326,8 +4453,8 @@ Mood: friendly and intelligent.
4326
4453
  });
4327
4454
  return;
4328
4455
  }
4329
- console.log("[EXULU] chunks table name.", getChunksTableName(context.name));
4330
- const chunks = await db3.from(getChunksTableName(context.name)).where({ source: req.params.id }).select("id", "content", "source", "embedding", "chunk_index", "created_at", "updated_at");
4456
+ console.log("[EXULU] chunks table name.", getChunksTableName(context.id));
4457
+ const chunks = await db3.from(getChunksTableName(context.id)).where({ source: req.params.id }).select("id", "content", "source", "embedding", "chunk_index", "created_at", "updated_at");
4331
4458
  console.log("[EXULU] chunks", chunks);
4332
4459
  res.status(200).json({
4333
4460
  ...item,
@@ -4342,6 +4469,88 @@ Mood: friendly and intelligent.
4342
4469
  }))
4343
4470
  });
4344
4471
  });
4472
+ app.post("/items/:context/chunks/generate/all", async (req, res) => {
4473
+ if (!req.params.context) {
4474
+ res.status(400).json({
4475
+ message: "Missing context in request."
4476
+ });
4477
+ return;
4478
+ }
4479
+ const authenticationResult = await requestValidators.authenticate(req);
4480
+ if (!authenticationResult.user?.id) {
4481
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4482
+ return;
4483
+ }
4484
+ const user = authenticationResult.user;
4485
+ const context = contexts.find((context2) => context2.id === req.params.context);
4486
+ if (!context) {
4487
+ res.status(400).json({
4488
+ message: "Context not found in registry."
4489
+ });
4490
+ return;
4491
+ }
4492
+ const jobs = await context.embeddings.generate.all(context, user.id, user.role?.id);
4493
+ res.status(200).json({
4494
+ message: "Embeddings generated successfully.",
4495
+ jobs
4496
+ });
4497
+ });
4498
+ app.post("/items/:context/chunks/generate/one/:id", async (req, res) => {
4499
+ if (!req.params.context) {
4500
+ res.status(400).json({
4501
+ message: "Missing context in request."
4502
+ });
4503
+ return;
4504
+ }
4505
+ if (!req.params.id) {
4506
+ res.status(400).json({
4507
+ message: "Missing id in request."
4508
+ });
4509
+ return;
4510
+ }
4511
+ const authenticationResult = await requestValidators.authenticate(req);
4512
+ if (!authenticationResult.user?.id) {
4513
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4514
+ return;
4515
+ }
4516
+ const user = authenticationResult.user;
4517
+ const context = contexts.find((context2) => context2.id === req.params.context);
4518
+ if (!context) {
4519
+ res.status(400).json({
4520
+ message: "Context not found in registry."
4521
+ });
4522
+ return;
4523
+ }
4524
+ const exists = await context.tableExists();
4525
+ if (!exists) {
4526
+ await context.createItemsTable();
4527
+ }
4528
+ const chunksTableExists = await context.chunksTableExists();
4529
+ if (!chunksTableExists && context.embedder) {
4530
+ await context.createChunksTable();
4531
+ }
4532
+ const { db: db3 } = await postgresClient();
4533
+ const item = await db3.from(getTableName(context.id)).where({ id: req.params.id }).select("*").first();
4534
+ if (!item) {
4535
+ res.status(404).json({
4536
+ message: "Item not found."
4537
+ });
4538
+ return;
4539
+ }
4540
+ const { job } = await context.embeddings.generate.one({
4541
+ item: {
4542
+ ...item,
4543
+ id: item.id
4544
+ },
4545
+ user: user.id,
4546
+ role: user.role?.id,
4547
+ trigger: "api"
4548
+ });
4549
+ res.status(200).json({
4550
+ message: job ? "Job scheduled to generate embeddings successfully." : "Embeddings generated successfully.",
4551
+ job
4552
+ });
4553
+ });
4345
4554
  app.post("/items/:context/:id", async (req, res) => {
4346
4555
  if (!req.params.context) {
4347
4556
  res.status(400).json({
@@ -4387,7 +4596,6 @@ Mood: friendly and intelligent.
4387
4596
  });
4388
4597
  app.post("/items/:context", async (req, res) => {
4389
4598
  try {
4390
- console.log("[EXULU] post items");
4391
4599
  if (!req.params.context) {
4392
4600
  res.status(400).json({
4393
4601
  message: "Missing context in request."
@@ -4484,6 +4692,10 @@ Mood: friendly and intelligent.
4484
4692
  if (!exists) {
4485
4693
  await context.createItemsTable();
4486
4694
  }
4695
+ const chunksTableExists = await context.chunksTableExists();
4696
+ if (!chunksTableExists && context.embedder) {
4697
+ await context.createChunksTable();
4698
+ }
4487
4699
  if (req.query.method && !Object.values(VectorMethodEnum).includes(req.query.method)) {
4488
4700
  res.status(400).json({
4489
4701
  message: "Invalid vector lookup method, must be one of: " + Object.values(VectorMethodEnum).join(", ")
@@ -4523,11 +4735,6 @@ Mood: friendly and intelligent.
4523
4735
  agents.forEach((agent) => {
4524
4736
  const slug = agent.slug;
4525
4737
  if (!slug) return;
4526
- routeLogs.push({
4527
- route: slug + "/:instance",
4528
- method: "POST",
4529
- note: `Agent endpoint for ${agent.id}`
4530
- });
4531
4738
  app.post(slug + "/:instance", async (req, res) => {
4532
4739
  const instance = req.params.instance;
4533
4740
  if (!instance) {
@@ -4649,8 +4856,6 @@ Mood: friendly and intelligent.
4649
4856
  } else {
4650
4857
  console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
4651
4858
  }
4652
- console.log("Routes:");
4653
- console.table(routeLogs);
4654
4859
  const TARGET_API = "https://api.anthropic.com";
4655
4860
  app.use("/gateway/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
4656
4861
  const path3 = req.url;
@@ -4995,8 +5200,6 @@ ${code}`
4995
5200
  }]
4996
5201
  })
4997
5202
  );
4998
- console.log("Contexts:");
4999
- console.table();
5000
5203
  };
5001
5204
  connect = async () => {
5002
5205
  if (!this.express) {
@@ -5051,14 +5254,6 @@ ${code}`
5051
5254
  };
5052
5255
  this.express.get("/mcp", handleSessionRequest);
5053
5256
  this.express.delete("/mcp", handleSessionRequest);
5054
- const routeLogs = [];
5055
- routeLogs.push(
5056
- { route: "/mcp", method: "GET", note: "Get MCP server status" },
5057
- { route: "/mcp", method: "POST", note: "Send MCP request" },
5058
- { route: "/mcp", method: "DELETE", note: "Terminate MCP session" }
5059
- );
5060
- console.log("MCP Routes:");
5061
- console.table(routeLogs);
5062
5257
  return this.express;
5063
5258
  };
5064
5259
  };
@@ -5067,9 +5262,8 @@ ${code}`
5067
5262
  var import_express8 = __toESM(require("express"), 1);
5068
5263
 
5069
5264
  // src/templates/agents/claude-code.ts
5070
- var agentId = "0832-5178-1145-2194";
5071
5265
  var claudeCodeAgent = new ExuluAgent({
5072
- id: `${agentId}-claude-code-agent`,
5266
+ id: `claude_code_agent`,
5073
5267
  name: `Claude Code Agent`,
5074
5268
  description: `Claude Code agent, enabling the creation of multiple Claude Code Agent instances with different configurations (rate limits, functions, etc).`,
5075
5269
  type: "custom"
@@ -5077,9 +5271,8 @@ var claudeCodeAgent = new ExuluAgent({
5077
5271
 
5078
5272
  // src/templates/agents/claude-opus-4.ts
5079
5273
  var import_anthropic = require("@ai-sdk/anthropic");
5080
- var agentId2 = "5434-5678-9143-2590";
5081
5274
  var defaultAgent = new ExuluAgent({
5082
- id: `${agentId2}-default-claude-4-opus-agent`,
5275
+ id: `default_claude_4_opus_agent`,
5083
5276
  name: `Default Claude 4 Opus Agent`,
5084
5277
  description: `Basic agent without any defined tools, that can support MCP's.`,
5085
5278
  type: "agent",
@@ -5146,7 +5339,7 @@ var logger_default = createLogger;
5146
5339
 
5147
5340
  // src/templates/contexts/code-standards.ts
5148
5341
  var codeStandardsContext = new ExuluContext({
5149
- id: "code-standards",
5342
+ id: "code_standards",
5150
5343
  name: "Code Standards",
5151
5344
  description: "Code standards that can be used with the Exulu CLI.",
5152
5345
  configuration: {
@@ -5184,6 +5377,13 @@ var projectsContext = new ExuluContext({
5184
5377
  });
5185
5378
 
5186
5379
  // src/registry/index.ts
5380
+ var isValidPostgresName = (id) => {
5381
+ console.log("[EXULU] validating context id.", id);
5382
+ const regex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
5383
+ const isValid = regex.test(id);
5384
+ const length = id.length;
5385
+ return isValid && length <= 80 && length > 5;
5386
+ };
5187
5387
  var ExuluApp = class {
5188
5388
  _agents = [];
5189
5389
  _config;
@@ -5214,6 +5414,28 @@ var ExuluApp = class {
5214
5414
  // Add agents as tools
5215
5415
  ...(agents || []).map((agent) => agent.tool())
5216
5416
  ];
5417
+ const checks = [
5418
+ ...Object.keys(this._contexts || {}).map((x) => ({
5419
+ name: this._contexts?.[x]?.name ?? "",
5420
+ id: this._contexts?.[x]?.id ?? "",
5421
+ type: "context"
5422
+ })),
5423
+ ...this._agents.map((agent) => ({
5424
+ name: agent.name ?? "",
5425
+ id: agent.id ?? "",
5426
+ type: "agent"
5427
+ })),
5428
+ ...this._tools.map((tool2) => ({
5429
+ name: tool2.name ?? "",
5430
+ id: tool2.id ?? "",
5431
+ type: "tool"
5432
+ }))
5433
+ ];
5434
+ const invalid = checks.filter((x) => !isValidPostgresName(x?.id ?? ""));
5435
+ if (invalid.length > 0) {
5436
+ console.error(`%c[EXULU] Invalid ID found for a context, tool or agent: ${invalid.map((x) => x.id).join(", ")}. An ID must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or underscores and be a max length of 80 characters and at least 5 characters long.`, "color: orange; font-weight: bold; \n \n");
5437
+ throw new Error(`Invalid ID found for a context, tool or agent: ${invalid.map((x) => x.id).join(", ")}. An ID must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or underscores and be a max length of 80 characters and at least 5 characters long.`);
5438
+ }
5217
5439
  const contextsArray = Object.values(contexts || {});
5218
5440
  const queues2 = [
5219
5441
  ...contextsArray?.length ? contextsArray.map((context) => context.embedder?.queue?.name || null) : []
@@ -5250,6 +5472,34 @@ var ExuluApp = class {
5250
5472
  get agents() {
5251
5473
  return this._agents;
5252
5474
  }
5475
+ embeddings = {
5476
+ generate: {
5477
+ one: async ({
5478
+ context: contextId,
5479
+ item: itemId
5480
+ }) => {
5481
+ const { db: db3 } = await postgresClient();
5482
+ const item = await db3.from(getTableName(contextId)).where({ id: itemId }).select("*").first();
5483
+ const context = this.contexts.find((x) => contextId === x.id);
5484
+ if (!context) {
5485
+ throw new Error(`Context ${contextId} not found in registry.`);
5486
+ }
5487
+ return await context.embeddings.generate.one({
5488
+ item,
5489
+ trigger: "api"
5490
+ });
5491
+ },
5492
+ all: async ({
5493
+ context: contextId
5494
+ }) => {
5495
+ const context = this.contexts.find((x) => contextId === x.id);
5496
+ if (!context) {
5497
+ throw new Error(`Context ${contextId} not found in registry.`);
5498
+ }
5499
+ return await context.embeddings.generate.all(context, void 0, void 0);
5500
+ }
5501
+ }
5502
+ };
5253
5503
  bullmq = {
5254
5504
  workers: {
5255
5505
  create: async () => {