@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 +3 -3
- package/dist/index.cjs +122 -6
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +122 -6
- 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.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
|
@@ -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
|
|
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 =
|
|
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 =
|
|
4998
|
+
const fileStats = fs3.statSync(filePath);
|
|
4884
4999
|
if (fileStats.mtime < dateToKeep) {
|
|
4885
|
-
|
|
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
|
|
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,
|
|
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
|
|
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 =
|
|
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 =
|
|
4956
|
+
const fileStats = fs3.statSync(filePath);
|
|
4842
4957
|
if (fileStats.mtime < dateToKeep) {
|
|
4843
|
-
|
|
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: () =>
|
|
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.
|
|
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