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