@exulu/backend 1.14.1 → 1.16.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,9 +1,9 @@
1
- ## [1.14.1](https://github.com/Qventu/exulu-backend/compare/v1.14.0...v1.14.1) (2025-08-21)
1
+ # [1.16.0](https://github.com/Qventu/exulu-backend/compare/v1.15.0...v1.16.0) (2025-08-23)
2
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * function for default values in core schema and init-db ([2b9a786](https://github.com/Qventu/exulu-backend/commit/2b9a7862e343c02f97a59230a07d5197c056ac56))
6
+ * add user and role to statistics ([f9c6d84](https://github.com/Qventu/exulu-backend/commit/f9c6d84a8cf4b5c32a2d5935bf9074fc6960f10b))
7
7
 
8
8
  # [1.1.0](https://github.com/Qventu/exulu-backend/compare/v1.0.1...v1.1.0) (2025-07-30)
9
9
 
package/dist/index.cjs CHANGED
@@ -478,7 +478,7 @@ function sanitizeToolName(name) {
478
478
  }
479
479
  return sanitized;
480
480
  }
481
- var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
481
+ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) => {
482
482
  if (!tools) return {};
483
483
  const sanitizedTools = tools ? tools.map((tool2) => ({
484
484
  ...tool2,
@@ -513,6 +513,8 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
513
513
  // is available, after we added the .value property
514
514
  // by hydrating it from the variables table.
515
515
  providerApiKey,
516
+ user,
517
+ role,
516
518
  config: config ? config.config.reduce((acc, curr) => {
517
519
  acc[curr.name] = curr.value;
518
520
  return acc;
@@ -589,6 +591,7 @@ var ExuluAgent = class {
589
591
  this.config = config;
590
592
  this.type = type;
591
593
  this.capabilities = capabilities || {
594
+ text: false,
592
595
  images: [],
593
596
  files: [],
594
597
  audio: [],
@@ -617,10 +620,12 @@ var ExuluAgent = class {
617
620
  }),
618
621
  description: `A function that calls an AI agent named: ${this.name}. The agent does the following: ${this.description}.`,
619
622
  config: [],
620
- execute: async ({ prompt, config, providerApiKey }) => {
623
+ execute: async ({ prompt, config, providerApiKey, user, role }) => {
621
624
  return await this.generateSync({
622
625
  prompt,
623
626
  providerApiKey,
627
+ user,
628
+ role,
624
629
  statistics: {
625
630
  label: "",
626
631
  trigger: "tool"
@@ -629,7 +634,7 @@ var ExuluAgent = class {
629
634
  }
630
635
  });
631
636
  };
632
- generateSync = async ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
637
+ generateSync = async ({ prompt, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
633
638
  if (!this.model) {
634
639
  throw new Error("Model is required for streaming.");
635
640
  }
@@ -665,7 +670,7 @@ var ExuluAgent = class {
665
670
  messages: messages ? (0, import_ai.convertToModelMessages)(messages) : void 0,
666
671
  prompt,
667
672
  maxRetries: 2,
668
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
673
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
669
674
  stopWhen: [(0, import_ai.stepCountIs)(5)]
670
675
  });
671
676
  if (statistics) {
@@ -674,12 +679,14 @@ var ExuluAgent = class {
674
679
  label: statistics.label,
675
680
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
676
681
  trigger: statistics.trigger,
677
- count: 1
682
+ count: 1,
683
+ user,
684
+ role
678
685
  });
679
686
  }
680
687
  return text;
681
688
  };
682
- generateStream = async ({ express: express3, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
689
+ generateStream = async ({ express: express3, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
683
690
  if (!this.model) {
684
691
  throw new Error("Model is required for streaming.");
685
692
  }
@@ -712,7 +719,7 @@ var ExuluAgent = class {
712
719
  messages: messages ? (0, import_ai.convertToModelMessages)(messages) : void 0,
713
720
  system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
714
721
  maxRetries: 2,
715
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
722
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
716
723
  onError: (error) => console.error("[EXULU] chat stream error.", error),
717
724
  stopWhen: [(0, import_ai.stepCountIs)(5)]
718
725
  });
@@ -740,7 +747,9 @@ var ExuluAgent = class {
740
747
  label: statistics.label,
741
748
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
742
749
  trigger: statistics.trigger,
743
- count: 1
750
+ count: 1,
751
+ user,
752
+ role
744
753
  });
745
754
  }
746
755
  }
@@ -784,14 +793,16 @@ var ExuluEmbedder = class {
784
793
  this.queue = queue;
785
794
  this.generateEmbeddings = generateEmbeddings;
786
795
  }
787
- async generateFromQuery(query, statistics) {
796
+ async generateFromQuery(query, statistics, user, role) {
788
797
  if (statistics) {
789
798
  await updateStatistic({
790
799
  name: "count",
791
800
  label: statistics.label,
792
801
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
793
802
  trigger: statistics.trigger,
794
- count: 1
803
+ count: 1,
804
+ user,
805
+ role
795
806
  });
796
807
  }
797
808
  return await this.generateEmbeddings({
@@ -804,14 +815,16 @@ var ExuluEmbedder = class {
804
815
  }]
805
816
  });
806
817
  }
807
- async generateFromDocument(input, statistics) {
818
+ async generateFromDocument(input, statistics, user, role) {
808
819
  if (statistics) {
809
820
  await updateStatistic({
810
821
  name: "count",
811
822
  label: statistics.label,
812
823
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
813
824
  trigger: statistics.trigger,
814
- count: 1
825
+ count: 1,
826
+ user,
827
+ role
815
828
  });
816
829
  }
817
830
  if (!this.chunker) {
@@ -1065,7 +1078,7 @@ var ExuluContext = class {
1065
1078
  const tableExists = await db3.schema.hasTable(this.getTableName());
1066
1079
  return tableExists;
1067
1080
  };
1068
- async updateItem(user, id, item) {
1081
+ async updateItem(user, id, item, role) {
1069
1082
  if (!id) {
1070
1083
  throw new Error("Id is required for updating an item.");
1071
1084
  }
@@ -1106,7 +1119,7 @@ var ExuluContext = class {
1106
1119
  }, {
1107
1120
  label: this.name,
1108
1121
  trigger: "agent"
1109
- });
1122
+ }, user, role);
1110
1123
  const exists = await db3.schema.hasTable(this.getChunksTableName());
1111
1124
  if (!exists) {
1112
1125
  await this.createChunksTable();
@@ -1127,7 +1140,7 @@ var ExuluContext = class {
1127
1140
  job: void 0
1128
1141
  };
1129
1142
  }
1130
- async insertItem(user, item, upsert = false) {
1143
+ async insertItem(user, item, upsert = false, role) {
1131
1144
  if (!item.name) {
1132
1145
  throw new Error("Name field is required.");
1133
1146
  }
@@ -1138,14 +1151,14 @@ var ExuluContext = class {
1138
1151
  throw new Error("Item with external id " + item.external_id + " already exists.");
1139
1152
  }
1140
1153
  if (existingItem && upsert) {
1141
- await this.updateItem(user, existingItem.id, item);
1154
+ await this.updateItem(user, existingItem.id, item, role);
1142
1155
  return existingItem.id;
1143
1156
  }
1144
1157
  }
1145
1158
  if (upsert && item.id) {
1146
1159
  const existingItem = await db3.from(this.getTableName()).where({ id: item.id }).first();
1147
1160
  if (existingItem && upsert) {
1148
- await this.updateItem(user, existingItem.id, item);
1161
+ await this.updateItem(user, existingItem.id, item, role);
1149
1162
  return existingItem.id;
1150
1163
  }
1151
1164
  }
@@ -1189,7 +1202,7 @@ var ExuluContext = class {
1189
1202
  }, {
1190
1203
  label: this.name,
1191
1204
  trigger: "agent"
1192
- });
1205
+ }, user, role);
1193
1206
  const exists = await db3.schema.hasTable(this.getChunksTableName());
1194
1207
  if (!exists) {
1195
1208
  await this.createChunksTable();
@@ -1217,6 +1230,8 @@ var ExuluContext = class {
1217
1230
  order,
1218
1231
  page,
1219
1232
  name,
1233
+ user,
1234
+ role,
1220
1235
  archived,
1221
1236
  query,
1222
1237
  method
@@ -1285,7 +1300,9 @@ var ExuluContext = class {
1285
1300
  name: "count",
1286
1301
  label: statistics.label,
1287
1302
  type: STATISTICS_TYPE_ENUM.CONTEXT_RETRIEVE,
1288
- trigger: statistics.trigger
1303
+ trigger: statistics.trigger,
1304
+ user,
1305
+ role
1289
1306
  });
1290
1307
  }
1291
1308
  if (this.queryRewriter) {
@@ -1301,7 +1318,10 @@ var ExuluContext = class {
1301
1318
  itemsQuery.select(chunksTable + ".chunk_index");
1302
1319
  itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
1303
1320
  itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
1304
- const { chunks } = await this.embedder.generateFromQuery(query);
1321
+ const { chunks } = await this.embedder.generateFromQuery(query, {
1322
+ label: this.name,
1323
+ trigger: "agent"
1324
+ }, user, role);
1305
1325
  if (!chunks?.[0]?.vector) {
1306
1326
  throw new Error("No vector generated for query.");
1307
1327
  }
@@ -1449,11 +1469,13 @@ var ExuluContext = class {
1449
1469
  }),
1450
1470
  config: [],
1451
1471
  description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
1452
- execute: async ({ query }) => {
1472
+ execute: async ({ query, user, role }) => {
1453
1473
  return await this.getItems({
1454
1474
  page: 1,
1455
1475
  limit: 10,
1456
1476
  query,
1477
+ user,
1478
+ role,
1457
1479
  statistics: {
1458
1480
  label: this.name,
1459
1481
  trigger: "agent"
@@ -2484,6 +2506,14 @@ function createQueries(table) {
2484
2506
  const result = await query.first();
2485
2507
  return result;
2486
2508
  },
2509
+ [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2510
+ const { db: db3 } = context;
2511
+ const requestedFields = getRequestedFields(info);
2512
+ let query = db3.from(tableNamePlural).select(requestedFields).whereIn("id", args.ids);
2513
+ query = applyAccessControl(table, context.user, query);
2514
+ const result = await query;
2515
+ return result;
2516
+ },
2487
2517
  [`${tableNameSingular}One`]: async (_, args, context, info) => {
2488
2518
  const { filters = [], sort } = args;
2489
2519
  const { db: db3 } = context;
@@ -2648,6 +2678,7 @@ function createSDL(tables) {
2648
2678
  console.log("[EXULU] Adding table >>>>>", tableNamePlural);
2649
2679
  typeDefs += `
2650
2680
  ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2681
+ ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
2651
2682
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2652
2683
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2653
2684
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
@@ -2884,6 +2915,10 @@ var agentsSchema = {
2884
2915
  name: "name",
2885
2916
  type: "text"
2886
2917
  },
2918
+ {
2919
+ name: "image",
2920
+ type: "text"
2921
+ },
2887
2922
  {
2888
2923
  name: "description",
2889
2924
  type: "text"
@@ -2926,6 +2961,10 @@ var usersSchema = {
2926
2961
  type: "number",
2927
2962
  index: true
2928
2963
  },
2964
+ {
2965
+ name: "favourite_agents",
2966
+ type: "json"
2967
+ },
2929
2968
  {
2930
2969
  name: "firstname",
2931
2970
  type: "text"
@@ -3679,6 +3718,9 @@ Intelligence Management Platform
3679
3718
  };
3680
3719
 
3681
3720
  // src/registry/routes.ts
3721
+ var import_openai = __toESM(require("openai"), 1);
3722
+ var import_fs = __toESM(require("fs"), 1);
3723
+ var import_node_crypto = require("crypto");
3682
3724
  var REQUEST_SIZE_LIMIT = "50mb";
3683
3725
  var global_queues = {
3684
3726
  logs_cleaner: "logs-cleaner"
@@ -3859,6 +3901,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3859
3901
  rights_mode: agent.rights_mode,
3860
3902
  slug: backend?.slug,
3861
3903
  rateLimit: backend?.rateLimit,
3904
+ image: agent.image,
3862
3905
  streaming: backend?.streaming,
3863
3906
  capabilities: backend?.capabilities,
3864
3907
  RBAC: agent.RBAC,
@@ -3906,6 +3949,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3906
3949
  model: backend?.model?.create({ apiKey: "" }).modelId,
3907
3950
  active: agent.active,
3908
3951
  public: agent.public,
3952
+ image: agent.image,
3909
3953
  type: agent.type,
3910
3954
  rights_mode: agent.rights_mode,
3911
3955
  slug: backend?.slug,
@@ -3929,6 +3973,97 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3929
3973
  inputSchema: tool2.inputSchema ? (0, import_zodex.zerialize)(tool2.inputSchema) : null
3930
3974
  })));
3931
3975
  });
3976
+ app.post("/generate/agent/image", async (req, res) => {
3977
+ console.log("[EXULU] generate/agent/image", req.body);
3978
+ const authenticationResult = await requestValidators.authenticate(req);
3979
+ if (!authenticationResult.user?.id) {
3980
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3981
+ return;
3982
+ }
3983
+ const { name, description, style } = req.body;
3984
+ if (!name || !description) {
3985
+ res.status(400).json({
3986
+ message: "Missing name or description in request."
3987
+ });
3988
+ return;
3989
+ }
3990
+ const { db: db3 } = await postgresClient();
3991
+ const variable = await db3.from("variables").where({ name: "OPENAI_IMAGE_GENERATION_API_KEY" }).first();
3992
+ if (!variable) {
3993
+ res.status(400).json({
3994
+ message: "Provider API key variable not found."
3995
+ });
3996
+ return;
3997
+ }
3998
+ let providerApiKey = variable.value;
3999
+ if (!variable.encrypted) {
4000
+ res.status(400).json({
4001
+ message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
4002
+ });
4003
+ return;
4004
+ }
4005
+ if (variable.encrypted) {
4006
+ const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
4007
+ providerApiKey = bytes.toString(import_crypto_js3.default.enc.Utf8);
4008
+ }
4009
+ const openai = new import_openai.default({
4010
+ apiKey: providerApiKey
4011
+ });
4012
+ let style_reference = "";
4013
+ if (style === "origami") {
4014
+ style_reference = "minimalistic origami-style, futuristic robot, portrait, focus on face.";
4015
+ } else if (style === "anime") {
4016
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
4017
+ } else if (style === "japanese_anime") {
4018
+ style_reference = "minimalistic, make it in the style of japanese anime, futuristic robot, portrait, focus on face.";
4019
+ } else if (style === "vaporwave") {
4020
+ style_reference = "minimalistic, make it in the style of a vaporwave album cover, futuristic robot, portrait, focus on face.";
4021
+ } else if (style === "lego") {
4022
+ style_reference = "minimalistic, make it in the style of LEGO minifigures, futuristic robot, portrait, focus on face.";
4023
+ } else if (style === "paper_cut") {
4024
+ style_reference = "minimalistic, make it in the style of Paper-cut style portrait with color layers, futuristic robot, portrait, focus on face.";
4025
+ } else if (style === "felt_puppet") {
4026
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
4027
+ } else if (style === "app_icon") {
4028
+ style_reference = "A playful and modern app icon design of a robot, minimal flat vector style, glossy highlights, soft shadows, centered composition, high contrast, vibrant colors, rounded corners, on a transparent background, icon-friendly, no text, no details outside the frame, size is 1024x1024.";
4029
+ } else if (style === "pixel_art") {
4030
+ style_reference = "A pixel art style of a robot, minimal flat vector style, glossy highlights, soft shadows, centered composition, high contrast, vibrant colors, rounded corners, on a transparent background, icon-friendly, no text, no details outside the frame, size is 1024x1024.";
4031
+ } else if (style === "isometric") {
4032
+ style_reference = "3D isometric icon of a robot, centered composition, on a transparent background, no text, no details outside the frame, size is 1024x1024.";
4033
+ } else {
4034
+ style_reference = "A minimalist 3D, robot, portrait, focus on face, floating in space, low-poly design with pastel colors.";
4035
+ }
4036
+ const prompt = `
4037
+ A digital portrait of ${name}, visualized as a futuristic robot.
4038
+ The robot\u2019s design reflects '${description}', with props, tools, or symbolic objects that represent its expertise or area of work.
4039
+ Example: if the agent is a financial analyst, it may hold a stack of papers; if it\u2019s a creative strategist it may be painting on a canvas.
4040
+ Style: ${style_reference}.
4041
+ The portrait should have a clean background.
4042
+ Framing: bust portrait, centered.
4043
+ Mood: friendly and intelligent.
4044
+ `;
4045
+ const result = await openai.images.generate({
4046
+ model: "gpt-image-1",
4047
+ prompt
4048
+ });
4049
+ const image_base64 = result.data?.[0]?.b64_json;
4050
+ if (!image_base64) {
4051
+ res.status(500).json({
4052
+ message: "Failed to generate image."
4053
+ });
4054
+ return;
4055
+ }
4056
+ const image_bytes = Buffer.from(image_base64, "base64");
4057
+ const uuid = (0, import_node_crypto.randomUUID)();
4058
+ if (!import_fs.default.existsSync("public")) {
4059
+ import_fs.default.mkdirSync("public");
4060
+ }
4061
+ import_fs.default.writeFileSync(`public/${uuid}.png`, image_bytes);
4062
+ res.status(200).json({
4063
+ message: "Image generated successfully.",
4064
+ image: `${process.env.BACKEND}/${uuid}.png`
4065
+ });
4066
+ });
3932
4067
  app.get("/tools/:id", async (req, res) => {
3933
4068
  const id = req.params.id;
3934
4069
  if (!id) {
@@ -4104,7 +4239,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4104
4239
  if (!exists) {
4105
4240
  await context.createItemsTable();
4106
4241
  }
4107
- const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body);
4242
+ const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body, authenticationResult.user.role?.id);
4108
4243
  res.status(200).json({
4109
4244
  message: "Item updated successfully.",
4110
4245
  id: result
@@ -4151,7 +4286,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4151
4286
  await context.createItemsTable();
4152
4287
  }
4153
4288
  console.log("[EXULU] inserting item", req.body);
4154
- const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert);
4289
+ const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert, authenticationResult.user.role?.id);
4155
4290
  console.log("[EXULU] result", result);
4156
4291
  res.status(200).json({
4157
4292
  message: "Item created successfully.",
@@ -4212,6 +4347,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4212
4347
  const result = await context.getItems({
4213
4348
  sort,
4214
4349
  order,
4350
+ user: authenticationResult.user?.id,
4351
+ role: authenticationResult.user?.role?.id,
4215
4352
  page,
4216
4353
  limit,
4217
4354
  archived: req.query.archived === "true",
@@ -4373,6 +4510,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4373
4510
  const items = await context.getItems({
4374
4511
  page: 1,
4375
4512
  // todo add pagination
4513
+ user: authenticationResult.user?.id,
4514
+ role: authenticationResult.user?.role?.id,
4376
4515
  limit: 500
4377
4516
  });
4378
4517
  const csv = Papa.unparse(items);
@@ -4523,7 +4662,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4523
4662
  res,
4524
4663
  req
4525
4664
  },
4526
- user: headers.user,
4665
+ user: user?.id,
4666
+ role: user?.role?.id,
4527
4667
  session: headers.session,
4528
4668
  message: req.body.message,
4529
4669
  tools: enabledTools,
@@ -4537,8 +4677,9 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4537
4677
  return;
4538
4678
  } else {
4539
4679
  const response = await agent.generateSync({
4540
- user: headers.user,
4680
+ user: user?.id,
4541
4681
  session: headers.session,
4682
+ role: user?.role?.id,
4542
4683
  message: req.body.message,
4543
4684
  tools: enabledTools.map((tool2) => tool2.tool),
4544
4685
  providerApiKey,
@@ -4637,7 +4778,9 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4637
4778
  label: "Claude Code",
4638
4779
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
4639
4780
  trigger: "claude-code",
4640
- count: 1
4781
+ count: 1,
4782
+ user: authenticationResult.user?.id,
4783
+ role: authenticationResult.user.role?.id
4641
4784
  });
4642
4785
  response.headers.forEach((value, key) => {
4643
4786
  res.setHeader(key, value);
@@ -4675,6 +4818,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4675
4818
  }
4676
4819
  }
4677
4820
  });
4821
+ app.use(import_express4.default.static("public"));
4678
4822
  return app;
4679
4823
  };
4680
4824
  var createCustomAnthropicStreamingMessage = (message) => {
@@ -4739,7 +4883,7 @@ var bullmq = {
4739
4883
  };
4740
4884
 
4741
4885
  // src/registry/workers.ts
4742
- var fs2 = __toESM(require("fs"), 1);
4886
+ var fs3 = __toESM(require("fs"), 1);
4743
4887
  var import_path = __toESM(require("path"), 1);
4744
4888
  var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
4745
4889
  var redisConnection;
@@ -4873,16 +5017,16 @@ var createLogsCleanerWorker = (logsDir) => {
4873
5017
  global_queues.logs_cleaner,
4874
5018
  async (job) => {
4875
5019
  console.log(`[EXULU] recurring job ${job.id}.`);
4876
- const folder = fs2.readdirSync(logsDir);
5020
+ const folder = fs3.readdirSync(logsDir);
4877
5021
  const files = folder.filter((file) => file.endsWith(".log"));
4878
5022
  const now = /* @__PURE__ */ new Date();
4879
5023
  const daysToKeep = job.data.ttld;
4880
5024
  const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
4881
5025
  files.forEach((file) => {
4882
5026
  const filePath = import_path.default.join(logsDir, file);
4883
- const fileStats = fs2.statSync(filePath);
5027
+ const fileStats = fs3.statSync(filePath);
4884
5028
  if (fileStats.mtime < dateToKeep) {
4885
- fs2.unlinkSync(filePath);
5029
+ fs3.unlinkSync(filePath);
4886
5030
  }
4887
5031
  });
4888
5032
  },
@@ -4902,7 +5046,7 @@ var createLogsCleanerWorker = (logsDir) => {
4902
5046
 
4903
5047
  // src/mcp/index.ts
4904
5048
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
4905
- var import_node_crypto = require("crypto");
5049
+ var import_node_crypto2 = require("crypto");
4906
5050
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
4907
5051
  var import_types = require("@modelcontextprotocol/sdk/types.js");
4908
5052
  var import_zod3 = require("zod");
@@ -4996,7 +5140,7 @@ ${code}`
4996
5140
  transport = this.transports[sessionId];
4997
5141
  } else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
4998
5142
  transport = new import_streamableHttp.StreamableHTTPServerTransport({
4999
- sessionIdGenerator: () => (0, import_node_crypto.randomUUID)(),
5143
+ sessionIdGenerator: () => (0, import_node_crypto2.randomUUID)(),
5000
5144
  onsessioninitialized: (sessionId2) => {
5001
5145
  this.transports[sessionId2] = transport;
5002
5146
  }
@@ -5064,6 +5208,7 @@ var defaultAgent = new ExuluAgent({
5064
5208
  description: `Basic agent without any defined tools, that can support MCP's.`,
5065
5209
  type: "agent",
5066
5210
  capabilities: {
5211
+ text: true,
5067
5212
  images: [],
5068
5213
  files: [],
5069
5214
  audio: [],
package/dist/index.d.cts CHANGED
@@ -119,6 +119,7 @@ interface ExuluAgentParams {
119
119
  description: string;
120
120
  config?: ExuluAgentConfig | undefined;
121
121
  capabilities?: {
122
+ text: boolean;
122
123
  images: string[];
123
124
  files: string[];
124
125
  audio: string[];
@@ -152,6 +153,7 @@ declare class ExuluAgent {
152
153
  }) => LanguageModel;
153
154
  };
154
155
  capabilities: {
156
+ text: boolean;
155
157
  images: string[];
156
158
  files: string[];
157
159
  audio: string[];
@@ -161,9 +163,10 @@ declare class ExuluAgent {
161
163
  get providerName(): string;
162
164
  get modelName(): string;
163
165
  tool: () => ExuluTool;
164
- generateSync: ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }: {
166
+ generateSync: ({ prompt, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }: {
165
167
  prompt?: string;
166
168
  user?: string;
169
+ role?: string;
167
170
  session?: string;
168
171
  message?: UIMessage;
169
172
  tools?: ExuluTool[];
@@ -171,12 +174,13 @@ declare class ExuluAgent {
171
174
  toolConfigs?: ExuluAgentToolConfig[];
172
175
  providerApiKey: string;
173
176
  }) => Promise<string>;
174
- generateStream: ({ express, user, session, message, tools, statistics, toolConfigs, providerApiKey }: {
177
+ generateStream: ({ express, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }: {
175
178
  express: {
176
179
  res: Response;
177
180
  req: Request;
178
181
  };
179
182
  user: string;
183
+ role: string;
180
184
  session: string;
181
185
  message?: UIMessage;
182
186
  tools?: ExuluTool[];
@@ -230,8 +234,8 @@ declare class ExuluEmbedder {
230
234
  vectorDimensions: number;
231
235
  maxChunkSize: number;
232
236
  });
233
- generateFromQuery(query: string, statistics?: ExuluStatisticParams): VectorGenerationResponse;
234
- generateFromDocument(input: Item, statistics?: ExuluStatisticParams): VectorGenerationResponse;
237
+ generateFromQuery(query: string, statistics?: ExuluStatisticParams, user?: string, role?: string): VectorGenerationResponse;
238
+ generateFromDocument(input: Item, statistics?: ExuluStatisticParams, user?: string, role?: string): VectorGenerationResponse;
235
239
  }
236
240
  declare class ExuluLogger {
237
241
  private readonly logPath?;
@@ -384,21 +388,23 @@ declare class ExuluContext {
384
388
  getTableName: () => string;
385
389
  getChunksTableName: () => string;
386
390
  tableExists: () => Promise<boolean>;
387
- updateItem(user: string, id: string, item: Item): Promise<{
391
+ updateItem(user: string, id: string, item: Item, role?: string): Promise<{
388
392
  id: string;
389
393
  job?: string;
390
394
  }>;
391
- insertItem(user: string, item: Item, upsert?: boolean): Promise<{
395
+ insertItem(user: string, item: Item, upsert?: boolean, role?: string): Promise<{
392
396
  id: string;
393
397
  job?: string;
394
398
  }>;
395
- getItems: ({ statistics, limit, sort, order, page, name, archived, query, method }: {
399
+ getItems: ({ statistics, limit, sort, order, page, name, user, role, archived, query, method }: {
396
400
  statistics?: ExuluStatisticParams;
397
401
  page: number;
398
402
  sort?: "created_at" | "embeddings_updated_at";
399
403
  order?: "desc" | "asc";
400
404
  limit: number;
401
405
  name?: string;
406
+ user?: string;
407
+ role?: string;
402
408
  archived?: boolean;
403
409
  query?: string;
404
410
  method?: VectorMethod;
@@ -479,11 +485,12 @@ type ExuluSourceUpdaterArgs = {
479
485
  example: string;
480
486
  }>;
481
487
  };
488
+ type STATISTICS_LABELS = "tool" | "agent" | "flow" | "api" | "claude-code";
482
489
  type ExuluStatistic = {
483
490
  name: string;
484
491
  label: string;
485
492
  type: STATISTICS_TYPE;
486
- trigger: "tool" | "agent" | "flow" | "api" | "claude-code";
493
+ trigger: STATISTICS_LABELS;
487
494
  total: number;
488
495
  };
489
496
  type ExuluStatisticParams = Omit<ExuluStatistic, "total" | "name" | "type">;
@@ -544,6 +551,7 @@ type User = {
544
551
  type?: "api" | "user";
545
552
  anthropic_token?: string;
546
553
  super_admin?: boolean;
554
+ favourite_agents?: string[];
547
555
  role: {
548
556
  id: string;
549
557
  name: string;
package/dist/index.d.ts CHANGED
@@ -119,6 +119,7 @@ interface ExuluAgentParams {
119
119
  description: string;
120
120
  config?: ExuluAgentConfig | undefined;
121
121
  capabilities?: {
122
+ text: boolean;
122
123
  images: string[];
123
124
  files: string[];
124
125
  audio: string[];
@@ -152,6 +153,7 @@ declare class ExuluAgent {
152
153
  }) => LanguageModel;
153
154
  };
154
155
  capabilities: {
156
+ text: boolean;
155
157
  images: string[];
156
158
  files: string[];
157
159
  audio: string[];
@@ -161,9 +163,10 @@ declare class ExuluAgent {
161
163
  get providerName(): string;
162
164
  get modelName(): string;
163
165
  tool: () => ExuluTool;
164
- generateSync: ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }: {
166
+ generateSync: ({ prompt, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }: {
165
167
  prompt?: string;
166
168
  user?: string;
169
+ role?: string;
167
170
  session?: string;
168
171
  message?: UIMessage;
169
172
  tools?: ExuluTool[];
@@ -171,12 +174,13 @@ declare class ExuluAgent {
171
174
  toolConfigs?: ExuluAgentToolConfig[];
172
175
  providerApiKey: string;
173
176
  }) => Promise<string>;
174
- generateStream: ({ express, user, session, message, tools, statistics, toolConfigs, providerApiKey }: {
177
+ generateStream: ({ express, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }: {
175
178
  express: {
176
179
  res: Response;
177
180
  req: Request;
178
181
  };
179
182
  user: string;
183
+ role: string;
180
184
  session: string;
181
185
  message?: UIMessage;
182
186
  tools?: ExuluTool[];
@@ -230,8 +234,8 @@ declare class ExuluEmbedder {
230
234
  vectorDimensions: number;
231
235
  maxChunkSize: number;
232
236
  });
233
- generateFromQuery(query: string, statistics?: ExuluStatisticParams): VectorGenerationResponse;
234
- generateFromDocument(input: Item, statistics?: ExuluStatisticParams): VectorGenerationResponse;
237
+ generateFromQuery(query: string, statistics?: ExuluStatisticParams, user?: string, role?: string): VectorGenerationResponse;
238
+ generateFromDocument(input: Item, statistics?: ExuluStatisticParams, user?: string, role?: string): VectorGenerationResponse;
235
239
  }
236
240
  declare class ExuluLogger {
237
241
  private readonly logPath?;
@@ -384,21 +388,23 @@ declare class ExuluContext {
384
388
  getTableName: () => string;
385
389
  getChunksTableName: () => string;
386
390
  tableExists: () => Promise<boolean>;
387
- updateItem(user: string, id: string, item: Item): Promise<{
391
+ updateItem(user: string, id: string, item: Item, role?: string): Promise<{
388
392
  id: string;
389
393
  job?: string;
390
394
  }>;
391
- insertItem(user: string, item: Item, upsert?: boolean): Promise<{
395
+ insertItem(user: string, item: Item, upsert?: boolean, role?: string): Promise<{
392
396
  id: string;
393
397
  job?: string;
394
398
  }>;
395
- getItems: ({ statistics, limit, sort, order, page, name, archived, query, method }: {
399
+ getItems: ({ statistics, limit, sort, order, page, name, user, role, archived, query, method }: {
396
400
  statistics?: ExuluStatisticParams;
397
401
  page: number;
398
402
  sort?: "created_at" | "embeddings_updated_at";
399
403
  order?: "desc" | "asc";
400
404
  limit: number;
401
405
  name?: string;
406
+ user?: string;
407
+ role?: string;
402
408
  archived?: boolean;
403
409
  query?: string;
404
410
  method?: VectorMethod;
@@ -479,11 +485,12 @@ type ExuluSourceUpdaterArgs = {
479
485
  example: string;
480
486
  }>;
481
487
  };
488
+ type STATISTICS_LABELS = "tool" | "agent" | "flow" | "api" | "claude-code";
482
489
  type ExuluStatistic = {
483
490
  name: string;
484
491
  label: string;
485
492
  type: STATISTICS_TYPE;
486
- trigger: "tool" | "agent" | "flow" | "api" | "claude-code";
493
+ trigger: STATISTICS_LABELS;
487
494
  total: number;
488
495
  };
489
496
  type ExuluStatisticParams = Omit<ExuluStatistic, "total" | "name" | "type">;
@@ -544,6 +551,7 @@ type User = {
544
551
  type?: "api" | "user";
545
552
  anthropic_token?: string;
546
553
  super_admin?: boolean;
554
+ favourite_agents?: string[];
547
555
  role: {
548
556
  id: string;
549
557
  name: string;
package/dist/index.js CHANGED
@@ -436,7 +436,7 @@ function sanitizeToolName(name) {
436
436
  }
437
437
  return sanitized;
438
438
  }
439
- var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
439
+ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) => {
440
440
  if (!tools) return {};
441
441
  const sanitizedTools = tools ? tools.map((tool2) => ({
442
442
  ...tool2,
@@ -471,6 +471,8 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
471
471
  // is available, after we added the .value property
472
472
  // by hydrating it from the variables table.
473
473
  providerApiKey,
474
+ user,
475
+ role,
474
476
  config: config ? config.config.reduce((acc, curr) => {
475
477
  acc[curr.name] = curr.value;
476
478
  return acc;
@@ -547,6 +549,7 @@ var ExuluAgent = class {
547
549
  this.config = config;
548
550
  this.type = type;
549
551
  this.capabilities = capabilities || {
552
+ text: false,
550
553
  images: [],
551
554
  files: [],
552
555
  audio: [],
@@ -575,10 +578,12 @@ var ExuluAgent = class {
575
578
  }),
576
579
  description: `A function that calls an AI agent named: ${this.name}. The agent does the following: ${this.description}.`,
577
580
  config: [],
578
- execute: async ({ prompt, config, providerApiKey }) => {
581
+ execute: async ({ prompt, config, providerApiKey, user, role }) => {
579
582
  return await this.generateSync({
580
583
  prompt,
581
584
  providerApiKey,
585
+ user,
586
+ role,
582
587
  statistics: {
583
588
  label: "",
584
589
  trigger: "tool"
@@ -587,7 +592,7 @@ var ExuluAgent = class {
587
592
  }
588
593
  });
589
594
  };
590
- generateSync = async ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
595
+ generateSync = async ({ prompt, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
591
596
  if (!this.model) {
592
597
  throw new Error("Model is required for streaming.");
593
598
  }
@@ -623,7 +628,7 @@ var ExuluAgent = class {
623
628
  messages: messages ? convertToModelMessages(messages) : void 0,
624
629
  prompt,
625
630
  maxRetries: 2,
626
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
631
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
627
632
  stopWhen: [stepCountIs(5)]
628
633
  });
629
634
  if (statistics) {
@@ -632,12 +637,14 @@ var ExuluAgent = class {
632
637
  label: statistics.label,
633
638
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
634
639
  trigger: statistics.trigger,
635
- count: 1
640
+ count: 1,
641
+ user,
642
+ role
636
643
  });
637
644
  }
638
645
  return text;
639
646
  };
640
- generateStream = async ({ express: express3, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
647
+ generateStream = async ({ express: express3, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
641
648
  if (!this.model) {
642
649
  throw new Error("Model is required for streaming.");
643
650
  }
@@ -670,7 +677,7 @@ var ExuluAgent = class {
670
677
  messages: messages ? convertToModelMessages(messages) : void 0,
671
678
  system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
672
679
  maxRetries: 2,
673
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
680
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
674
681
  onError: (error) => console.error("[EXULU] chat stream error.", error),
675
682
  stopWhen: [stepCountIs(5)]
676
683
  });
@@ -698,7 +705,9 @@ var ExuluAgent = class {
698
705
  label: statistics.label,
699
706
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
700
707
  trigger: statistics.trigger,
701
- count: 1
708
+ count: 1,
709
+ user,
710
+ role
702
711
  });
703
712
  }
704
713
  }
@@ -742,14 +751,16 @@ var ExuluEmbedder = class {
742
751
  this.queue = queue;
743
752
  this.generateEmbeddings = generateEmbeddings;
744
753
  }
745
- async generateFromQuery(query, statistics) {
754
+ async generateFromQuery(query, statistics, user, role) {
746
755
  if (statistics) {
747
756
  await updateStatistic({
748
757
  name: "count",
749
758
  label: statistics.label,
750
759
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
751
760
  trigger: statistics.trigger,
752
- count: 1
761
+ count: 1,
762
+ user,
763
+ role
753
764
  });
754
765
  }
755
766
  return await this.generateEmbeddings({
@@ -762,14 +773,16 @@ var ExuluEmbedder = class {
762
773
  }]
763
774
  });
764
775
  }
765
- async generateFromDocument(input, statistics) {
776
+ async generateFromDocument(input, statistics, user, role) {
766
777
  if (statistics) {
767
778
  await updateStatistic({
768
779
  name: "count",
769
780
  label: statistics.label,
770
781
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
771
782
  trigger: statistics.trigger,
772
- count: 1
783
+ count: 1,
784
+ user,
785
+ role
773
786
  });
774
787
  }
775
788
  if (!this.chunker) {
@@ -1023,7 +1036,7 @@ var ExuluContext = class {
1023
1036
  const tableExists = await db3.schema.hasTable(this.getTableName());
1024
1037
  return tableExists;
1025
1038
  };
1026
- async updateItem(user, id, item) {
1039
+ async updateItem(user, id, item, role) {
1027
1040
  if (!id) {
1028
1041
  throw new Error("Id is required for updating an item.");
1029
1042
  }
@@ -1064,7 +1077,7 @@ var ExuluContext = class {
1064
1077
  }, {
1065
1078
  label: this.name,
1066
1079
  trigger: "agent"
1067
- });
1080
+ }, user, role);
1068
1081
  const exists = await db3.schema.hasTable(this.getChunksTableName());
1069
1082
  if (!exists) {
1070
1083
  await this.createChunksTable();
@@ -1085,7 +1098,7 @@ var ExuluContext = class {
1085
1098
  job: void 0
1086
1099
  };
1087
1100
  }
1088
- async insertItem(user, item, upsert = false) {
1101
+ async insertItem(user, item, upsert = false, role) {
1089
1102
  if (!item.name) {
1090
1103
  throw new Error("Name field is required.");
1091
1104
  }
@@ -1096,14 +1109,14 @@ var ExuluContext = class {
1096
1109
  throw new Error("Item with external id " + item.external_id + " already exists.");
1097
1110
  }
1098
1111
  if (existingItem && upsert) {
1099
- await this.updateItem(user, existingItem.id, item);
1112
+ await this.updateItem(user, existingItem.id, item, role);
1100
1113
  return existingItem.id;
1101
1114
  }
1102
1115
  }
1103
1116
  if (upsert && item.id) {
1104
1117
  const existingItem = await db3.from(this.getTableName()).where({ id: item.id }).first();
1105
1118
  if (existingItem && upsert) {
1106
- await this.updateItem(user, existingItem.id, item);
1119
+ await this.updateItem(user, existingItem.id, item, role);
1107
1120
  return existingItem.id;
1108
1121
  }
1109
1122
  }
@@ -1147,7 +1160,7 @@ var ExuluContext = class {
1147
1160
  }, {
1148
1161
  label: this.name,
1149
1162
  trigger: "agent"
1150
- });
1163
+ }, user, role);
1151
1164
  const exists = await db3.schema.hasTable(this.getChunksTableName());
1152
1165
  if (!exists) {
1153
1166
  await this.createChunksTable();
@@ -1175,6 +1188,8 @@ var ExuluContext = class {
1175
1188
  order,
1176
1189
  page,
1177
1190
  name,
1191
+ user,
1192
+ role,
1178
1193
  archived,
1179
1194
  query,
1180
1195
  method
@@ -1243,7 +1258,9 @@ var ExuluContext = class {
1243
1258
  name: "count",
1244
1259
  label: statistics.label,
1245
1260
  type: STATISTICS_TYPE_ENUM.CONTEXT_RETRIEVE,
1246
- trigger: statistics.trigger
1261
+ trigger: statistics.trigger,
1262
+ user,
1263
+ role
1247
1264
  });
1248
1265
  }
1249
1266
  if (this.queryRewriter) {
@@ -1259,7 +1276,10 @@ var ExuluContext = class {
1259
1276
  itemsQuery.select(chunksTable + ".chunk_index");
1260
1277
  itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
1261
1278
  itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
1262
- const { chunks } = await this.embedder.generateFromQuery(query);
1279
+ const { chunks } = await this.embedder.generateFromQuery(query, {
1280
+ label: this.name,
1281
+ trigger: "agent"
1282
+ }, user, role);
1263
1283
  if (!chunks?.[0]?.vector) {
1264
1284
  throw new Error("No vector generated for query.");
1265
1285
  }
@@ -1407,11 +1427,13 @@ var ExuluContext = class {
1407
1427
  }),
1408
1428
  config: [],
1409
1429
  description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
1410
- execute: async ({ query }) => {
1430
+ execute: async ({ query, user, role }) => {
1411
1431
  return await this.getItems({
1412
1432
  page: 1,
1413
1433
  limit: 10,
1414
1434
  query,
1435
+ user,
1436
+ role,
1415
1437
  statistics: {
1416
1438
  label: this.name,
1417
1439
  trigger: "agent"
@@ -2442,6 +2464,14 @@ function createQueries(table) {
2442
2464
  const result = await query.first();
2443
2465
  return result;
2444
2466
  },
2467
+ [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2468
+ const { db: db3 } = context;
2469
+ const requestedFields = getRequestedFields(info);
2470
+ let query = db3.from(tableNamePlural).select(requestedFields).whereIn("id", args.ids);
2471
+ query = applyAccessControl(table, context.user, query);
2472
+ const result = await query;
2473
+ return result;
2474
+ },
2445
2475
  [`${tableNameSingular}One`]: async (_, args, context, info) => {
2446
2476
  const { filters = [], sort } = args;
2447
2477
  const { db: db3 } = context;
@@ -2606,6 +2636,7 @@ function createSDL(tables) {
2606
2636
  console.log("[EXULU] Adding table >>>>>", tableNamePlural);
2607
2637
  typeDefs += `
2608
2638
  ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2639
+ ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
2609
2640
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2610
2641
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2611
2642
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
@@ -2842,6 +2873,10 @@ var agentsSchema = {
2842
2873
  name: "name",
2843
2874
  type: "text"
2844
2875
  },
2876
+ {
2877
+ name: "image",
2878
+ type: "text"
2879
+ },
2845
2880
  {
2846
2881
  name: "description",
2847
2882
  type: "text"
@@ -2884,6 +2919,10 @@ var usersSchema = {
2884
2919
  type: "number",
2885
2920
  index: true
2886
2921
  },
2922
+ {
2923
+ name: "favourite_agents",
2924
+ type: "json"
2925
+ },
2887
2926
  {
2888
2927
  name: "firstname",
2889
2928
  type: "text"
@@ -3637,6 +3676,9 @@ Intelligence Management Platform
3637
3676
  };
3638
3677
 
3639
3678
  // src/registry/routes.ts
3679
+ import OpenAI from "openai";
3680
+ import fs2 from "fs";
3681
+ import { randomUUID } from "crypto";
3640
3682
  var REQUEST_SIZE_LIMIT = "50mb";
3641
3683
  var global_queues = {
3642
3684
  logs_cleaner: "logs-cleaner"
@@ -3817,6 +3859,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3817
3859
  rights_mode: agent.rights_mode,
3818
3860
  slug: backend?.slug,
3819
3861
  rateLimit: backend?.rateLimit,
3862
+ image: agent.image,
3820
3863
  streaming: backend?.streaming,
3821
3864
  capabilities: backend?.capabilities,
3822
3865
  RBAC: agent.RBAC,
@@ -3864,6 +3907,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3864
3907
  model: backend?.model?.create({ apiKey: "" }).modelId,
3865
3908
  active: agent.active,
3866
3909
  public: agent.public,
3910
+ image: agent.image,
3867
3911
  type: agent.type,
3868
3912
  rights_mode: agent.rights_mode,
3869
3913
  slug: backend?.slug,
@@ -3887,6 +3931,97 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3887
3931
  inputSchema: tool2.inputSchema ? zerialize(tool2.inputSchema) : null
3888
3932
  })));
3889
3933
  });
3934
+ app.post("/generate/agent/image", async (req, res) => {
3935
+ console.log("[EXULU] generate/agent/image", req.body);
3936
+ const authenticationResult = await requestValidators.authenticate(req);
3937
+ if (!authenticationResult.user?.id) {
3938
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3939
+ return;
3940
+ }
3941
+ const { name, description, style } = req.body;
3942
+ if (!name || !description) {
3943
+ res.status(400).json({
3944
+ message: "Missing name or description in request."
3945
+ });
3946
+ return;
3947
+ }
3948
+ const { db: db3 } = await postgresClient();
3949
+ const variable = await db3.from("variables").where({ name: "OPENAI_IMAGE_GENERATION_API_KEY" }).first();
3950
+ if (!variable) {
3951
+ res.status(400).json({
3952
+ message: "Provider API key variable not found."
3953
+ });
3954
+ return;
3955
+ }
3956
+ let providerApiKey = variable.value;
3957
+ if (!variable.encrypted) {
3958
+ res.status(400).json({
3959
+ message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
3960
+ });
3961
+ return;
3962
+ }
3963
+ if (variable.encrypted) {
3964
+ const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
3965
+ providerApiKey = bytes.toString(CryptoJS3.enc.Utf8);
3966
+ }
3967
+ const openai = new OpenAI({
3968
+ apiKey: providerApiKey
3969
+ });
3970
+ let style_reference = "";
3971
+ if (style === "origami") {
3972
+ style_reference = "minimalistic origami-style, futuristic robot, portrait, focus on face.";
3973
+ } else if (style === "anime") {
3974
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
3975
+ } else if (style === "japanese_anime") {
3976
+ style_reference = "minimalistic, make it in the style of japanese anime, futuristic robot, portrait, focus on face.";
3977
+ } else if (style === "vaporwave") {
3978
+ style_reference = "minimalistic, make it in the style of a vaporwave album cover, futuristic robot, portrait, focus on face.";
3979
+ } else if (style === "lego") {
3980
+ style_reference = "minimalistic, make it in the style of LEGO minifigures, futuristic robot, portrait, focus on face.";
3981
+ } else if (style === "paper_cut") {
3982
+ style_reference = "minimalistic, make it in the style of Paper-cut style portrait with color layers, futuristic robot, portrait, focus on face.";
3983
+ } else if (style === "felt_puppet") {
3984
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
3985
+ } else if (style === "app_icon") {
3986
+ style_reference = "A playful and modern app icon design of a robot, minimal flat vector style, glossy highlights, soft shadows, centered composition, high contrast, vibrant colors, rounded corners, on a transparent background, icon-friendly, no text, no details outside the frame, size is 1024x1024.";
3987
+ } else if (style === "pixel_art") {
3988
+ style_reference = "A pixel art style of a robot, minimal flat vector style, glossy highlights, soft shadows, centered composition, high contrast, vibrant colors, rounded corners, on a transparent background, icon-friendly, no text, no details outside the frame, size is 1024x1024.";
3989
+ } else if (style === "isometric") {
3990
+ style_reference = "3D isometric icon of a robot, centered composition, on a transparent background, no text, no details outside the frame, size is 1024x1024.";
3991
+ } else {
3992
+ style_reference = "A minimalist 3D, robot, portrait, focus on face, floating in space, low-poly design with pastel colors.";
3993
+ }
3994
+ const prompt = `
3995
+ A digital portrait of ${name}, visualized as a futuristic robot.
3996
+ The robot\u2019s design reflects '${description}', with props, tools, or symbolic objects that represent its expertise or area of work.
3997
+ Example: if the agent is a financial analyst, it may hold a stack of papers; if it\u2019s a creative strategist it may be painting on a canvas.
3998
+ Style: ${style_reference}.
3999
+ The portrait should have a clean background.
4000
+ Framing: bust portrait, centered.
4001
+ Mood: friendly and intelligent.
4002
+ `;
4003
+ const result = await openai.images.generate({
4004
+ model: "gpt-image-1",
4005
+ prompt
4006
+ });
4007
+ const image_base64 = result.data?.[0]?.b64_json;
4008
+ if (!image_base64) {
4009
+ res.status(500).json({
4010
+ message: "Failed to generate image."
4011
+ });
4012
+ return;
4013
+ }
4014
+ const image_bytes = Buffer.from(image_base64, "base64");
4015
+ const uuid = randomUUID();
4016
+ if (!fs2.existsSync("public")) {
4017
+ fs2.mkdirSync("public");
4018
+ }
4019
+ fs2.writeFileSync(`public/${uuid}.png`, image_bytes);
4020
+ res.status(200).json({
4021
+ message: "Image generated successfully.",
4022
+ image: `${process.env.BACKEND}/${uuid}.png`
4023
+ });
4024
+ });
3890
4025
  app.get("/tools/:id", async (req, res) => {
3891
4026
  const id = req.params.id;
3892
4027
  if (!id) {
@@ -4062,7 +4197,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4062
4197
  if (!exists) {
4063
4198
  await context.createItemsTable();
4064
4199
  }
4065
- const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body);
4200
+ const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body, authenticationResult.user.role?.id);
4066
4201
  res.status(200).json({
4067
4202
  message: "Item updated successfully.",
4068
4203
  id: result
@@ -4109,7 +4244,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4109
4244
  await context.createItemsTable();
4110
4245
  }
4111
4246
  console.log("[EXULU] inserting item", req.body);
4112
- const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert);
4247
+ const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert, authenticationResult.user.role?.id);
4113
4248
  console.log("[EXULU] result", result);
4114
4249
  res.status(200).json({
4115
4250
  message: "Item created successfully.",
@@ -4170,6 +4305,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4170
4305
  const result = await context.getItems({
4171
4306
  sort,
4172
4307
  order,
4308
+ user: authenticationResult.user?.id,
4309
+ role: authenticationResult.user?.role?.id,
4173
4310
  page,
4174
4311
  limit,
4175
4312
  archived: req.query.archived === "true",
@@ -4331,6 +4468,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4331
4468
  const items = await context.getItems({
4332
4469
  page: 1,
4333
4470
  // todo add pagination
4471
+ user: authenticationResult.user?.id,
4472
+ role: authenticationResult.user?.role?.id,
4334
4473
  limit: 500
4335
4474
  });
4336
4475
  const csv = Papa.unparse(items);
@@ -4481,7 +4620,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4481
4620
  res,
4482
4621
  req
4483
4622
  },
4484
- user: headers.user,
4623
+ user: user?.id,
4624
+ role: user?.role?.id,
4485
4625
  session: headers.session,
4486
4626
  message: req.body.message,
4487
4627
  tools: enabledTools,
@@ -4495,8 +4635,9 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4495
4635
  return;
4496
4636
  } else {
4497
4637
  const response = await agent.generateSync({
4498
- user: headers.user,
4638
+ user: user?.id,
4499
4639
  session: headers.session,
4640
+ role: user?.role?.id,
4500
4641
  message: req.body.message,
4501
4642
  tools: enabledTools.map((tool2) => tool2.tool),
4502
4643
  providerApiKey,
@@ -4595,7 +4736,9 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4595
4736
  label: "Claude Code",
4596
4737
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
4597
4738
  trigger: "claude-code",
4598
- count: 1
4739
+ count: 1,
4740
+ user: authenticationResult.user?.id,
4741
+ role: authenticationResult.user.role?.id
4599
4742
  });
4600
4743
  response.headers.forEach((value, key) => {
4601
4744
  res.setHeader(key, value);
@@ -4633,6 +4776,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4633
4776
  }
4634
4777
  }
4635
4778
  });
4779
+ app.use(express.static("public"));
4636
4780
  return app;
4637
4781
  };
4638
4782
  var createCustomAnthropicStreamingMessage = (message) => {
@@ -4697,7 +4841,7 @@ var bullmq = {
4697
4841
  };
4698
4842
 
4699
4843
  // src/registry/workers.ts
4700
- import * as fs2 from "fs";
4844
+ import * as fs3 from "fs";
4701
4845
  import path2 from "path";
4702
4846
  var defaultLogsDir = path2.join(process.cwd(), "logs");
4703
4847
  var redisConnection;
@@ -4831,16 +4975,16 @@ var createLogsCleanerWorker = (logsDir) => {
4831
4975
  global_queues.logs_cleaner,
4832
4976
  async (job) => {
4833
4977
  console.log(`[EXULU] recurring job ${job.id}.`);
4834
- const folder = fs2.readdirSync(logsDir);
4978
+ const folder = fs3.readdirSync(logsDir);
4835
4979
  const files = folder.filter((file) => file.endsWith(".log"));
4836
4980
  const now = /* @__PURE__ */ new Date();
4837
4981
  const daysToKeep = job.data.ttld;
4838
4982
  const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
4839
4983
  files.forEach((file) => {
4840
4984
  const filePath = path2.join(logsDir, file);
4841
- const fileStats = fs2.statSync(filePath);
4985
+ const fileStats = fs3.statSync(filePath);
4842
4986
  if (fileStats.mtime < dateToKeep) {
4843
- fs2.unlinkSync(filePath);
4987
+ fs3.unlinkSync(filePath);
4844
4988
  }
4845
4989
  });
4846
4990
  },
@@ -4860,7 +5004,7 @@ var createLogsCleanerWorker = (logsDir) => {
4860
5004
 
4861
5005
  // src/mcp/index.ts
4862
5006
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4863
- import { randomUUID } from "crypto";
5007
+ import { randomUUID as randomUUID2 } from "crypto";
4864
5008
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4865
5009
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4866
5010
  import { z as z2 } from "zod";
@@ -4954,7 +5098,7 @@ ${code}`
4954
5098
  transport = this.transports[sessionId];
4955
5099
  } else if (!sessionId && isInitializeRequest(req.body)) {
4956
5100
  transport = new StreamableHTTPServerTransport({
4957
- sessionIdGenerator: () => randomUUID(),
5101
+ sessionIdGenerator: () => randomUUID2(),
4958
5102
  onsessioninitialized: (sessionId2) => {
4959
5103
  this.transports[sessionId2] = transport;
4960
5104
  }
@@ -5022,6 +5166,7 @@ var defaultAgent = new ExuluAgent({
5022
5166
  description: `Basic agent without any defined tools, that can support MCP's.`,
5023
5167
  type: "agent",
5024
5168
  capabilities: {
5169
+ text: true,
5025
5170
  images: [],
5026
5171
  files: [],
5027
5172
  audio: [],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "1.14.1",
4
+ "version": "1.16.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {
@@ -35,6 +35,10 @@
35
35
  "@types/bun": "latest",
36
36
  "@types/node": "^22.14.0",
37
37
  "@types/pg": "^8.15.1",
38
+ "@types/bcrypt": "^5.0.2",
39
+ "@types/cors": "^2.8.18",
40
+ "@types/express": "^5.0.1",
41
+ "@types/graphql-type-json": "^0.3.5",
38
42
  "conventional-changelog-atom": "^5.0.0",
39
43
  "husky": "^9.1.7",
40
44
  "semantic-release": "^24.2.7",
@@ -51,13 +55,7 @@
51
55
  "@aws-sdk/client-s3": "^3.338.0",
52
56
  "@aws-sdk/client-sts": "^3.338.0",
53
57
  "@aws-sdk/s3-request-presigner": "^3.338.0",
54
- "@inkjs/ui": "^2.0.0",
55
58
  "@modelcontextprotocol/sdk": "^1.14.0",
56
- "@types/bcrypt": "^5.0.2",
57
- "@types/cors": "^2.8.18",
58
- "@types/express": "^5.0.1",
59
- "@types/graphql-type-json": "^0.3.5",
60
- "@types/multer": "^1.4.12",
61
59
  "ai": "^5.0.15",
62
60
  "apollo-server": "^3.13.0",
63
61
  "bcryptjs": "^3.0.2",
@@ -69,21 +67,14 @@
69
67
  "csv-parse": "^5.6.0",
70
68
  "dotenv": "^16.5.0",
71
69
  "express": "^5.1.0",
72
- "fullscreen-ink": "^0.0.2",
73
70
  "graphql": "^16.11.0",
74
71
  "graphql-tools": "^9.0.18",
75
72
  "graphql-type-json": "^0.3.2",
76
73
  "http-proxy-middleware": "^3.0.5",
77
- "ink": "^6.0.0",
78
- "ink-big-text": "^2.0.0",
79
- "ink-gradient": "^3.0.0",
80
- "ink-select-input": "^6.2.0",
81
- "ink-table": "^3.1.0",
82
74
  "jose": "^6.0.10",
83
75
  "jsonwebtoken": "^9.0.2",
84
76
  "knex": "^3.1.0",
85
77
  "link": "^2.1.1",
86
- "multer": "^1.4.5-lts.2",
87
78
  "openai": "^4.94.0",
88
79
  "papaparse": "^5.5.2",
89
80
  "pg": "^8.16.3",
@@ -91,7 +82,6 @@
91
82
  "redis": "^4.7.0",
92
83
  "reflect-metadata": "^0.2.2",
93
84
  "tiktoken": "^1.0.21",
94
- "type-graphql": "^2.0.0-rc.2",
95
85
  "uninstall": "^0.0.0",
96
86
  "uuid": "^11.1.0",
97
87
  "zod": "^3.24.2",
@@ -14,6 +14,7 @@ export interface Agent {
14
14
  type?: "context";
15
15
  }[];
16
16
  capabilities?: {
17
+ text: boolean;
17
18
  images: string[];
18
19
  files: string[];
19
20
  audio: string[];
@@ -5,6 +5,7 @@ export type User = {
5
5
  type?: "api" | "user"
6
6
  anthropic_token?: string;
7
7
  super_admin?: boolean;
8
+ favourite_agents?: string[];
8
9
  role: {
9
10
  id: string;
10
11
  name: string;