@exulu/backend 1.22.0 → 1.23.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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
- # [1.22.0](https://github.com/Qventu/exulu-backend/compare/v1.21.0...v1.22.0) (2025-09-03)
1
+ # [1.23.0](https://github.com/Qventu/exulu-backend/compare/v1.22.0...v1.23.0) (2025-09-09)
2
2
 
3
3
 
4
4
  ### Features
5
5
 
6
- * improved UI layout for chat interface ([4802a3c](https://github.com/Qventu/exulu-backend/commit/4802a3c0ac3eb21fc6a0f492f8786de341bd7103))
6
+ * uploads in chat ([e18407c](https://github.com/Qventu/exulu-backend/commit/e18407c8aa77e821ec70960223aa3fb4c5901f9b))
package/dist/index.cjs CHANGED
@@ -43,7 +43,6 @@ __export(index_exports, {
43
43
  ExuluOtel: () => ExuluOtel,
44
44
  ExuluQueues: () => queues,
45
45
  ExuluTool: () => ExuluTool2,
46
- ExuluZodFileType: () => ExuluZodFileType,
47
46
  db: () => db2
48
47
  });
49
48
  module.exports = __toCommonJS(index_exports);
@@ -803,6 +802,10 @@ var agentSessionsSchema = {
803
802
  {
804
803
  name: "title",
805
804
  type: "text"
805
+ },
806
+ {
807
+ name: "project",
808
+ type: "uuid"
806
809
  }
807
810
  ]
808
811
  };
@@ -880,6 +883,37 @@ var workflowTemplatesSchema = {
880
883
  }
881
884
  ]
882
885
  };
886
+ var projectsSchema = {
887
+ type: "projects",
888
+ name: {
889
+ plural: "projects",
890
+ singular: "project"
891
+ },
892
+ RBAC: true,
893
+ fields: [
894
+ {
895
+ name: "name",
896
+ type: "text",
897
+ required: true
898
+ },
899
+ {
900
+ name: "description",
901
+ type: "text"
902
+ },
903
+ {
904
+ name: "image",
905
+ type: "text"
906
+ },
907
+ {
908
+ name: "custom_instructions",
909
+ type: "longText"
910
+ },
911
+ {
912
+ name: "context_files",
913
+ type: "json"
914
+ }
915
+ ]
916
+ };
883
917
  var agentsSchema = {
884
918
  type: "agents",
885
919
  name: {
@@ -939,6 +973,10 @@ var usersSchema = {
939
973
  name: "favourite_agents",
940
974
  type: "json"
941
975
  },
976
+ {
977
+ name: "favourite_projects",
978
+ type: "json"
979
+ },
942
980
  {
943
981
  name: "firstname",
944
982
  type: "text"
@@ -1226,6 +1264,10 @@ var rbacSchema = {
1226
1264
  name: "user_id",
1227
1265
  type: "number"
1228
1266
  },
1267
+ {
1268
+ name: "project_id",
1269
+ type: "uuid"
1270
+ },
1229
1271
  {
1230
1272
  name: "rights",
1231
1273
  type: "text",
@@ -1257,6 +1299,7 @@ var coreSchemas = {
1257
1299
  agentsSchema: () => addRBACfields(agentsSchema),
1258
1300
  agentMessagesSchema: () => addRBACfields(agentMessagesSchema),
1259
1301
  agentSessionsSchema: () => addRBACfields(agentSessionsSchema),
1302
+ projectsSchema: () => addRBACfields(projectsSchema),
1260
1303
  usersSchema: () => addRBACfields(usersSchema),
1261
1304
  rolesSchema: () => addRBACfields(rolesSchema),
1262
1305
  statisticsSchema: () => addRBACfields(statisticsSchema),
@@ -1495,7 +1538,7 @@ var getRequestedFields = (info) => {
1495
1538
  return fields.filter((field) => field !== "pageInfo" && field !== "items" && field !== "RBAC");
1496
1539
  };
1497
1540
  var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRbacRecords) => {
1498
- const { users = [], roles = [] } = rbacData;
1541
+ const { users = [], roles = [], projects = [] } = rbacData;
1499
1542
  if (!existingRbacRecords) {
1500
1543
  existingRbacRecords = await db3.from("rbac").where({
1501
1544
  entity: entityName,
@@ -1504,18 +1547,25 @@ var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRba
1504
1547
  }
1505
1548
  const newUserRecords = new Set(users.map((u) => `${u.id}:${u.rights}`));
1506
1549
  const newRoleRecords = new Set(roles.map((r) => `${r.id}:${r.rights}`));
1550
+ const newProjectRecords = new Set(projects.map((p) => `${p.id}:${p.rights}`));
1507
1551
  const existingUserRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "User").map((r) => `${r.user_id}:${r.rights}`));
1508
1552
  const existingRoleRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "Role").map((r) => `${r.role_id}:${r.rights}`));
1553
+ const existingProjectRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "Project").map((r) => `${r.project_id}:${r.rights}`));
1509
1554
  const usersToCreate = users.filter((u) => !existingUserRecords.has(`${u.id}:${u.rights}`));
1510
1555
  const rolesToCreate = roles.filter((r) => !existingRoleRecords.has(`${r.id}:${r.rights}`));
1556
+ const projectsToCreate = projects.filter((p) => !existingProjectRecords.has(`${p.id}:${p.rights}`));
1511
1557
  const usersToRemove = existingRbacRecords.filter((r) => r.access_type === "User" && !newUserRecords.has(`${r.user_id}:${r.rights}`));
1512
1558
  const rolesToRemove = existingRbacRecords.filter((r) => r.access_type === "Role" && !newRoleRecords.has(`${r.role_id}:${r.rights}`));
1559
+ const projectsToRemove = existingRbacRecords.filter((r) => r.access_type === "Project" && !newProjectRecords.has(`${r.project_id}:${r.rights}`));
1513
1560
  if (usersToRemove.length > 0) {
1514
1561
  await db3.from("rbac").whereIn("id", usersToRemove.map((r) => r.id)).del();
1515
1562
  }
1516
1563
  if (rolesToRemove.length > 0) {
1517
1564
  await db3.from("rbac").whereIn("id", rolesToRemove.map((r) => r.id)).del();
1518
1565
  }
1566
+ if (projectsToRemove.length > 0) {
1567
+ await db3.from("rbac").whereIn("id", projectsToRemove.map((r) => r.id)).del();
1568
+ }
1519
1569
  const recordsToInsert = [];
1520
1570
  usersToCreate.forEach((user) => {
1521
1571
  recordsToInsert.push({
@@ -1539,6 +1589,17 @@ var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRba
1539
1589
  updatedAt: /* @__PURE__ */ new Date()
1540
1590
  });
1541
1591
  });
1592
+ projectsToCreate.forEach((project) => {
1593
+ recordsToInsert.push({
1594
+ entity: entityName,
1595
+ access_type: "Project",
1596
+ target_resource_id: resourceId,
1597
+ project_id: project.id,
1598
+ rights: project.rights,
1599
+ createdAt: /* @__PURE__ */ new Date(),
1600
+ updatedAt: /* @__PURE__ */ new Date()
1601
+ });
1602
+ });
1542
1603
  if (recordsToInsert.length > 0) {
1543
1604
  await db3.from("rbac").insert(recordsToInsert);
1544
1605
  }
@@ -1603,6 +1664,52 @@ function createMutations(table, agents, contexts, tools) {
1603
1664
  }
1604
1665
  throw new Error("Insufficient role permissions to edit this record");
1605
1666
  }
1667
+ if (record.rights_mode === "projects") {
1668
+ const projects = await db3.from("rbac").where({
1669
+ entity: table.name.singular,
1670
+ target_resource_id: id,
1671
+ access_type: "Project",
1672
+ rights: "write"
1673
+ });
1674
+ if (projects.length === 0) {
1675
+ throw new Error("Entity ${table.name.singular} has its rights mode set to projects, but is not shared with any projects.");
1676
+ }
1677
+ const checks = await Promise.all(projects.map(async (project) => {
1678
+ if (project.rights_mode === "private" && project.created_by !== user.id) {
1679
+ return false;
1680
+ }
1681
+ if (project.rights_mode === "users") {
1682
+ const rbacRecord = await db3.from("rbac").where({
1683
+ entity: "project",
1684
+ target_resource_id: project.id,
1685
+ access_type: "User",
1686
+ user_id: user.id,
1687
+ rights: "write"
1688
+ }).first();
1689
+ if (rbacRecord) {
1690
+ return true;
1691
+ }
1692
+ return false;
1693
+ }
1694
+ if (record.rights_mode === "roles" && user.role) {
1695
+ const rbacRecord = await db3.from("rbac").where({
1696
+ entity: "project",
1697
+ target_resource_id: project.id,
1698
+ access_type: "Role",
1699
+ role_id: user.role,
1700
+ rights: "write"
1701
+ }).first();
1702
+ if (rbacRecord) {
1703
+ return true;
1704
+ }
1705
+ return false;
1706
+ }
1707
+ return false;
1708
+ }));
1709
+ if (checks.some((check) => check)) {
1710
+ return true;
1711
+ }
1712
+ }
1606
1713
  throw new Error("Insufficient permissions to edit this record");
1607
1714
  } catch (error) {
1608
1715
  console.error("Write access validation error:", error);
@@ -2590,13 +2697,16 @@ var RBACResolver = async (db3, entityName, resourceId, rights_mode) => {
2590
2697
  }).select("*");
