@exulu/backend 1.39.3 → 1.40.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
@@ -15,7 +15,6 @@ var redisServer = {
15
15
  // src/redis/client.ts
16
16
  var client = {};
17
17
  async function redisClient() {
18
- console.log("[EXULU] redisServer:", redisServer);
19
18
  if (!redisServer.host || !redisServer.port) {
20
19
  return { client: null };
21
20
  }
@@ -96,7 +95,6 @@ var db = {};
96
95
  var databaseExistsChecked = false;
97
96
  var dbName = process.env.POSTGRES_DB_NAME || "exulu";
98
97
  async function ensureDatabaseExists() {
99
- console.log(`[EXULU] Ensuring ${dbName} database exists...`);
100
98
  const defaultKnex = Knex({
101
99
  client: "pg",
102
100
  connection: {
@@ -140,16 +138,7 @@ async function ensureDatabaseExists() {
140
138
  async function postgresClient() {
141
139
  if (!db["exulu"]) {
142
140
  try {
143
- console.log(`[EXULU] Connecting to ${dbName} database.`);
144
- console.log("[EXULU] POSTGRES_DB_HOST:", process.env.POSTGRES_DB_HOST);
145
- console.log("[EXULU] POSTGRES_DB_PORT:", process.env.POSTGRES_DB_PORT);
146
- console.log("[EXULU] POSTGRES_DB_USER:", process.env.POSTGRES_DB_USER);
147
- console.log("[EXULU] POSTGRES_DB_PASSWORD:", process.env.POSTGRES_DB_PASSWORD);
148
- console.log("[EXULU] POSTGRES_DB_NAME:", dbName);
149
- console.log("[EXULU] POSTGRES_DB_SSL:", process.env.POSTGRES_DB_SSL);
150
- console.log("[EXULU] Database exists checked:", databaseExistsChecked);
151
141
  if (!databaseExistsChecked) {
152
- console.log(`[EXULU] Ensuring ${dbName} database exists...`);
153
142
  await ensureDatabaseExists();
154
143
  databaseExistsChecked = true;
155
144
  }
@@ -474,7 +463,6 @@ var authentication = async ({
474
463
  }
475
464
  if (authtoken) {
476
465
  try {
477
- console.log("[EXULU] authtoken", authtoken);
478
466
  if (!authtoken?.email) {
479
467
  return {
480
468
  error: true,
@@ -3678,7 +3666,10 @@ type PageInfo {
3678
3666
  const config2 = await queue.use();
3679
3667
  return {
3680
3668
  name: config2.queue.name,
3681
- concurrency: config2.concurrency,
3669
+ concurrency: {
3670
+ worker: config2.concurrency?.worker || void 0,
3671
+ queue: config2.concurrency?.queue || void 0
3672
+ },
3682
3673
  ratelimit: config2.ratelimit,
3683
3674
  isMaxed: await config2.queue.isMaxed(),
3684
3675
  isPaused: await config2.queue.isPaused(),
@@ -3728,7 +3719,10 @@ type PageInfo {
3728
3719
  if (!agentInstance) {
3729
3720
  throw new Error("Agent instance not found for eval run.");
3730
3721
  }
3731
- const evalQueue = await queues.register("eval_runs", 1, 1).use();
3722
+ const evalQueue = await queues.register("eval_runs", {
3723
+ worker: 1,
3724
+ queue: 1
3725
+ }, 1).use();
3732
3726
  const jobIds = [];
3733
3727
  for (const testCase of testCases) {
3734
3728
  const jobData = {
@@ -3852,7 +3846,6 @@ type PageInfo {
3852
3846
  if (!client2) {
3853
3847
  throw new Error("Redis client not created properly");
3854
3848
  }
3855
- console.log("[EXULU] Jobs pagination args", args);
3856
3849
  const {
3857
3850
  jobs,
3858
3851
  count
@@ -3862,7 +3855,6 @@ type PageInfo {
3862
3855
  args.page || 1,
3863
3856
  args.limit || 100
3864
3857
  );
3865
- console.log("[EXULU] jobs", jobs.map((job) => job.name));
3866
3858
  const requestedFields = getRequestedFields(info);
3867
3859
  return {
3868
3860
  items: await Promise.all(jobs.map(async (job) => {
@@ -3891,6 +3883,25 @@ type PageInfo {
3891
3883
  };
3892
3884
  resolvers.Query["contexts"] = async (_, args, context, info) => {
3893
3885
  const data = await Promise.all(contexts.map(async (context2) => {
3886
+ let processors = await Promise.all(context2.fields.map(async (field) => {
3887
+ if (field.processor) {
3888
+ let queueName = void 0;
3889
+ if (field.processor?.config?.queue) {
3890
+ const config2 = await field.processor?.config?.queue;
3891
+ queueName = config2?.queue?.name || void 0;
3892
+ }
3893
+ return {
3894
+ field: field.name,
3895
+ description: field.processor?.description,
3896
+ queue: queueName,
3897
+ trigger: field.processor?.config?.trigger,
3898
+ timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
3899
+ generateEmbeddings: field.processor?.config?.generateEmbeddings || false
3900
+ };
3901
+ }
3902
+ return null;
3903
+ }));
3904
+ processors = processors.filter((processor) => processor !== null);
3894
3905
  const sources = await Promise.all(context2.sources.map(async (source) => {
3895
3906
  let queueName = void 0;
3896
3907
  if (source.config) {
@@ -3922,6 +3933,7 @@ type PageInfo {
3922
3933
  slug: "/contexts/" + context2.id,
3923
3934
  active: context2.active,
3924
3935
  sources,
3936
+ processors,
3925
3937
  fields: context2.fields.map((field) => {
3926
3938
  return {
3927
3939
  ...field,
@@ -3947,6 +3959,25 @@ type PageInfo {
3947
3959
  if (!data) {
3948
3960
  return null;
3949
3961
  }
3962
+ let processors = await Promise.all(data.fields.map(async (field) => {
3963
+ if (field.processor) {
3964
+ let queueName = void 0;
3965
+ if (field.processor?.config?.queue) {
3966
+ const config2 = await field.processor?.config?.queue;
3967
+ queueName = config2?.queue?.name || void 0;
3968
+ }
3969
+ return {
3970
+ field: field.name,
3971
+ description: field.processor?.description,
3972
+ queue: queueName,
3973
+ trigger: field.processor?.config?.trigger,
3974
+ timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
3975
+ generateEmbeddings: field.processor?.config?.generateEmbeddings || false
3976
+ };
3977
+ }
3978
+ return null;
3979
+ }));
3980
+ processors = processors.filter((processor) => processor !== null);
3950
3981
  const sources = await Promise.all(data.sources.map(async (source) => {
3951
3982
  let queueName = void 0;
3952
3983
  if (source.config) {
@@ -3983,6 +4014,7 @@ type PageInfo {
3983
4014
  slug: "/contexts/" + data.id,
3984
4015
  active: data.active,
3985
4016
  sources,
4017
+ processors,
3986
4018
  fields: await Promise.all(data.fields.map(async (field) => {
3987
4019
  const label = field.name?.replace("_s3key", "");
3988
4020
  if (field.type === "file" && !field.name.endsWith("_s3key")) {
@@ -4006,7 +4038,10 @@ type PageInfo {
4006
4038
  queue: {
4007
4039
  name: queue?.queue.name || void 0,
4008
4040
  ratelimit: queue?.ratelimit || void 0,
4009
- concurrency: queue?.concurrency || void 0
4041
+ concurrency: {
4042
+ worker: queue?.concurrency?.worker || void 0,
4043
+ queue: queue?.concurrency?.queue || void 0
4044
+ }
4010
4045
  }
4011
4046
  },
4012
4047
  execute: "function"
@@ -4079,13 +4114,19 @@ type PageInfo {
4079
4114
  modelDefs += `
4080
4115
  type QueueResult {
4081
4116
  name: String!
4082
- concurrency: Int!
4117
+ concurrency: QueueConcurrency!
4083
4118
  ratelimit: Int!
4084
4119
  isMaxed: Boolean!
4085
4120
  isPaused: Boolean!
4086
4121
  jobs: QueueJobsCounts
4087
4122
  }
4088
4123
  `;
4124
+ modelDefs += `
4125
+ type QueueConcurrency {
4126
+ worker: Int
4127
+ queue: Int
4128
+ }
4129
+ `;
4089
4130
  modelDefs += `
4090
4131
  type QueueJobsCounts {
4091
4132
  paused: Int!
@@ -4200,7 +4241,8 @@ type Context {
4200
4241
  active: Boolean
4201
4242
  fields: JSON
4202
4243
  configuration: JSON
4203
- sources: [ContextSource!]
4244
+ sources: [ContextSource]
4245
+ processors: [ContextProcessor]
4204
4246
  }
4205
4247
  type Embedder {
4206
4248
  name: String!
@@ -4213,6 +4255,14 @@ type EmbedderConfig {
4213
4255
  description: String
4214
4256
  default: String
4215
4257
  }
4258
+ type ContextProcessor {
4259
+ field: String!
4260
+ description: String
4261
+ queue: String
4262
+ trigger: String
4263
+ timeoutInSeconds: Int
4264
+ generateEmbeddings: Boolean
4265
+ }
4216
4266
 
4217
4267
  type ContextSource {
4218
4268
  id: String!
@@ -4331,10 +4381,7 @@ async function getJobsByQueueName(queueName, statusses, page, limit) {
4331
4381
  const config = await queue.use();
4332
4382
  const startIndex = (page || 1) - 1;
4333
4383
  const endIndex = startIndex - 1 + (limit || 100);
4334
- console.log("[EXULU] Jobs pagination startIndex", startIndex);
4335
- console.log("[EXULU] Jobs pagination endIndex", endIndex);
4336
4384
  const jobs = await config.queue.getJobs(statusses || [], startIndex, endIndex, false);
4337
- console.log("[EXULU] Jobs pagination jobs", jobs?.length);
4338
4385
  const counts = await config.queue.getJobCounts(...statusses || []);
4339
4386
  let total = 0;
4340
4387
  if (counts) {
@@ -4394,21 +4441,10 @@ function getS3Client(config) {
4394
4441
  });
4395
4442
  return s3Client;
4396
4443
  }
4397
- var getPresignedUrl = async (key, config) => {
4444
+ var getPresignedUrl = async (bucket, key, config) => {
4398
4445
  if (!config.fileUploads) {
4399
4446
  throw new Error("File uploads are not configured");
4400
4447
  }
4401
- let bucket = config.fileUploads.s3Bucket;
4402
- if (key.includes("[bucket:")) {
4403
- console.log("[EXULU] key includes [bucket:name]", key);
4404
- bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4405
- if (!bucket?.length) {
4406
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4407
- }
4408
- key = key.split("]")[1] || "";
4409
- console.log("[EXULU] bucket", bucket);
4410
- console.log("[EXULU] key", key);
4411
- }
4412
4448
  const url = await getSignedUrl(
4413
4449
  getS3Client(config),
4414
4450
  new GetObjectCommand({
@@ -4419,7 +4455,7 @@ var getPresignedUrl = async (key, config) => {
4419
4455
  );
4420
4456
  return url;
4421
4457
  };
4422
- var addPrefixToKey = (keyPath, config) => {
4458
+ var addGeneralPrefixToKey = (keyPath, config) => {
4423
4459
  if (!config.fileUploads) {
4424
4460
  throw new Error("File uploads are not configured");
4425
4461
  }
@@ -4432,58 +4468,50 @@ var addPrefixToKey = (keyPath, config) => {
4432
4468
  }
4433
4469
  return `${prefix}/${keyPath}`;
4434
4470
  };
4435
- var uploadFile = async (file, key, config, options = {}, user) => {
4471
+ var addUserPrefixToKey = (key, user) => {
4472
+ if (!user) {
4473
+ return key;
4474
+ }
4475
+ if (key.includes(`/user_${user}/`)) {
4476
+ return key;
4477
+ }
4478
+ return `user_${user}/${key}`;
4479
+ };
4480
+ var addBucketPrefixToKey = (key, bucket) => {
4481
+ if (key.includes(`/${bucket}/`)) {
4482
+ return key;
4483
+ }
4484
+ return `${bucket}/${key}`;
4485
+ };
4486
+ var uploadFile = async (file, fileName, config, options = {}, user, customBucket) => {
4436
4487
  if (!config.fileUploads) {
4437
4488
  throw new Error("File uploads are not configured (in the exported uploadFile function)");
4438
4489
  }
4439
4490
  const client2 = getS3Client(config);
4440
4491
  let defaultBucket = config.fileUploads.s3Bucket;
4441
- let customBucket = false;
4442
- if (key.includes("[bucket:")) {
4443
- console.log("[EXULU] key includes [bucket:name]", key);
4444
- customBucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4445
- if (!customBucket?.length) {
4446
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4447
- }
4448
- key = key.split("]")[1] || "";
4449
- console.log("[EXULU] custom bucket", customBucket);
4450
- }
4451
- let folder = user ? `${user}/` : "";
4452
- const fullKey = addPrefixToKey(!key.includes(folder) ? folder + key : key, config);
4453
- console.log("[EXULU] uploading file to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4492
+ let key = fileName;
4493
+ key = addGeneralPrefixToKey(key, config);
4494
+ key = addUserPrefixToKey(key, user || "api");
4495
+ console.log("[EXULU] uploading file to s3 into bucket", defaultBucket, "with key", key);
4454
4496
  const command = new PutObjectCommand({
4455
4497
  Bucket: customBucket || defaultBucket,
4456
- Key: fullKey,
4498
+ Key: key,
4457
4499
  Body: file,
4458
4500
  ContentType: options.contentType,
4459
4501
  Metadata: options.metadata,
4460
4502
  ContentLength: file.byteLength
4461
4503
  });
4462
4504
  await client2.send(command);
4463
- console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4464
- if (customBucket) {
4465
- return "[bucket:" + customBucket + "]" + fullKey;
4466
- }
4467
- return fullKey;
4505
+ console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key", key);
4506
+ return addBucketPrefixToKey(
4507
+ key,
4508
+ customBucket || defaultBucket
4509
+ );
4468
4510
  };
4469
- var createUppyRoutes = async (app, config) => {
4511
+ var createUppyRoutes = async (app, contexts, config) => {
4470
4512
  if (!config.fileUploads) {
4471
4513
  throw new Error("File uploads are not configured");
4472
4514
  }
4473
- const extractUserPrefix = (key) => {
4474
- if (!config.fileUploads) {
4475
- throw new Error("File uploads are not configured");
4476
- }
4477
- if (!config.fileUploads.s3prefix) {
4478
- return key.split("/")[0];
4479
- }
4480
- const prefix = config.fileUploads.s3prefix.replace(/\/$/, "");
4481
- if (key.startsWith(prefix + "/")) {
4482
- const keyWithoutPrefix = key.slice(prefix.length + 1);
4483
- return keyWithoutPrefix.split("/")[0];
4484
- }
4485
- return key.split("/")[0];
4486
- };
4487
4515
  const policy = {
4488
4516
  Version: "2012-10-17",
4489
4517
  Statement: [
@@ -4535,20 +4563,16 @@ var createUppyRoutes = async (app, config) => {
4535
4563
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4536
4564
  return;
4537
4565
  }
4566
+ const user = authenticationResult.user;
4538
4567
  let { key } = req.query;
4539
4568
  if (typeof key !== "string" || key.trim() === "") {
4540
4569
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4541
4570
  return;
4542
4571
  }
4543
- let bucket = config.fileUploads.s3Bucket;
4544
- if (key.includes("[bucket:")) {
4545
- console.log("[EXULU] key includes [bucket:name]", key);
4546
- bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4547
- if (!bucket?.length) {
4548
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4549
- }
4550
- key = key.split("]")[1] || "";
4551
- console.log("[EXULU] bucket", bucket);
4572
+ let bucket = key.split("/")[0];
4573
+ if (user.type !== "api" && !key.includes(`/user_${user.id}/`) && !user.super_admin) {
4574
+ res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4575
+ return;
4552
4576
  }
4553
4577
  const client2 = getS3Client(config);
4554
4578
  const command = new DeleteObjectCommand({
@@ -4576,13 +4600,32 @@ var createUppyRoutes = async (app, config) => {
4576
4600
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4577
4601
  return;
4578
4602
  }
4579
- const { key } = req.query;
4603
+ const user = authenticationResult.user;
4604
+ let { key } = req.query;
4605
+ if (!key || typeof key !== "string" || key.trim() === "") {
4606
+ res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4607
+ return;
4608
+ }
4609
+ let bucket = key.split("/")[0];
4610
+ if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
4611
+ res.status(400).json({ error: "Missing or invalid `bucket` (should be the first part of the key before the first slash)." });
4612
+ return;
4613
+ }
4614
+ key = key.split("/").slice(1).join("/");
4580
4615
  if (typeof key !== "string" || key.trim() === "") {
4581
4616
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4582
4617
  return;
4583
4618
  }
4619
+ let allowed = false;
4620
+ if (user.type === "api" || user.super_admin || key.includes(`user_${user.id}/`)) {
4621
+ allowed = true;
4622
+ }
4623
+ if (!allowed) {
4624
+ res.status(405).json({ error: "Not allowed to access the file based on authenticated user." });
4625
+ return;
4626
+ }
4584
4627
  try {
4585
- const url = await getPresignedUrl(key, config);
4628
+ const url = await getPresignedUrl(bucket, key, config);
4586
4629
  res.setHeader("Access-Control-Allow-Origin", "*");
4587
4630
  res.json({ url, method: "GET", expiresIn });
4588
4631
  } catch (err) {
@@ -4611,16 +4654,15 @@ var createUppyRoutes = async (app, config) => {
4611
4654
  return;
4612
4655
  }
4613
4656
  let { key } = req.body;
4614
- let bucket = config.fileUploads.s3Bucket;
4615
- if (key.includes("[bucket:")) {
4616
- console.log("[EXULU] key includes [bucket:name]", key);
4617
- bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4618
- console.log("[EXULU] bucket", bucket);
4619
- if (!bucket?.length) {
4620
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4621
- }
4622
- key = key.split("]")[1] || "";
4623
- console.log("[EXULU] key", key);
4657
+ let bucket = key.split("/")[0];
4658
+ if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
4659
+ res.status(400).json({ error: "Missing or invalid `bucket` (should be the first part of the key before the first slash)." });
4660
+ return;
4661
+ }
4662
+ key = key.split("/").slice(1).join("/");
4663
+ if (!key || typeof key !== "string" || key.trim() === "") {
4664
+ res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4665
+ return;
4624
4666
  }
4625
4667
  const client2 = getS3Client(config);
4626
4668
  const command = new HeadObjectCommand({
@@ -4724,11 +4766,12 @@ var createUppyRoutes = async (app, config) => {
4724
4766
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4725
4767
  return;
4726
4768
  }
4769
+ const user = authenticationResult.user;
4727
4770
  const { filename, contentType } = extractFileParameters(req);
4728
4771
  validateFileParameters(filename, contentType);
4729
4772
  const key = generateS3Key2(filename);
4730
- let folder = `${authenticationResult.user.id}/`;
4731
- const fullKey = addPrefixToKey(folder + key, config);
4773
+ let fullKey = addGeneralPrefixToKey(key, config);
4774
+ fullKey = addUserPrefixToKey(fullKey, user.type === "api" ? "api" : user.id);
4732
4775
  getSignedUrl(
4733
4776
  getS3Client(config),
4734
4777
  new PutObjectCommand({
@@ -4772,6 +4815,7 @@ var createUppyRoutes = async (app, config) => {
4772
4815
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4773
4816
  return;
4774
4817
  }
4818
+ const user = authenticationResult.user;
4775
4819
  const client2 = getS3Client(config);
4776
4820
  const { type, metadata, filename } = req.body;
4777
4821
  if (typeof filename !== "string") {
@@ -4781,13 +4825,8 @@ var createUppyRoutes = async (app, config) => {
4781
4825
  return res.status(400).json({ error: "s3: content type must be a string" });
4782
4826
  }
4783
4827
  const key = `${randomUUID()}-_EXULU_${filename}`;
4784
- let folder = "";
4785
- if (authenticationResult.user.type === "api") {
4786
- folder = `api/`;
4787
- } else {
4788
- folder = `${authenticationResult.user.id}/`;
4789
- }
4790
- const fullKey = addPrefixToKey(folder + key, config);
4828
+ let fullKey = addGeneralPrefixToKey(key, config);
4829
+ fullKey = addUserPrefixToKey(fullKey, user.type === "api" ? "api" : user.id);
4791
4830
  const params = {
4792
4831
  Bucket: config.fileUploads.s3Bucket,
4793
4832
  Key: fullKey,
@@ -4990,7 +5029,7 @@ var createProjectRetrievalTool = async ({
4990
5029
  if (!context) {
4991
5030
  throw new Error("The item added to the project does not have a valid gid with the context id as the prefix before the first slash.");
4992
5031
  }
4993
- const id = item.split("/")[1];
5032
+ const id = item.split("/").slice(1).join("/");
4994
5033
  if (set[context]) {
4995
5034
  set[context].push(id);
4996
5035
  } else {
@@ -5936,12 +5975,20 @@ var ExuluStorage = class {
5936
5975
  this.config = config;
5937
5976
  }
5938
5977
  getPresignedUrl = async (key) => {
5939
- return await getPresignedUrl(key, this.config);
5978
+ const bucket = key.split("/")[0];
5979
+ if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
5980
+ throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
5981
+ }
5982
+ key = key.split("/").slice(1).join("/");
5983
+ if (!key || typeof key !== "string" || key.trim() === "") {
5984
+ throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
5985
+ }
5986
+ return await getPresignedUrl(bucket, key, this.config);
5940
5987
  };
5941
- uploadFile = async (file, key, type, user, metadata) => {
5988
+ uploadFile = async (file, fileName, type, user, metadata, customBucket) => {
5942
5989
  return await uploadFile(
5943
5990
  file,
5944
- key,
5991
+ fileName,
5945
5992
  this.config,
5946
5993
  {
5947
5994
  contentType: type,
@@ -5950,7 +5997,8 @@ var ExuluStorage = class {
5950
5997
  type
5951
5998
  }
5952
5999
  },
5953
- user
6000
+ user,
6001
+ customBucket
5954
6002
  );
5955
6003
  };
5956
6004
  // todo add upload and delete methods
@@ -6001,6 +6049,9 @@ var ExuluContext = class {
6001
6049
  this.resultReranker = resultReranker;
6002
6050
  }
6003
6051
  processField = async (trigger, item, exuluConfig, user, role) => {
6052
+ if (!item.field) {
6053
+ throw new Error("Field property on item is required for running a specific processor.");
6054
+ }
6004
6055
  console.log("[EXULU] processing field", item.field, " in context", this.id);
6005
6056
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
6006
6057
  const field = this.fields.find((field2) => {
@@ -6032,27 +6083,32 @@ var ExuluContext = class {
6032
6083
  trigger
6033
6084
  });
6034
6085
  return {
6035
- result: "",
6086
+ result: {},
6036
6087
  job: job.id
6037
6088
  };
6038
6089
  }
6039
6090
  console.log("[EXULU] POS 1 -- EXULU CONTEXT PROCESS FIELD");
6040
- const result = await field.processor.execute({
6091
+ const processorResult = await field.processor.execute({
6041
6092
  item,
6042
6093
  user,
6043
6094
  role,
6044
6095
  utils: {
6045
- storage: exuluStorage,
6046
- items: {
6047
- update: this.updateItem,
6048
- create: this.createItem,
6049
- delete: this.deleteItem
6050
- }
6096
+ storage: exuluStorage
6051
6097
  },
6052
6098
  exuluConfig
6053
6099
  });
6100
+ if (!processorResult) {
6101
+ throw new Error("Processor result is required for updating the item in the db.");
6102
+ }
6103
+ const { db: db3 } = await postgresClient();
6104
+ delete processorResult.field;
6105
+ await db3.from(getTableName(this.id)).where({
6106
+ id: processorResult.id
6107
+ }).update({
6108
+ ...processorResult
6109
+ });
6054
6110
  return {
6055
- result,
6111
+ result: processorResult,
6056
6112
  job: void 0
6057
6113
  };
6058
6114
  };
@@ -6137,6 +6193,8 @@ var ExuluContext = class {
6137
6193
  };
6138
6194
  };
6139
6195
  createItem = async (item, config, user, role, upsert, generateEmbeddingsOverwrite) => {
6196
+ console.log("[EXULU] creating item", item);
6197
+ console.log("[EXULU] upsert", upsert);
6140
6198
  if (upsert && (!item.id && !item.external_id)) {
6141
6199
  throw new Error("Item id or external id is required for upsert.");
6142
6200
  }
@@ -6164,7 +6222,7 @@ var ExuluContext = class {
6164
6222
  }
6165
6223
  console.log("[EXULU] context configuration", this.configuration);
6166
6224
  let jobs = [];
6167
- let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
6225
+ let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
6168
6226
  for (const [key, value] of Object.entries(item)) {
6169
6227
  console.log("[EXULU] Checking for processors for field", key);
6170
6228
  const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
@@ -6187,8 +6245,13 @@ var ExuluContext = class {
6187
6245
  if (processorJob) {
6188
6246
  jobs.push(processorJob);
6189
6247
  }
6190
- if (!processorJob && processor.config?.generateEmbeddings) {
6191
- shouldGenerateEmbeddings = true;
6248
+ if (!processorJob) {
6249
+ await db3.from(getTableName(this.id)).where({ id: results[0].id }).update({
6250
+ ...processorResult
6251
+ });
6252
+ if (processor.config?.generateEmbeddings) {
6253
+ shouldGenerateEmbeddings = true;
6254
+ }
6192
6255
  }
6193
6256
  }
6194
6257
  }
@@ -6214,6 +6277,7 @@ var ExuluContext = class {
6214
6277
  };
6215
6278
  };
6216
6279
  updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
6280
+ console.log("[EXULU] updating item", item);
6217
6281
  const { db: db3 } = await postgresClient();
6218
6282
  if (item.field) {
6219
6283
  delete item.field;
@@ -6259,8 +6323,13 @@ var ExuluContext = class {
6259
6323
  if (processorJob) {
6260
6324
  jobs.push(processorJob);
6261
6325
  }
6262
- if (!processorJob && processor.config?.generateEmbeddings) {
6263
- shouldGenerateEmbeddings = true;
6326
+ if (!processorJob) {
6327
+ await db3.from(getTableName(this.id)).where({ id: record.id }).update({
6328
+ ...processorResult
6329
+ });
6330
+ if (processor.config?.generateEmbeddings) {
6331
+ shouldGenerateEmbeddings = true;
6332
+ }
6264
6333
  }
6265
6334
  }
6266
6335
  }
@@ -6284,23 +6353,23 @@ var ExuluContext = class {
6284
6353
  };
6285
6354
  deleteItem = async (item, user, role) => {
6286
6355
  const { db: db3 } = await postgresClient();
6356
+ if (!item.id && !item.external_id) {
6357
+ throw new Error("Item id or external id is required for deleting an item.");
6358
+ }
6287
6359
  if (!item.id?.length && item?.external_id) {
6288
6360
  item = await db3.from(getTableName(this.id)).where({ external_id: item.external_id }).first();
6289
6361
  if (!item || !item.id) {
6290
6362
  throw new Error(`Item not found for external id ${item?.external_id || "undefined"}.`);
6291
6363
  }
6292
6364
  }
6293
- await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
6294
- if (!this.embedder) {
6295
- return {
6296
- id: item.id,
6297
- job: void 0
6298
- };
6299
- }
6300
- const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
6301
- if (chunks.length > 0) {
6302
- await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
6365
+ const chunkTableExists = await this.chunksTableExists();
6366
+ if (chunkTableExists) {
6367
+ const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
6368
+ if (chunks.length > 0) {
6369
+ await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
6370
+ }
6303
6371
  }
6372
+ await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
6304
6373
  return {
6305
6374
  id: item.id,
6306
6375
  job: void 0
@@ -7111,7 +7180,7 @@ Mood: friendly and intelligent.
7111
7180
  });
7112
7181
  });
7113
7182
  if (config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
7114
- await createUppyRoutes(app, config);
7183
+ await createUppyRoutes(app, contexts ?? [], config);
7115
7184
  } else {
7116
7185
  console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
7117
7186
  }
@@ -7424,6 +7493,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7424
7493
  async (bullmqJob) => {
7425
7494
  console.log("[EXULU] starting execution for job", logMetadata2(bullmqJob.name, {
7426
7495
  name: bullmqJob.name,
7496
+ jobId: bullmqJob.id,
7427
7497
  status: await bullmqJob.getState(),
7428
7498
  type: bullmqJob.data.type
7429
7499
  }));
@@ -7438,6 +7508,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7438
7508
  });
7439
7509
  const workPromise = (async () => {
7440
7510
  try {
7511
+ console.log(`[EXULU] Job ${bullmqJob.id} - Log file: logs/jobs/job-${bullmqJob.id}.log`);
7441
7512
  bullmq.validate(bullmqJob.id, data);
7442
7513
  if (data.type === "embedder") {
7443
7514
  console.log("[EXULU] running an embedder job.", logMetadata2(bullmqJob.name));
@@ -7460,17 +7531,17 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7460
7531
  if (!embedder) {
7461
7532
  throw new Error(`Embedder ${data.embedder} not found in the registry.`);
7462
7533
  }
7463
- const result = await context.createAndUpsertEmbeddings(data.inputs, config, data.user, {
7534
+ const result2 = await context.createAndUpsertEmbeddings(data.inputs, config, data.user, {
7464
7535
  label: embedder.name,
7465
7536
  trigger: data.trigger
7466
7537
  }, data.role, bullmqJob.id);
7467
7538
  return {
7468
- result,
7539
+ result: result2,
7469
7540
  metadata: {}
7470
7541
  };
7471
7542
  }
7472
7543
  if (data.type === "processor") {
7473
- console.log("[EXULU] running a processor job.", logMetadata2(bullmqJob.name));
7544
+ console.log("[EXULU] running a processor job, job name: ", bullmqJob.name, " job id: ", bullmqJob.id, " job data: ", data, " job queue: ", bullmqJob.queueName);
7474
7545
  const label = `processor-${bullmqJob.name}`;
7475
7546
  await db3.from("job_results").insert({
7476
7547
  job_id: bullmqJob.id,
@@ -7492,35 +7563,42 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7492
7563
  if (!field.processor) {
7493
7564
  throw new Error(`Processor not set for field ${data.inputs.field} in the context ${data.context}.`);
7494
7565
  }
7495
- const exuluStorage = new ExuluStorage({ config });
7496
- if (!data.user) {
7497
- throw new Error(`User not set for processor job.`);
7498
- }
7499
- if (!data.role) {
7500
- throw new Error(`Role not set for processor job.`);
7566
+ if (!data.inputs.id) {
7567
+ throw new Error(`[EXULU] Item not set for processor ${field.name} in context ${context.id}, running in job ${bullmqJob.id}.`);
7501
7568
  }
7569
+ const exuluStorage = new ExuluStorage({ config });
7502
7570
  console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
7503
- const result = await field.processor.execute({
7571
+ const processorResult = await field.processor.execute({
7504
7572
  item: data.inputs,
7505
7573
  user: data.user,
7506
7574
  role: data.role,
7507
7575
  utils: {
7508
- storage: exuluStorage,
7509
- items: {
7510
- update: context.updateItem,
7511
- create: context.createItem,
7512
- delete: context.deleteItem
7513
- }
7576
+ storage: exuluStorage
7514
7577
  },
7515
- config
7578
+ exuluConfig: config
7579
+ });
7580
+ if (!processorResult) {
7581
+ throw new Error(`[EXULU] Processor ${field.name} in context ${context.id}, running in job ${bullmqJob.id} did not return an item.`);
7582
+ }
7583
+ delete processorResult.field;
7584
+ await db3.from(getTableName(context.id)).where({
7585
+ id: processorResult.id
7586
+ }).update({
7587
+ ...processorResult
7516
7588
  });
7517
7589
  let jobs = [];
7518
7590
  if (field.processor.config?.generateEmbeddings) {
7591
+ const fullItem = await db3.from(getTableName(context.id)).where({
7592
+ id: processorResult.id
7593
+ }).first();
7594
+ if (!fullItem) {
7595
+ throw new Error(`[EXULU] Item ${processorResult.id} not found after processor update in context ${context.id}`);
7596
+ }
7519
7597
  const { job: embeddingsJob } = await context.embeddings.generate.one({
7520
- item: data.inputs,
7598
+ item: fullItem,
7521
7599
  user: data.user,
7522
7600
  role: data.role,
7523
- trigger: "api",
7601
+ trigger: "processor",
7524
7602
  config
7525
7603
  });
7526
7604
  if (embeddingsJob) {
@@ -7528,7 +7606,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7528
7606
  }
7529
7607
  }
7530
7608
  return {
7531
- result,
7609
+ result: processorResult,
7532
7610
  metadata: {
7533
7611
  jobs: jobs.length > 0 ? jobs.join(",") : void 0
7534
7612
  }
@@ -7595,9 +7673,9 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7595
7673
  }
7596
7674
  }
7597
7675
  });
7598
- const result = await promise;
7599
- const messages = result.messages;
7600
- const metadata = result.metadata;
7676
+ const result2 = await promise;
7677
+ const messages = result2.messages;
7678
+ const metadata = result2.metadata;
7601
7679
  const evalFunctions = evalRun.eval_functions;
7602
7680
  let evalFunctionResults = [];
7603
7681
  for (const evalFunction of evalFunctions) {
@@ -7605,7 +7683,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7605
7683
  if (!evalMethod) {
7606
7684
  throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
7607
7685
  }
7608
- let result2;
7686
+ let result3;
7609
7687
  if (evalMethod.queue) {
7610
7688
  const queue2 = await evalMethod.queue;
7611
7689
  const jobData = {
@@ -7636,21 +7714,21 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7636
7714
  if (!job.id) {
7637
7715
  throw new Error(`Tried to add job to queue ${queue2.queue.name} but failed to get the job ID.`);
7638
7716
  }
7639
- result2 = await pollJobResult({ queue: queue2, jobId: job.id });
7717
+ result3 = await pollJobResult({ queue: queue2, jobId: job.id });
7640
7718
  const evalFunctionResult = {
7641
7719
  test_case_id: testCase.id,
7642
7720
  eval_run_id: evalRun.id,
7643
7721
  eval_function_id: evalFunction.id,
7644
7722
  eval_function_name: evalFunction.name,
7645
7723
  eval_function_config: evalFunction.config || {},
7646
- result: result2 || 0
7724
+ result: result3 || 0
7647
7725
  };
7648
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
7649
- result: result2 || 0
7726
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result3}`, logMetadata2(bullmqJob.name, {
7727
+ result: result3 || 0
7650
7728
  }));
7651
7729
  evalFunctionResults.push(evalFunctionResult);
7652
7730
  } else {
7653
- result2 = await evalMethod.run(
7731
+ result3 = await evalMethod.run(
7654
7732
  agentInstance,
7655
7733
  agentBackend,
7656
7734
  testCase,
@@ -7661,15 +7739,15 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7661
7739
  test_case_id: testCase.id,
7662
7740
  eval_run_id: evalRun.id,
7663
7741
  eval_function_id: evalFunction.id,
7664
- result: result2 || 0
7742
+ result: result3 || 0
7665
7743
  };
7666
7744
  evalFunctionResults.push(evalFunctionResult);
7667
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
7668
- result: result2 || 0
7745
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result3}`, logMetadata2(bullmqJob.name, {
7746
+ result: result3 || 0
7669
7747
  }));
7670
7748
  }
7671
7749
  }
7672
- const scores = evalFunctionResults.map((result2) => result2.result);
7750
+ const scores = evalFunctionResults.map((result3) => result3.result);
7673
7751
  console.log("[EXULU] Exulu eval run scores for test case: " + testCase.id, scores);
7674
7752
  let score = 0;
7675
7753
  switch (data.scoring_method?.toLowerCase()) {
@@ -7734,25 +7812,25 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7734
7812
  messages: inputMessages
7735
7813
  } = await validateEvalPayload(data, agents);
7736
7814
  const evalFunctions = evalRun.eval_functions;
7737
- let result;
7815
+ let result2;
7738
7816
  for (const evalFunction of evalFunctions) {
7739
7817
  const evalMethod = evals.find((e) => e.id === evalFunction.id);
7740
7818
  if (!evalMethod) {
7741
7819
  throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
7742
7820
  }
7743
- result = await evalMethod.run(
7821
+ result2 = await evalMethod.run(
7744
7822
  agentInstance,
7745
7823
  backend,
7746
7824
  testCase,
7747
7825
  inputMessages,
7748
7826
  evalFunction.config || {}
7749
7827
  );
7750
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result}`, logMetadata2(bullmqJob.name, {
7751
- result: result || 0
7828
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
7829
+ result: result2 || 0
7752
7830
  }));
7753
7831
  }
7754
7832
  return {
7755
- result,
7833
+ result: result2,
7756
7834
  metadata: {}
7757
7835
  };
7758
7836
  }
@@ -7772,10 +7850,10 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7772
7850
  if (!source) {
7773
7851
  throw new Error(`Source ${data.source} not found in the context ${context.id}.`);
7774
7852
  }
7775
- const result = await source.execute(data.inputs);
7853
+ const result2 = await source.execute(data.inputs);
7776
7854
  let jobs = [];
7777
7855
  let items = [];
7778
- for (const item of result) {
7856
+ for (const item of result2) {
7779
7857
  const { item: createdItem, job } = await context.createItem(
7780
7858
  item,
7781
7859
  config,
@@ -7807,7 +7885,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7807
7885
  role: data?.role
7808
7886
  });
7809
7887
  return {
7810
- result,
7888
+ result: result2,
7811
7889
  metadata: {
7812
7890
  jobs,
7813
7891
  items
@@ -7820,11 +7898,13 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7820
7898
  throw error;
7821
7899
  }
7822
7900
  })();
7823
- return Promise.race([workPromise, timeoutPromise]);
7901
+ const result = await Promise.race([workPromise, timeoutPromise]);
7902
+ return result;
7824
7903
  },
7825
7904
  {
7826
7905
  autorun: true,
7827
7906
  connection: redisConnection,
7907
+ concurrency: queue.concurrency?.worker || 1,
7828
7908
  removeOnComplete: { count: 1e3 },
7829
7909
  removeOnFail: { count: 5e3 },
7830
7910
  ...queue.ratelimit && {
@@ -9028,18 +9108,23 @@ var ExuluQueues = class {
9028
9108
  // method of ExuluQueues we need to store the desired rate limit on the queue
9029
9109
  // here so we can use the value when creating workers for the queue instance
9030
9110
  // as there is no way to store a rate limit value natively on a bullm queue.
9031
- register = (name, concurrency = 1, ratelimit = 1) => {
9111
+ register = (name, concurrency, ratelimit = 1) => {
9112
+ const queueConcurrency = concurrency.queue || 1;
9113
+ const workerConcurrency = concurrency.worker || 1;
9032
9114
  const use = async () => {
9033
9115
  const existing = this.queues.find((x) => x.queue?.name === name);
9034
9116
  if (existing) {
9035
9117
  const globalConcurrency = await existing.queue.getGlobalConcurrency();
9036
- if (globalConcurrency !== concurrency) {
9037
- await existing.queue.setGlobalConcurrency(concurrency);
9118
+ if (globalConcurrency !== queueConcurrency) {
9119
+ await existing.queue.setGlobalConcurrency(queueConcurrency);
9038
9120
  }
9039
9121
  return {
9040
9122
  queue: existing.queue,
9041
9123
  ratelimit,
9042
- concurrency
9124
+ concurrency: {
9125
+ worker: workerConcurrency,
9126
+ queue: queueConcurrency
9127
+ }
9043
9128
  };
9044
9129
  }
9045
9130
  if (!redisServer.host?.length || !redisServer.port?.length) {
@@ -9058,21 +9143,30 @@ var ExuluQueues = class {
9058
9143
  telemetry: new BullMQOtel("simple-guide")
9059
9144
  }
9060
9145
  );
9061
- await newQueue.setGlobalConcurrency(concurrency);
9146
+ await newQueue.setGlobalConcurrency(queueConcurrency);
9062
9147
  this.queues.push({
9063
9148
  queue: newQueue,
9064
9149
  ratelimit,
9065
- concurrency
9150
+ concurrency: {
9151
+ worker: workerConcurrency,
9152
+ queue: queueConcurrency
9153
+ }
9066
9154
  });
9067
9155
  return {
9068
9156
  queue: newQueue,
9069
9157
  ratelimit,
9070
- concurrency
9158
+ concurrency: {
9159
+ worker: workerConcurrency,
9160
+ queue: queueConcurrency
9161
+ }
9071
9162
  };
9072
9163
  };
9073
9164
  this.list.set(name, {
9074
9165
  name,
9075
- concurrency,
9166
+ concurrency: {
9167
+ worker: workerConcurrency,
9168
+ queue: queueConcurrency
9169
+ },
9076
9170
  ratelimit,
9077
9171
  use
9078
9172
  });
@@ -9128,7 +9222,10 @@ var llmAsJudgeEval = () => {
9128
9222
  name: "prompt",
9129
9223
  description: "The prompt to send to the LLM as a judge, make sure to instruct the LLM to output a numerical score between 0 and 100. Add {actual_output} to the prompt to replace with the last message content, and {expected_output} to replace with the expected output."
9130
9224
  }],
9131
- queue: queues.register("llm_as_judge", 1, 1).use(),
9225
+ queue: queues.register("llm_as_judge", {
9226
+ worker: 1,
9227
+ queue: 1
9228
+ }, 1).use(),
9132
9229
  llm: true
9133
9230
  });
9134
9231
  }
@@ -9763,11 +9860,16 @@ var previewPdfTool = new ExuluTool2({
9763
9860
  type: "function",
9764
9861
  config: [],
9765
9862
  inputSchema: z5.object({
9766
- s3key: z5.string().describe("The S3 key of the PDF file to preview, can also optionally include a [bucket:name] to specify the bucket."),
9863
+ s3key: z5.string().describe("The S3 key of the PDF file to preview."),
9767
9864
  page: z5.number().describe("The page number to preview, defaults to 1.").optional()
9768
9865
  }),
9769
9866
  execute: async ({ s3key, page, exuluConfig }) => {
9770
- const url = await getPresignedUrl(s3key, exuluConfig);
9867
+ const bucket = s3key.split("/")[0];
9868
+ const key = s3key.split("/").slice(1).join("/");
9869
+ if (!bucket || !key) {
9870
+ throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
9871
+ }
9872
+ const url = await getPresignedUrl(bucket, key, exuluConfig);
9771
9873
  if (!url) {
9772
9874
  throw new Error("No URL provided for PDF preview");
9773
9875
  }
@@ -10161,7 +10263,10 @@ var ExuluApp = class {
10161
10263
  }
10162
10264
  const queueSet = /* @__PURE__ */ new Set();
10163
10265
  if (redisServer.host?.length && redisServer.port?.length) {
10164
- queues.register(global_queues.eval_runs, 1, 1);
10266
+ queues.register(global_queues.eval_runs, {
10267
+ worker: 1,
10268
+ queue: 1
10269
+ }, 1);
10165
10270
  for (const queue of queues.list.values()) {
10166
10271
  const config2 = await queue.use();
10167
10272
  queueSet.add(config2);