@exulu/backend 1.14.0 → 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 +2 -2
- package/dist/index.cjs +162 -23
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +162 -23
- package/package.json +5 -15
- package/types/models/agent.ts +1 -0
- package/types/models/user.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# [1.
|
|
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
4
|
### Features
|
|
5
5
|
|
|
6
|
-
*
|
|
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
|
@@ -305,57 +305,80 @@ var bullmqDecorator = async ({
|
|
|
305
305
|
// src/registry/utils/map-types.ts
|
|
306
306
|
var mapType = (t, type, name, defaultValue, unique) => {
|
|
307
307
|
if (type === "text" || type === "enum") {
|
|
308
|
-
|
|
308
|
+
if (defaultValue) {
|
|
309
|
+
t.text(name).defaultTo(defaultValue);
|
|
310
|
+
} else {
|
|
311
|
+
t.text(name);
|
|
312
|
+
}
|
|
309
313
|
if (unique) t.unique(name);
|
|
310
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
311
314
|
return;
|
|
312
315
|
}
|
|
313
316
|
if (type === "longText") {
|
|
314
|
-
|
|
317
|
+
if (defaultValue) {
|
|
318
|
+
t.text(name).defaultTo(defaultValue);
|
|
319
|
+
} else {
|
|
320
|
+
t.text(name);
|
|
321
|
+
}
|
|
315
322
|
if (unique) t.unique(name);
|
|
316
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
317
323
|
return;
|
|
318
324
|
}
|
|
319
325
|
if (type === "shortText") {
|
|
320
|
-
|
|
326
|
+
if (defaultValue) {
|
|
327
|
+
t.string(name, 100).defaultTo(defaultValue);
|
|
328
|
+
} else {
|
|
329
|
+
t.string(name, 100);
|
|
330
|
+
}
|
|
321
331
|
if (unique) t.unique(name);
|
|
322
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
323
332
|
return;
|
|
324
333
|
}
|
|
325
334
|
if (type === "number") {
|
|
326
|
-
|
|
335
|
+
if (defaultValue) {
|
|
336
|
+
t.float(name).defaultTo(defaultValue);
|
|
337
|
+
} else {
|
|
338
|
+
t.float(name);
|
|
339
|
+
}
|
|
327
340
|
if (unique) t.unique(name);
|
|
328
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
329
341
|
return;
|
|
330
342
|
}
|
|
331
343
|
if (type === "boolean") {
|
|
332
344
|
t.boolean(name).defaultTo(defaultValue || false);
|
|
333
345
|
if (unique) t.unique(name);
|
|
334
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
335
346
|
return;
|
|
336
347
|
}
|
|
337
348
|
if (type === "code") {
|
|
338
|
-
|
|
349
|
+
if (defaultValue) {
|
|
350
|
+
t.text(name).defaultTo(defaultValue);
|
|
351
|
+
} else {
|
|
352
|
+
t.text(name);
|
|
353
|
+
}
|
|
339
354
|
if (unique) t.unique(name);
|
|
340
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
341
355
|
return;
|
|
342
356
|
}
|
|
343
357
|
if (type === "json") {
|
|
344
|
-
|
|
358
|
+
if (defaultValue) {
|
|
359
|
+
t.jsonb(name).defaultTo(defaultValue);
|
|
360
|
+
} else {
|
|
361
|
+
t.jsonb(name);
|
|
362
|
+
}
|
|
345
363
|
if (unique) t.unique(name);
|
|
346
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
347
364
|
return;
|
|
348
365
|
}
|
|
349
366
|
if (type === "date") {
|
|
350
|
-
|
|
367
|
+
if (defaultValue) {
|
|
368
|
+
t.timestamp(name).defaultTo(defaultValue);
|
|
369
|
+
} else {
|
|
370
|
+
t.timestamp(name);
|
|
371
|
+
}
|
|
351
372
|
if (unique) t.unique(name);
|
|
352
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
353
373
|
return;
|
|
354
374
|
}
|
|
355
375
|
if (type === "uuid") {
|
|
356
|
-
|
|
376
|
+
if (defaultValue) {
|
|
377
|
+
t.uuid(name).defaultTo(defaultValue);
|
|
378
|
+
} else {
|
|
379
|
+
t.uuid(name);
|
|
380
|
+
}
|
|
357
381
|
if (unique) t.unique(name);
|
|
358
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
359
382
|
return;
|
|
360
383
|
}
|
|
361
384
|
throw new Error("Invalid type: " + type);
|
|
@@ -566,6 +589,7 @@ var ExuluAgent = class {
|
|
|
566
589
|
this.config = config;
|
|
567
590
|
this.type = type;
|
|
568
591
|
this.capabilities = capabilities || {
|
|
592
|
+
text: false,
|
|
569
593
|
images: [],
|
|
570
594
|
files: [],
|
|
571
595
|
audio: [],
|
|
@@ -2461,6 +2485,14 @@ function createQueries(table) {
|
|
|
2461
2485
|
const result = await query.first();
|
|
2462
2486
|
return result;
|
|
2463
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
|
+
},
|
|
2464
2496
|
[`${tableNameSingular}One`]: async (_, args, context, info) => {
|
|
2465
2497
|
const { filters = [], sort } = args;
|
|
2466
2498
|
const { db: db3 } = context;
|
|
@@ -2625,6 +2657,7 @@ function createSDL(tables) {
|
|
|
2625
2657
|
console.log("[EXULU] Adding table >>>>>", tableNamePlural);
|
|
2626
2658
|
typeDefs += `
|
|
2627
2659
|
${tableNameSingular}ById(id: ID!): ${tableNameSingular}
|
|
2660
|
+
${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
|
|
2628
2661
|
${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
|
|
2629
2662
|
${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
|
|
2630
2663
|
${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
|
|
@@ -2861,6 +2894,10 @@ var agentsSchema = {
|
|
|
2861
2894
|
name: "name",
|
|
2862
2895
|
type: "text"
|
|
2863
2896
|
},
|
|
2897
|
+
{
|
|
2898
|
+
name: "image",
|
|
2899
|
+
type: "text"
|
|
2900
|
+
},
|
|
2864
2901
|
{
|
|
2865
2902
|
name: "description",
|
|
2866
2903
|
type: "text"
|
|
@@ -2903,6 +2940,10 @@ var usersSchema = {
|
|
|
2903
2940
|
type: "number",
|
|
2904
2941
|
index: true
|
|
2905
2942
|
},
|
|
2943
|
+
{
|
|
2944
|
+
name: "favourite_agents",
|
|
2945
|
+
type: "json"
|
|
2946
|
+
},
|
|
2906
2947
|
{
|
|
2907
2948
|
name: "firstname",
|
|
2908
2949
|
type: "text"
|
|
@@ -3656,6 +3697,9 @@ Intelligence Management Platform
|
|
|
3656
3697
|
};
|
|
3657
3698
|
|
|
3658
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");
|
|
3659
3703
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
3660
3704
|
var global_queues = {
|
|
3661
3705
|
logs_cleaner: "logs-cleaner"
|
|
@@ -3836,6 +3880,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
3836
3880
|
rights_mode: agent.rights_mode,
|
|
3837
3881
|
slug: backend?.slug,
|
|
3838
3882
|
rateLimit: backend?.rateLimit,
|
|
3883
|
+
image: agent.image,
|
|
3839
3884
|
streaming: backend?.streaming,
|
|
3840
3885
|
capabilities: backend?.capabilities,
|
|
3841
3886
|
RBAC: agent.RBAC,
|
|
@@ -3883,6 +3928,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
3883
3928
|
model: backend?.model?.create({ apiKey: "" }).modelId,
|
|
3884
3929
|
active: agent.active,
|
|
3885
3930
|
public: agent.public,
|
|
3931
|
+
image: agent.image,
|
|
3886
3932
|
type: agent.type,
|
|
3887
3933
|
rights_mode: agent.rights_mode,
|
|
3888
3934
|
slug: backend?.slug,
|
|
@@ -3906,6 +3952,97 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
3906
3952
|
inputSchema: tool2.inputSchema ? (0, import_zodex.zerialize)(tool2.inputSchema) : null
|
|
3907
3953
|
})));
|
|
3908
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
|
+
});
|
|
3909
4046
|
app.get("/tools/:id", async (req, res) => {
|
|
3910
4047
|
const id = req.params.id;
|
|
3911
4048
|
if (!id) {
|
|
@@ -4652,6 +4789,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
4652
4789
|
}
|
|
4653
4790
|
}
|
|
4654
4791
|
});
|
|
4792
|
+
app.use(import_express4.default.static("public"));
|
|
4655
4793
|
return app;
|
|
4656
4794
|
};
|
|
4657
4795
|
var createCustomAnthropicStreamingMessage = (message) => {
|
|
@@ -4716,7 +4854,7 @@ var bullmq = {
|
|
|
4716
4854
|
};
|
|
4717
4855
|
|
|
4718
4856
|
// src/registry/workers.ts
|
|
4719
|
-
var
|
|
4857
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
4720
4858
|
var import_path = __toESM(require("path"), 1);
|
|
4721
4859
|
var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
|
|
4722
4860
|
var redisConnection;
|
|
@@ -4850,16 +4988,16 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4850
4988
|
global_queues.logs_cleaner,
|
|
4851
4989
|
async (job) => {
|
|
4852
4990
|
console.log(`[EXULU] recurring job ${job.id}.`);
|
|
4853
|
-
const folder =
|
|
4991
|
+
const folder = fs3.readdirSync(logsDir);
|
|
4854
4992
|
const files = folder.filter((file) => file.endsWith(".log"));
|
|
4855
4993
|
const now = /* @__PURE__ */ new Date();
|
|
4856
4994
|
const daysToKeep = job.data.ttld;
|
|
4857
4995
|
const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
|
|
4858
4996
|
files.forEach((file) => {
|
|
4859
4997
|
const filePath = import_path.default.join(logsDir, file);
|
|
4860
|
-
const fileStats =
|
|
4998
|
+
const fileStats = fs3.statSync(filePath);
|
|
4861
4999
|
if (fileStats.mtime < dateToKeep) {
|
|
4862
|
-
|
|
5000
|
+
fs3.unlinkSync(filePath);
|
|
4863
5001
|
}
|
|
4864
5002
|
});
|
|
4865
5003
|
},
|
|
@@ -4879,7 +5017,7 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4879
5017
|
|
|
4880
5018
|
// src/mcp/index.ts
|
|
4881
5019
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4882
|
-
var
|
|
5020
|
+
var import_node_crypto2 = require("crypto");
|
|
4883
5021
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
4884
5022
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
4885
5023
|
var import_zod3 = require("zod");
|
|
@@ -4973,7 +5111,7 @@ ${code}`
|
|
|
4973
5111
|
transport = this.transports[sessionId];
|
|
4974
5112
|
} else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
|
|
4975
5113
|
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
4976
|
-
sessionIdGenerator: () => (0,
|
|
5114
|
+
sessionIdGenerator: () => (0, import_node_crypto2.randomUUID)(),
|
|
4977
5115
|
onsessioninitialized: (sessionId2) => {
|
|
4978
5116
|
this.transports[sessionId2] = transport;
|
|
4979
5117
|
}
|
|
@@ -5041,6 +5179,7 @@ var defaultAgent = new ExuluAgent({
|
|
|
5041
5179
|
description: `Basic agent without any defined tools, that can support MCP's.`,
|
|
5042
5180
|
type: "agent",
|
|
5043
5181
|
capabilities: {
|
|
5182
|
+
text: true,
|
|
5044
5183
|
images: [],
|
|
5045
5184
|
files: [],
|
|
5046
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
|
@@ -263,57 +263,80 @@ var bullmqDecorator = async ({
|
|
|
263
263
|
// src/registry/utils/map-types.ts
|
|
264
264
|
var mapType = (t, type, name, defaultValue, unique) => {
|
|
265
265
|
if (type === "text" || type === "enum") {
|
|
266
|
-
|
|
266
|
+
if (defaultValue) {
|
|
267
|
+
t.text(name).defaultTo(defaultValue);
|
|
268
|
+
} else {
|
|
269
|
+
t.text(name);
|
|
270
|
+
}
|
|
267
271
|
if (unique) t.unique(name);
|
|
268
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
269
272
|
return;
|
|
270
273
|
}
|
|
271
274
|
if (type === "longText") {
|
|
272
|
-
|
|
275
|
+
if (defaultValue) {
|
|
276
|
+
t.text(name).defaultTo(defaultValue);
|
|
277
|
+
} else {
|
|
278
|
+
t.text(name);
|
|
279
|
+
}
|
|
273
280
|
if (unique) t.unique(name);
|
|
274
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
275
281
|
return;
|
|
276
282
|
}
|
|
277
283
|
if (type === "shortText") {
|
|
278
|
-
|
|
284
|
+
if (defaultValue) {
|
|
285
|
+
t.string(name, 100).defaultTo(defaultValue);
|
|
286
|
+
} else {
|
|
287
|
+
t.string(name, 100);
|
|
288
|
+
}
|
|
279
289
|
if (unique) t.unique(name);
|
|
280
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
281
290
|
return;
|
|
282
291
|
}
|
|
283
292
|
if (type === "number") {
|
|
284
|
-
|
|
293
|
+
if (defaultValue) {
|
|
294
|
+
t.float(name).defaultTo(defaultValue);
|
|
295
|
+
} else {
|
|
296
|
+
t.float(name);
|
|
297
|
+
}
|
|
285
298
|
if (unique) t.unique(name);
|
|
286
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
287
299
|
return;
|
|
288
300
|
}
|
|
289
301
|
if (type === "boolean") {
|
|
290
302
|
t.boolean(name).defaultTo(defaultValue || false);
|
|
291
303
|
if (unique) t.unique(name);
|
|
292
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
293
304
|
return;
|
|
294
305
|
}
|
|
295
306
|
if (type === "code") {
|
|
296
|
-
|
|
307
|
+
if (defaultValue) {
|
|
308
|
+
t.text(name).defaultTo(defaultValue);
|
|
309
|
+
} else {
|
|
310
|
+
t.text(name);
|
|
311
|
+
}
|
|
297
312
|
if (unique) t.unique(name);
|
|
298
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
299
313
|
return;
|
|
300
314
|
}
|
|
301
315
|
if (type === "json") {
|
|
302
|
-
|
|
316
|
+
if (defaultValue) {
|
|
317
|
+
t.jsonb(name).defaultTo(defaultValue);
|
|
318
|
+
} else {
|
|
319
|
+
t.jsonb(name);
|
|
320
|
+
}
|
|
303
321
|
if (unique) t.unique(name);
|
|
304
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
305
322
|
return;
|
|
306
323
|
}
|
|
307
324
|
if (type === "date") {
|
|
308
|
-
|
|
325
|
+
if (defaultValue) {
|
|
326
|
+
t.timestamp(name).defaultTo(defaultValue);
|
|
327
|
+
} else {
|
|
328
|
+
t.timestamp(name);
|
|
329
|
+
}
|
|
309
330
|
if (unique) t.unique(name);
|
|
310
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
311
331
|
return;
|
|
312
332
|
}
|
|
313
333
|
if (type === "uuid") {
|
|
314
|
-
|
|
334
|
+
if (defaultValue) {
|
|
335
|
+
t.uuid(name).defaultTo(defaultValue);
|
|
336
|
+
} else {
|
|
337
|
+
t.uuid(name);
|
|
338
|
+
}
|
|
315
339
|
if (unique) t.unique(name);
|
|
316
|
-
if (defaultValue) t.defaultTo(defaultValue);
|
|
317
340
|
return;
|
|
318
341
|
}
|
|
319
342
|
throw new Error("Invalid type: " + type);
|
|
@@ -524,6 +547,7 @@ var ExuluAgent = class {
|
|
|
524
547
|
this.config = config;
|
|
525
548
|
this.type = type;
|
|
526
549
|
this.capabilities = capabilities || {
|
|
550
|
+
text: false,
|
|
527
551
|
images: [],
|
|
528
552
|
files: [],
|
|
529
553
|
audio: [],
|
|
@@ -2419,6 +2443,14 @@ function createQueries(table) {
|
|
|
2419
2443
|
const result = await query.first();
|
|
2420
2444
|
return result;
|
|
2421
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
|
+
},
|
|
2422
2454
|
[`${tableNameSingular}One`]: async (_, args, context, info) => {
|
|
2423
2455
|
const { filters = [], sort } = args;
|
|
2424
2456
|
const { db: db3 } = context;
|
|
@@ -2583,6 +2615,7 @@ function createSDL(tables) {
|
|
|
2583
2615
|
console.log("[EXULU] Adding table >>>>>", tableNamePlural);
|
|
2584
2616
|
typeDefs += `
|
|
2585
2617
|
${tableNameSingular}ById(id: ID!): ${tableNameSingular}
|
|
2618
|
+
${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
|
|
2586
2619
|
${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
|
|
2587
2620
|
${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
|
|
2588
2621
|
${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
|
|
@@ -2819,6 +2852,10 @@ var agentsSchema = {
|
|
|
2819
2852
|
name: "name",
|
|
2820
2853
|
type: "text"
|
|
2821
2854
|
},
|
|
2855
|
+
{
|
|
2856
|
+
name: "image",
|
|
2857
|
+
type: "text"
|
|
2858
|
+
},
|
|
2822
2859
|
{
|
|
2823
2860
|
name: "description",
|
|
2824
2861
|
type: "text"
|
|
@@ -2861,6 +2898,10 @@ var usersSchema = {
|
|
|
2861
2898
|
type: "number",
|
|
2862
2899
|
index: true
|
|
2863
2900
|
},
|
|
2901
|
+
{
|
|
2902
|
+
name: "favourite_agents",
|
|
2903
|
+
type: "json"
|
|
2904
|
+
},
|
|
2864
2905
|
{
|
|
2865
2906
|
name: "firstname",
|
|
2866
2907
|
type: "text"
|
|
@@ -3614,6 +3655,9 @@ Intelligence Management Platform
|
|
|
3614
3655
|
};
|
|
3615
3656
|
|
|
3616
3657
|
// src/registry/routes.ts
|
|
3658
|
+
import OpenAI from "openai";
|
|
3659
|
+
import fs2 from "fs";
|
|
3660
|
+
import { randomUUID } from "crypto";
|
|
3617
3661
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
3618
3662
|
var global_queues = {
|
|
3619
3663
|
logs_cleaner: "logs-cleaner"
|
|
@@ -3794,6 +3838,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
3794
3838
|
rights_mode: agent.rights_mode,
|
|
3795
3839
|
slug: backend?.slug,
|
|
3796
3840
|
rateLimit: backend?.rateLimit,
|
|
3841
|
+
image: agent.image,
|
|
3797
3842
|
streaming: backend?.streaming,
|
|
3798
3843
|
capabilities: backend?.capabilities,
|
|
3799
3844
|
RBAC: agent.RBAC,
|
|
@@ -3841,6 +3886,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
3841
3886
|
model: backend?.model?.create({ apiKey: "" }).modelId,
|
|
3842
3887
|
active: agent.active,
|
|
3843
3888
|
public: agent.public,
|
|
3889
|
+
image: agent.image,
|
|
3844
3890
|
type: agent.type,
|
|
3845
3891
|
rights_mode: agent.rights_mode,
|
|
3846
3892
|
slug: backend?.slug,
|
|
@@ -3864,6 +3910,97 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
3864
3910
|
inputSchema: tool2.inputSchema ? zerialize(tool2.inputSchema) : null
|
|
3865
3911
|
})));
|
|
3866
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
|
+
});
|
|
3867
4004
|
app.get("/tools/:id", async (req, res) => {
|
|
3868
4005
|
const id = req.params.id;
|
|
3869
4006
|
if (!id) {
|
|
@@ -4610,6 +4747,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
|
|
|
4610
4747
|
}
|
|
4611
4748
|
}
|
|
4612
4749
|
});
|
|
4750
|
+
app.use(express.static("public"));
|
|
4613
4751
|
return app;
|
|
4614
4752
|
};
|
|
4615
4753
|
var createCustomAnthropicStreamingMessage = (message) => {
|
|
@@ -4674,7 +4812,7 @@ var bullmq = {
|
|
|
4674
4812
|
};
|
|
4675
4813
|
|
|
4676
4814
|
// src/registry/workers.ts
|
|
4677
|
-
import * as
|
|
4815
|
+
import * as fs3 from "fs";
|
|
4678
4816
|
import path2 from "path";
|
|
4679
4817
|
var defaultLogsDir = path2.join(process.cwd(), "logs");
|
|
4680
4818
|
var redisConnection;
|
|
@@ -4808,16 +4946,16 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4808
4946
|
global_queues.logs_cleaner,
|
|
4809
4947
|
async (job) => {
|
|
4810
4948
|
console.log(`[EXULU] recurring job ${job.id}.`);
|
|
4811
|
-
const folder =
|
|
4949
|
+
const folder = fs3.readdirSync(logsDir);
|
|
4812
4950
|
const files = folder.filter((file) => file.endsWith(".log"));
|
|
4813
4951
|
const now = /* @__PURE__ */ new Date();
|
|
4814
4952
|
const daysToKeep = job.data.ttld;
|
|
4815
4953
|
const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
|
|
4816
4954
|
files.forEach((file) => {
|
|
4817
4955
|
const filePath = path2.join(logsDir, file);
|
|
4818
|
-
const fileStats =
|
|
4956
|
+
const fileStats = fs3.statSync(filePath);
|
|
4819
4957
|
if (fileStats.mtime < dateToKeep) {
|
|
4820
|
-
|
|
4958
|
+
fs3.unlinkSync(filePath);
|
|
4821
4959
|
}
|
|
4822
4960
|
});
|
|
4823
4961
|
},
|
|
@@ -4837,7 +4975,7 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
4837
4975
|
|
|
4838
4976
|
// src/mcp/index.ts
|
|
4839
4977
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4840
|
-
import { randomUUID } from "crypto";
|
|
4978
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4841
4979
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4842
4980
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
4843
4981
|
import { z as z2 } from "zod";
|
|
@@ -4931,7 +5069,7 @@ ${code}`
|
|
|
4931
5069
|
transport = this.transports[sessionId];
|
|
4932
5070
|
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
4933
5071
|
transport = new StreamableHTTPServerTransport({
|
|
4934
|
-
sessionIdGenerator: () =>
|
|
5072
|
+
sessionIdGenerator: () => randomUUID2(),
|
|
4935
5073
|
onsessioninitialized: (sessionId2) => {
|
|
4936
5074
|
this.transports[sessionId2] = transport;
|
|
4937
5075
|
}
|
|
@@ -4999,6 +5137,7 @@ var defaultAgent = new ExuluAgent({
|
|
|
4999
5137
|
description: `Basic agent without any defined tools, that can support MCP's.`,
|
|
5000
5138
|
type: "agent",
|
|
5001
5139
|
capabilities: {
|
|
5140
|
+
text: true,
|
|
5002
5141
|
images: [],
|
|
5003
5142
|
files: [],
|
|
5004
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.
|
|
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",
|
package/types/models/agent.ts
CHANGED