@exulu/backend 1.36.0 → 1.37.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.js CHANGED
@@ -687,25 +687,11 @@ var requestValidators = {
687
687
  message: "Missing body."
688
688
  };
689
689
  }
690
- if (!req.headers["user"]) {
690
+ if (!req.body.message && !req.body.messages) {
691
691
  return {
692
692
  error: true,
693
693
  code: 400,
694
- message: 'Missing "user" property in headers.'
695
- };
696
- }
697
- if (!req.headers["session"]) {
698
- return {
699
- error: true,
700
- code: 400,
701
- message: 'Missing "session" property in headers.'
702
- };
703
- }
704
- if (!req.body.message) {
705
- return {
706
- error: true,
707
- code: 400,
708
- message: 'Missing "message" property in body.'
694
+ message: 'Missing "message" or "messages" property in body.'
709
695
  };
710
696
  }
711
697
  return {
@@ -737,6 +723,12 @@ var agentMessagesSchema = {
737
723
  name: "user",
738
724
  type: "number"
739
725
  },
726
+ {
727
+ name: "message_id",
728
+ type: "text",
729
+ index: true,
730
+ unique: true
731
+ },
740
732
  {
741
733
  name: "session",
742
734
  type: "text"
@@ -2371,9 +2363,11 @@ function createMutations(table, agents, contexts, tools, config) {
2371
2363
  const name = args.field?.replace("_s3key", "");
2372
2364
  console.log("[EXULU] name", name);
2373
2365
  console.log("[EXULU] fields", exists.fields.map((field2) => field2.name));
2374
- const field = exists.fields.find((field2) => field2.name === name);
2366
+ const field = exists.fields.find((field2) => {
2367
+ return field2.name.replace("_s3key", "") === name;
2368
+ });
2375
2369
  if (!field) {
2376
- throw new Error(`Field ${name} not found in context ${exists.id}.`);
2370
+ throw new Error(`Field ${name} not found in context ${exists.id}].`);
2377
2371
  }
2378
2372
  if (!field.processor) {
2379
2373
  throw new Error(`Processor not set for field ${args.field} in context ${exists.id}.`);
@@ -2448,7 +2442,8 @@ function createMutations(table, agents, contexts, tools, config) {
2448
2442
  item,
2449
2443
  config,
2450
2444
  context.user.id,
2451
- context.user.role?.id
2445
+ context.user.role?.id,
2446
+ item.external_id || item.id ? true : false
2452
2447
  );
2453
2448
  if (job) {
2454
2449
  jobs.push(job);
@@ -2535,22 +2530,30 @@ function createMutations(table, agents, contexts, tools, config) {
2535
2530
  if (!id) {
2536
2531
  throw new Error(`Context ${table.id} not found.`);
2537
2532
  }
2538
- let query = db3.from(getTableName(id)).select("id");
2539
2533
  if (args.where) {
2534
+ let query = db3.from(getTableName(id)).select("id");
2540
2535
  query = applyFilters(query, args.where, table);
2536
+ const items = await query;
2537
+ if (items.length === 0) {
2538
+ throw new Error("No items found to delete chunks for.");
2539
+ }
2540
+ for (const item of items) {
2541
+ await db3.from(getChunksTableName(id)).where({ source: item.id }).delete();
2542
+ }
2543
+ return {
2544
+ message: "Chunks deleted successfully.",
2545
+ items: items.length,
2546
+ jobs: []
2547
+ };
2548
+ } else {
2549
+ const count = await db3.from(getChunksTableName(id)).count();
2550
+ await db3.from(getChunksTableName(id)).truncate();
2551
+ return {
2552
+ message: "Chunks deleted successfully.",
2553
+ items: parseInt(count[0].count),
2554
+ jobs: []
2555
+ };
2541
2556
  }
2542
- const items = await query;
2543
- if (items.length === 0) {
2544
- throw new Error("No items found to delete chunks for.");
2545
- }
2546
- for (const item of items) {
2547
- await db3.from(getChunksTableName(id)).where({ source: item.id }).delete();
2548
- }
2549
- return {
2550
- message: "Chunks deleted successfully.",
2551
- items: items.length,
2552
- jobs: []
2553
- };
2554
2557
  };
2555
2558
  }
2556
2559
  return mutations;
@@ -2825,6 +2828,13 @@ var postprocessDeletion = async ({
2825
2828
  }
2826
2829
  return result;
2827
2830
  }
2831
+ if (table.type === "agent_sessions") {
2832
+ if (!result.id) {
2833
+ return result;
2834
+ }
2835
+ const { db: db3 } = await postgresClient();
2836
+ await db3.from("agent_messages").where({ session: result.id }).where({ session: result.id }).delete();
2837
+ }
2828
2838
  }
2829
2839
  return result;
2830
2840
  };
@@ -3148,7 +3158,7 @@ var vectorSearch = async ({
3148
3158
  items = await itemsQuery;
3149
3159
  break;
3150
3160
  case "hybridSearch":
3151
- const matchCount = Math.min(limit * 5, 30);
3161
+ const matchCount = Math.min(limit * 5, 100);
3152
3162
  const fullTextWeight = 1;
3153
3163
  const semanticWeight = 1;
3154
3164
  const rrfK = 50;
@@ -3175,7 +3185,7 @@ var vectorSearch = async ({
3175
3185
  FROM ${chunksTable} c
3176
3186
  WHERE c.embedding IS NOT NULL
3177
3187
  ORDER BY rank_ix
3178
- LIMIT LEAST(?, 15) * 2
3188
+ LIMIT LEAST(?, 50) * 2
3179
3189
  )
3180
3190
  SELECT
3181
3191
  m.*,
@@ -3183,6 +3193,7 @@ var vectorSearch = async ({
3183
3193
  c.source,
3184
3194
  c.content,
3185
3195
  c.chunk_index,
3196
+ c.metadata,
3186
3197
  c."createdAt" AS chunk_created_at,
3187
3198
  c."updatedAt" AS chunk_updated_at,
3188
3199
  vector_dims(c.embedding) as embedding_size,
@@ -3206,7 +3217,7 @@ var vectorSearch = async ({
3206
3217
  JOIN ${mainTable} m
3207
3218
  ON m.id = c.source
3208
3219
  ORDER BY hybrid_score DESC
3209
- LIMIT LEAST(?, 10)
3220
+ LIMIT LEAST(?, 50)
3210
3221
  OFFSET 0
3211
3222
  `;
3212
3223
  const bindings = [
@@ -3232,6 +3243,7 @@ var vectorSearch = async ({
3232
3243
  ];
3233
3244
  items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
3234
3245
  }
3246
+ console.log("[EXULU] Vector search results:", items?.length);
3235
3247
  const seenSources = /* @__PURE__ */ new Map();
3236
3248
  items = items.reduce((acc, item) => {
3237
3249
  if (!seenSources.has(item.source)) {
@@ -3249,6 +3261,7 @@ var vectorSearch = async ({
3249
3261
  chunk_index: item.chunk_index,
3250
3262
  chunk_id: item.chunk_id,
3251
3263
  source: item.source,
3264
+ metadata: item.metadata,
3252
3265
  chunk_created_at: item.chunk_created_at,
3253
3266
  chunk_updated_at: item.chunk_updated_at,
3254
3267
  embedding_size: item.embedding_size,
@@ -3265,6 +3278,7 @@ var vectorSearch = async ({
3265
3278
  chunk_id: item.chunk_id,
3266
3279
  chunk_created_at: item.chunk_created_at,
3267
3280
  embedding_size: item.embedding_size,
3281
+ metadata: item.metadata,
3268
3282
  source: item.source,
3269
3283
  chunk_updated_at: item.chunk_updated_at,
3270
3284
  ...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
@@ -3274,6 +3288,7 @@ var vectorSearch = async ({
3274
3288
  }
3275
3289
  return acc;
3276
3290
  }, []);
3291
+ console.log("[EXULU] Vector search results after deduplication:", items?.length);
3277
3292
  items.forEach((item) => {
3278
3293
  if (!item.chunks?.length) {
3279
3294
  return;
@@ -3305,7 +3320,7 @@ var vectorSearch = async ({
3305
3320
  if (resultReranker && query) {
3306
3321
  items = await resultReranker(items);
3307
3322
  }
3308
- items = items.slice(0, limit);
3323
+ console.log("[EXULU] Vector search results after slicing:", items?.length);
3309
3324
  await updateStatistic({
3310
3325
  name: "count",
3311
3326
  label: table.name.singular,
@@ -3967,6 +3982,10 @@ type PageInfo {
3967
3982
  }
3968
3983
  };
3969
3984
  }));
3985
+ let embedderQueue = void 0;
3986
+ if (data.embedder?.queue) {
3987
+ embedderQueue = await data.embedder.queue;
3988
+ }
3970
3989
  const clean = {
3971
3990
  id: data.id,
3972
3991
  name: data.name,
@@ -3974,7 +3993,8 @@ type PageInfo {
3974
3993
  embedder: data.embedder ? {
3975
3994
  name: data.embedder.name,
3976
3995
  id: data.embedder.id,
3977
- config: data.embedder?.config || void 0
3996
+ config: data.embedder?.config || void 0,
3997
+ queue: embedderQueue?.queue.name || void 0
3978
3998
  } : void 0,
3979
3999
  slug: "/contexts/" + data.id,
3980
4000
  active: data.active,
@@ -4161,6 +4181,7 @@ type ItemChunks {
4161
4181
  chunk_created_at: Date
4162
4182
  chunk_updated_at: Date
4163
4183
  embedding_size: Float
4184
+ metadata: JSON
4164
4185
  }
4165
4186
 
4166
4187
  type Provider {
@@ -4201,6 +4222,7 @@ type Embedder {
4201
4222
  name: String!
4202
4223
  id: ID!
4203
4224
  config: [EmbedderConfig!]
4225
+ queue: String
4204
4226
  }
4205
4227
  type EmbedderConfig {
4206
4228
  name: String!
@@ -4392,10 +4414,21 @@ var getPresignedUrl = async (key, config) => {
4392
4414
  if (!config.fileUploads) {
4393
4415
  throw new Error("File uploads are not configured");
4394
4416
  }
4417
+ let bucket = config.fileUploads.s3Bucket;
4418
+ if (key.includes("[bucket:")) {
4419
+ console.log("[EXULU] key includes [bucket:name]", key);
4420
+ bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4421
+ if (!bucket?.length) {
4422
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4423
+ }
4424
+ key = key.split("]")[1] || "";
4425
+ console.log("[EXULU] bucket", bucket);
4426
+ console.log("[EXULU] key", key);
4427
+ }
4395
4428
  const url = await getSignedUrl(
4396
4429
  getS3Client(config),
4397
4430
  new GetObjectCommand({
4398
- Bucket: config.fileUploads.s3Bucket,
4431
+ Bucket: bucket,
4399
4432
  Key: key
4400
4433
  }),
4401
4434
  { expiresIn }
@@ -4432,7 +4465,7 @@ var uploadFile = async (user, file, key, config, options = {}) => {
4432
4465
  ContentLength: file.byteLength
4433
4466
  });
4434
4467
  await client2.send(command);
4435
- return key;
4468
+ return fullKey;
4436
4469
  };
4437
4470
  var createUppyRoutes = async (app, config) => {
4438
4471
  if (!config.fileUploads) {
@@ -4503,29 +4536,24 @@ var createUppyRoutes = async (app, config) => {
4503
4536
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4504
4537
  return;
4505
4538
  }
4506
- const { key } = req.query;
4539
+ let { key } = req.query;
4507
4540
  if (typeof key !== "string" || key.trim() === "") {
4508
4541
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4509
4542
  return;
4510
4543
  }
4511
- const userPrefix = extractUserPrefix(key);
4512
- console.log("userPrefix", userPrefix);
4513
- console.log("authenticationResult.user.id", authenticationResult.user.id);
4514
- if (!userPrefix) {
4515
- res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
4516
- return;
4517
- }
4518
- if (userPrefix !== authenticationResult.user.id.toString()) {
4519
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4520
- return;
4521
- }
4522
- if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
4523
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4524
- return;
4544
+ let bucket = config.fileUploads.s3Bucket;
4545
+ if (key.includes("[bucket:")) {
4546
+ console.log("[EXULU] key includes [bucket:name]", key);
4547
+ bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4548
+ if (!bucket?.length) {
4549
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4550
+ }
4551
+ key = key.split("]")[1] || "";
4552
+ console.log("[EXULU] bucket", bucket);
4525
4553
  }
4526
4554
  const client2 = getS3Client(config);
4527
4555
  const command = new DeleteObjectCommand({
4528
- Bucket: config.fileUploads.s3Bucket,
4556
+ Bucket: bucket,
4529
4557
  Key: key
4530
4558
  });
4531
4559
  await client2.send(command);
@@ -4554,19 +4582,6 @@ var createUppyRoutes = async (app, config) => {
4554
4582
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4555
4583
  return;
4556
4584
  }
4557
- const userPrefix = extractUserPrefix(key);
4558
- if (!userPrefix) {
4559
- res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
4560
- return;
4561
- }
4562
- if (userPrefix !== authenticationResult.user.id.toString()) {
4563
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4564
- return;
4565
- }
4566
- if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
4567
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4568
- return;
4569
- }
4570
4585
  try {
4571
4586
  const url = await getPresignedUrl(key, config);
4572
4587
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -4596,11 +4611,24 @@ var createUppyRoutes = async (app, config) => {
4596
4611
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4597
4612
  return;
4598
4613
  }
4599
- const { key } = req.body;
4614
+ let { key } = req.body;
4615
+ let bucket = config.fileUploads.s3Bucket;
4616
+ if (key.includes("[bucket:")) {
4617
+ console.log("[EXULU] key includes [bucket:name]", key);
4618
+ bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4619
+ console.log("[EXULU] bucket", bucket);
4620
+ if (!bucket?.length) {
4621
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4622
+ }
4623
+ key = key.split("]")[1] || "";
4624
+ console.log("[EXULU] key", key);
4625
+ }
4600
4626
  console.log("[EXULU] Getting object metadata from s3", key);
4627
+ console.log("[EXULU] bucket", bucket);
4628
+ console.log("[EXULU] key", key);
4601
4629
  const client2 = getS3Client(config);
4602
4630
  const command = new HeadObjectCommand({
4603
- Bucket: config.fileUploads.s3Bucket,
4631
+ Bucket: bucket,
4604
4632
  Key: key
4605
4633
  });
4606
4634
  const response = await client2.send(command);
@@ -4918,7 +4946,7 @@ function sanitizeToolName(name) {
4918
4946
  }
4919
4947
  return sanitized;
4920
4948
  }
4921
- var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID) => {
4949
+ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req) => {
4922
4950
  if (!currentTools) return {};
4923
4951
  if (!allExuluTools) return {};
4924
4952
  if (!contexts) {
@@ -5005,6 +5033,7 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5005
5033
  const response = await cur.tool.execute({
5006
5034
  ...inputs,
5007
5035
  sessionID,
5036
+ req,
5008
5037
  // Convert config to object format if a config object
5009
5038
  // is available, after we added the .value property
5010
5039
  // by hydrating it from the variables table.
@@ -5014,6 +5043,7 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5014
5043
  user,
5015
5044
  contexts: contextsMap,
5016
5045
  upload,
5046
+ exuluConfig,
5017
5047
  config: config ? config.config.reduce((acc, curr) => {
5018
5048
  acc[curr.name] = curr.value;
5019
5049
  return acc;
@@ -5235,6 +5265,7 @@ var ExuluAgent2 = class {
5235
5265
  };
5236
5266
  generateSync = async ({
5237
5267
  prompt,
5268
+ req,
5238
5269
  user,
5239
5270
  session,
5240
5271
  inputMessages,
@@ -5316,7 +5347,8 @@ var ExuluAgent2 = class {
5316
5347
  contexts,
5317
5348
  user,
5318
5349
  exuluConfig,
5319
- session
5350
+ session,
5351
+ req
5320
5352
  ),
5321
5353
  stopWhen: [stepCountIs(2)]
5322
5354
  });
@@ -5375,7 +5407,8 @@ var ExuluAgent2 = class {
5375
5407
  contexts,
5376
5408
  user,
5377
5409
  exuluConfig,
5378
- session
5410
+ session,
5411
+ req
5379
5412
  ),
5380
5413
  stopWhen: [stepCountIs(2)]
5381
5414
  });
@@ -5427,7 +5460,8 @@ var ExuluAgent2 = class {
5427
5460
  providerapikey,
5428
5461
  contexts,
5429
5462
  exuluConfig,
5430
- instructions
5463
+ instructions,
5464
+ req
5431
5465
  }) => {
5432
5466
  if (!this.model) {
5433
5467
  console.error("[EXULU] Model is required for streaming.");
@@ -5450,7 +5484,7 @@ var ExuluAgent2 = class {
5450
5484
  console.log("[EXULU] loading previous messages from session: " + session);
5451
5485
  const previousMessages2 = await getAgentMessages({
5452
5486
  session,
5453
- user: user.id,
5487
+ user: user?.id,
5454
5488
  limit: 50,
5455
5489
  page: 1
5456
5490
  });
@@ -5489,7 +5523,8 @@ var ExuluAgent2 = class {
5489
5523
  contexts,
5490
5524
  user,
5491
5525
  exuluConfig,
5492
- session
5526
+ session,
5527
+ req
5493
5528
  ),
5494
5529
  onError: (error) => {
5495
5530
  console.error("[EXULU] chat stream error.", error);
@@ -5507,7 +5542,7 @@ var ExuluAgent2 = class {
5507
5542
  var getAgentMessages = async ({ session, user, limit, page }) => {
5508
5543
  const { db: db3 } = await postgresClient();
5509
5544
  console.log("[EXULU] getting agent messages for session: " + session + " and user: " + user + " and page: " + page);
5510
- const query = db3.from("agent_messages").where({ session, user }).limit(limit);
5545
+ const query = db3.from("agent_messages").where({ session, user: user || null }).limit(limit);
5511
5546
  if (page > 0) {
5512
5547
  query.offset((page - 1) * limit);
5513
5548
  }
@@ -5526,12 +5561,15 @@ var getSession = async ({ sessionID }) => {
5526
5561
  var saveChat = async ({ session, user, messages }) => {
5527
5562
  const { db: db3 } = await postgresClient();
5528
5563
  const promises = messages.map((message) => {
5529
- return db3.from("agent_messages").insert({
5564
+ const mutation = db3.from("agent_messages").insert({
5530
5565
  session,
5531
5566
  user,
5532
5567
  content: JSON.stringify(message),
5568
+ message_id: message.id,
5533
5569
  title: message.role === "user" ? "User" : "Assistant"
5534
- });
5570
+ }).returning("id");
5571
+ mutation.onConflict("message_id").merge();
5572
+ return mutation;
5535
5573
  });
5536
5574
  await Promise.all(promises);
5537
5575
  };
@@ -5574,19 +5612,36 @@ var ExuluEmbedder = class {
5574
5612
  });
5575
5613
  for (const config of this.config || []) {
5576
5614
  const name = config.name;
5615
+ const setting = variables.find((v) => v.name === name);
5616
+ if (!setting) {
5617
+ throw new Error("Setting value not found for embedder setting: " + name + ", for context: " + context + " and embedder: " + this.id + ". Make sure to set the value for this setting in the embedder settings.");
5618
+ }
5577
5619
  const {
5578
5620
  value: variableName,
5579
5621
  id
5580
- } = variables.find((v) => v.name === name);
5622
+ } = setting;
5581
5623
  let value = "";
5582
5624
  console.log("[EXULU] variable name", variableName);
5583
5625
  const variable = await db3.from("variables").where({ name: variableName }).first();
5584
5626
  if (!variable) {
5585
5627
  throw new Error("Variable not found for embedder setting: " + name + " in context: " + context + " and embedder: " + this.id);
5586
5628
  }
5629
+ console.log("[EXULU] variable", variable);
5587
5630
  if (variable.encrypted) {
5588
- const bytes = CryptoJS2.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5589
- value = bytes.toString(CryptoJS2.enc.Utf8);
5631
+ if (!process.env.NEXTAUTH_SECRET) {
5632
+ throw new Error("NEXTAUTH_SECRET environment variable is not set, cannot decrypt variable: " + name);
5633
+ }
5634
+ try {
5635
+ const bytes = CryptoJS2.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5636
+ const decrypted = bytes.toString(CryptoJS2.enc.Utf8);
5637
+ if (!decrypted) {
5638
+ throw new Error("Decryption returned empty string - invalid key or corrupted data");
5639
+ }
5640
+ value = decrypted;
5641
+ console.log("[EXULU] successfully decrypted value for", name);
5642
+ } catch (error) {
5643
+ throw new Error(`Failed to decrypt variable "${name}" for embedder setting in context "${context}": ${error instanceof Error ? error.message : "Unknown error"}. Verify that NEXTAUTH_SECRET matches the key used during encryption.`);
5644
+ }
5590
5645
  } else {
5591
5646
  value = variable.value;
5592
5647
  }
@@ -5745,7 +5800,9 @@ var ExuluContext = class {
5745
5800
  processField = async (trigger, user, role, item, config) => {
5746
5801
  console.log("[EXULU] processing field", item.field, " in context", this.id);
5747
5802
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
5748
- const field = this.fields.find((field2) => field2.name === item.field?.replace("_s3key", ""));
5803
+ const field = this.fields.find((field2) => {
5804
+ return field2.name.replace("_s3key", "") === item.field.replace("_s3key", "");
5805
+ });
5749
5806
  if (!field || !field.processor) {
5750
5807
  console.error("[EXULU] field not found or processor not set for field", item.field, " in context", this.id);
5751
5808
  throw new Error("Field not found or processor not set for field " + item.field + " in context " + this.id);
@@ -5755,7 +5812,7 @@ var ExuluContext = class {
5755
5812
  if (queue?.queue.name) {
5756
5813
  console.log("[EXULU] processor is in queue mode, scheduling job.");
5757
5814
  const job = await bullmqDecorator({
5758
- timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 180,
5815
+ timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
5759
5816
  label: `${this.name} ${field.name} data processor`,
5760
5817
  processor: `${this.id}-${field.name}`,
5761
5818
  context: this.id,
@@ -5846,6 +5903,7 @@ var ExuluContext = class {
5846
5903
  if (chunks?.length) {
5847
5904
  await db3.from(getChunksTableName(this.id)).insert(chunks.map((chunk) => ({
5848
5905
  source,
5906
+ metadata: chunk.metadata,
5849
5907
  content: chunk.content,
5850
5908
  chunk_index: chunk.index,
5851
5909
  embedding: pgvector2.toSql(chunk.vector)
@@ -5874,15 +5932,27 @@ var ExuluContext = class {
5874
5932
  }
5875
5933
  ).returning("id");
5876
5934
  if (upsert) {
5877
- mutation.onConflict().merge();
5935
+ if (item.external_id) {
5936
+ mutation.onConflict("external_id").merge();
5937
+ } else if (item.id) {
5938
+ mutation.onConflict("id").merge();
5939
+ } else {
5940
+ throw new Error("Either id or external_id must be provided for upsert");
5941
+ }
5878
5942
  }
5879
5943
  const results = await mutation;
5880
5944
  if (!results[0]) {
5881
5945
  throw new Error("Failed to create item.");
5882
5946
  }
5947
+ console.log("[EXULU] context configuration", this.configuration);
5883
5948
  if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
5949
+ console.log("[EXULU] generating embeddings for item", results[0].id);
5884
5950
  const { job } = await this.embeddings.generate.one({
5885
- item: results[0],
5951
+ item: {
5952
+ ...item,
5953
+ // important we need to full record here with all fields for the embedder
5954
+ id: results[0].id
5955
+ },
5886
5956
  user,
5887
5957
  role,
5888
5958
  trigger: "api",
@@ -5925,7 +5995,7 @@ var ExuluContext = class {
5925
5995
  if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
5926
5996
  const { job } = await this.embeddings.generate.one({
5927
5997
  item: record,
5928
- // important we need to full record here with all fields
5998
+ // important we need to full record here with all fields for the embedder
5929
5999
  user,
5930
6000
  role,
5931
6001
  trigger: "api",
@@ -5942,10 +6012,13 @@ var ExuluContext = class {
5942
6012
  };
5943
6013
  };
5944
6014
  deleteItem = async (item, user, role) => {
5945
- if (!item.id) {
5946
- throw new Error("Item id is required for deleting item.");
5947
- }
5948
6015
  const { db: db3 } = await postgresClient();
6016
+ if (!item.id?.length && item?.external_id) {
6017
+ item = await db3.from(getTableName(this.id)).where({ external_id: item.external_id }).first();
6018
+ if (!item || !item.id) {
6019
+ throw new Error(`Item not found for external id ${item?.external_id || "undefined"}.`);
6020
+ }
6021
+ }
5949
6022
  await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
5950
6023
  if (!this.embedder) {
5951
6024
  return {
@@ -6050,14 +6123,13 @@ var ExuluContext = class {
6050
6123
  table.text("description");
6051
6124
  table.text("tags");
6052
6125
  table.boolean("archived").defaultTo(false);
6053
- table.text("external_id");
6126
+ table.text("external_id").unique();
6054
6127
  table.text("created_by");
6055
6128
  table.text("ttl");
6056
6129
  table.text("rights_mode").defaultTo(this.configuration?.defaultRightsMode ?? "private");
6057
6130
  table.integer("textlength");
6058
6131
  table.text("source");
6059
6132
  table.timestamp("embeddings_updated_at");
6060
- table.unique(["id", "external_id"]);
6061
6133
  for (const field of this.fields) {
6062
6134
  let { type, name, unique } = field;
6063
6135
  if (!type || !name) {
@@ -6120,7 +6192,7 @@ var ExuluContext = class {
6120
6192
  const { db: db3 } = await postgresClient();
6121
6193
  const result = await vectorSearch({
6122
6194
  page: 1,
6123
- limit: 10,
6195
+ limit: 50,
6124
6196
  query,
6125
6197
  filters: [],
6126
6198
  user,
@@ -6148,7 +6220,6 @@ var ExuluContext = class {
6148
6220
  };
6149
6221
  };
6150
6222
  var updateStatistic = async (statistic) => {
6151
- console.log("[EXULU] updating statistic", statistic);
6152
6223
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6153
6224
  const { db: db3 } = await postgresClient();
6154
6225
  const existing = await db3.from("tracking").where({
@@ -6273,6 +6344,7 @@ var CLAUDE_MESSAGES = {
6273
6344
 
6274
6345
  // src/registry/routes.ts
6275
6346
  import { createIdGenerator } from "ai";
6347
+ import cookieParser from "cookie-parser";
6276
6348
  var REQUEST_SIZE_LIMIT = "50mb";
6277
6349
  var global_queues = {
6278
6350
  eval_runs: "eval_runs"
@@ -6309,6 +6381,7 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tr
6309
6381
  app.use(cors(corsOptions));
6310
6382
  app.use(bodyParser.urlencoded({ extended: true, limit: REQUEST_SIZE_LIMIT }));
6311
6383
  app.use(bodyParser.json({ limit: REQUEST_SIZE_LIMIT }));
6384
+ app.use(cookieParser());
6312
6385
  console.log(`
6313
6386
  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
6314
6387
  \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
@@ -6530,8 +6603,9 @@ Mood: friendly and intelligent.
6530
6603
  res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
6531
6604
  return;
6532
6605
  }
6606
+ console.log("[EXULU] agentInstance.rights_mode", agentInstance.rights_mode);
6533
6607
  const authenticationResult = await requestValidators.authenticate(req);
6534
- if (!authenticationResult.user?.id) {
6608
+ if (!authenticationResult.user?.id && agentInstance.rights_mode !== "public") {
6535
6609
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
6536
6610
  return;
6537
6611
  }
@@ -6555,7 +6629,7 @@ Mood: friendly and intelligent.
6555
6629
  return;
6556
6630
  }
6557
6631
  }
6558
- if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
6632
+ if (user?.type !== "api" && !user?.super_admin && req.body.resourceId !== user?.id) {
6559
6633
  res.status(400).json({
6560
6634
  message: "The provided user id in the resourceId field is not the same as the authenticated user. Only super admins and API users can impersonate other users."
6561
6635
  });
@@ -6576,7 +6650,6 @@ Mood: friendly and intelligent.
6576
6650
  return;
6577
6651
  }
6578
6652
  providerapikey = variable.value;
6579
- console.log("[EXULU] encrypted value", providerapikey);
6580
6653
  if (!variable.encrypted) {
6581
6654
  res.status(400).json({
6582
6655
  message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
@@ -6586,7 +6659,6 @@ Mood: friendly and intelligent.
6586
6659
  if (variable.encrypted) {
6587
6660
  const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
6588
6661
  providerapikey = bytes.toString(CryptoJS3.enc.Utf8);
6589
- console.log("[EXULU] decrypted value", providerapikey);
6590
6662
  }
6591
6663
  }
6592
6664
  if (!!headers.stream) {
@@ -6594,17 +6666,27 @@ Mood: friendly and intelligent.
6594
6666
  label: agent.name,
6595
6667
  trigger: "agent"
6596
6668
  };
6669
+ let previousMessages = [];
6670
+ let message;
6671
+ if (!req.body.message && !headers.session && req.body.messages) {
6672
+ message = req.body.messages[req.body.messages.length - 1];
6673
+ previousMessages = req.body.messages.slice(0, -1);
6674
+ } else {
6675
+ message = req.body.message;
6676
+ }
6597
6677
  const result = await agent.generateStream({
6598
6678
  contexts,
6599
6679
  user,
6600
6680
  instructions: agentInstance.instructions,
6601
6681
  session: headers.session,
6602
- message: req.body.message,
6682
+ message,
6683
+ previousMessages,
6603
6684
  currentTools: enabledTools,
6604
6685
  allExuluTools: tools,
6605
6686
  providerapikey,
6606
6687
  toolConfigs: agentInstance.tools,
6607
- exuluConfig: config
6688
+ exuluConfig: config,
6689
+ req
6608
6690
  });
6609
6691
  result.stream.consumeStream();
6610
6692
  result.stream.pipeUIMessageStreamToResponse(res, {
@@ -6631,11 +6713,12 @@ Mood: friendly and intelligent.
6631
6713
  size: 16
6632
6714
  }),
6633
6715
  onFinish: async ({ messages, isContinuation, isAborted, responseMessage }) => {
6634
- if (headers.session) {
6716
+ console.log("[EXULU] onFinish", messages?.map((msg) => msg.parts?.map((part) => part.type === "text" ? part.text : null)).join("\n"));
6717
+ if (headers.session && user?.id) {
6635
6718
  await saveChat({
6636
6719
  session: headers.session,
6637
6720
  user: user.id,
6638
- messages: messages.filter((x) => !result.previousMessages.find((y) => y.id === x.id))
6721
+ messages
6639
6722
  });
6640
6723
  }
6641
6724
  const metadata = messages[messages.length - 1]?.metadata;
@@ -6652,7 +6735,7 @@ Mood: friendly and intelligent.
6652
6735
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
6653
6736
  trigger: statistics.trigger,
6654
6737
  count: 1,
6655
- user: user.id,
6738
+ user: user?.id,
6656
6739
  role: user?.role?.id
6657
6740
  }),
6658
6741
  ...metadata?.inputTokens ? [
@@ -6662,7 +6745,7 @@ Mood: friendly and intelligent.
6662
6745
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
6663
6746
  trigger: statistics.trigger,
6664
6747
  count: metadata?.inputTokens,
6665
- user: user.id,
6748
+ user: user?.id,
6666
6749
  role: user?.role?.id
6667
6750
  })
6668
6751
  ] : [],
@@ -6683,6 +6766,7 @@ Mood: friendly and intelligent.
6683
6766
  } else {
6684
6767
  const response = await agent.generateSync({
6685
6768
  user,
6769
+ req,
6686
6770
  instructions: agentInstance.instructions,
6687
6771
  session: headers.session,
6688
6772
  inputMessages: [req.body.message],
@@ -7021,7 +7105,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7021
7105
  }));
7022
7106
  const { db: db3 } = await postgresClient();
7023
7107
  const data = bullmqJob.data;
7024
- const timeoutInSeconds = data.timeoutInSeconds || 300;
7108
+ const timeoutInSeconds = data.timeoutInSeconds || 600;
7025
7109
  const timeoutMs = timeoutInSeconds * 1e3;
7026
7110
  const timeoutPromise = new Promise((_, reject) => {
7027
7111
  setTimeout(() => {
@@ -7075,7 +7159,9 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7075
7159
  if (!context) {
7076
7160
  throw new Error(`Context ${data.context} not found in the registry.`);
7077
7161
  }
7078
- const field = context.fields.find((field2) => field2.name === data.inputs.field);
7162
+ const field = context.fields.find((field2) => {
7163
+ return field2.name.replace("_s3key", "") === data.inputs.field.replace("_s3key", "");
7164
+ });
7079
7165
  if (!field) {
7080
7166
  throw new Error(`Field ${data.inputs.field} not found in the context ${data.context}.`);
7081
7167
  }
@@ -7350,7 +7436,13 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7350
7436
  let jobs = [];
7351
7437
  let items = [];
7352
7438
  for (const item of result) {
7353
- const { item: createdItem, job } = await context.createItem(item, config, data.user, data.role);
7439
+ const { item: createdItem, job } = await context.createItem(
7440
+ item,
7441
+ config,
7442
+ data.user,
7443
+ data.role,
7444
+ item.external_id || item.id ? true : false
7445
+ );
7354
7446
  if (job) {
7355
7447
  jobs.push(job);
7356
7448
  console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`, logMetadata2(bullmqJob.name, {
@@ -7404,14 +7496,12 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7404
7496
  }
7405
7497
  );
7406
7498
  worker.on("completed", async (job, returnvalue) => {
7407
- console.log(`[EXULU] completed job ${job.id}.`, logMetadata2(job.name, {
7408
- result: returnvalue
7409
- }));
7499
+ console.log(`[EXULU] completed job ${job.id}.`, returnvalue);
7410
7500
  const { db: db3 } = await postgresClient();
7411
7501
  await db3.from("job_results").where({ job_id: job.id }).update({
7412
7502
  state: JOB_STATUS_ENUM.completed,
7413
- result: returnvalue.result,
7414
- metadata: returnvalue.metadata
7503
+ result: returnvalue.result ? JSON.stringify(returnvalue.result) : null,
7504
+ metadata: returnvalue.metadata ? JSON.stringify(returnvalue.metadata) : null
7415
7505
  });
7416
7506
  });
7417
7507
  worker.on("failed", async (job, error, prev) => {
@@ -8095,7 +8185,6 @@ var claudeSonnet45Agent = new ExuluAgent2({
8095
8185
 
8096
8186
  // src/templates/agents/google/vertex/index.ts
8097
8187
  import { createVertex } from "@ai-sdk/google-vertex";
8098
- import "@ai-sdk/google-vertex";
8099
8188
  var vertexAuthenticationInformation = `
8100
8189
  ### Vertex Authentication Setup (Google Auth)
8101
8190
 
@@ -8150,7 +8239,6 @@ var vertexGemini25FlashAgent = new ExuluAgent2({
8150
8239
  instructions: "",
8151
8240
  model: {
8152
8241
  create: ({ apiKey }) => {
8153
- console.log("[EXULU] apiKey", apiKey);
8154
8242
  if (!apiKey) {
8155
8243
  throw new Error("Auth credentials not found for Google Vertex agent, make sure you have set the provider api key to a valid google authentication json.");
8156
8244
  }
@@ -8161,8 +8249,45 @@ var vertexGemini25FlashAgent = new ExuluAgent2({
8161
8249
  if (!googleAuthPayload.location) {
8162
8250
  throw new Error("Location not set in authentication json for Google Vertex Gemini 2.5 Flash agent, should be for example 'europe-west1'");
8163
8251
  }
8164
- const vertex2 = createVertex(googleAuthPayload);
8165
- const model = vertex2("gemini-2.5-flash");
8252
+ const vertex = createVertex(googleAuthPayload);
8253
+ const model = vertex("gemini-2.5-flash");
8254
+ return model;
8255
+ }
8256
+ }
8257
+ }
8258
+ });
8259
+ var vertexGemini20FlashAgent = new ExuluAgent2({
8260
+ id: `default_vertex_gemini_2_0_flash_agent`,
8261
+ name: `GEMINI-2.0-FLASH`,
8262
+ provider: "vertex",
8263
+ description: `Google Vertex Gemini 2.0 Flash model. High intelligence and capability. Moderately Fast.`,
8264
+ type: "agent",
8265
+ capabilities: {
8266
+ text: true,
8267
+ images: [".png", ".jpg", ".jpeg", ".webp"],
8268
+ files: [".pdf", ".txt"],
8269
+ audio: [".mpeg", ".mp3", ".m4a", ".wav", ".mp4"],
8270
+ video: [".mp4", ".mpeg"]
8271
+ },
8272
+ authenticationInformation: vertexAuthenticationInformation,
8273
+ maxContextLength: 1048576,
8274
+ config: {
8275
+ name: `GEMINI-2.0-FLASH`,
8276
+ instructions: "",
8277
+ model: {
8278
+ create: ({ apiKey }) => {
8279
+ if (!apiKey) {
8280
+ throw new Error("Auth credentials not found for Google Vertex agent, make sure you have set the provider api key to a valid google authentication json.");
8281
+ }
8282
+ const googleAuthPayload = JSON.parse(apiKey || "{}");
8283
+ if (!googleAuthPayload) {
8284
+ throw new Error("API key not found for Google Vertex Gemini 2.0 Flash agent.");
8285
+ }
8286
+ if (!googleAuthPayload.location) {
8287
+ throw new Error("Location not set in authentication json for Google Vertex Gemini 2.0 Flash agent, should be for example 'europe-west1'");
8288
+ }
8289
+ const vertex = createVertex(googleAuthPayload);
8290
+ const model = vertex("gemini-2.0-flash");
8166
8291
  return model;
8167
8292
  }
8168
8293
  }
@@ -8188,7 +8313,6 @@ var vertexGemini3ProAgent = new ExuluAgent2({
8188
8313
  instructions: "",
8189
8314
  model: {
8190
8315
  create: ({ apiKey }) => {
8191
- console.log("[EXULU] apiKey", apiKey);
8192
8316
  if (!apiKey) {
8193
8317
  throw new Error("Auth credentials not found for Google Vertex agent, make sure you have set the provider api key to a valid google authentication json.");
8194
8318
  }
@@ -8199,8 +8323,8 @@ var vertexGemini3ProAgent = new ExuluAgent2({
8199
8323
  if (!googleAuthPayload.location) {
8200
8324
  throw new Error("Location not set in authentication json for Google Vertex Gemini 3 Pro agent, should be for example 'europe-west1'");
8201
8325
  }
8202
- const vertex2 = createVertex(googleAuthPayload);
8203
- const model = vertex2("gemini-3-pro-preview");
8326
+ const vertex = createVertex(googleAuthPayload);
8327
+ const model = vertex("gemini-3-pro-preview");
8204
8328
  return model;
8205
8329
  }
8206
8330
  }
@@ -9290,6 +9414,32 @@ var mathTools = [
9290
9414
  degreesToRadiansTool
9291
9415
  ];
9292
9416
 
9417
+ // src/templates/tools/preview-pdf.ts
9418
+ import { z as z5 } from "zod";
9419
+ var previewPdfTool = new ExuluTool2({
9420
+ id: "preview_pdf",
9421
+ name: "Preview PDF",
9422
+ description: "Used to display a PDF file in an iframe web view",
9423
+ type: "function",
9424
+ config: [],
9425
+ inputSchema: z5.object({
9426
+ s3key: z5.string().describe("The S3 key of the PDF file to preview, can also optionally include a [bucket:name] to specify the bucket."),
9427
+ page: z5.number().describe("The page number to preview, defaults to 1.").optional()
9428
+ }),
9429
+ execute: async ({ s3key, page, exuluConfig }) => {
9430
+ const url = await getPresignedUrl(s3key, exuluConfig);
9431
+ if (!url) {
9432
+ throw new Error("No URL provided for PDF preview");
9433
+ }
9434
+ return {
9435
+ result: JSON.stringify({
9436
+ url,
9437
+ page: page ?? 1
9438
+ })
9439
+ };
9440
+ }
9441
+ });
9442
+
9293
9443
  // src/templates/tools/todo/todowrite.txt
9294
9444
  var todowrite_default = `Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
9295
9445
  It also helps the user understand the progress of the task and overall progress of their requests.
@@ -9476,12 +9626,12 @@ Usage:
9476
9626
  - If no todos exist yet, an empty list will be returned`;
9477
9627
 
9478
9628
  // src/templates/tools/todo/todo.ts
9479
- import z5 from "zod";
9480
- var TodoSchema = z5.object({
9481
- content: z5.string().describe("Brief description of the task"),
9482
- status: z5.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
9483
- priority: z5.string().describe("Priority level of the task: high, medium, low"),
9484
- id: z5.string().describe("Unique identifier for the todo item")
9629
+ import z6 from "zod";
9630
+ var TodoSchema = z6.object({
9631
+ content: z6.string().describe("Brief description of the task"),
9632
+ status: z6.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
9633
+ priority: z6.string().describe("Priority level of the task: high, medium, low"),
9634
+ id: z6.string().describe("Unique identifier for the todo item")
9485
9635
  });
9486
9636
  var TodoWriteTool = new ExuluTool2({
9487
9637
  id: "todo_write",
@@ -9494,8 +9644,8 @@ var TodoWriteTool = new ExuluTool2({
9494
9644
  description: "The description of the todo list, if set overwrites the default description.",
9495
9645
  default: todowrite_default
9496
9646
  }],
9497
- inputSchema: z5.object({
9498
- todos: z5.array(TodoSchema).describe("The updated todo list")
9647
+ inputSchema: z6.object({
9648
+ todos: z6.array(TodoSchema).describe("The updated todo list")
9499
9649
  }),
9500
9650
  execute: async (inputs) => {
9501
9651
  const { sessionID, todos, user } = inputs;
@@ -9526,7 +9676,7 @@ var TodoReadTool = new ExuluTool2({
9526
9676
  id: "todo_read",
9527
9677
  name: "Todo Read",
9528
9678
  description: "Use this tool to read your todo list",
9529
- inputSchema: z5.object({}),
9679
+ inputSchema: z6.object({}),
9530
9680
  type: "function",
9531
9681
  category: "todo",
9532
9682
  config: [{
@@ -9638,6 +9788,7 @@ var ExuluApp = class {
9638
9788
  this._tools = [
9639
9789
  ...tools ?? [],
9640
9790
  ...mathTools,
9791
+ ...[previewPdfTool],
9641
9792
  ...todoTools,
9642
9793
  // Add contexts as tools
9643
9794
  ...Object.values(contexts || {}).map((context) => context.tool())