@exulu/backend 1.14.1 → 1.15.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.15.0](https://github.com/Qventu/exulu-backend/compare/v1.14.1...v1.15.0) (2025-08-22)
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
+ * image generation for agent profile images, added "ByIds" to graphql Schema ([c084070](https://github.com/Qventu/exulu-backend/commit/c084070b9f61830fcdf0745da7727c70b718fa64))
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
@@ -589,6 +589,7 @@ var ExuluAgent = class {
589
589
  this.config = config;
590
590
  this.type = type;
591
591
  this.capabilities = capabilities || {
592
+ text: false,
592
593
  images: [],
593
594
  files: [],
594
595
  audio: [],
@@ -2484,6 +2485,14 @@ function createQueries(table) {
2484
2485
  const result = await query.first();
2485
2486
  return result;
2486
2487
  },
2488
+ [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2489
+ const { db: db3 } = context;
2490
+ const requestedFields = getRequestedFields(info);
2491
+ let query = db3.from(tableNamePlural).select(requestedFields).whereIn("id", args.ids);
2492
+ query = applyAccessControl(table, context.user, query);
2493
+ const result = await query;
2494
+ return result;
2495
+ },
2487
2496
  [`${tableNameSingular}One`]: async (_, args, context, info) => {
2488
2497
  const { filters = [], sort } = args;
2489
2498
  const { db: db3 } = context;
@@ -2648,6 +2657,7 @@ function createSDL(tables) {
2648
2657
  console.log("[EXULU] Adding table >>>>>", tableNamePlural);
2649
2658
  typeDefs += `
2650
2659
  ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2660
+ ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
2651
2661
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2652
2662
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2653
2663
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
@@ -2884,6 +2894,10 @@ var agentsSchema = {
2884
2894
  name: "name",
2885
2895
  type: "text"
2886
2896
  },
2897
+ {
2898
+ name: "image",
2899
+ type: "text"
2900
+ },
2887
2901
  {
2888
2902
  name: "description",
2889
2903
  type: "text"
@@ -2926,6 +2940,10 @@ var usersSchema = {
2926
2940
  type: "number",
2927
2941
  index: true
2928
2942
  },
2943
+ {
2944
+ name: "favourite_agents",
2945
+ type: "json"
2946
+ },
2929
2947
  {
2930
2948
  name: "firstname",
2931
2949
  type: "text"
@@ -3679,6 +3697,9 @@ Intelligence Management Platform
3679
3697
  };
3680
3698
 
3681
3699
  // src/registry/routes.ts
3700
+ var import_openai = __toESM(require("openai"), 1);
3701
+ var import_fs = __toESM(require("fs"), 1);
3702
+ var import_node_crypto = require("crypto");
3682
3703
  var REQUEST_SIZE_LIMIT = "50mb";
3683
3704
  var global_queues = {
3684
3705
  logs_cleaner: "logs-cleaner"
@@ -3859,6 +3880,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3859
3880
  rights_mode: agent.rights_mode,
3860
3881
  slug: backend?.slug,
3861
3882
  rateLimit: backend?.rateLimit,
3883
+ image: agent.image,
3862
3884
  streaming: backend?.streaming,
3863
3885
  capabilities: backend?.capabilities,
3864
3886
  RBAC: agent.RBAC,
@@ -3906,6 +3928,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3906
3928
  model: backend?.model?.create({ apiKey: "" }).modelId,
3907
3929
  active: agent.active,
3908
3930
  public: agent.public,
3931
+ image: agent.image,
3909
3932
  type: agent.type,
3910
3933
  rights_mode: agent.rights_mode,
3911
3934
  slug: backend?.slug,
@@ -3929,6 +3952,97 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3929
3952
  inputSchema: tool2.inputSchema ? (0, import_zodex.zerialize)(tool2.inputSchema) : null
3930
3953
  })));
3931
3954
  });
3955
+ app.post("/generate/agent/image", async (req, res) => {
3956
+ console.log("[EXULU] generate/agent/image", req.body);
3957
+ const authenticationResult = await requestValidators.authenticate(req);
3958
+ if (!authenticationResult.user?.id) {
3959
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3960
+ return;
3961
+ }
3962
+ const { name, description, style } = req.body;
3963
+ if (!name || !description) {
3964
+ res.status(400).json({
3965
+ message: "Missing name or description in request."
3966
+ });
3967
+ return;
3968
+ }
3969
+ const { db: db3 } = await postgresClient();
3970
+ const variable = await db3.from("variables").where({ name: "OPENAI_IMAGE_GENERATION_API_KEY" }).first();
3971
+ if (!variable) {
3972
+ res.status(400).json({
3973
+ message: "Provider API key variable not found."
3974
+ });
3975
+ return;
3976
+ }
3977
+ let providerApiKey = variable.value;
3978
+ if (!variable.encrypted) {
3979
+ res.status(400).json({
3980
+ message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
3981
+ });
3982
+ return;
3983
+ }
3984
+ if (variable.encrypted) {
3985
+ const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
3986
+ providerApiKey = bytes.toString(import_crypto_js3.default.enc.Utf8);
3987
+ }
3988
+ const openai = new import_openai.default({
3989
+ apiKey: providerApiKey
3990
+ });
3991
+ let style_reference = "";
3992
+ if (style === "origami") {
3993
+ style_reference = "minimalistic origami-style, futuristic robot, portrait, focus on face.";
3994
+ } else if (style === "anime") {
3995
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
3996
+ } else if (style === "japanese_anime") {
3997
+ style_reference = "minimalistic, make it in the style of japanese anime, futuristic robot, portrait, focus on face.";
3998
+ } else if (style === "vaporwave") {
3999
+ style_reference = "minimalistic, make it in the style of a vaporwave album cover, futuristic robot, portrait, focus on face.";
4000
+ } else if (style === "lego") {
4001
+ style_reference = "minimalistic, make it in the style of LEGO minifigures, futuristic robot, portrait, focus on face.";
4002
+ } else if (style === "paper_cut") {
4003
+ style_reference = "minimalistic, make it in the style of Paper-cut style portrait with color layers, futuristic robot, portrait, focus on face.";
4004
+ } else if (style === "felt_puppet") {
4005
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
4006
+ } else if (style === "app_icon") {
4007
+ 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.";
4008
+ } else if (style === "pixel_art") {
4009
+ 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.";
4010
+ } else if (style === "isometric") {
4011
+ style_reference = "3D isometric icon of a robot, centered composition, on a transparent background, no text, no details outside the frame, size is 1024x1024.";
4012
+ } else {
4013
+ style_reference = "A minimalist 3D, robot, portrait, focus on face, floating in space, low-poly design with pastel colors.";
4014
+ }
4015
+ const prompt = `
4016
+ A digital portrait of ${name}, visualized as a futuristic robot.
4017
+ The robot\u2019s design reflects '${description}', with props, tools, or symbolic objects that represent its expertise or area of work.
4018
+ 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.
4019
+ Style: ${style_reference}.
4020
+ The portrait should have a clean background.
4021
+ Framing: bust portrait, centered.
4022
+ Mood: friendly and intelligent.
4023
+ `;
4024
+ const result = await openai.images.generate({
4025
+ model: "gpt-image-1",
4026
+ prompt
4027
+ });
4028
+ const image_base64 = result.data?.[0]?.b64_json;
4029
+ if (!image_base64) {
4030
+ res.status(500).json({
4031
+ message: "Failed to generate image."
4032
+ });
4033
+ return;
4034
+ }
4035
+ const image_bytes = Buffer.from(image_base64, "base64");
4036
+ const uuid = (0, import_node_crypto.randomUUID)();
4037
+ if (!import_fs.default.existsSync("public")) {
4038
+ import_fs.default.mkdirSync("public");
4039
+ }
4040
+ import_fs.default.writeFileSync(`public/${uuid}.png`, image_bytes);
4041
+ res.status(200).json({
4042
+ message: "Image generated successfully.",
4043
+ image: `${process.env.BACKEND}/${uuid}.png`
4044
+ });
4045
+ });
3932
4046
  app.get("/tools/:id", async (req, res) => {
3933
4047
  const id = req.params.id;
3934
4048
  if (!id) {
@@ -4675,6 +4789,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4675
4789
  }
4676
4790
  }
4677
4791
  });
4792
+ app.use(import_express4.default.static("public"));
4678
4793
  return app;
4679
4794
  };
4680
4795
  var createCustomAnthropicStreamingMessage = (message) => {
@@ -4739,7 +4854,7 @@ var bullmq = {
4739
4854
  };
4740
4855
 
4741
4856
  // src/registry/workers.ts
4742
- var fs2 = __toESM(require("fs"), 1);
4857
+ var fs3 = __toESM(require("fs"), 1);
4743
4858
  var import_path = __toESM(require("path"), 1);
4744
4859
  var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
4745
4860
  var redisConnection;
@@ -4873,16 +4988,16 @@ var createLogsCleanerWorker = (logsDir) => {
4873
4988
  global_queues.logs_cleaner,
4874
4989
  async (job) => {
4875
4990
  console.log(`[EXULU] recurring job ${job.id}.`);
4876
- const folder = fs2.readdirSync(logsDir);
4991
+ const folder = fs3.readdirSync(logsDir);
4877
4992
  const files = folder.filter((file) => file.endsWith(".log"));
4878
4993
  const now = /* @__PURE__ */ new Date();
4879
4994
  const daysToKeep = job.data.ttld;
4880
4995
  const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
4881
4996
  files.forEach((file) => {
4882
4997
  const filePath = import_path.default.join(logsDir, file);
4883
- const fileStats = fs2.statSync(filePath);
4998
+ const fileStats = fs3.statSync(filePath);
4884
4999
  if (fileStats.mtime < dateToKeep) {
4885
- fs2.unlinkSync(filePath);
5000
+ fs3.unlinkSync(filePath);
4886
5001
  }
4887
5002
  });
4888
5003
  },
@@ -4902,7 +5017,7 @@ var createLogsCleanerWorker = (logsDir) => {
4902
5017
 
4903
5018
  // src/mcp/index.ts
4904
5019
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
4905
- var import_node_crypto = require("crypto");
5020
+ var import_node_crypto2 = require("crypto");
4906
5021
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
4907
5022
  var import_types = require("@modelcontextprotocol/sdk/types.js");
4908
5023
  var import_zod3 = require("zod");
@@ -4996,7 +5111,7 @@ ${code}`
4996
5111
  transport = this.transports[sessionId];
4997
5112
  } else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
4998
5113
  transport = new import_streamableHttp.StreamableHTTPServerTransport({
4999
- sessionIdGenerator: () => (0, import_node_crypto.randomUUID)(),
5114
+ sessionIdGenerator: () => (0, import_node_crypto2.randomUUID)(),
5000
5115
  onsessioninitialized: (sessionId2) => {
5001
5116
  this.transports[sessionId2] = transport;
5002
5117
  }
@@ -5064,6 +5179,7 @@ var defaultAgent = new ExuluAgent({
5064
5179
  description: `Basic agent without any defined tools, that can support MCP's.`,
5065
5180
  type: "agent",
5066
5181
  capabilities: {
5182
+ text: true,
5067
5183
  images: [],
5068
5184
  files: [],
5069
5185
  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[];
@@ -544,6 +546,7 @@ type User = {
544
546
  type?: "api" | "user";
545
547
  anthropic_token?: string;
546
548
  super_admin?: boolean;
549
+ favourite_agents?: string[];
547
550
  role: {
548
551
  id: string;
549
552
  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[];
@@ -544,6 +546,7 @@ type User = {
544
546
  type?: "api" | "user";
545
547
  anthropic_token?: string;
546
548
  super_admin?: boolean;
549
+ favourite_agents?: string[];
547
550
  role: {
548
551
  id: string;
549
552
  name: string;
package/dist/index.js CHANGED
@@ -547,6 +547,7 @@ var ExuluAgent = class {
547
547
  this.config = config;
548
548
  this.type = type;
549
549
  this.capabilities = capabilities || {
550
+ text: false,
550
551
  images: [],
551
552
  files: [],
552
553
  audio: [],
@@ -2442,6 +2443,14 @@ function createQueries(table) {
2442
2443
  const result = await query.first();
2443
2444
  return result;
2444
2445
  },
2446
+ [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2447
+ const { db: db3 } = context;
2448
+ const requestedFields = getRequestedFields(info);
2449
+ let query = db3.from(tableNamePlural).select(requestedFields).whereIn("id", args.ids);
2450
+ query = applyAccessControl(table, context.user, query);
2451
+ const result = await query;
2452
+ return result;
2453
+ },
2445
2454
  [`${tableNameSingular}One`]: async (_, args, context, info) => {
2446
2455
  const { filters = [], sort } = args;
2447
2456
  const { db: db3 } = context;
@@ -2606,6 +2615,7 @@ function createSDL(tables) {
2606
2615
  console.log("[EXULU] Adding table >>>>>", tableNamePlural);
2607
2616
  typeDefs += `
2608
2617
  ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2618
+ ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
2609
2619
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2610
2620
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2611
2621
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
@@ -2842,6 +2852,10 @@ var agentsSchema = {
2842
2852
  name: "name",
2843
2853
  type: "text"
2844
2854
  },
2855
+ {
2856
+ name: "image",
2857
+ type: "text"
2858
+ },
2845
2859
  {
2846
2860
  name: "description",
2847
2861
  type: "text"
@@ -2884,6 +2898,10 @@ var usersSchema = {
2884
2898
  type: "number",
2885
2899
  index: true
2886
2900
  },
2901
+ {
2902
+ name: "favourite_agents",
2903
+ type: "json"
2904
+ },
2887
2905
  {
2888
2906
  name: "firstname",
2889
2907
  type: "text"
@@ -3637,6 +3655,9 @@ Intelligence Management Platform
3637
3655
  };
3638
3656
 
3639
3657
  // src/registry/routes.ts
3658
+ import OpenAI from "openai";
3659
+ import fs2 from "fs";
3660
+ import { randomUUID } from "crypto";
3640
3661
  var REQUEST_SIZE_LIMIT = "50mb";
3641
3662
  var global_queues = {
3642
3663
  logs_cleaner: "logs-cleaner"
@@ -3817,6 +3838,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3817
3838
  rights_mode: agent.rights_mode,
3818
3839
  slug: backend?.slug,
3819
3840
  rateLimit: backend?.rateLimit,
3841
+ image: agent.image,
3820
3842
  streaming: backend?.streaming,
3821
3843
  capabilities: backend?.capabilities,
3822
3844
  RBAC: agent.RBAC,
@@ -3864,6 +3886,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3864
3886
  model: backend?.model?.create({ apiKey: "" }).modelId,
3865
3887
  active: agent.active,
3866
3888
  public: agent.public,
3889
+ image: agent.image,
3867
3890
  type: agent.type,
3868
3891
  rights_mode: agent.rights_mode,
3869
3892
  slug: backend?.slug,
@@ -3887,6 +3910,97 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3887
3910
  inputSchema: tool2.inputSchema ? zerialize(tool2.inputSchema) : null
3888
3911
  })));
3889
3912
  });
3913
+ app.post("/generate/agent/image", async (req, res) => {
3914
+ console.log("[EXULU] generate/agent/image", req.body);
3915
+ const authenticationResult = await requestValidators.authenticate(req);
3916
+ if (!authenticationResult.user?.id) {
3917
+ res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3918
+ return;
3919
+ }
3920
+ const { name, description, style } = req.body;
3921
+ if (!name || !description) {
3922
+ res.status(400).json({
3923
+ message: "Missing name or description in request."
3924
+ });
3925
+ return;
3926
+ }
3927
+ const { db: db3 } = await postgresClient();
3928
+ const variable = await db3.from("variables").where({ name: "OPENAI_IMAGE_GENERATION_API_KEY" }).first();
3929
+ if (!variable) {
3930
+ res.status(400).json({
3931
+ message: "Provider API key variable not found."
3932
+ });
3933
+ return;
3934
+ }
3935
+ let providerApiKey = variable.value;
3936
+ if (!variable.encrypted) {
3937
+ res.status(400).json({
3938
+ message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
3939
+ });
3940
+ return;
3941
+ }
3942
+ if (variable.encrypted) {
3943
+ const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
3944
+ providerApiKey = bytes.toString(CryptoJS3.enc.Utf8);
3945
+ }
3946
+ const openai = new OpenAI({
3947
+ apiKey: providerApiKey
3948
+ });
3949
+ let style_reference = "";
3950
+ if (style === "origami") {
3951
+ style_reference = "minimalistic origami-style, futuristic robot, portrait, focus on face.";
3952
+ } else if (style === "anime") {
3953
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
3954
+ } else if (style === "japanese_anime") {
3955
+ style_reference = "minimalistic, make it in the style of japanese anime, futuristic robot, portrait, focus on face.";
3956
+ } else if (style === "vaporwave") {
3957
+ style_reference = "minimalistic, make it in the style of a vaporwave album cover, futuristic robot, portrait, focus on face.";
3958
+ } else if (style === "lego") {
3959
+ style_reference = "minimalistic, make it in the style of LEGO minifigures, futuristic robot, portrait, focus on face.";
3960
+ } else if (style === "paper_cut") {
3961
+ style_reference = "minimalistic, make it in the style of Paper-cut style portrait with color layers, futuristic robot, portrait, focus on face.";
3962
+ } else if (style === "felt_puppet") {
3963
+ style_reference = "minimalistic, make it in the style of a felt puppet, futuristic robot, portrait, focus on face.";
3964
+ } else if (style === "app_icon") {
3965
+ 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.";
3966
+ } else if (style === "pixel_art") {
3967
+ 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.";
3968
+ } else if (style === "isometric") {
3969
+ style_reference = "3D isometric icon of a robot, centered composition, on a transparent background, no text, no details outside the frame, size is 1024x1024.";
3970
+ } else {
3971
+ style_reference = "A minimalist 3D, robot, portrait, focus on face, floating in space, low-poly design with pastel colors.";
3972
+ }
3973
+ const prompt = `
3974
+ A digital portrait of ${name}, visualized as a futuristic robot.
3975
+ The robot\u2019s design reflects '${description}', with props, tools, or symbolic objects that represent its expertise or area of work.
3976
+ 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.
3977
+ Style: ${style_reference}.
3978
+ The portrait should have a clean background.
3979
+ Framing: bust portrait, centered.
3980
+ Mood: friendly and intelligent.
3981
+ `;
3982
+ const result = await openai.images.generate({
3983
+ model: "gpt-image-1",
3984
+ prompt
3985
+ });
3986
+ const image_base64 = result.data?.[0]?.b64_json;
3987
+ if (!image_base64) {
3988
+ res.status(500).json({
3989
+ message: "Failed to generate image."
3990
+ });
3991
+ return;
3992
+ }
3993
+ const image_bytes = Buffer.from(image_base64, "base64");
3994
+ const uuid = randomUUID();
3995
+ if (!fs2.existsSync("public")) {
3996
+ fs2.mkdirSync("public");
3997
+ }
3998
+ fs2.writeFileSync(`public/${uuid}.png`, image_bytes);
3999
+ res.status(200).json({
4000
+ message: "Image generated successfully.",
4001
+ image: `${process.env.BACKEND}/${uuid}.png`
4002
+ });
4003
+ });
3890
4004
  app.get("/tools/:id", async (req, res) => {
3891
4005
  const id = req.params.id;
3892
4006
  if (!id) {
@@ -4633,6 +4747,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4633
4747
  }
4634
4748
  }
4635
4749
  });
4750
+ app.use(express.static("public"));
4636
4751
  return app;
4637
4752
  };
4638
4753
  var createCustomAnthropicStreamingMessage = (message) => {
@@ -4697,7 +4812,7 @@ var bullmq = {
4697
4812
  };
4698
4813
 
4699
4814
  // src/registry/workers.ts
4700
- import * as fs2 from "fs";
4815
+ import * as fs3 from "fs";
4701
4816
  import path2 from "path";
4702
4817
  var defaultLogsDir = path2.join(process.cwd(), "logs");
4703
4818
  var redisConnection;
@@ -4831,16 +4946,16 @@ var createLogsCleanerWorker = (logsDir) => {
4831
4946
  global_queues.logs_cleaner,
4832
4947
  async (job) => {
4833
4948
  console.log(`[EXULU] recurring job ${job.id}.`);
4834
- const folder = fs2.readdirSync(logsDir);
4949
+ const folder = fs3.readdirSync(logsDir);
4835
4950
  const files = folder.filter((file) => file.endsWith(".log"));
4836
4951
  const now = /* @__PURE__ */ new Date();
4837
4952
  const daysToKeep = job.data.ttld;
4838
4953
  const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
4839
4954
  files.forEach((file) => {
4840
4955
  const filePath = path2.join(logsDir, file);
4841
- const fileStats = fs2.statSync(filePath);
4956
+ const fileStats = fs3.statSync(filePath);
4842
4957
  if (fileStats.mtime < dateToKeep) {
4843
- fs2.unlinkSync(filePath);
4958
+ fs3.unlinkSync(filePath);
4844
4959
  }
4845
4960
  });
4846
4961
  },
@@ -4860,7 +4975,7 @@ var createLogsCleanerWorker = (logsDir) => {
4860
4975
 
4861
4976
  // src/mcp/index.ts
4862
4977
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4863
- import { randomUUID } from "crypto";
4978
+ import { randomUUID as randomUUID2 } from "crypto";
4864
4979
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4865
4980
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4866
4981
  import { z as z2 } from "zod";
@@ -4954,7 +5069,7 @@ ${code}`
4954
5069
  transport = this.transports[sessionId];
4955
5070
  } else if (!sessionId && isInitializeRequest(req.body)) {
4956
5071
  transport = new StreamableHTTPServerTransport({
4957
- sessionIdGenerator: () => randomUUID(),
5072
+ sessionIdGenerator: () => randomUUID2(),
4958
5073
  onsessioninitialized: (sessionId2) => {
4959
5074
  this.transports[sessionId2] = transport;
4960
5075
  }
@@ -5022,6 +5137,7 @@ var defaultAgent = new ExuluAgent({
5022
5137
  description: `Basic agent without any defined tools, that can support MCP's.`,
5023
5138
  type: "agent",
5024
5139
  capabilities: {
5140
+ text: true,
5025
5141
  images: [],
5026
5142
  files: [],
5027
5143
  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.15.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;