@exulu/backend 0.2.0 → 0.2.2
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/dist/index.cjs +175 -124
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +175 -124
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -58,25 +58,19 @@ var redisServer = {
|
|
|
58
58
|
// src/redis/client.ts
|
|
59
59
|
var client = {};
|
|
60
60
|
async function redisClient() {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
console.error(`[EXULU] no redis server configured.`);
|
|
64
|
-
return {
|
|
65
|
-
client: null
|
|
66
|
-
};
|
|
61
|
+
if (!redisServer.host || !redisServer.port) {
|
|
62
|
+
return { client: null };
|
|
67
63
|
}
|
|
68
64
|
if (!client["exulu"]) {
|
|
69
65
|
try {
|
|
70
66
|
const url = `redis://${redisServer.host}:${redisServer.port}`;
|
|
71
|
-
console.log(`[EXULU] connecting to redis.`);
|
|
72
67
|
client["exulu"] = (0, import_redis.createClient)({
|
|
73
|
-
// todo add password
|
|
74
68
|
url
|
|
75
69
|
});
|
|
76
70
|
await client["exulu"].connect();
|
|
77
71
|
} catch (error) {
|
|
78
|
-
console.error(`[EXULU] error connecting to redis
|
|
79
|
-
|
|
72
|
+
console.error(`[EXULU] error connecting to redis:`, error);
|
|
73
|
+
return { client: null };
|
|
80
74
|
}
|
|
81
75
|
}
|
|
82
76
|
return {
|
|
@@ -200,6 +194,10 @@ var usersSchema = {
|
|
|
200
194
|
name: "apikey",
|
|
201
195
|
type: "text"
|
|
202
196
|
},
|
|
197
|
+
{
|
|
198
|
+
name: "last_used",
|
|
199
|
+
type: "date"
|
|
200
|
+
},
|
|
203
201
|
{
|
|
204
202
|
name: "role",
|
|
205
203
|
type: "reference",
|
|
@@ -208,10 +206,6 @@ var usersSchema = {
|
|
|
208
206
|
field: "id",
|
|
209
207
|
onDelete: "CASCADE"
|
|
210
208
|
}
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
name: "last_used",
|
|
214
|
-
type: "date"
|
|
215
209
|
}
|
|
216
210
|
]
|
|
217
211
|
};
|
|
@@ -410,7 +404,61 @@ var sanitizeName = (name) => {
|
|
|
410
404
|
};
|
|
411
405
|
|
|
412
406
|
// src/postgres/init-db.ts
|
|
407
|
+
var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
|
|
408
|
+
|
|
409
|
+
// src/auth/generate-key.ts
|
|
413
410
|
var import_bcryptjs = __toESM(require("bcryptjs"), 1);
|
|
411
|
+
var SALT_ROUNDS = 12;
|
|
412
|
+
async function encryptApiKey(apikey) {
|
|
413
|
+
const hash = await import_bcryptjs.default.hash(apikey, SALT_ROUNDS);
|
|
414
|
+
return hash;
|
|
415
|
+
}
|
|
416
|
+
var generateApiKey = async (name, email) => {
|
|
417
|
+
const { db: db2 } = await postgresClient();
|
|
418
|
+
console.log("[EXULU] Inserting default user and admin role.");
|
|
419
|
+
const existingRole = await db2.from("roles").where({ name: "admin" }).first();
|
|
420
|
+
let roleId;
|
|
421
|
+
if (!existingRole) {
|
|
422
|
+
console.log("[EXULU] Creating default admin role.");
|
|
423
|
+
const role = await db2.from("roles").insert({
|
|
424
|
+
name: "admin",
|
|
425
|
+
is_admin: true,
|
|
426
|
+
agents: []
|
|
427
|
+
}).returning("id");
|
|
428
|
+
roleId = role[0].id;
|
|
429
|
+
} else {
|
|
430
|
+
roleId = existingRole.id;
|
|
431
|
+
}
|
|
432
|
+
const newKeyName = name;
|
|
433
|
+
const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
|
|
434
|
+
const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
|
|
435
|
+
const encryptedKey = await encryptApiKey(plainKey);
|
|
436
|
+
const existingApiUser = await db2.from("users").where({ email }).first();
|
|
437
|
+
if (!existingApiUser) {
|
|
438
|
+
console.log("[EXULU] Creating default api user.");
|
|
439
|
+
await db2.from("users").insert({
|
|
440
|
+
name,
|
|
441
|
+
email,
|
|
442
|
+
super_admin: true,
|
|
443
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
444
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
445
|
+
type: "api",
|
|
446
|
+
apikey: `${encryptedKey}${postFix}`,
|
|
447
|
+
// password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
|
|
448
|
+
role: roleId
|
|
449
|
+
});
|
|
450
|
+
console.log("[EXULU] Default api user created. Key: ", `${plainKey}${postFix}`);
|
|
451
|
+
} else {
|
|
452
|
+
console.log("[EXULU] API user with that name already exists.");
|
|
453
|
+
}
|
|
454
|
+
console.log("[EXULU] Key generated, copy and use the plain key from here, you will not be able to access it again.");
|
|
455
|
+
console.log("[EXULU] Key: ", `${plainKey}${postFix}`);
|
|
456
|
+
return {
|
|
457
|
+
key: `${plainKey}${postFix}`
|
|
458
|
+
};
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/postgres/init-db.ts
|
|
414
462
|
var up = async function(knex) {
|
|
415
463
|
if (!await knex.schema.hasTable("roles")) {
|
|
416
464
|
await knex.schema.createTable("roles", (table) => {
|
|
@@ -515,6 +563,7 @@ var up = async function(knex) {
|
|
|
515
563
|
table.timestamp("emailVerified", { useTz: true });
|
|
516
564
|
table.text("image");
|
|
517
565
|
for (const field of usersSchema.fields) {
|
|
566
|
+
console.log("[EXULU] field", field);
|
|
518
567
|
const { type, name, references, default: defaultValue } = field;
|
|
519
568
|
if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
|
|
520
569
|
continue;
|
|
@@ -558,9 +607,9 @@ var up = async function(knex) {
|
|
|
558
607
|
});
|
|
559
608
|
}
|
|
560
609
|
};
|
|
561
|
-
var
|
|
562
|
-
async function
|
|
563
|
-
const hash = await
|
|
610
|
+
var SALT_ROUNDS2 = 12;
|
|
611
|
+
async function encryptApiKey2(apikey) {
|
|
612
|
+
const hash = await import_bcryptjs2.default.hash(apikey, SALT_ROUNDS2);
|
|
564
613
|
return hash;
|
|
565
614
|
}
|
|
566
615
|
var execute = async () => {
|
|
@@ -584,7 +633,7 @@ var execute = async () => {
|
|
|
584
633
|
const newKeyName = "exulu_default_key";
|
|
585
634
|
const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
|
|
586
635
|
const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
|
|
587
|
-
const encryptedKey = await
|
|
636
|
+
const encryptedKey = await encryptApiKey2(plainKey);
|
|
588
637
|
const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
|
|
589
638
|
if (!existingUser) {
|
|
590
639
|
console.log("[EXULU] Creating default admin user.");
|
|
@@ -599,23 +648,9 @@ var execute = async () => {
|
|
|
599
648
|
role: roleId
|
|
600
649
|
});
|
|
601
650
|
}
|
|
602
|
-
const
|
|
603
|
-
if (!existingApiUser) {
|
|
604
|
-
console.log("[EXULU] Creating default api user.");
|
|
605
|
-
await db2.from("users").insert({
|
|
606
|
-
name: "exulu",
|
|
607
|
-
email: "admin@exulu.com",
|
|
608
|
-
super_admin: true,
|
|
609
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
610
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
611
|
-
type: "api",
|
|
612
|
-
apikey: `${encryptedKey}${postFix}`,
|
|
613
|
-
// password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
|
|
614
|
-
role: roleId
|
|
615
|
-
});
|
|
616
|
-
}
|
|
651
|
+
const { key } = await generateApiKey("exulu", "api@exulu.com");
|
|
617
652
|
console.log("[EXULU] Database initialized.");
|
|
618
|
-
console.log("[EXULU] Default api key: ", `${
|
|
653
|
+
console.log("[EXULU] Default api key: ", `${key}`);
|
|
619
654
|
return;
|
|
620
655
|
};
|
|
621
656
|
|
|
@@ -1436,30 +1471,38 @@ var import_express3 = require("express");
|
|
|
1436
1471
|
|
|
1437
1472
|
// src/registry/rate-limiter.ts
|
|
1438
1473
|
var rateLimiter = async (key, windowSeconds, limit, points) => {
|
|
1439
|
-
|
|
1440
|
-
|
|
1474
|
+
try {
|
|
1475
|
+
const { client: client2 } = await redisClient();
|
|
1476
|
+
if (!client2) {
|
|
1477
|
+
console.warn("[EXULU] Rate limiting disabled - Redis not available");
|
|
1478
|
+
return {
|
|
1479
|
+
status: true,
|
|
1480
|
+
retryAfter: null
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
const redisKey = `exulu/${key}`;
|
|
1484
|
+
const current = await client2.incrBy(redisKey, points);
|
|
1485
|
+
if (current === points) {
|
|
1486
|
+
await client2.expire(redisKey, windowSeconds);
|
|
1487
|
+
}
|
|
1488
|
+
if (current > limit) {
|
|
1489
|
+
const ttl = await client2.ttl(redisKey);
|
|
1490
|
+
return {
|
|
1491
|
+
status: false,
|
|
1492
|
+
retryAfter: ttl
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1441
1495
|
return {
|
|
1442
|
-
status:
|
|
1443
|
-
retryAfter:
|
|
1444
|
-
// 10 seconds
|
|
1496
|
+
status: true,
|
|
1497
|
+
retryAfter: null
|
|
1445
1498
|
};
|
|
1446
|
-
}
|
|
1447
|
-
|
|
1448
|
-
const current = await client2.incrBy(redisKey, points);
|
|
1449
|
-
if (current === points) {
|
|
1450
|
-
await client2.expire(redisKey, windowSeconds);
|
|
1451
|
-
}
|
|
1452
|
-
if (current > limit) {
|
|
1453
|
-
const ttl = await client2.ttl(redisKey);
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
console.error("[EXULU] Rate limiting error:", error);
|
|
1454
1501
|
return {
|
|
1455
|
-
status:
|
|
1456
|
-
retryAfter:
|
|
1502
|
+
status: true,
|
|
1503
|
+
retryAfter: null
|
|
1457
1504
|
};
|
|
1458
1505
|
}
|
|
1459
|
-
return {
|
|
1460
|
-
status: true,
|
|
1461
|
-
retryAfter: null
|
|
1462
|
-
};
|
|
1463
1506
|
};
|
|
1464
1507
|
|
|
1465
1508
|
// src/registry/route-validators/index.ts
|
|
@@ -1467,13 +1510,14 @@ var import_express = require("express");
|
|
|
1467
1510
|
var import_jwt = require("next-auth/jwt");
|
|
1468
1511
|
|
|
1469
1512
|
// src/auth/auth.ts
|
|
1470
|
-
var
|
|
1513
|
+
var import_bcryptjs3 = __toESM(require("bcryptjs"), 1);
|
|
1471
1514
|
var authentication = async ({
|
|
1472
1515
|
apikey,
|
|
1473
1516
|
authtoken,
|
|
1474
1517
|
internalkey,
|
|
1475
1518
|
db: db2
|
|
1476
1519
|
}) => {
|
|
1520
|
+
console.log("[EXULU] apikey", apikey);
|
|
1477
1521
|
if (internalkey) {
|
|
1478
1522
|
if (!process.env.INTERNAL_SECRET) {
|
|
1479
1523
|
return {
|
|
@@ -1541,31 +1585,38 @@ var authentication = async ({
|
|
|
1541
1585
|
code: 401
|
|
1542
1586
|
};
|
|
1543
1587
|
}
|
|
1544
|
-
const
|
|
1545
|
-
const
|
|
1546
|
-
const
|
|
1547
|
-
|
|
1588
|
+
const request_key_parts = apikey.split("/");
|
|
1589
|
+
const request_key_name = request_key_parts.pop();
|
|
1590
|
+
const request_key_last_slash_index = apikey.lastIndexOf("/");
|
|
1591
|
+
const request_key_compare_value = apikey.substring(0, request_key_last_slash_index);
|
|
1592
|
+
if (!request_key_name) {
|
|
1548
1593
|
return {
|
|
1549
1594
|
error: true,
|
|
1550
1595
|
message: "Provided api key does not include postfix with key name ({key}/{name}).",
|
|
1551
1596
|
code: 401
|
|
1552
1597
|
};
|
|
1553
1598
|
}
|
|
1554
|
-
if (!
|
|
1599
|
+
if (!request_key_compare_value) {
|
|
1555
1600
|
return {
|
|
1556
1601
|
error: true,
|
|
1557
1602
|
message: "Provided api key is not in the correct format.",
|
|
1558
1603
|
code: 401
|
|
1559
1604
|
};
|
|
1560
1605
|
}
|
|
1561
|
-
|
|
1606
|
+
console.log("[EXULU] users", users);
|
|
1607
|
+
console.log("[EXULU] request_key_name", request_key_name);
|
|
1608
|
+
console.log("[EXULU] request_key_compare_value", request_key_compare_value);
|
|
1609
|
+
const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(request_key_name));
|
|
1610
|
+
console.log("[EXULU] filtered", filtered);
|
|
1562
1611
|
for (const user of filtered) {
|
|
1563
|
-
const
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1612
|
+
const user_key_last_slash_index = user.apikey.lastIndexOf("/");
|
|
1613
|
+
const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
|
|
1614
|
+
console.log("[EXULU] user_key_compare_value", user_key_compare_value);
|
|
1615
|
+
const isMatch = await import_bcryptjs3.default.compare(request_key_compare_value, user_key_compare_value);
|
|
1616
|
+
console.log("[EXULU] isMatch", isMatch);
|
|
1566
1617
|
if (isMatch) {
|
|
1567
1618
|
await db2.from("users").where({ id: user.id }).update({
|
|
1568
|
-
|
|
1619
|
+
last_used: /* @__PURE__ */ new Date()
|
|
1569
1620
|
}).returning("id");
|
|
1570
1621
|
return {
|
|
1571
1622
|
error: false,
|
|
@@ -1574,6 +1625,12 @@ var authentication = async ({
|
|
|
1574
1625
|
};
|
|
1575
1626
|
}
|
|
1576
1627
|
}
|
|
1628
|
+
console.log("[EXULU] No matching api key found.");
|
|
1629
|
+
return {
|
|
1630
|
+
error: true,
|
|
1631
|
+
message: "No matching api key found.",
|
|
1632
|
+
code: 401
|
|
1633
|
+
};
|
|
1577
1634
|
}
|
|
1578
1635
|
return {
|
|
1579
1636
|
error: true,
|
|
@@ -2509,6 +2566,7 @@ var createUppyRoutes = async (app) => {
|
|
|
2509
2566
|
};
|
|
2510
2567
|
|
|
2511
2568
|
// src/registry/routes.ts
|
|
2569
|
+
var import_utils = require("@apollo/utils.keyvaluecache");
|
|
2512
2570
|
var Papa = require("papaparse");
|
|
2513
2571
|
var global_queues = {
|
|
2514
2572
|
logs_cleaner: "logs-cleaner"
|
|
@@ -2608,8 +2666,15 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2608
2666
|
console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
|
|
2609
2667
|
}
|
|
2610
2668
|
const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
|
|
2611
|
-
|
|
2669
|
+
console.log("[EXULU] graphql server");
|
|
2670
|
+
const server = new import_server3.ApolloServer({
|
|
2671
|
+
cache: new import_utils.InMemoryLRUCache(),
|
|
2672
|
+
schema,
|
|
2673
|
+
introspection: true
|
|
2674
|
+
});
|
|
2675
|
+
console.log("[EXULU] starting graphql server");
|
|
2612
2676
|
await server.start();
|
|
2677
|
+
console.log("[EXULU] graphql server started");
|
|
2613
2678
|
app.use(
|
|
2614
2679
|
"/graphql",
|
|
2615
2680
|
(0, import_cors.default)(),
|
|
@@ -2830,12 +2895,25 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2830
2895
|
});
|
|
2831
2896
|
app.post("/items/:context", async (req, res) => {
|
|
2832
2897
|
try {
|
|
2898
|
+
console.log("[EXULU] post items");
|
|
2833
2899
|
if (!req.params.context) {
|
|
2834
2900
|
res.status(400).json({
|
|
2835
2901
|
message: "Missing context in request."
|
|
2836
2902
|
});
|
|
2837
2903
|
return;
|
|
2838
2904
|
}
|
|
2905
|
+
if (!req.body) {
|
|
2906
|
+
res.status(400).json({
|
|
2907
|
+
message: "Missing body in request."
|
|
2908
|
+
});
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
if (!req.body.name) {
|
|
2912
|
+
res.status(400).json({
|
|
2913
|
+
message: "Missing in body of request."
|
|
2914
|
+
});
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2839
2917
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
2840
2918
|
if (!authenticationResult.user?.id) {
|
|
2841
2919
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
@@ -2942,6 +3020,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2942
3020
|
method: "DELETE",
|
|
2943
3021
|
note: `Delete specific embedding for a context.`
|
|
2944
3022
|
});
|
|
3023
|
+
console.log("[EXULU] delete embedding by id");
|
|
2945
3024
|
app.delete(`items/:context/:id`, async (req, res) => {
|
|
2946
3025
|
const id = req.params.id;
|
|
2947
3026
|
if (!req.params.context) {
|
|
@@ -2973,6 +3052,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2973
3052
|
message: "Embedding deleted."
|
|
2974
3053
|
});
|
|
2975
3054
|
});
|
|
3055
|
+
console.log("[EXULU] statistics timeseries");
|
|
2976
3056
|
app.post("/statistics/timeseries", async (req, res) => {
|
|
2977
3057
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
2978
3058
|
if (!authenticationResult.user?.id) {
|
|
@@ -3021,6 +3101,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3021
3101
|
}
|
|
3022
3102
|
});
|
|
3023
3103
|
});
|
|
3104
|
+
console.log("[EXULU] statistics totals");
|
|
3024
3105
|
app.post("/statistics/totals", async (req, res) => {
|
|
3025
3106
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3026
3107
|
if (!authenticationResult.user?.id) {
|
|
@@ -3049,6 +3130,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3049
3130
|
}
|
|
3050
3131
|
});
|
|
3051
3132
|
});
|
|
3133
|
+
console.log("[EXULU] contexts statistics");
|
|
3052
3134
|
app.get("/contexts/statistics", async (req, res) => {
|
|
3053
3135
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3054
3136
|
if (!authenticationResult.user?.id) {
|
|
@@ -3080,6 +3162,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3080
3162
|
}
|
|
3081
3163
|
});
|
|
3082
3164
|
});
|
|
3165
|
+
console.log("[EXULU] context by id");
|
|
3083
3166
|
app.get(`/contexts/:id`, async (req, res) => {
|
|
3084
3167
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3085
3168
|
if (!authenticationResult.user?.id) {
|
|
@@ -3125,6 +3208,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3125
3208
|
// todo
|
|
3126
3209
|
});
|
|
3127
3210
|
});
|
|
3211
|
+
console.log("[EXULU] items export by context");
|
|
3128
3212
|
app.get(`/items/export/:context`, async (req, res) => {
|
|
3129
3213
|
if (!req.params.context) {
|
|
3130
3214
|
res.status(400).json({
|
|
@@ -3153,6 +3237,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3153
3237
|
const ISOTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
3154
3238
|
res.status(200).attachment(`${context.name}-items-export-${ISOTime}.csv`).send(csv);
|
|
3155
3239
|
});
|
|
3240
|
+
console.log("[EXULU] contexts get list");
|
|
3156
3241
|
app.get(`/contexts`, async (req, res) => {
|
|
3157
3242
|
console.log("contexts!!");
|
|
3158
3243
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
@@ -3182,6 +3267,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3182
3267
|
}))
|
|
3183
3268
|
})));
|
|
3184
3269
|
});
|
|
3270
|
+
console.log("[EXULU] workflows get list");
|
|
3185
3271
|
app.get(`/workflows`, async (req, res) => {
|
|
3186
3272
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3187
3273
|
if (!authenticationResult.user?.id) {
|
|
@@ -3197,6 +3283,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3197
3283
|
inputSchema: workflow.inputSchema ? (0, import_zodex.zerialize)(workflow.inputSchema) : null
|
|
3198
3284
|
})));
|
|
3199
3285
|
});
|
|
3286
|
+
console.log("[EXULU] workflow by id");
|
|
3200
3287
|
app.get(`/workflows/:id`, async (req, res) => {
|
|
3201
3288
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3202
3289
|
if (!authenticationResult.user?.id) {
|
|
@@ -3224,6 +3311,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3224
3311
|
workflow: void 0
|
|
3225
3312
|
});
|
|
3226
3313
|
});
|
|
3314
|
+
console.log("[EXULU] contexts");
|
|
3227
3315
|
contexts.forEach((context) => {
|
|
3228
3316
|
const sources = context.sources.get();
|
|
3229
3317
|
if (!Array.isArray(sources)) {
|
|
@@ -3239,66 +3327,14 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3239
3327
|
note: `Webhook updater for ${context.name}`
|
|
3240
3328
|
});
|
|
3241
3329
|
app.post(`${updater.slug}/${updater.type}/:context`, async (req, res) => {
|
|
3242
|
-
|
|
3243
|
-
if (!id) {
|
|
3244
|
-
res.status(400).json({
|
|
3245
|
-
message: "Missing context id in request."
|
|
3246
|
-
});
|
|
3247
|
-
return;
|
|
3248
|
-
}
|
|
3249
|
-
const context2 = contexts.find((context3) => context3.id === id);
|
|
3250
|
-
if (!context2) {
|
|
3251
|
-
res.status(400).json({
|
|
3252
|
-
message: `Context for provided id: ${id} not found.`
|
|
3253
|
-
});
|
|
3254
|
-
return;
|
|
3255
|
-
}
|
|
3256
|
-
if (!context2.embedder.queue) {
|
|
3257
|
-
res.status(500).json({ detail: "No queue set for embedder." });
|
|
3258
|
-
return;
|
|
3259
|
-
}
|
|
3260
|
-
const authenticationResult = await requestValidators.authenticate(req);
|
|
3261
|
-
if (!authenticationResult.user?.id) {
|
|
3262
|
-
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
|
-
const requestValidationResult = requestValidators.embedders(req, updater.configuration);
|
|
3266
|
-
if (requestValidationResult.error) {
|
|
3267
|
-
res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
|
|
3268
|
-
return;
|
|
3269
|
-
}
|
|
3270
|
-
const documents = await updater.fn(req.body.configuration);
|
|
3271
|
-
const batches = [];
|
|
3272
|
-
for (let i = 0; i < documents.length; i += context2.embedder.batchSize) {
|
|
3273
|
-
batches.push(documents.slice(i, i + context2.embedder.batchSize));
|
|
3274
|
-
}
|
|
3275
|
-
let promises2 = [];
|
|
3276
|
-
if (batches.length > 0) {
|
|
3277
|
-
promises2 = batches.map((documents2) => {
|
|
3278
|
-
return bullmqDecorator({
|
|
3279
|
-
label: `Job running context '${context2.name}' with embedder '${context2.embedder.name}' for '${req.body.label}'`,
|
|
3280
|
-
type: "embedder",
|
|
3281
|
-
embedder: context2.embedder.id,
|
|
3282
|
-
updater: updater.id,
|
|
3283
|
-
context: context2.id,
|
|
3284
|
-
trigger: updater.type,
|
|
3285
|
-
source: source.id,
|
|
3286
|
-
inputs: req.body.inputs,
|
|
3287
|
-
...updater.configuration && { configuration: req.body.configuration },
|
|
3288
|
-
documents: documents2,
|
|
3289
|
-
queue: context2.embedder.queue,
|
|
3290
|
-
user: authenticationResult.user.id
|
|
3291
|
-
});
|
|
3292
|
-
});
|
|
3293
|
-
}
|
|
3294
|
-
const jobs = await Promise.all(promises2);
|
|
3295
|
-
res.status(200).json(jobs);
|
|
3330
|
+
res.status(200).json([]);
|
|
3296
3331
|
return;
|
|
3297
3332
|
});
|
|
3298
3333
|
}
|
|
3299
3334
|
});
|
|
3300
3335
|
});
|
|
3301
3336
|
});
|
|
3337
|
+
console.log("[EXULU] agents");
|
|
3302
3338
|
agents.forEach((agent) => {
|
|
3303
3339
|
const slug = agent.slug;
|
|
3304
3340
|
if (!slug) return;
|
|
@@ -3399,6 +3435,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3399
3435
|
}
|
|
3400
3436
|
});
|
|
3401
3437
|
});
|
|
3438
|
+
console.log("[EXULU] workflows");
|
|
3402
3439
|
workflows.forEach((workflow) => {
|
|
3403
3440
|
routeLogs.push({
|
|
3404
3441
|
route: workflow.slug,
|
|
@@ -3464,7 +3501,11 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3464
3501
|
return;
|
|
3465
3502
|
});
|
|
3466
3503
|
});
|
|
3467
|
-
|
|
3504
|
+
if (process.env.COMPANION_S3_REGION && process.env.COMPANION_S3_KEY && process.env.COMPANION_S3_SECRET) {
|
|
3505
|
+
await createUppyRoutes(app);
|
|
3506
|
+
} else {
|
|
3507
|
+
console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
|
|
3508
|
+
}
|
|
3468
3509
|
console.log("Routes:");
|
|
3469
3510
|
console.table(routeLogs);
|
|
3470
3511
|
};
|
|
@@ -3564,11 +3605,18 @@ var bullmq = {
|
|
|
3564
3605
|
var fs2 = __toESM(require("fs"), 1);
|
|
3565
3606
|
var import_path = __toESM(require("path"), 1);
|
|
3566
3607
|
var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
|
|
3567
|
-
var redisConnection
|
|
3568
|
-
...redisServer,
|
|
3569
|
-
maxRetriesPerRequest: null
|
|
3570
|
-
});
|
|
3608
|
+
var redisConnection;
|
|
3571
3609
|
var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) => {
|
|
3610
|
+
if (!redisServer.host || !redisServer.port) {
|
|
3611
|
+
console.error("[EXULU] you are trying to start workers, but no redis server is configured in the environment.");
|
|
3612
|
+
throw new Error("No redis server configured in the environment, so cannot start workers.");
|
|
3613
|
+
}
|
|
3614
|
+
if (!redisConnection) {
|
|
3615
|
+
redisConnection = new import_ioredis.default({
|
|
3616
|
+
...redisServer,
|
|
3617
|
+
maxRetriesPerRequest: null
|
|
3618
|
+
});
|
|
3619
|
+
}
|
|
3572
3620
|
const logsDir = _logsDir || defaultLogsDir;
|
|
3573
3621
|
const workers = queues2.map((queue) => {
|
|
3574
3622
|
console.log(`[EXULU] creating worker for queue ${queue}.`);
|
|
@@ -3803,6 +3851,9 @@ var ExuluJobs = {
|
|
|
3803
3851
|
var ExuluDatabase = {
|
|
3804
3852
|
init: async () => {
|
|
3805
3853
|
await execute();
|
|
3854
|
+
},
|
|
3855
|
+
generateApiKey: async (name, email) => {
|
|
3856
|
+
return await generateApiKey(name, email);
|
|
3806
3857
|
}
|
|
3807
3858
|
};
|
|
3808
3859
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.d.cts
CHANGED
|
@@ -449,6 +449,9 @@ declare const ExuluJobs: {
|
|
|
449
449
|
|
|
450
450
|
declare const ExuluDatabase: {
|
|
451
451
|
init: () => Promise<void>;
|
|
452
|
+
generateApiKey: (name: string, email: string) => Promise<{
|
|
453
|
+
key: string;
|
|
454
|
+
}>;
|
|
452
455
|
};
|
|
453
456
|
|
|
454
457
|
export { type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, ExuluAgent, ExuluApp, authentication as ExuluAuthentication, ExuluContext, ExuluDatabase, ExuluEmbedder, ExuluJobs, queues as ExuluQueues, ExuluSource, ExuluTool, ExuluWorkflow, ExuluZodFileType };
|
package/dist/index.d.ts
CHANGED
|
@@ -449,6 +449,9 @@ declare const ExuluJobs: {
|
|
|
449
449
|
|
|
450
450
|
declare const ExuluDatabase: {
|
|
451
451
|
init: () => Promise<void>;
|
|
452
|
+
generateApiKey: (name: string, email: string) => Promise<{
|
|
453
|
+
key: string;
|
|
454
|
+
}>;
|
|
452
455
|
};
|
|
453
456
|
|
|
454
457
|
export { type STATISTICS_TYPE as EXULU_STATISTICS_TYPE, STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM, ExuluAgent, ExuluApp, authentication as ExuluAuthentication, ExuluContext, ExuluDatabase, ExuluEmbedder, ExuluJobs, queues as ExuluQueues, ExuluSource, ExuluTool, ExuluWorkflow, ExuluZodFileType };
|
package/dist/index.js
CHANGED
|
@@ -17,25 +17,19 @@ var redisServer = {
|
|
|
17
17
|
// src/redis/client.ts
|
|
18
18
|
var client = {};
|
|
19
19
|
async function redisClient() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
console.error(`[EXULU] no redis server configured.`);
|
|
23
|
-
return {
|
|
24
|
-
client: null
|
|
25
|
-
};
|
|
20
|
+
if (!redisServer.host || !redisServer.port) {
|
|
21
|
+
return { client: null };
|
|
26
22
|
}
|
|
27
23
|
if (!client["exulu"]) {
|
|
28
24
|
try {
|
|
29
25
|
const url = `redis://${redisServer.host}:${redisServer.port}`;
|
|
30
|
-
console.log(`[EXULU] connecting to redis.`);
|
|
31
26
|
client["exulu"] = createClient({
|
|
32
|
-
// todo add password
|
|
33
27
|
url
|
|
34
28
|
});
|
|
35
29
|
await client["exulu"].connect();
|
|
36
30
|
} catch (error) {
|
|
37
|
-
console.error(`[EXULU] error connecting to redis
|
|
38
|
-
|
|
31
|
+
console.error(`[EXULU] error connecting to redis:`, error);
|
|
32
|
+
return { client: null };
|
|
39
33
|
}
|
|
40
34
|
}
|
|
41
35
|
return {
|
|
@@ -159,6 +153,10 @@ var usersSchema = {
|
|
|
159
153
|
name: "apikey",
|
|
160
154
|
type: "text"
|
|
161
155
|
},
|
|
156
|
+
{
|
|
157
|
+
name: "last_used",
|
|
158
|
+
type: "date"
|
|
159
|
+
},
|
|
162
160
|
{
|
|
163
161
|
name: "role",
|
|
164
162
|
type: "reference",
|
|
@@ -167,10 +165,6 @@ var usersSchema = {
|
|
|
167
165
|
field: "id",
|
|
168
166
|
onDelete: "CASCADE"
|
|
169
167
|
}
|
|
170
|
-
},
|
|
171
|
-
{
|
|
172
|
-
name: "last_used",
|
|
173
|
-
type: "date"
|
|
174
168
|
}
|
|
175
169
|
]
|
|
176
170
|
};
|
|
@@ -369,7 +363,61 @@ var sanitizeName = (name) => {
|
|
|
369
363
|
};
|
|
370
364
|
|
|
371
365
|
// src/postgres/init-db.ts
|
|
366
|
+
import bcrypt2 from "bcryptjs";
|
|
367
|
+
|
|
368
|
+
// src/auth/generate-key.ts
|
|
372
369
|
import bcrypt from "bcryptjs";
|
|
370
|
+
var SALT_ROUNDS = 12;
|
|
371
|
+
async function encryptApiKey(apikey) {
|
|
372
|
+
const hash = await bcrypt.hash(apikey, SALT_ROUNDS);
|
|
373
|
+
return hash;
|
|
374
|
+
}
|
|
375
|
+
var generateApiKey = async (name, email) => {
|
|
376
|
+
const { db: db2 } = await postgresClient();
|
|
377
|
+
console.log("[EXULU] Inserting default user and admin role.");
|
|
378
|
+
const existingRole = await db2.from("roles").where({ name: "admin" }).first();
|
|
379
|
+
let roleId;
|
|
380
|
+
if (!existingRole) {
|
|
381
|
+
console.log("[EXULU] Creating default admin role.");
|
|
382
|
+
const role = await db2.from("roles").insert({
|
|
383
|
+
name: "admin",
|
|
384
|
+
is_admin: true,
|
|
385
|
+
agents: []
|
|
386
|
+
}).returning("id");
|
|
387
|
+
roleId = role[0].id;
|
|
388
|
+
} else {
|
|
389
|
+
roleId = existingRole.id;
|
|
390
|
+
}
|
|
391
|
+
const newKeyName = name;
|
|
392
|
+
const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
|
|
393
|
+
const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
|
|
394
|
+
const encryptedKey = await encryptApiKey(plainKey);
|
|
395
|
+
const existingApiUser = await db2.from("users").where({ email }).first();
|
|
396
|
+
if (!existingApiUser) {
|
|
397
|
+
console.log("[EXULU] Creating default api user.");
|
|
398
|
+
await db2.from("users").insert({
|
|
399
|
+
name,
|
|
400
|
+
email,
|
|
401
|
+
super_admin: true,
|
|
402
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
403
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
404
|
+
type: "api",
|
|
405
|
+
apikey: `${encryptedKey}${postFix}`,
|
|
406
|
+
// password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
|
|
407
|
+
role: roleId
|
|
408
|
+
});
|
|
409
|
+
console.log("[EXULU] Default api user created. Key: ", `${plainKey}${postFix}`);
|
|
410
|
+
} else {
|
|
411
|
+
console.log("[EXULU] API user with that name already exists.");
|
|
412
|
+
}
|
|
413
|
+
console.log("[EXULU] Key generated, copy and use the plain key from here, you will not be able to access it again.");
|
|
414
|
+
console.log("[EXULU] Key: ", `${plainKey}${postFix}`);
|
|
415
|
+
return {
|
|
416
|
+
key: `${plainKey}${postFix}`
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// src/postgres/init-db.ts
|
|
373
421
|
var up = async function(knex) {
|
|
374
422
|
if (!await knex.schema.hasTable("roles")) {
|
|
375
423
|
await knex.schema.createTable("roles", (table) => {
|
|
@@ -474,6 +522,7 @@ var up = async function(knex) {
|
|
|
474
522
|
table.timestamp("emailVerified", { useTz: true });
|
|
475
523
|
table.text("image");
|
|
476
524
|
for (const field of usersSchema.fields) {
|
|
525
|
+
console.log("[EXULU] field", field);
|
|
477
526
|
const { type, name, references, default: defaultValue } = field;
|
|
478
527
|
if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
|
|
479
528
|
continue;
|
|
@@ -517,9 +566,9 @@ var up = async function(knex) {
|
|
|
517
566
|
});
|
|
518
567
|
}
|
|
519
568
|
};
|
|
520
|
-
var
|
|
521
|
-
async function
|
|
522
|
-
const hash = await
|
|
569
|
+
var SALT_ROUNDS2 = 12;
|
|
570
|
+
async function encryptApiKey2(apikey) {
|
|
571
|
+
const hash = await bcrypt2.hash(apikey, SALT_ROUNDS2);
|
|
523
572
|
return hash;
|
|
524
573
|
}
|
|
525
574
|
var execute = async () => {
|
|
@@ -543,7 +592,7 @@ var execute = async () => {
|
|
|
543
592
|
const newKeyName = "exulu_default_key";
|
|
544
593
|
const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
|
|
545
594
|
const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
|
|
546
|
-
const encryptedKey = await
|
|
595
|
+
const encryptedKey = await encryptApiKey2(plainKey);
|
|
547
596
|
const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
|
|
548
597
|
if (!existingUser) {
|
|
549
598
|
console.log("[EXULU] Creating default admin user.");
|
|
@@ -558,23 +607,9 @@ var execute = async () => {
|
|
|
558
607
|
role: roleId
|
|
559
608
|
});
|
|
560
609
|
}
|
|
561
|
-
const
|
|
562
|
-
if (!existingApiUser) {
|
|
563
|
-
console.log("[EXULU] Creating default api user.");
|
|
564
|
-
await db2.from("users").insert({
|
|
565
|
-
name: "exulu",
|
|
566
|
-
email: "admin@exulu.com",
|
|
567
|
-
super_admin: true,
|
|
568
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
569
|
-
updatedAt: /* @__PURE__ */ new Date(),
|
|
570
|
-
type: "api",
|
|
571
|
-
apikey: `${encryptedKey}${postFix}`,
|
|
572
|
-
// password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
|
|
573
|
-
role: roleId
|
|
574
|
-
});
|
|
575
|
-
}
|
|
610
|
+
const { key } = await generateApiKey("exulu", "api@exulu.com");
|
|
576
611
|
console.log("[EXULU] Database initialized.");
|
|
577
|
-
console.log("[EXULU] Default api key: ", `${
|
|
612
|
+
console.log("[EXULU] Default api key: ", `${key}`);
|
|
578
613
|
return;
|
|
579
614
|
};
|
|
580
615
|
|
|
@@ -1395,30 +1430,38 @@ import "express";
|
|
|
1395
1430
|
|
|
1396
1431
|
// src/registry/rate-limiter.ts
|
|
1397
1432
|
var rateLimiter = async (key, windowSeconds, limit, points) => {
|
|
1398
|
-
|
|
1399
|
-
|
|
1433
|
+
try {
|
|
1434
|
+
const { client: client2 } = await redisClient();
|
|
1435
|
+
if (!client2) {
|
|
1436
|
+
console.warn("[EXULU] Rate limiting disabled - Redis not available");
|
|
1437
|
+
return {
|
|
1438
|
+
status: true,
|
|
1439
|
+
retryAfter: null
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1442
|
+
const redisKey = `exulu/${key}`;
|
|
1443
|
+
const current = await client2.incrBy(redisKey, points);
|
|
1444
|
+
if (current === points) {
|
|
1445
|
+
await client2.expire(redisKey, windowSeconds);
|
|
1446
|
+
}
|
|
1447
|
+
if (current > limit) {
|
|
1448
|
+
const ttl = await client2.ttl(redisKey);
|
|
1449
|
+
return {
|
|
1450
|
+
status: false,
|
|
1451
|
+
retryAfter: ttl
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1400
1454
|
return {
|
|
1401
|
-
status:
|
|
1402
|
-
retryAfter:
|
|
1403
|
-
// 10 seconds
|
|
1455
|
+
status: true,
|
|
1456
|
+
retryAfter: null
|
|
1404
1457
|
};
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
const current = await client2.incrBy(redisKey, points);
|
|
1408
|
-
if (current === points) {
|
|
1409
|
-
await client2.expire(redisKey, windowSeconds);
|
|
1410
|
-
}
|
|
1411
|
-
if (current > limit) {
|
|
1412
|
-
const ttl = await client2.ttl(redisKey);
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
console.error("[EXULU] Rate limiting error:", error);
|
|
1413
1460
|
return {
|
|
1414
|
-
status:
|
|
1415
|
-
retryAfter:
|
|
1461
|
+
status: true,
|
|
1462
|
+
retryAfter: null
|
|
1416
1463
|
};
|
|
1417
1464
|
}
|
|
1418
|
-
return {
|
|
1419
|
-
status: true,
|
|
1420
|
-
retryAfter: null
|
|
1421
|
-
};
|
|
1422
1465
|
};
|
|
1423
1466
|
|
|
1424
1467
|
// src/registry/route-validators/index.ts
|
|
@@ -1426,13 +1469,14 @@ import "express";
|
|
|
1426
1469
|
import { getToken } from "next-auth/jwt";
|
|
1427
1470
|
|
|
1428
1471
|
// src/auth/auth.ts
|
|
1429
|
-
import
|
|
1472
|
+
import bcrypt3 from "bcryptjs";
|
|
1430
1473
|
var authentication = async ({
|
|
1431
1474
|
apikey,
|
|
1432
1475
|
authtoken,
|
|
1433
1476
|
internalkey,
|
|
1434
1477
|
db: db2
|
|
1435
1478
|
}) => {
|
|
1479
|
+
console.log("[EXULU] apikey", apikey);
|
|
1436
1480
|
if (internalkey) {
|
|
1437
1481
|
if (!process.env.INTERNAL_SECRET) {
|
|
1438
1482
|
return {
|
|
@@ -1500,31 +1544,38 @@ var authentication = async ({
|
|
|
1500
1544
|
code: 401
|
|
1501
1545
|
};
|
|
1502
1546
|
}
|
|
1503
|
-
const
|
|
1504
|
-
const
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1547
|
+
const request_key_parts = apikey.split("/");
|
|
1548
|
+
const request_key_name = request_key_parts.pop();
|
|
1549
|
+
const request_key_last_slash_index = apikey.lastIndexOf("/");
|
|
1550
|
+
const request_key_compare_value = apikey.substring(0, request_key_last_slash_index);
|
|
1551
|
+
if (!request_key_name) {
|
|
1507
1552
|
return {
|
|
1508
1553
|
error: true,
|
|
1509
1554
|
message: "Provided api key does not include postfix with key name ({key}/{name}).",
|
|
1510
1555
|
code: 401
|
|
1511
1556
|
};
|
|
1512
1557
|
}
|
|
1513
|
-
if (!
|
|
1558
|
+
if (!request_key_compare_value) {
|
|
1514
1559
|
return {
|
|
1515
1560
|
error: true,
|
|
1516
1561
|
message: "Provided api key is not in the correct format.",
|
|
1517
1562
|
code: 401
|
|
1518
1563
|
};
|
|
1519
1564
|
}
|
|
1520
|
-
|
|
1565
|
+
console.log("[EXULU] users", users);
|
|
1566
|
+
console.log("[EXULU] request_key_name", request_key_name);
|
|
1567
|
+
console.log("[EXULU] request_key_compare_value", request_key_compare_value);
|
|
1568
|
+
const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(request_key_name));
|
|
1569
|
+
console.log("[EXULU] filtered", filtered);
|
|
1521
1570
|
for (const user of filtered) {
|
|
1522
|
-
const
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1571
|
+
const user_key_last_slash_index = user.apikey.lastIndexOf("/");
|
|
1572
|
+
const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
|
|
1573
|
+
console.log("[EXULU] user_key_compare_value", user_key_compare_value);
|
|
1574
|
+
const isMatch = await bcrypt3.compare(request_key_compare_value, user_key_compare_value);
|
|
1575
|
+
console.log("[EXULU] isMatch", isMatch);
|
|
1525
1576
|
if (isMatch) {
|
|
1526
1577
|
await db2.from("users").where({ id: user.id }).update({
|
|
1527
|
-
|
|
1578
|
+
last_used: /* @__PURE__ */ new Date()
|
|
1528
1579
|
}).returning("id");
|
|
1529
1580
|
return {
|
|
1530
1581
|
error: false,
|
|
@@ -1533,6 +1584,12 @@ var authentication = async ({
|
|
|
1533
1584
|
};
|
|
1534
1585
|
}
|
|
1535
1586
|
}
|
|
1587
|
+
console.log("[EXULU] No matching api key found.");
|
|
1588
|
+
return {
|
|
1589
|
+
error: true,
|
|
1590
|
+
message: "No matching api key found.",
|
|
1591
|
+
code: 401
|
|
1592
|
+
};
|
|
1536
1593
|
}
|
|
1537
1594
|
return {
|
|
1538
1595
|
error: true,
|
|
@@ -2468,6 +2525,7 @@ var createUppyRoutes = async (app) => {
|
|
|
2468
2525
|
};
|
|
2469
2526
|
|
|
2470
2527
|
// src/registry/routes.ts
|
|
2528
|
+
import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
|
|
2471
2529
|
var Papa = __require("papaparse");
|
|
2472
2530
|
var global_queues = {
|
|
2473
2531
|
logs_cleaner: "logs-cleaner"
|
|
@@ -2567,8 +2625,15 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2567
2625
|
console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
|
|
2568
2626
|
}
|
|
2569
2627
|
const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
|
|
2570
|
-
|
|
2628
|
+
console.log("[EXULU] graphql server");
|
|
2629
|
+
const server = new ApolloServer({
|
|
2630
|
+
cache: new InMemoryLRUCache(),
|
|
2631
|
+
schema,
|
|
2632
|
+
introspection: true
|
|
2633
|
+
});
|
|
2634
|
+
console.log("[EXULU] starting graphql server");
|
|
2571
2635
|
await server.start();
|
|
2636
|
+
console.log("[EXULU] graphql server started");
|
|
2572
2637
|
app.use(
|
|
2573
2638
|
"/graphql",
|
|
2574
2639
|
cors(),
|
|
@@ -2789,12 +2854,25 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2789
2854
|
});
|
|
2790
2855
|
app.post("/items/:context", async (req, res) => {
|
|
2791
2856
|
try {
|
|
2857
|
+
console.log("[EXULU] post items");
|
|
2792
2858
|
if (!req.params.context) {
|
|
2793
2859
|
res.status(400).json({
|
|
2794
2860
|
message: "Missing context in request."
|
|
2795
2861
|
});
|
|
2796
2862
|
return;
|
|
2797
2863
|
}
|
|
2864
|
+
if (!req.body) {
|
|
2865
|
+
res.status(400).json({
|
|
2866
|
+
message: "Missing body in request."
|
|
2867
|
+
});
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
if (!req.body.name) {
|
|
2871
|
+
res.status(400).json({
|
|
2872
|
+
message: "Missing in body of request."
|
|
2873
|
+
});
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2798
2876
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
2799
2877
|
if (!authenticationResult.user?.id) {
|
|
2800
2878
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
@@ -2901,6 +2979,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2901
2979
|
method: "DELETE",
|
|
2902
2980
|
note: `Delete specific embedding for a context.`
|
|
2903
2981
|
});
|
|
2982
|
+
console.log("[EXULU] delete embedding by id");
|
|
2904
2983
|
app.delete(`items/:context/:id`, async (req, res) => {
|
|
2905
2984
|
const id = req.params.id;
|
|
2906
2985
|
if (!req.params.context) {
|
|
@@ -2932,6 +3011,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2932
3011
|
message: "Embedding deleted."
|
|
2933
3012
|
});
|
|
2934
3013
|
});
|
|
3014
|
+
console.log("[EXULU] statistics timeseries");
|
|
2935
3015
|
app.post("/statistics/timeseries", async (req, res) => {
|
|
2936
3016
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
2937
3017
|
if (!authenticationResult.user?.id) {
|
|
@@ -2980,6 +3060,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
2980
3060
|
}
|
|
2981
3061
|
});
|
|
2982
3062
|
});
|
|
3063
|
+
console.log("[EXULU] statistics totals");
|
|
2983
3064
|
app.post("/statistics/totals", async (req, res) => {
|
|
2984
3065
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
2985
3066
|
if (!authenticationResult.user?.id) {
|
|
@@ -3008,6 +3089,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3008
3089
|
}
|
|
3009
3090
|
});
|
|
3010
3091
|
});
|
|
3092
|
+
console.log("[EXULU] contexts statistics");
|
|
3011
3093
|
app.get("/contexts/statistics", async (req, res) => {
|
|
3012
3094
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3013
3095
|
if (!authenticationResult.user?.id) {
|
|
@@ -3039,6 +3121,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3039
3121
|
}
|
|
3040
3122
|
});
|
|
3041
3123
|
});
|
|
3124
|
+
console.log("[EXULU] context by id");
|
|
3042
3125
|
app.get(`/contexts/:id`, async (req, res) => {
|
|
3043
3126
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3044
3127
|
if (!authenticationResult.user?.id) {
|
|
@@ -3084,6 +3167,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3084
3167
|
// todo
|
|
3085
3168
|
});
|
|
3086
3169
|
});
|
|
3170
|
+
console.log("[EXULU] items export by context");
|
|
3087
3171
|
app.get(`/items/export/:context`, async (req, res) => {
|
|
3088
3172
|
if (!req.params.context) {
|
|
3089
3173
|
res.status(400).json({
|
|
@@ -3112,6 +3196,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3112
3196
|
const ISOTime = (/* @__PURE__ */ new Date()).toISOString();
|
|
3113
3197
|
res.status(200).attachment(`${context.name}-items-export-${ISOTime}.csv`).send(csv);
|
|
3114
3198
|
});
|
|
3199
|
+
console.log("[EXULU] contexts get list");
|
|
3115
3200
|
app.get(`/contexts`, async (req, res) => {
|
|
3116
3201
|
console.log("contexts!!");
|
|
3117
3202
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
@@ -3141,6 +3226,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3141
3226
|
}))
|
|
3142
3227
|
})));
|
|
3143
3228
|
});
|
|
3229
|
+
console.log("[EXULU] workflows get list");
|
|
3144
3230
|
app.get(`/workflows`, async (req, res) => {
|
|
3145
3231
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3146
3232
|
if (!authenticationResult.user?.id) {
|
|
@@ -3156,6 +3242,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3156
3242
|
inputSchema: workflow.inputSchema ? zerialize(workflow.inputSchema) : null
|
|
3157
3243
|
})));
|
|
3158
3244
|
});
|
|
3245
|
+
console.log("[EXULU] workflow by id");
|
|
3159
3246
|
app.get(`/workflows/:id`, async (req, res) => {
|
|
3160
3247
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3161
3248
|
if (!authenticationResult.user?.id) {
|
|
@@ -3183,6 +3270,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3183
3270
|
workflow: void 0
|
|
3184
3271
|
});
|
|
3185
3272
|
});
|
|
3273
|
+
console.log("[EXULU] contexts");
|
|
3186
3274
|
contexts.forEach((context) => {
|
|
3187
3275
|
const sources = context.sources.get();
|
|
3188
3276
|
if (!Array.isArray(sources)) {
|
|
@@ -3198,66 +3286,14 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3198
3286
|
note: `Webhook updater for ${context.name}`
|
|
3199
3287
|
});
|
|
3200
3288
|
app.post(`${updater.slug}/${updater.type}/:context`, async (req, res) => {
|
|
3201
|
-
|
|
3202
|
-
if (!id) {
|
|
3203
|
-
res.status(400).json({
|
|
3204
|
-
message: "Missing context id in request."
|
|
3205
|
-
});
|
|
3206
|
-
return;
|
|
3207
|
-
}
|
|
3208
|
-
const context2 = contexts.find((context3) => context3.id === id);
|
|
3209
|
-
if (!context2) {
|
|
3210
|
-
res.status(400).json({
|
|
3211
|
-
message: `Context for provided id: ${id} not found.`
|
|
3212
|
-
});
|
|
3213
|
-
return;
|
|
3214
|
-
}
|
|
3215
|
-
if (!context2.embedder.queue) {
|
|
3216
|
-
res.status(500).json({ detail: "No queue set for embedder." });
|
|
3217
|
-
return;
|
|
3218
|
-
}
|
|
3219
|
-
const authenticationResult = await requestValidators.authenticate(req);
|
|
3220
|
-
if (!authenticationResult.user?.id) {
|
|
3221
|
-
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
3222
|
-
return;
|
|
3223
|
-
}
|
|
3224
|
-
const requestValidationResult = requestValidators.embedders(req, updater.configuration);
|
|
3225
|
-
if (requestValidationResult.error) {
|
|
3226
|
-
res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
|
|
3227
|
-
return;
|
|
3228
|
-
}
|
|
3229
|
-
const documents = await updater.fn(req.body.configuration);
|
|
3230
|
-
const batches = [];
|
|
3231
|
-
for (let i = 0; i < documents.length; i += context2.embedder.batchSize) {
|
|
3232
|
-
batches.push(documents.slice(i, i + context2.embedder.batchSize));
|
|
3233
|
-
}
|
|
3234
|
-
let promises2 = [];
|
|
3235
|
-
if (batches.length > 0) {
|
|
3236
|
-
promises2 = batches.map((documents2) => {
|
|
3237
|
-
return bullmqDecorator({
|
|
3238
|
-
label: `Job running context '${context2.name}' with embedder '${context2.embedder.name}' for '${req.body.label}'`,
|
|
3239
|
-
type: "embedder",
|
|
3240
|
-
embedder: context2.embedder.id,
|
|
3241
|
-
updater: updater.id,
|
|
3242
|
-
context: context2.id,
|
|
3243
|
-
trigger: updater.type,
|
|
3244
|
-
source: source.id,
|
|
3245
|
-
inputs: req.body.inputs,
|
|
3246
|
-
...updater.configuration && { configuration: req.body.configuration },
|
|
3247
|
-
documents: documents2,
|
|
3248
|
-
queue: context2.embedder.queue,
|
|
3249
|
-
user: authenticationResult.user.id
|
|
3250
|
-
});
|
|
3251
|
-
});
|
|
3252
|
-
}
|
|
3253
|
-
const jobs = await Promise.all(promises2);
|
|
3254
|
-
res.status(200).json(jobs);
|
|
3289
|
+
res.status(200).json([]);
|
|
3255
3290
|
return;
|
|
3256
3291
|
});
|
|
3257
3292
|
}
|
|
3258
3293
|
});
|
|
3259
3294
|
});
|
|
3260
3295
|
});
|
|
3296
|
+
console.log("[EXULU] agents");
|
|
3261
3297
|
agents.forEach((agent) => {
|
|
3262
3298
|
const slug = agent.slug;
|
|
3263
3299
|
if (!slug) return;
|
|
@@ -3358,6 +3394,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3358
3394
|
}
|
|
3359
3395
|
});
|
|
3360
3396
|
});
|
|
3397
|
+
console.log("[EXULU] workflows");
|
|
3361
3398
|
workflows.forEach((workflow) => {
|
|
3362
3399
|
routeLogs.push({
|
|
3363
3400
|
route: workflow.slug,
|
|
@@ -3423,7 +3460,11 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3423
3460
|
return;
|
|
3424
3461
|
});
|
|
3425
3462
|
});
|
|
3426
|
-
|
|
3463
|
+
if (process.env.COMPANION_S3_REGION && process.env.COMPANION_S3_KEY && process.env.COMPANION_S3_SECRET) {
|
|
3464
|
+
await createUppyRoutes(app);
|
|
3465
|
+
} else {
|
|
3466
|
+
console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
|
|
3467
|
+
}
|
|
3427
3468
|
console.log("Routes:");
|
|
3428
3469
|
console.table(routeLogs);
|
|
3429
3470
|
};
|
|
@@ -3523,11 +3564,18 @@ var bullmq = {
|
|
|
3523
3564
|
import * as fs2 from "fs";
|
|
3524
3565
|
import path2 from "path";
|
|
3525
3566
|
var defaultLogsDir = path2.join(process.cwd(), "logs");
|
|
3526
|
-
var redisConnection
|
|
3527
|
-
...redisServer,
|
|
3528
|
-
maxRetriesPerRequest: null
|
|
3529
|
-
});
|
|
3567
|
+
var redisConnection;
|
|
3530
3568
|
var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) => {
|
|
3569
|
+
if (!redisServer.host || !redisServer.port) {
|
|
3570
|
+
console.error("[EXULU] you are trying to start workers, but no redis server is configured in the environment.");
|
|
3571
|
+
throw new Error("No redis server configured in the environment, so cannot start workers.");
|
|
3572
|
+
}
|
|
3573
|
+
if (!redisConnection) {
|
|
3574
|
+
redisConnection = new IORedis({
|
|
3575
|
+
...redisServer,
|
|
3576
|
+
maxRetriesPerRequest: null
|
|
3577
|
+
});
|
|
3578
|
+
}
|
|
3531
3579
|
const logsDir = _logsDir || defaultLogsDir;
|
|
3532
3580
|
const workers = queues2.map((queue) => {
|
|
3533
3581
|
console.log(`[EXULU] creating worker for queue ${queue}.`);
|
|
@@ -3762,6 +3810,9 @@ var ExuluJobs = {
|
|
|
3762
3810
|
var ExuluDatabase = {
|
|
3763
3811
|
init: async () => {
|
|
3764
3812
|
await execute();
|
|
3813
|
+
},
|
|
3814
|
+
generateApiKey: async (name, email) => {
|
|
3815
|
+
return await generateApiKey(name, email);
|
|
3765
3816
|
}
|
|
3766
3817
|
};
|
|
3767
3818
|
export {
|