@exulu/backend 1.39.2 → 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.cjs CHANGED
@@ -67,7 +67,6 @@ var redisServer = {
67
67
  // src/redis/client.ts
68
68
  var client = {};
69
69
  async function redisClient() {
70
- console.log("[EXULU] redisServer:", redisServer);
71
70
  if (!redisServer.host || !redisServer.port) {
72
71
  return { client: null };
73
72
  }
@@ -148,7 +147,6 @@ var db = {};
148
147
  var databaseExistsChecked = false;
149
148
  var dbName = process.env.POSTGRES_DB_NAME || "exulu";
150
149
  async function ensureDatabaseExists() {
151
- console.log(`[EXULU] Ensuring ${dbName} database exists...`);
152
150
  const defaultKnex = (0, import_knex.default)({
153
151
  client: "pg",
154
152
  connection: {
@@ -192,16 +190,7 @@ async function ensureDatabaseExists() {
192
190
  async function postgresClient() {
193
191
  if (!db["exulu"]) {
194
192
  try {
195
- console.log(`[EXULU] Connecting to ${dbName} database.`);
196
- console.log("[EXULU] POSTGRES_DB_HOST:", process.env.POSTGRES_DB_HOST);
197
- console.log("[EXULU] POSTGRES_DB_PORT:", process.env.POSTGRES_DB_PORT);
198
- console.log("[EXULU] POSTGRES_DB_USER:", process.env.POSTGRES_DB_USER);
199
- console.log("[EXULU] POSTGRES_DB_PASSWORD:", process.env.POSTGRES_DB_PASSWORD);
200
- console.log("[EXULU] POSTGRES_DB_NAME:", dbName);
201
- console.log("[EXULU] POSTGRES_DB_SSL:", process.env.POSTGRES_DB_SSL);
202
- console.log("[EXULU] Database exists checked:", databaseExistsChecked);
203
193
  if (!databaseExistsChecked) {
204
- console.log(`[EXULU] Ensuring ${dbName} database exists...`);
205
194
  await ensureDatabaseExists();
206
195
  databaseExistsChecked = true;
207
196
  }
@@ -526,7 +515,6 @@ var authentication = async ({
526
515
  }
527
516
  if (authtoken) {
528
517
  try {
529
- console.log("[EXULU] authtoken", authtoken);
530
518
  if (!authtoken?.email) {
531
519
  return {
532
520
  error: true,
@@ -3730,7 +3718,10 @@ type PageInfo {
3730
3718
  const config2 = await queue.use();
3731
3719
  return {
3732
3720
  name: config2.queue.name,
3733
- concurrency: config2.concurrency,
3721
+ concurrency: {
3722
+ worker: config2.concurrency?.worker || void 0,
3723
+ queue: config2.concurrency?.queue || void 0
3724
+ },
3734
3725
  ratelimit: config2.ratelimit,
3735
3726
  isMaxed: await config2.queue.isMaxed(),
3736
3727
  isPaused: await config2.queue.isPaused(),
@@ -3780,7 +3771,10 @@ type PageInfo {
3780
3771
  if (!agentInstance) {
3781
3772
  throw new Error("Agent instance not found for eval run.");
3782
3773
  }
3783
- const evalQueue = await queues.register("eval_runs", 1, 1).use();
3774
+ const evalQueue = await queues.register("eval_runs", {
3775
+ worker: 1,
3776
+ queue: 1
3777
+ }, 1).use();
3784
3778
  const jobIds = [];
3785
3779
  for (const testCase of testCases) {
3786
3780
  const jobData = {
@@ -3904,7 +3898,6 @@ type PageInfo {
3904
3898
  if (!client2) {
3905
3899
  throw new Error("Redis client not created properly");
3906
3900
  }
3907
- console.log("[EXULU] Jobs pagination args", args);
3908
3901
  const {
3909
3902
  jobs,
3910
3903
  count
@@ -3914,7 +3907,6 @@ type PageInfo {
3914
3907
  args.page || 1,
3915
3908
  args.limit || 100
3916
3909
  );
3917
- console.log("[EXULU] jobs", jobs.map((job) => job.name));
3918
3910
  const requestedFields = getRequestedFields(info);
3919
3911
  return {
3920
3912
  items: await Promise.all(jobs.map(async (job) => {
@@ -3943,6 +3935,25 @@ type PageInfo {
3943
3935
  };
3944
3936
  resolvers.Query["contexts"] = async (_, args, context, info) => {
3945
3937
  const data = await Promise.all(contexts.map(async (context2) => {
3938
+ let processors = await Promise.all(context2.fields.map(async (field) => {
3939
+ if (field.processor) {
3940
+ let queueName = void 0;
3941
+ if (field.processor?.config?.queue) {
3942
+ const config2 = await field.processor?.config?.queue;
3943
+ queueName = config2?.queue?.name || void 0;
3944
+ }
3945
+ return {
3946
+ field: field.name,
3947
+ description: field.processor?.description,
3948
+ queue: queueName,
3949
+ trigger: field.processor?.config?.trigger,
3950
+ timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
3951
+ generateEmbeddings: field.processor?.config?.generateEmbeddings || false
3952
+ };
3953
+ }
3954
+ return null;
3955
+ }));
3956
+ processors = processors.filter((processor) => processor !== null);
3946
3957
  const sources = await Promise.all(context2.sources.map(async (source) => {
3947
3958
  let queueName = void 0;
3948
3959
  if (source.config) {
@@ -3974,6 +3985,7 @@ type PageInfo {
3974
3985
  slug: "/contexts/" + context2.id,
3975
3986
  active: context2.active,
3976
3987
  sources,
3988
+ processors,
3977
3989
  fields: context2.fields.map((field) => {
3978
3990
  return {
3979
3991
  ...field,
@@ -3999,6 +4011,25 @@ type PageInfo {
3999
4011
  if (!data) {
4000
4012
  return null;
4001
4013
  }
4014
+ let processors = await Promise.all(data.fields.map(async (field) => {
4015
+ if (field.processor) {
4016
+ let queueName = void 0;
4017
+ if (field.processor?.config?.queue) {
4018
+ const config2 = await field.processor?.config?.queue;
4019
+ queueName = config2?.queue?.name || void 0;
4020
+ }
4021
+ return {
4022
+ field: field.name,
4023
+ description: field.processor?.description,
4024
+ queue: queueName,
4025
+ trigger: field.processor?.config?.trigger,
4026
+ timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
4027
+ generateEmbeddings: field.processor?.config?.generateEmbeddings || false
4028
+ };
4029
+ }
4030
+ return null;
4031
+ }));
4032
+ processors = processors.filter((processor) => processor !== null);
4002
4033
  const sources = await Promise.all(data.sources.map(async (source) => {
4003
4034
  let queueName = void 0;
4004
4035
  if (source.config) {
@@ -4035,6 +4066,7 @@ type PageInfo {
4035
4066
  slug: "/contexts/" + data.id,
4036
4067
  active: data.active,
4037
4068
  sources,
4069
+ processors,
4038
4070
  fields: await Promise.all(data.fields.map(async (field) => {
4039
4071
  const label = field.name?.replace("_s3key", "");
4040
4072
  if (field.type === "file" && !field.name.endsWith("_s3key")) {
@@ -4058,7 +4090,10 @@ type PageInfo {
4058
4090
  queue: {
4059
4091
  name: queue?.queue.name || void 0,
4060
4092
  ratelimit: queue?.ratelimit || void 0,
4061
- concurrency: queue?.concurrency || void 0
4093
+ concurrency: {
4094
+ worker: queue?.concurrency?.worker || void 0,
4095
+ queue: queue?.concurrency?.queue || void 0
4096
+ }
4062
4097
  }
4063
4098
  },
4064
4099
  execute: "function"
@@ -4131,13 +4166,19 @@ type PageInfo {
4131
4166
  modelDefs += `
4132
4167
  type QueueResult {
4133
4168
  name: String!
4134
- concurrency: Int!
4169
+ concurrency: QueueConcurrency!
4135
4170
  ratelimit: Int!
4136
4171
  isMaxed: Boolean!
4137
4172
  isPaused: Boolean!
4138
4173
  jobs: QueueJobsCounts
4139
4174
  }
4140
4175
  `;
4176
+ modelDefs += `
4177
+ type QueueConcurrency {
4178
+ worker: Int
4179
+ queue: Int
4180
+ }
4181
+ `;
4141
4182
  modelDefs += `
4142
4183
  type QueueJobsCounts {
4143
4184
  paused: Int!
@@ -4252,7 +4293,8 @@ type Context {
4252
4293
  active: Boolean
4253
4294
  fields: JSON
4254
4295
  configuration: JSON
4255
- sources: [ContextSource!]
4296
+ sources: [ContextSource]
4297
+ processors: [ContextProcessor]
4256
4298
  }
4257
4299
  type Embedder {
4258
4300
  name: String!
@@ -4265,6 +4307,14 @@ type EmbedderConfig {
4265
4307
  description: String
4266
4308
  default: String
4267
4309
  }
4310
+ type ContextProcessor {
4311
+ field: String!
4312
+ description: String
4313
+ queue: String
4314
+ trigger: String
4315
+ timeoutInSeconds: Int
4316
+ generateEmbeddings: Boolean
4317
+ }
4268
4318
 
4269
4319
  type ContextSource {
4270
4320
  id: String!
@@ -4383,10 +4433,7 @@ async function getJobsByQueueName(queueName, statusses, page, limit) {
4383
4433
  const config = await queue.use();
4384
4434
  const startIndex = (page || 1) - 1;
4385
4435
  const endIndex = startIndex - 1 + (limit || 100);
4386
- console.log("[EXULU] Jobs pagination startIndex", startIndex);
4387
- console.log("[EXULU] Jobs pagination endIndex", endIndex);
4388
4436
  const jobs = await config.queue.getJobs(statusses || [], startIndex, endIndex, false);
4389
- console.log("[EXULU] Jobs pagination jobs", jobs?.length);
4390
4437
  const counts = await config.queue.getJobCounts(...statusses || []);
4391
4438
  let total = 0;
4392
4439
  if (counts) {
@@ -4427,21 +4474,10 @@ function getS3Client(config) {
4427
4474
  });
4428
4475
  return s3Client;
4429
4476
  }
4430
- var getPresignedUrl = async (key, config) => {
4477
+ var getPresignedUrl = async (bucket, key, config) => {
4431
4478
  if (!config.fileUploads) {
4432
4479
  throw new Error("File uploads are not configured");
4433
4480
  }
4434
- let bucket = config.fileUploads.s3Bucket;
4435
- if (key.includes("[bucket:")) {
4436
- console.log("[EXULU] key includes [bucket:name]", key);
4437
- bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4438
- if (!bucket?.length) {
4439
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4440
- }
4441
- key = key.split("]")[1] || "";
4442
- console.log("[EXULU] bucket", bucket);
4443
- console.log("[EXULU] key", key);
4444
- }
4445
4481
  const url = await (0, import_s3_request_presigner.getSignedUrl)(
4446
4482
  getS3Client(config),
4447
4483
  new import_client_s3.GetObjectCommand({
@@ -4452,7 +4488,7 @@ var getPresignedUrl = async (key, config) => {
4452
4488
  );
4453
4489
  return url;
4454
4490
  };
4455
- var addPrefixToKey = (keyPath, config) => {
4491
+ var addGeneralPrefixToKey = (keyPath, config) => {
4456
4492
  if (!config.fileUploads) {
4457
4493
  throw new Error("File uploads are not configured");
4458
4494
  }
@@ -4465,58 +4501,50 @@ var addPrefixToKey = (keyPath, config) => {
4465
4501
  }
4466
4502
  return `${prefix}/${keyPath}`;
4467
4503
  };
4468
- var uploadFile = async (file, key, config, options = {}, user) => {
4504
+ var addUserPrefixToKey = (key, user) => {
4505
+ if (!user) {
4506
+ return key;
4507
+ }
4508
+ if (key.includes(`/user_${user}/`)) {
4509
+ return key;
4510
+ }
4511
+ return `user_${user}/${key}`;
4512
+ };
4513
+ var addBucketPrefixToKey = (key, bucket) => {
4514
+ if (key.includes(`/${bucket}/`)) {
4515
+ return key;
4516
+ }
4517
+ return `${bucket}/${key}`;
4518
+ };
4519
+ var uploadFile = async (file, fileName, config, options = {}, user, customBucket) => {
4469
4520
  if (!config.fileUploads) {
4470
4521
  throw new Error("File uploads are not configured (in the exported uploadFile function)");
4471
4522
  }
4472
4523
  const client2 = getS3Client(config);
4473
4524
  let defaultBucket = config.fileUploads.s3Bucket;
4474
- let customBucket = false;
4475
- if (key.includes("[bucket:")) {
4476
- console.log("[EXULU] key includes [bucket:name]", key);
4477
- customBucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4478
- if (!customBucket?.length) {
4479
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4480
- }
4481
- key = key.split("]")[1] || "";
4482
- console.log("[EXULU] custom bucket", customBucket);
4483
- }
4484
- let folder = user ? `${user}/` : "";
4485
- const fullKey = addPrefixToKey(!key.includes(folder) ? folder + key : key, config);
4486
- console.log("[EXULU] uploading file to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4525
+ let key = fileName;
4526
+ key = addGeneralPrefixToKey(key, config);
4527
+ key = addUserPrefixToKey(key, user || "api");
4528
+ console.log("[EXULU] uploading file to s3 into bucket", defaultBucket, "with key", key);
4487
4529
  const command = new import_client_s3.PutObjectCommand({
4488
4530
  Bucket: customBucket || defaultBucket,
4489
- Key: fullKey,
4531
+ Key: key,
4490
4532
  Body: file,
4491
4533
  ContentType: options.contentType,
4492
4534
  Metadata: options.metadata,
4493
4535
  ContentLength: file.byteLength
4494
4536
  });
4495
4537
  await client2.send(command);
4496
- console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4497
- if (customBucket) {
4498
- return "[bucket:" + customBucket + "]" + fullKey;
4499
- }
4500
- return fullKey;
4538
+ console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key", key);
4539
+ return addBucketPrefixToKey(
4540
+ key,
4541
+ customBucket || defaultBucket
4542
+ );
4501
4543
  };
4502
- var createUppyRoutes = async (app, config) => {
4544
+ var createUppyRoutes = async (app, contexts, config) => {
4503
4545
  if (!config.fileUploads) {
4504
4546
  throw new Error("File uploads are not configured");
4505
4547
  }
4506
- const extractUserPrefix = (key) => {
4507
- if (!config.fileUploads) {
4508
- throw new Error("File uploads are not configured");
4509
- }
4510
- if (!config.fileUploads.s3prefix) {
4511
- return key.split("/")[0];
4512
- }
4513
- const prefix = config.fileUploads.s3prefix.replace(/\/$/, "");
4514
- if (key.startsWith(prefix + "/")) {
4515
- const keyWithoutPrefix = key.slice(prefix.length + 1);
4516
- return keyWithoutPrefix.split("/")[0];
4517
- }
4518
- return key.split("/")[0];
4519
- };
4520
4548
  const policy = {
4521
4549
  Version: "2012-10-17",
4522
4550
  Statement: [
@@ -4568,20 +4596,16 @@ var createUppyRoutes = async (app, config) => {
4568
4596
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4569
4597
  return;
4570
4598
  }
4599
+ const user = authenticationResult.user;
4571
4600
  let { key } = req.query;
4572
4601
  if (typeof key !== "string" || key.trim() === "") {
4573
4602
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4574
4603
  return;
4575
4604
  }
4576
- let bucket = config.fileUploads.s3Bucket;
4577
- if (key.includes("[bucket:")) {
4578
- console.log("[EXULU] key includes [bucket:name]", key);
4579
- bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4580
- if (!bucket?.length) {
4581
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4582
- }
4583
- key = key.split("]")[1] || "";
4584
- console.log("[EXULU] bucket", bucket);
4605
+ let bucket = key.split("/")[0];
4606
+ if (user.type !== "api" && !key.includes(`/user_${user.id}/`) && !user.super_admin) {
4607
+ res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4608
+ return;
4585
4609
  }
4586
4610
  const client2 = getS3Client(config);
4587
4611
  const command = new import_client_s3.DeleteObjectCommand({
@@ -4609,13 +4633,32 @@ var createUppyRoutes = async (app, config) => {
4609
4633
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4610
4634
  return;
4611
4635
  }
4612
- const { key } = req.query;
4636
+ const user = authenticationResult.user;
4637
+ let { key } = req.query;
4638
+ if (!key || typeof key !== "string" || key.trim() === "") {
4639
+ res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4640
+ return;
4641
+ }
4642
+ let bucket = key.split("/")[0];
4643
+ if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
4644
+ res.status(400).json({ error: "Missing or invalid `bucket` (should be the first part of the key before the first slash)." });
4645
+ return;
4646
+ }
4647
+ key = key.split("/").slice(1).join("/");
4613
4648
  if (typeof key !== "string" || key.trim() === "") {
4614
4649
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4615
4650
  return;
4616
4651
  }
4652
+ let allowed = false;
4653
+ if (user.type === "api" || user.super_admin || key.includes(`user_${user.id}/`)) {
4654
+ allowed = true;
4655
+ }
4656
+ if (!allowed) {
4657
+ res.status(405).json({ error: "Not allowed to access the file based on authenticated user." });
4658
+ return;
4659
+ }
4617
4660
  try {
4618
- const url = await getPresignedUrl(key, config);
4661
+ const url = await getPresignedUrl(bucket, key, config);
4619
4662
  res.setHeader("Access-Control-Allow-Origin", "*");
4620
4663
  res.json({ url, method: "GET", expiresIn });
4621
4664
  } catch (err) {
@@ -4644,16 +4687,15 @@ var createUppyRoutes = async (app, config) => {
4644
4687
  return;
4645
4688
  }
4646
4689
  let { key } = req.body;
4647
- let bucket = config.fileUploads.s3Bucket;
4648
- if (key.includes("[bucket:")) {
4649
- console.log("[EXULU] key includes [bucket:name]", key);
4650
- bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4651
- console.log("[EXULU] bucket", bucket);
4652
- if (!bucket?.length) {
4653
- throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4654
- }
4655
- key = key.split("]")[1] || "";
4656
- console.log("[EXULU] key", key);
4690
+ let bucket = key.split("/")[0];
4691
+ if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
4692
+ res.status(400).json({ error: "Missing or invalid `bucket` (should be the first part of the key before the first slash)." });
4693
+ return;
4694
+ }
4695
+ key = key.split("/").slice(1).join("/");
4696
+ if (!key || typeof key !== "string" || key.trim() === "") {
4697
+ res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4698
+ return;
4657
4699
  }
4658
4700
  const client2 = getS3Client(config);
4659
4701
  const command = new import_client_s3.HeadObjectCommand({
@@ -4757,11 +4799,12 @@ var createUppyRoutes = async (app, config) => {
4757
4799
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4758
4800
  return;
4759
4801
  }
4802
+ const user = authenticationResult.user;
4760
4803
  const { filename, contentType } = extractFileParameters(req);
4761
4804
  validateFileParameters(filename, contentType);
4762
4805
  const key = generateS3Key2(filename);
4763
- let folder = `${authenticationResult.user.id}/`;
4764
- const fullKey = addPrefixToKey(folder + key, config);
4806
+ let fullKey = addGeneralPrefixToKey(key, config);
4807
+ fullKey = addUserPrefixToKey(fullKey, user.type === "api" ? "api" : user.id);
4765
4808
  (0, import_s3_request_presigner.getSignedUrl)(
4766
4809
  getS3Client(config),
4767
4810
  new import_client_s3.PutObjectCommand({
@@ -4805,6 +4848,7 @@ var createUppyRoutes = async (app, config) => {
4805
4848
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4806
4849
  return;
4807
4850
  }
4851
+ const user = authenticationResult.user;
4808
4852
  const client2 = getS3Client(config);
4809
4853
  const { type, metadata, filename } = req.body;
4810
4854
  if (typeof filename !== "string") {
@@ -4814,13 +4858,8 @@ var createUppyRoutes = async (app, config) => {
4814
4858
  return res.status(400).json({ error: "s3: content type must be a string" });
4815
4859
  }
4816
4860
  const key = `${(0, import_node_crypto.randomUUID)()}-_EXULU_${filename}`;
4817
- let folder = "";
4818
- if (authenticationResult.user.type === "api") {
4819
- folder = `api/`;
4820
- } else {
4821
- folder = `${authenticationResult.user.id}/`;
4822
- }
4823
- const fullKey = addPrefixToKey(folder + key, config);
4861
+ let fullKey = addGeneralPrefixToKey(key, config);
4862
+ fullKey = addUserPrefixToKey(fullKey, user.type === "api" ? "api" : user.id);
4824
4863
  const params = {
4825
4864
  Bucket: config.fileUploads.s3Bucket,
4826
4865
  Key: fullKey,
@@ -5023,7 +5062,7 @@ var createProjectRetrievalTool = async ({
5023
5062
  if (!context) {
5024
5063
  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.");
5025
5064
  }
5026
- const id = item.split("/")[1];
5065
+ const id = item.split("/").slice(1).join("/");
5027
5066
  if (set[context]) {
5028
5067
  set[context].push(id);
5029
5068
  } else {
@@ -5969,12 +6008,20 @@ var ExuluStorage = class {
5969
6008
  this.config = config;
5970
6009
  }
5971
6010
  getPresignedUrl = async (key) => {
5972
- return await getPresignedUrl(key, this.config);
6011
+ const bucket = key.split("/")[0];
6012
+ if (!bucket || typeof bucket !== "string" || bucket.trim() === "") {
6013
+ throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
6014
+ }
6015
+ key = key.split("/").slice(1).join("/");
6016
+ if (!key || typeof key !== "string" || key.trim() === "") {
6017
+ throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
6018
+ }
6019
+ return await getPresignedUrl(bucket, key, this.config);
5973
6020
  };
5974
- uploadFile = async (file, key, type, user, metadata) => {
6021
+ uploadFile = async (file, fileName, type, user, metadata, customBucket) => {
5975
6022
  return await uploadFile(
5976
6023
  file,
5977
- key,
6024
+ fileName,
5978
6025
  this.config,
5979
6026
  {
5980
6027
  contentType: type,
@@ -5983,7 +6030,8 @@ var ExuluStorage = class {
5983
6030
  type
5984
6031
  }
5985
6032
  },
5986
- user
6033
+ user,
6034
+ customBucket
5987
6035
  );
5988
6036
  };
5989
6037
  // todo add upload and delete methods
@@ -6034,6 +6082,9 @@ var ExuluContext = class {
6034
6082
  this.resultReranker = resultReranker;
6035
6083
  }
6036
6084
  processField = async (trigger, item, exuluConfig, user, role) => {
6085
+ if (!item.field) {
6086
+ throw new Error("Field property on item is required for running a specific processor.");
6087
+ }
6037
6088
  console.log("[EXULU] processing field", item.field, " in context", this.id);
6038
6089
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
6039
6090
  const field = this.fields.find((field2) => {
@@ -6065,27 +6116,32 @@ var ExuluContext = class {
6065
6116
  trigger
6066
6117
  });
6067
6118
  return {
6068
- result: "",
6119
+ result: {},
6069
6120
  job: job.id
6070
6121
  };
6071
6122
  }
6072
6123
  console.log("[EXULU] POS 1 -- EXULU CONTEXT PROCESS FIELD");
6073
- const result = await field.processor.execute({
6124
+ const processorResult = await field.processor.execute({
6074
6125
  item,
6075
6126
  user,
6076
6127
  role,
6077
6128
  utils: {
6078
- storage: exuluStorage,
6079
- items: {
6080
- update: this.updateItem,
6081
- create: this.createItem,
6082
- delete: this.deleteItem
6083
- }
6129
+ storage: exuluStorage
6084
6130
  },
6085
6131
  exuluConfig
6086
6132
  });
6133
+ if (!processorResult) {
6134
+ throw new Error("Processor result is required for updating the item in the db.");
6135
+ }
6136
+ const { db: db3 } = await postgresClient();
6137
+ delete processorResult.field;
6138
+ await db3.from(getTableName(this.id)).where({
6139
+ id: processorResult.id
6140
+ }).update({
6141
+ ...processorResult
6142
+ });
6087
6143
  return {
6088
- result,
6144
+ result: processorResult,
6089
6145
  job: void 0
6090
6146
  };
6091
6147
  };
@@ -6170,6 +6226,8 @@ var ExuluContext = class {
6170
6226
  };
6171
6227
  };
6172
6228
  createItem = async (item, config, user, role, upsert, generateEmbeddingsOverwrite) => {
6229
+ console.log("[EXULU] creating item", item);
6230
+ console.log("[EXULU] upsert", upsert);
6173
6231
  if (upsert && (!item.id && !item.external_id)) {
6174
6232
  throw new Error("Item id or external id is required for upsert.");
6175
6233
  }
@@ -6197,7 +6255,7 @@ var ExuluContext = class {
6197
6255
  }
6198
6256
  console.log("[EXULU] context configuration", this.configuration);
6199
6257
  let jobs = [];
6200
- let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
6258
+ let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
6201
6259
  for (const [key, value] of Object.entries(item)) {
6202
6260
  console.log("[EXULU] Checking for processors for field", key);
6203
6261
  const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
@@ -6220,8 +6278,13 @@ var ExuluContext = class {
6220
6278
  if (processorJob) {
6221
6279
  jobs.push(processorJob);
6222
6280
  }
6223
- if (!processorJob && processor.config?.generateEmbeddings) {
6224
- shouldGenerateEmbeddings = true;
6281
+ if (!processorJob) {
6282
+ await db3.from(getTableName(this.id)).where({ id: results[0].id }).update({
6283
+ ...processorResult
6284
+ });
6285
+ if (processor.config?.generateEmbeddings) {
6286
+ shouldGenerateEmbeddings = true;
6287
+ }
6225
6288
  }
6226
6289
  }
6227
6290
  }
@@ -6247,6 +6310,7 @@ var ExuluContext = class {
6247
6310
  };
6248
6311
  };
6249
6312
  updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
6313
+ console.log("[EXULU] updating item", item);
6250
6314
  const { db: db3 } = await postgresClient();
6251
6315
  if (item.field) {
6252
6316
  delete item.field;
@@ -6292,8 +6356,13 @@ var ExuluContext = class {
6292
6356
  if (processorJob) {
6293
6357
  jobs.push(processorJob);
6294
6358
  }
6295
- if (!processorJob && processor.config?.generateEmbeddings) {
6296
- shouldGenerateEmbeddings = true;
6359
+ if (!processorJob) {
6360
+ await db3.from(getTableName(this.id)).where({ id: record.id }).update({
6361
+ ...processorResult
6362
+ });
6363
+ if (processor.config?.generateEmbeddings) {
6364
+ shouldGenerateEmbeddings = true;
6365
+ }
6297
6366
  }
6298
6367
  }
6299
6368
  }
@@ -6317,23 +6386,23 @@ var ExuluContext = class {
6317
6386
  };
6318
6387
  deleteItem = async (item, user, role) => {
6319
6388
  const { db: db3 } = await postgresClient();
6389
+ if (!item.id && !item.external_id) {
6390
+ throw new Error("Item id or external id is required for deleting an item.");
6391
+ }
6320
6392
  if (!item.id?.length && item?.external_id) {
6321
6393
  item = await db3.from(getTableName(this.id)).where({ external_id: item.external_id }).first();
6322
6394
  if (!item || !item.id) {
6323
6395
  throw new Error(`Item not found for external id ${item?.external_id || "undefined"}.`);
6324
6396
  }
6325
6397
  }
6326
- await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
6327
- if (!this.embedder) {
6328
- return {
6329
- id: item.id,
6330
- job: void 0
6331
- };
6332
- }
6333
- const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
6334
- if (chunks.length > 0) {
6335
- await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
6398
+ const chunkTableExists = await this.chunksTableExists();
6399
+ if (chunkTableExists) {
6400
+ const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
6401
+ if (chunks.length > 0) {
6402
+ await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
6403
+ }
6336
6404
  }
6405
+ await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
6337
6406
  return {
6338
6407
  id: item.id,
6339
6408
  job: void 0
@@ -6684,6 +6753,18 @@ var CLAUDE_MESSAGES = {
6684
6753
  var import_ai2 = require("ai");
6685
6754
  var import_cookie_parser = __toESM(require("cookie-parser"), 1);
6686
6755
  var REQUEST_SIZE_LIMIT = "50mb";
6756
+ var getExuluVersionNumber = async () => {
6757
+ try {
6758
+ const path = process.cwd();
6759
+ const packageJson = import_fs.default.readFileSync(path + "/package.json", "utf8");
6760
+ const packageData = JSON.parse(packageJson);
6761
+ const exuluVersion = packageData.dependencies["@exulu/backend"];
6762
+ console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
6763
+ return exuluVersion;
6764
+ } catch (error) {
6765
+ console.error("Could not find or import package.json:", error.message);
6766
+ }
6767
+ };
6687
6768
  var global_queues = {
6688
6769
  eval_runs: "eval_runs"
6689
6770
  };
@@ -6720,6 +6801,13 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tr
6720
6801
  app.use(import_body_parser.default.urlencoded({ extended: true, limit: REQUEST_SIZE_LIMIT }));
6721
6802
  app.use(import_body_parser.default.json({ limit: REQUEST_SIZE_LIMIT }));
6722
6803
  app.use((0, import_cookie_parser.default)());
6804
+ app.use(async (req, res, next) => {
6805
+ const version = await getExuluVersionNumber();
6806
+ if (version) {
6807
+ res.setHeader("exulu-version", version);
6808
+ }
6809
+ next();
6810
+ });
6723
6811
  console.log(`
6724
6812
  \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
6725
6813
  \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
@@ -7125,7 +7213,7 @@ Mood: friendly and intelligent.
7125
7213
  });
7126
7214
  });
7127
7215
  if (config?.fileUploads && config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
7128
- await createUppyRoutes(app, config);
7216
+ await createUppyRoutes(app, contexts ?? [], config);
7129
7217
  } else {
7130
7218
  console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
7131
7219
  }
@@ -7438,6 +7526,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7438
7526
  async (bullmqJob) => {
7439
7527
  console.log("[EXULU] starting execution for job", logMetadata2(bullmqJob.name, {
7440
7528
  name: bullmqJob.name,
7529
+ jobId: bullmqJob.id,
7441
7530
  status: await bullmqJob.getState(),
7442
7531
  type: bullmqJob.data.type
7443
7532
  }));
@@ -7452,6 +7541,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7452
7541
  });
7453
7542
  const workPromise = (async () => {
7454
7543
  try {
7544
+ console.log(`[EXULU] Job ${bullmqJob.id} - Log file: logs/jobs/job-${bullmqJob.id}.log`);
7455
7545
  bullmq.validate(bullmqJob.id, data);
7456
7546
  if (data.type === "embedder") {
7457
7547
  console.log("[EXULU] running an embedder job.", logMetadata2(bullmqJob.name));
@@ -7474,17 +7564,17 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7474
7564
  if (!embedder) {
7475
7565
  throw new Error(`Embedder ${data.embedder} not found in the registry.`);
7476
7566
  }
7477
- const result = await context.createAndUpsertEmbeddings(data.inputs, config, data.user, {
7567
+ const result2 = await context.createAndUpsertEmbeddings(data.inputs, config, data.user, {
7478
7568
  label: embedder.name,
7479
7569
  trigger: data.trigger
7480
7570
  }, data.role, bullmqJob.id);
7481
7571
  return {
7482
- result,
7572
+ result: result2,
7483
7573
  metadata: {}
7484
7574
  };
7485
7575
  }
7486
7576
  if (data.type === "processor") {
7487
- console.log("[EXULU] running a processor job.", logMetadata2(bullmqJob.name));
7577
+ console.log("[EXULU] running a processor job, job name: ", bullmqJob.name, " job id: ", bullmqJob.id, " job data: ", data, " job queue: ", bullmqJob.queueName);
7488
7578
  const label = `processor-${bullmqJob.name}`;
7489
7579
  await db3.from("job_results").insert({
7490
7580
  job_id: bullmqJob.id,
@@ -7506,35 +7596,42 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7506
7596
  if (!field.processor) {
7507
7597
  throw new Error(`Processor not set for field ${data.inputs.field} in the context ${data.context}.`);
7508
7598
  }
7509
- const exuluStorage = new ExuluStorage({ config });
7510
- if (!data.user) {
7511
- throw new Error(`User not set for processor job.`);
7512
- }
7513
- if (!data.role) {
7514
- throw new Error(`Role not set for processor job.`);
7599
+ if (!data.inputs.id) {
7600
+ throw new Error(`[EXULU] Item not set for processor ${field.name} in context ${context.id}, running in job ${bullmqJob.id}.`);
7515
7601
  }
7602
+ const exuluStorage = new ExuluStorage({ config });
7516
7603
  console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
7517
- const result = await field.processor.execute({
7604
+ const processorResult = await field.processor.execute({
7518
7605
  item: data.inputs,
7519
7606
  user: data.user,
7520
7607
  role: data.role,
7521
7608
  utils: {
7522
- storage: exuluStorage,
7523
- items: {
7524
- update: context.updateItem,
7525
- create: context.createItem,
7526
- delete: context.deleteItem
7527
- }
7609
+ storage: exuluStorage
7528
7610
  },
7529
- config
7611
+ exuluConfig: config
7612
+ });
7613
+ if (!processorResult) {
7614
+ throw new Error(`[EXULU] Processor ${field.name} in context ${context.id}, running in job ${bullmqJob.id} did not return an item.`);
7615
+ }
7616
+ delete processorResult.field;
7617
+ await db3.from(getTableName(context.id)).where({
7618
+ id: processorResult.id
7619
+ }).update({
7620
+ ...processorResult
7530
7621
  });
7531
7622
  let jobs = [];
7532
7623
  if (field.processor.config?.generateEmbeddings) {
7624
+ const fullItem = await db3.from(getTableName(context.id)).where({
7625
+ id: processorResult.id
7626
+ }).first();
7627
+ if (!fullItem) {
7628
+ throw new Error(`[EXULU] Item ${processorResult.id} not found after processor update in context ${context.id}`);
7629
+ }
7533
7630
  const { job: embeddingsJob } = await context.embeddings.generate.one({
7534
- item: data.inputs,
7631
+ item: fullItem,
7535
7632
  user: data.user,
7536
7633
  role: data.role,
7537
- trigger: "api",
7634
+ trigger: "processor",
7538
7635
  config
7539
7636
  });
7540
7637
  if (embeddingsJob) {
@@ -7542,7 +7639,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7542
7639
  }
7543
7640
  }
7544
7641
  return {
7545
- result,
7642
+ result: processorResult,
7546
7643
  metadata: {
7547
7644
  jobs: jobs.length > 0 ? jobs.join(",") : void 0
7548
7645
  }
@@ -7609,9 +7706,9 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7609
7706
  }
7610
7707
  }
7611
7708
  });
7612
- const result = await promise;
7613
- const messages = result.messages;
7614
- const metadata = result.metadata;
7709
+ const result2 = await promise;
7710
+ const messages = result2.messages;
7711
+ const metadata = result2.metadata;
7615
7712
  const evalFunctions = evalRun.eval_functions;
7616
7713
  let evalFunctionResults = [];
7617
7714
  for (const evalFunction of evalFunctions) {
@@ -7619,7 +7716,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7619
7716
  if (!evalMethod) {
7620
7717
  throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
7621
7718
  }
7622
- let result2;
7719
+ let result3;
7623
7720
  if (evalMethod.queue) {
7624
7721
  const queue2 = await evalMethod.queue;
7625
7722
  const jobData = {
@@ -7650,21 +7747,21 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7650
7747
  if (!job.id) {
7651
7748
  throw new Error(`Tried to add job to queue ${queue2.queue.name} but failed to get the job ID.`);
7652
7749
  }
7653
- result2 = await pollJobResult({ queue: queue2, jobId: job.id });
7750
+ result3 = await pollJobResult({ queue: queue2, jobId: job.id });
7654
7751
  const evalFunctionResult = {
7655
7752
  test_case_id: testCase.id,
7656
7753
  eval_run_id: evalRun.id,
7657
7754
  eval_function_id: evalFunction.id,
7658
7755
  eval_function_name: evalFunction.name,
7659
7756
  eval_function_config: evalFunction.config || {},
7660
- result: result2 || 0
7757
+ result: result3 || 0
7661
7758
  };
7662
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
7663
- result: result2 || 0
7759
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result3}`, logMetadata2(bullmqJob.name, {
7760
+ result: result3 || 0
7664
7761
  }));
7665
7762
  evalFunctionResults.push(evalFunctionResult);
7666
7763
  } else {
7667
- result2 = await evalMethod.run(
7764
+ result3 = await evalMethod.run(
7668
7765
  agentInstance,
7669
7766
  agentBackend,
7670
7767
  testCase,
@@ -7675,15 +7772,15 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7675
7772
  test_case_id: testCase.id,
7676
7773
  eval_run_id: evalRun.id,
7677
7774
  eval_function_id: evalFunction.id,
7678
- result: result2 || 0
7775
+ result: result3 || 0
7679
7776
  };
7680
7777
  evalFunctionResults.push(evalFunctionResult);
7681
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
7682
- result: result2 || 0
7778
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result3}`, logMetadata2(bullmqJob.name, {
7779
+ result: result3 || 0
7683
7780
  }));
7684
7781
  }
7685
7782
  }
7686
- const scores = evalFunctionResults.map((result2) => result2.result);
7783
+ const scores = evalFunctionResults.map((result3) => result3.result);
7687
7784
  console.log("[EXULU] Exulu eval run scores for test case: " + testCase.id, scores);
7688
7785
  let score = 0;
7689
7786
  switch (data.scoring_method?.toLowerCase()) {
@@ -7748,25 +7845,25 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7748
7845
  messages: inputMessages
7749
7846
  } = await validateEvalPayload(data, agents);
7750
7847
  const evalFunctions = evalRun.eval_functions;
7751
- let result;
7848
+ let result2;
7752
7849
  for (const evalFunction of evalFunctions) {
7753
7850
  const evalMethod = evals.find((e) => e.id === evalFunction.id);
7754
7851
  if (!evalMethod) {
7755
7852
  throw new Error(`Eval function ${evalFunction.id} not found in the registry, check your code and make sure the eval function is registered correctly.`);
7756
7853
  }
7757
- result = await evalMethod.run(
7854
+ result2 = await evalMethod.run(
7758
7855
  agentInstance,
7759
7856
  backend,
7760
7857
  testCase,
7761
7858
  inputMessages,
7762
7859
  evalFunction.config || {}
7763
7860
  );
7764
- console.log(`[EXULU] eval function ${evalFunction.id} result: ${result}`, logMetadata2(bullmqJob.name, {
7765
- result: result || 0
7861
+ console.log(`[EXULU] eval function ${evalFunction.id} result: ${result2}`, logMetadata2(bullmqJob.name, {
7862
+ result: result2 || 0
7766
7863
  }));
7767
7864
  }
7768
7865
  return {
7769
- result,
7866
+ result: result2,
7770
7867
  metadata: {}
7771
7868
  };
7772
7869
  }
@@ -7786,10 +7883,10 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7786
7883
  if (!source) {
7787
7884
  throw new Error(`Source ${data.source} not found in the context ${context.id}.`);
7788
7885
  }
7789
- const result = await source.execute(data.inputs);
7886
+ const result2 = await source.execute(data.inputs);
7790
7887
  let jobs = [];
7791
7888
  let items = [];
7792
- for (const item of result) {
7889
+ for (const item of result2) {
7793
7890
  const { item: createdItem, job } = await context.createItem(
7794
7891
  item,
7795
7892
  config,
@@ -7821,7 +7918,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7821
7918
  role: data?.role
7822
7919
  });
7823
7920
  return {
7824
- result,
7921
+ result: result2,
7825
7922
  metadata: {
7826
7923
  jobs,
7827
7924
  items
@@ -7834,11 +7931,13 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7834
7931
  throw error;
7835
7932
  }
7836
7933
  })();
7837
- return Promise.race([workPromise, timeoutPromise]);
7934
+ const result = await Promise.race([workPromise, timeoutPromise]);
7935
+ return result;
7838
7936
  },
7839
7937
  {
7840
7938
  autorun: true,
7841
7939
  connection: redisConnection,
7940
+ concurrency: queue.concurrency?.worker || 1,
7842
7941
  removeOnComplete: { count: 1e3 },
7843
7942
  removeOnFail: { count: 5e3 },
7844
7943
  ...queue.ratelimit && {
@@ -9042,18 +9141,23 @@ var ExuluQueues = class {
9042
9141
  // method of ExuluQueues we need to store the desired rate limit on the queue
9043
9142
  // here so we can use the value when creating workers for the queue instance
9044
9143
  // as there is no way to store a rate limit value natively on a bullm queue.
9045
- register = (name, concurrency = 1, ratelimit = 1) => {
9144
+ register = (name, concurrency, ratelimit = 1) => {
9145
+ const queueConcurrency = concurrency.queue || 1;
9146
+ const workerConcurrency = concurrency.worker || 1;
9046
9147
  const use = async () => {
9047
9148
  const existing = this.queues.find((x) => x.queue?.name === name);
9048
9149
  if (existing) {
9049
9150
  const globalConcurrency = await existing.queue.getGlobalConcurrency();
9050
- if (globalConcurrency !== concurrency) {
9051
- await existing.queue.setGlobalConcurrency(concurrency);
9151
+ if (globalConcurrency !== queueConcurrency) {
9152
+ await existing.queue.setGlobalConcurrency(queueConcurrency);
9052
9153
  }
9053
9154
  return {
9054
9155
  queue: existing.queue,
9055
9156
  ratelimit,
9056
- concurrency
9157
+ concurrency: {
9158
+ worker: workerConcurrency,
9159
+ queue: queueConcurrency
9160
+ }
9057
9161
  };
9058
9162
  }
9059
9163
  if (!redisServer.host?.length || !redisServer.port?.length) {
@@ -9072,21 +9176,30 @@ var ExuluQueues = class {
9072
9176
  telemetry: new import_bullmq_otel.BullMQOtel("simple-guide")
9073
9177
  }
9074
9178
  );
9075
- await newQueue.setGlobalConcurrency(concurrency);
9179
+ await newQueue.setGlobalConcurrency(queueConcurrency);
9076
9180
  this.queues.push({
9077
9181
  queue: newQueue,
9078
9182
  ratelimit,
9079
- concurrency
9183
+ concurrency: {
9184
+ worker: workerConcurrency,
9185
+ queue: queueConcurrency
9186
+ }
9080
9187
  });
9081
9188
  return {
9082
9189
  queue: newQueue,
9083
9190
  ratelimit,
9084
- concurrency
9191
+ concurrency: {
9192
+ worker: workerConcurrency,
9193
+ queue: queueConcurrency
9194
+ }
9085
9195
  };
9086
9196
  };
9087
9197
  this.list.set(name, {
9088
9198
  name,
9089
- concurrency,
9199
+ concurrency: {
9200
+ worker: workerConcurrency,
9201
+ queue: queueConcurrency
9202
+ },
9090
9203
  ratelimit,
9091
9204
  use
9092
9205
  });
@@ -9142,7 +9255,10 @@ var llmAsJudgeEval = () => {
9142
9255
  name: "prompt",
9143
9256
  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."
9144
9257
  }],
9145
- queue: queues.register("llm_as_judge", 1, 1).use(),
9258
+ queue: queues.register("llm_as_judge", {
9259
+ worker: 1,
9260
+ queue: 1
9261
+ }, 1).use(),
9146
9262
  llm: true
9147
9263
  });
9148
9264
  }
@@ -9777,11 +9893,16 @@ var previewPdfTool = new ExuluTool2({
9777
9893
  type: "function",
9778
9894
  config: [],
9779
9895
  inputSchema: import_zod5.z.object({
9780
- s3key: import_zod5.z.string().describe("The S3 key of the PDF file to preview, can also optionally include a [bucket:name] to specify the bucket."),
9896
+ s3key: import_zod5.z.string().describe("The S3 key of the PDF file to preview."),
9781
9897
  page: import_zod5.z.number().describe("The page number to preview, defaults to 1.").optional()
9782
9898
  }),
9783
9899
  execute: async ({ s3key, page, exuluConfig }) => {
9784
- const url = await getPresignedUrl(s3key, exuluConfig);
9900
+ const bucket = s3key.split("/")[0];
9901
+ const key = s3key.split("/").slice(1).join("/");
9902
+ if (!bucket || !key) {
9903
+ throw new Error("Invalid S3 key, must be in the format of <bucket>/<key>.");
9904
+ }
9905
+ const url = await getPresignedUrl(bucket, key, exuluConfig);
9785
9906
  if (!url) {
9786
9907
  throw new Error("No URL provided for PDF preview");
9787
9908
  }
@@ -10175,7 +10296,10 @@ var ExuluApp = class {
10175
10296
  }
10176
10297
  const queueSet = /* @__PURE__ */ new Set();
10177
10298
  if (redisServer.host?.length && redisServer.port?.length) {
10178
- queues.register(global_queues.eval_runs, 1, 1);
10299
+ queues.register(global_queues.eval_runs, {
10300
+ worker: 1,
10301
+ queue: 1
10302
+ }, 1);
10179
10303
  for (const queue of queues.list.values()) {
10180
10304
  const config2 = await queue.use();
10181
10305
  queueSet.add(config2);