2591
2698
  const users = rbacRecords.filter((r) => r.access_type === "User")?.map((r) => ({ id: r.user_id, rights: r.rights }));
2592
2699
  const roles = rbacRecords.filter((r) => r.access_type === "Role")?.map((r) => ({ id: r.role_id, rights: r.rights }));
2700
+ const projects = rbacRecords.filter((r) => r.access_type === "Project")?.map((r) => ({ id: r.project_id, rights: r.rights }));
2593
2701
  let type = rights_mode || "private";
2594
2702
  if (type === "users" && users.length === 0) type = "private";
2595
2703
  if (type === "roles" && roles.length === 0) type = "private";
2704
+ if (type === "projects" && projects.length === 0) type = "private";
2596
2705
  return {
2597
2706
  type,
2598
2707
  users,
2599
- roles
2708
+ roles,
2709
+ projects
2600
2710
  };
2601
2711
  };
2602
2712
  var contextToTableDefinition = (context) => {
@@ -2689,6 +2799,7 @@ function createSDL(tables, contexts, agents, tools) {
2689
2799
  type: String!
2690
2800
  users: [RBACUser!]
2691
2801
  roles: [RBACRole!]
2802
+ projects: [RBACProject!]
2692
2803
  }
2693
2804
 
2694
2805
  type RBACUser {
@@ -2701,9 +2812,15 @@ function createSDL(tables, contexts, agents, tools) {
2701
2812
  rights: String!
2702
2813
  }
2703
2814
 
2815
+ type RBACProject {
2816
+ id: ID!
2817
+ rights: String!
2818
+ }
2819
+
2704
2820
  input RBACInput {
2705
2821
  users: [RBACUserInput!]
2706
2822
  roles: [RBACRoleInput!]
2823
+ projects: [RBACProjectInput!]
2707
2824
  }
2708
2825
 
2709
2826
  input RBACUserInput {
@@ -2716,6 +2833,11 @@ function createSDL(tables, contexts, agents, tools) {
2716
2833
  rights: String!
2717
2834
  }
2718
2835
 
2836
+ input RBACProjectInput {
2837
+ id: ID!
2838
+ rights: String!
2839
+ }
2840
+
2719
2841
  type Query {
2720
2842
  `;
2721
2843
  let mutationDefs = `
@@ -3126,21 +3248,6 @@ function generateSlug(name) {
3126
3248
  const slug = lowercase.replace(/[\W_]+/g, "-").replace(/^-+|-+$/g, "");
3127
3249
  return slug;
3128
3250
  }
3129
- var ExuluZodFileType = ({
3130
- name,
3131
- label,
3132
- description,
3133
- allowedFileTypes
3134
- }) => {
3135
- return import_zod2.z.object({
3136
- [`exulu_file_${name}`]: import_zod2.z.string().describe(JSON.stringify({
3137
- label,
3138
- isFile: true,
3139
- description,
3140
- allowedFileTypes
3141
- }))
3142
- });
3143
- };
3144
3251
  var ExuluAgent2 = class {
3145
3252
  // Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
3146
3253
  // underscores and be a max length of 80 characters and at least 5 characters long.
@@ -4241,23 +4348,11 @@ var import_express5 = require("@as-integrations/express5");
4241
4348
 
4242
4349
  // src/registry/uppy.ts
4243
4350
  var import_express2 = require("express");
4244
- var createUppyRoutes = async (app) => {
4245
- const {
4246
- S3Client,
4247
- AbortMultipartUploadCommand,
4248
- CompleteMultipartUploadCommand,
4249
- CreateMultipartUploadCommand,
4250
- GetObjectCommand,
4251
- ListPartsCommand,
4252
- PutObjectCommand,
4253
- UploadPartCommand,
4254
- ListObjectsV2Command
4255
- } = require("@aws-sdk/client-s3");
4256
- const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
4257
- const {
4258
- STSClient,
4259
- GetFederationTokenCommand
4260
- } = require("@aws-sdk/client-sts");
4351
+ var import_client_s3 = require("@aws-sdk/client-s3");
4352
+ var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
4353
+ var import_client_sts = require("@aws-sdk/client-sts");
4354
+ var import_node_crypto = require("crypto");
4355
+ var createUppyRoutes = async (app, config) => {
4261
4356
  const policy = {
4262
4357
  Version: "2012-10-17",
4263
4358
  Statement: [
@@ -4267,8 +4362,8 @@ var createUppyRoutes = async (app) => {
4267
4362
  "s3:PutObject"
4268
4363
  ],
4269
4364
  Resource: [
4270
- `arn:aws:s3:::${process.env.COMPANION_S3_BUCKET}/*`,
4271
- `arn:aws:s3:::${process.env.COMPANION_S3_BUCKET}`
4365
+ `arn:aws:s3:::${config.fileUploads.s3Bucket}/*`,
4366
+ `arn:aws:s3:::${config.fileUploads.s3Bucket}`
4272
4367
  ]
4273
4368
  }
4274
4369
  ]
@@ -4277,26 +4372,26 @@ var createUppyRoutes = async (app) => {
4277
4372
  let stsClient;
4278
4373
  const expiresIn = 60 * 60 * 24 * 1;
4279
4374
  function getS3Client() {
4280
- s3Client ??= new S3Client({
4281
- region: process.env.COMPANION_S3_REGION,
4282
- ...process.env.COMPANION_S3_ENDPOINT && {
4375
+ s3Client ??= new import_client_s3.S3Client({
4376
+ region: config.fileUploads.s3region,
4377
+ ...config.fileUploads.s3endpoint && {
4283
4378
  forcePathStyle: true,
4284
- endpoint: process.env.COMPANION_S3_ENDPOINT
4379
+ endpoint: config.fileUploads.s3endpoint
4285
4380
  },
4286
4381
  credentials: {
4287
- accessKeyId: process.env.COMPANION_S3_KEY,
4288
- secretAccessKey: process.env.COMPANION_S3_SECRET
4382
+ accessKeyId: config.fileUploads.s3key,
4383
+ secretAccessKey: config.fileUploads.s3secret
4289
4384
  }
4290
4385
  });
4291
4386
  return s3Client;
4292
4387
  }
4293
4388
  function getSTSClient() {
4294
- stsClient ??= new STSClient({
4295
- region: process.env.COMPANION_S3_REGION,
4296
- ...process.env.COMPANION_S3_ENDPOINT && { endpoint: process.env.COMPANION_S3_ENDPOINT },
4389
+ stsClient ??= new import_client_sts.STSClient({
4390
+ region: config.fileUploads.s3region,
4391
+ ...config.fileUploads.s3endpoint && { endpoint: config.fileUploads.s3endpoint },
4297
4392
  credentials: {
4298
- accessKeyId: process.env.COMPANION_S3_KEY,
4299
- secretAccessKey: process.env.COMPANION_S3_SECRET
4393
+ accessKeyId: config.fileUploads.s3key,
4394
+ secretAccessKey: config.fileUploads.s3secret
4300
4395
  }
4301
4396
  });
4302
4397
  return stsClient;
@@ -4328,8 +4423,8 @@ var createUppyRoutes = async (app) => {
4328
4423
  return;
4329
4424
  }
4330
4425
  try {
4331
- const command = new ListObjectsV2Command({
4332
- Bucket: process.env.COMPANION_S3_BUCKET,
4426
+ const command = new import_client_s3.ListObjectsV2Command({
4427
+ Bucket: config.fileUploads.s3Bucket,
4333
4428
  Prefix: prefix,
4334
4429
  MaxKeys: 1e3
4335
4430
  // Adjust this value based on your needs
@@ -4378,10 +4473,10 @@ var createUppyRoutes = async (app) => {
4378
4473
  return;
4379
4474
  }
4380
4475
  try {
4381
- const url = await getSignedUrl(
4476
+ const url = await (0, import_s3_request_presigner.getSignedUrl)(
4382
4477
  getS3Client(),
4383
- new GetObjectCommand({
4384
- Bucket: process.env.COMPANION_S3_BUCKET,
4478
+ new import_client_s3.GetObjectCommand({
4479
+ Bucket: config.fileUploads.s3Bucket,
4385
4480
  Key: key
4386
4481
  }),
4387
4482
  { expiresIn }
@@ -4393,7 +4488,7 @@ var createUppyRoutes = async (app) => {
4393
4488
  }
4394
4489
  });
4395
4490
  app.get("/s3/sts", (req, res, next) => {
4396
- getSTSClient().send(new GetFederationTokenCommand({
4491
+ getSTSClient().send(new import_client_sts.GetFederationTokenCommand({
4397
4492
  Name: "Exulu",
4398
4493
  // The duration, in seconds, of the role session. The value specified
4399
4494
  // can range from 900 seconds (15 minutes) up to the maximum session
@@ -4405,8 +4500,8 @@ var createUppyRoutes = async (app) => {
4405
4500
  res.setHeader("Cache-Control", `public,max-age=${expiresIn}`);
4406
4501
  res.json({
4407
4502
  credentials: response.Credentials,
4408
- bucket: process.env.COMPANION_S3_BUCKET,
4409
- region: process.env.COMPANION_S3_REGION
4503
+ bucket: config.fileUploads.s3Bucket,
4504
+ region: config.fileUploads.s3region
4410
4505
  });
4411
4506
  }, next);
4412
4507
  });
@@ -4423,7 +4518,7 @@ var createUppyRoutes = async (app) => {
4423
4518
  contentType: params.type
4424
4519
  };
4425
4520
  };
4426
- const generateS3Key = (filename) => `${crypto.randomUUID()}-${filename}`;
4521
+ const generateS3Key = (filename) => `${(0, import_node_crypto.randomUUID)()}-${filename}`;
4427
4522
  const signOnServer = async (req, res, next) => {
4428
4523
  const apikey = req.headers["exulu-api-key"] || null;
4429
4524
  const { db: db3 } = await postgresClient();
@@ -4449,10 +4544,10 @@ var createUppyRoutes = async (app) => {
4449
4544
  } else {
4450
4545
  folder = `${authenticationResult.user.id}/`;
4451
4546
  }
4452
- getSignedUrl(
4547
+ (0, import_s3_request_presigner.getSignedUrl)(
4453
4548
  getS3Client(),
4454
- new PutObjectCommand({
4455
- Bucket: process.env.COMPANION_S3_BUCKET,
4549
+ new import_client_s3.PutObjectCommand({
4550
+ Bucket: config.fileUploads.s3Bucket,
4456
4551
  Key: folder + key,
4457
4552
  ContentType: contentType
4458
4553
  }),
@@ -4460,6 +4555,7 @@ var createUppyRoutes = async (app) => {
4460
4555
  ).then((url) => {
4461
4556
  res.setHeader("Access-Control-Allow-Origin", "*");
4462
4557
  res.json({
4558
+ key,
4463
4559
  url,
4464
4560
  method: "PUT"
4465
4561
  });
@@ -4496,7 +4592,7 @@ var createUppyRoutes = async (app) => {
4496
4592
  if (typeof type !== "string") {
4497
4593
  return res.status(400).json({ error: "s3: content type must be a string" });
4498
4594
  }
4499
- const key = `${crypto.randomUUID()}-${filename}`;
4595
+ const key = `${(0, import_node_crypto.randomUUID)()}-${filename}`;
4500
4596
  let folder = "";
4501
4597
  if (authenticationResult.user.type === "api") {
4502
4598
  folder = `api/`;
@@ -4504,12 +4600,12 @@ var createUppyRoutes = async (app) => {
4504
4600
  folder = `${authenticationResult.user.id}/`;
4505
4601
  }
4506
4602
  const params = {
4507
- Bucket: process.env.COMPANION_S3_BUCKET,
4603
+ Bucket: config.fileUploads.s3Bucket,
4508
4604
  Key: folder + key,
4509
4605
  ContentType: type,
4510
4606
  Metadata: metadata
4511
4607
  };
4512
- const command = new CreateMultipartUploadCommand(params);
4608
+ const command = new import_client_s3.CreateMultipartUploadCommand(params);
4513
4609
  return client2.send(command, (err, data) => {
4514
4610
  if (err) {
4515
4611
  next(err);
@@ -4517,7 +4613,7 @@ var createUppyRoutes = async (app) => {
4517
4613
  }
4518
4614
  res.setHeader("Access-Control-Allow-Origin", "*");
4519
4615
  res.json({
4520
- key: data.Key,
4616
+ key,
4521
4617
  uploadId: data.UploadId
4522
4618
  });
4523
4619
  });
@@ -4535,11 +4631,11 @@ var createUppyRoutes = async (app) => {
4535
4631
  if (typeof key !== "string") {
4536
4632
  return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' });
4537
4633
  }
4538
- return getSignedUrl(getS3Client(), new UploadPartCommand({
4539
- Bucket: process.env.COMPANION_S3_BUCKET,
4634
+ return (0, import_s3_request_presigner.getSignedUrl)(getS3Client(), new import_client_s3.UploadPartCommand({
4635
+ Bucket: config.fileUploads.s3Bucket,
4540
4636
  Key: key,
4541
4637
  UploadId: uploadId,
4542
- PartNumber: partNumber,
4638
+ PartNumber: Number(partNumber),
4543
4639
  Body: ""
4544
4640
  }), { expiresIn }).then((url) => {
4545
4641
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -4556,8 +4652,8 @@ var createUppyRoutes = async (app) => {
4556
4652
  }
4557
4653
  const parts = [];
4558
4654
  function listPartsPage(startAt) {
4559
- client2.send(new ListPartsCommand({
4560
- Bucket: process.env.COMPANION_S3_BUCKET,
4655
+ client2.send(new import_client_s3.ListPartsCommand({
4656
+ Bucket: config.fileUploads.s3Bucket,
4561
4657
  Key: key,
4562
4658
  UploadId: uploadId,
4563
4659
  PartNumberMarker: startAt
@@ -4590,8 +4686,8 @@ var createUppyRoutes = async (app) => {
4590
4686
  if (!Array.isArray(parts) || !parts.every(isValidPart)) {
4591
4687
  return res.status(400).json({ error: "s3: `parts` must be an array of {ETag, PartNumber} objects." });
4592
4688
  }
4593
- return client2.send(new CompleteMultipartUploadCommand({
4594
- Bucket: process.env.COMPANION_S3_BUCKET,
4689
+ return client2.send(new import_client_s3.CompleteMultipartUploadCommand({
4690
+ Bucket: config.fileUploads.s3Bucket,
4595
4691
  Key: key,
4596
4692
  UploadId: uploadId,
4597
4693
  MultipartUpload: {
@@ -4604,6 +4700,7 @@ var createUppyRoutes = async (app) => {
4604
4700
  }
4605
4701
  res.setHeader("Access-Control-Allow-Origin", "*");
4606
4702
  res.json({
4703
+ key,
4607
4704
  location: data.Location
4608
4705
  });
4609
4706
  });
@@ -4615,8 +4712,8 @@ var createUppyRoutes = async (app) => {
4615
4712
  if (typeof key !== "string") {
4616
4713
  return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' });
4617
4714
  }
4618
- return client2.send(new AbortMultipartUploadCommand({
4619
- Bucket: process.env.COMPANION_S3_BUCKET,
4715
+ return client2.send(new import_client_s3.AbortMultipartUploadCommand({
4716
+ Bucket: config.fileUploads.s3Bucket,
4620
4717
  Key: key,
4621
4718
  UploadId: uploadId
4622
4719
  }), (err) => {
@@ -4624,7 +4721,9 @@ var createUppyRoutes = async (app) => {
4624
4721
  next(err);
4625
4722
  return;
4626
4723
  }
4627
- res.json({});
4724
+ res.json({
4725
+ key
4726
+ });
4628
4727
  });
4629
4728
  });
4630
4729
  return app;
@@ -4660,7 +4759,7 @@ var CLAUDE_MESSAGES = {
4660
4759
  // src/registry/routes.ts
4661
4760
  var import_openai = __toESM(require("openai"), 1);
4662
4761
  var import_fs = __toESM(require("fs"), 1);
4663
- var import_node_crypto = require("crypto");
4762
+ var import_node_crypto2 = require("crypto");
4664
4763
  var import_api2 = require("@opentelemetry/api");
4665
4764
  var REQUEST_SIZE_LIMIT = "50mb";
4666
4765
  var global_queues = {
@@ -4668,6 +4767,7 @@ var global_queues = {
4668
4767
  };
4669
4768
  var {
4670
4769
  agentsSchema: agentsSchema2,
4770
+ projectsSchema: projectsSchema2,
4671
4771
  evalResultsSchema: evalResultsSchema2,
4672
4772
  jobsSchema: jobsSchema2,
4673
4773
  agentSessionsSchema: agentSessionsSchema2,
@@ -4742,6 +4842,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
4742
4842
  usersSchema2(),
4743
4843
  rolesSchema2(),
4744
4844
  agentsSchema2(),
4845
+ projectsSchema2(),
4745
4846
  jobsSchema2(),
4746
4847
  evalResultsSchema2(),
4747
4848
  agentSessionsSchema2(),
@@ -4867,7 +4968,7 @@ Mood: friendly and intelligent.
4867
4968
  return;
4868
4969
  }
4869
4970
  const image_bytes = Buffer.from(image_base64, "base64");
4870
- const uuid = (0, import_node_crypto.randomUUID)();
4971
+ const uuid = (0, import_node_crypto2.randomUUID)();
4871
4972
  if (!import_fs.default.existsSync("public")) {
4872
4973
  import_fs.default.mkdirSync("public");
4873
4974
  }
@@ -5096,10 +5197,10 @@ Mood: friendly and intelligent.
5096
5197
  }
5097
5198
  });
5098
5199
  });
5099
- if (process.env.COMPANION_S3_REGION && process.env.COMPANION_S3_KEY && process.env.COMPANION_S3_SECRET) {
5100
- await createUppyRoutes(app);
5200
+ if (config?.fileUploads?.s3region && config?.fileUploads?.s3key && config?.fileUploads?.s3secret && config?.fileUploads?.s3Bucket) {
5201
+ await createUppyRoutes(app, config);
5101
5202
  } else {
5102
- console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
5203
+ console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
5103
5204
  }
5104
5205
  const TARGET_API = "https://api.anthropic.com";
5105
5206
  app.use("/gateway/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
@@ -5371,7 +5472,7 @@ var createLogsCleanerWorker = (logsDir) => {
5371
5472
 
5372
5473
  // src/mcp/index.ts
5373
5474
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
5374
- var import_node_crypto2 = require("crypto");
5475
+ var import_node_crypto3 = require("crypto");
5375
5476
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
5376
5477
  var import_types = require("@modelcontextprotocol/sdk/types.js");
5377
5478
  var import_zod3 = require("zod");
@@ -5464,7 +5565,7 @@ ${code}`
5464
5565
  transport = this.transports[sessionId];
5465
5566
  } else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
5466
5567
  transport = new import_streamableHttp.StreamableHTTPServerTransport({
5467
- sessionIdGenerator: () => (0, import_node_crypto2.randomUUID)(),
5568
+ sessionIdGenerator: () => (0, import_node_crypto3.randomUUID)(),
5468
5569
  onsessioninitialized: (sessionId2) => {
5469
5570
  this.transports[sessionId2] = transport;
5470
5571
  }
@@ -5523,8 +5624,8 @@ var defaultAgent = new ExuluAgent2({
5523
5624
  type: "agent",
5524
5625
  capabilities: {
5525
5626
  text: true,
5526
- images: [],
5527
- files: [],
5627
+ images: [".png", ".jpg", ".jpeg", ".webp"],
5628
+ files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt"],
5528
5629
  audio: [],
5529
5630
  video: []
5530
5631
  },
@@ -5621,6 +5722,49 @@ var projectsContext = new ExuluContext({
5621
5722
  active: true
5622
5723
  });
5623
5724
 
5725
+ // src/templates/contexts/files.ts
5726
+ var filesContext = new ExuluContext({
5727
+ id: "files_default_context",
5728
+ name: "Files",
5729
+ description: "Files that can be used with Exulu agents.",
5730
+ configuration: {
5731
+ defaultRightsMode: "private",
5732
+ calculateVectors: "manual"
5733
+ },
5734
+ fields: [
5735
+ {
5736
+ name: "type",
5737
+ type: "text"
5738
+ },
5739
+ {
5740
+ name: "s3bucket",
5741
+ type: "text"
5742
+ },
5743
+ {
5744
+ name: "s3region",
5745
+ type: "text"
5746
+ },
5747
+ {
5748
+ name: "url",
5749
+ type: "text"
5750
+ },
5751
+ {
5752
+ name: "s3key",
5753
+ // ID of the file in S3 storage
5754
+ type: "text"
5755
+ },
5756
+ {
5757
+ name: "s3endpoint",
5758
+ type: "text"
5759
+ },
5760
+ {
5761
+ name: "content",
5762
+ type: "longText"
5763
+ }
5764
+ ],
5765
+ active: true
5766
+ });
5767
+
5624
5768
  // src/registry/index.ts
5625
5769
  var isValidPostgresName = (id) => {
5626
5770
  console.log("[EXULU] validating context id.", id);
@@ -5644,7 +5788,8 @@ var ExuluApp = class {
5644
5788
  this._contexts = {
5645
5789
  ...contexts,
5646
5790
  projectsContext,
5647
- codeStandardsContext
5791
+ codeStandardsContext,
5792
+ filesContext
5648
5793
  };
5649
5794
  this._agents = [
5650
5795
  claudeCodeAgent,
@@ -7057,7 +7202,7 @@ var generateApiKey = async (name, email) => {
7057
7202
  };
7058
7203
 
7059
7204
  // src/postgres/init-db.ts
7060
- var { agentsSchema: agentsSchema3, evalResultsSchema: evalResultsSchema3, jobsSchema: jobsSchema3, agentSessionsSchema: agentSessionsSchema3, agentMessagesSchema: agentMessagesSchema3, rolesSchema: rolesSchema3, usersSchema: usersSchema3, statisticsSchema: statisticsSchema3, variablesSchema: variablesSchema3, workflowTemplatesSchema: workflowTemplatesSchema3, rbacSchema: rbacSchema3 } = coreSchemas.get();
7205
+ var { agentsSchema: agentsSchema3, evalResultsSchema: evalResultsSchema3, jobsSchema: jobsSchema3, agentSessionsSchema: agentSessionsSchema3, agentMessagesSchema: agentMessagesSchema3, rolesSchema: rolesSchema3, usersSchema: usersSchema3, statisticsSchema: statisticsSchema3, variablesSchema: variablesSchema3, workflowTemplatesSchema: workflowTemplatesSchema3, rbacSchema: rbacSchema3, projectsSchema: projectsSchema3 } = coreSchemas.get();
7061
7206
  var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
7062
7207
  for (const field of fields) {
7063
7208
  const { type, name, default: defaultValue, unique } = field;
@@ -7086,6 +7231,7 @@ var up = async function(knex) {
7086
7231
  rolesSchema3(),
7087
7232
  evalResultsSchema3(),
7088
7233
  statisticsSchema3(),
7234
+ projectsSchema3(),
7089
7235
  jobsSchema3(),
7090
7236
  rbacSchema3(),
7091
7237
  agentsSchema3(),
@@ -7336,6 +7482,5 @@ var ExuluChunkers = {
7336
7482
  ExuluOtel,
7337
7483
  ExuluQueues,
7338
7484
  ExuluTool,
7339
- ExuluZodFileType,
7340
7485
  db
7341
7486
  });