@exulu/backend 1.23.2 → 1.24.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 +4 -3
- package/dist/index.cjs +1420 -842
- package/dist/index.d.cts +191 -176
- package/dist/index.d.ts +191 -176
- package/dist/index.js +1405 -827
- package/package.json +6 -6
- package/types/enums/field-types.ts +1 -1
- package/types/models/agent.ts +18 -9
- package/types/models/project.ts +1 -0
- package/types/models/user.ts +1 -1
package/dist/index.cjs
CHANGED
|
@@ -37,12 +37,16 @@ __export(index_exports, {
|
|
|
37
37
|
ExuluAuthentication: () => authentication,
|
|
38
38
|
ExuluChunkers: () => ExuluChunkers,
|
|
39
39
|
ExuluContext: () => ExuluContext,
|
|
40
|
+
ExuluDefaultAgents: () => ExuluDefaultAgents,
|
|
41
|
+
ExuluDefaultContexts: () => ExuluDefaultContexts,
|
|
40
42
|
ExuluEmbedder: () => ExuluEmbedder,
|
|
41
43
|
ExuluEval: () => ExuluEval,
|
|
42
44
|
ExuluJobs: () => ExuluJobs,
|
|
43
45
|
ExuluOtel: () => ExuluOtel,
|
|
44
46
|
ExuluQueues: () => queues,
|
|
45
47
|
ExuluTool: () => ExuluTool2,
|
|
48
|
+
ExuluUtils: () => ExuluUtils,
|
|
49
|
+
ExuluVariables: () => ExuluVariables,
|
|
46
50
|
db: () => db2
|
|
47
51
|
});
|
|
48
52
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -110,11 +114,8 @@ var validateJob = (job) => {
|
|
|
110
114
|
};
|
|
111
115
|
|
|
112
116
|
// src/registry/classes.ts
|
|
113
|
-
var import_zod = require("zod");
|
|
114
117
|
var import_bullmq2 = require("bullmq");
|
|
115
|
-
var
|
|
116
|
-
var fs = require("fs");
|
|
117
|
-
var path = require("path");
|
|
118
|
+
var import_zod = require("zod");
|
|
118
119
|
var import_ai = require("ai");
|
|
119
120
|
|
|
120
121
|
// types/enums/statistics.ts
|
|
@@ -141,8 +142,9 @@ var import_knex2 = require("knex");
|
|
|
141
142
|
var import_knex3 = require("pgvector/knex");
|
|
142
143
|
var db = {};
|
|
143
144
|
var databaseExistsChecked = false;
|
|
145
|
+
var dbName = process.env.POSTGRES_DB_NAME || "exulu";
|
|
144
146
|
async function ensureDatabaseExists() {
|
|
145
|
-
console.log(
|
|
147
|
+
console.log(`[EXULU] Ensuring ${dbName} database exists...`);
|
|
146
148
|
const defaultKnex = (0, import_knex.default)({
|
|
147
149
|
client: "pg",
|
|
148
150
|
connection: {
|
|
@@ -157,14 +159,14 @@ async function ensureDatabaseExists() {
|
|
|
157
159
|
});
|
|
158
160
|
try {
|
|
159
161
|
const result = await defaultKnex.raw(`
|
|
160
|
-
SELECT 1 FROM pg_database WHERE datname = '
|
|
162
|
+
SELECT 1 FROM pg_database WHERE datname = '${dbName}'
|
|
161
163
|
`);
|
|
162
164
|
if (result.rows.length === 0) {
|
|
163
|
-
console.log(
|
|
164
|
-
await defaultKnex.raw(`CREATE DATABASE
|
|
165
|
-
console.log(
|
|
165
|
+
console.log(`[EXULU] Database '${dbName}' does not exist. Creating it...`);
|
|
166
|
+
await defaultKnex.raw(`CREATE DATABASE ${dbName}`);
|
|
167
|
+
console.log(`[EXULU] Database '${dbName}' created successfully.`);
|
|
166
168
|
} else {
|
|
167
|
-
console.log(
|
|
169
|
+
console.log(`[EXULU] Database '${dbName}' already exists.`);
|
|
168
170
|
}
|
|
169
171
|
} finally {
|
|
170
172
|
await defaultKnex.destroy();
|
|
@@ -173,7 +175,7 @@ async function ensureDatabaseExists() {
|
|
|
173
175
|
async function postgresClient() {
|
|
174
176
|
if (!db["exulu"]) {
|
|
175
177
|
try {
|
|
176
|
-
console.log(
|
|
178
|
+
console.log(`[EXULU] Connecting to ${dbName} database.`);
|
|
177
179
|
console.log("[EXULU] POSTGRES_DB_HOST:", process.env.POSTGRES_DB_HOST);
|
|
178
180
|
console.log("[EXULU] POSTGRES_DB_PORT:", process.env.POSTGRES_DB_PORT);
|
|
179
181
|
console.log("[EXULU] POSTGRES_DB_USER:", process.env.POSTGRES_DB_USER);
|
|
@@ -181,7 +183,7 @@ async function postgresClient() {
|
|
|
181
183
|
console.log("[EXULU] POSTGRES_DB_SSL:", process.env.POSTGRES_DB_SSL);
|
|
182
184
|
console.log("[EXULU] Database exists checked:", databaseExistsChecked);
|
|
183
185
|
if (!databaseExistsChecked) {
|
|
184
|
-
console.log(
|
|
186
|
+
console.log(`[EXULU] Ensuring ${dbName} database exists...`);
|
|
185
187
|
await ensureDatabaseExists();
|
|
186
188
|
databaseExistsChecked = true;
|
|
187
189
|
}
|
|
@@ -191,7 +193,7 @@ async function postgresClient() {
|
|
|
191
193
|
host: process.env.POSTGRES_DB_HOST,
|
|
192
194
|
port: parseInt(process.env.POSTGRES_DB_PORT || "5432"),
|
|
193
195
|
user: process.env.POSTGRES_DB_USER,
|
|
194
|
-
database:
|
|
196
|
+
database: dbName,
|
|
195
197
|
password: process.env.POSTGRES_DB_PASSWORD,
|
|
196
198
|
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
|
|
197
199
|
}
|
|
@@ -207,6 +209,16 @@ async function postgresClient() {
|
|
|
207
209
|
db: db["exulu"]
|
|
208
210
|
};
|
|
209
211
|
}
|
|
212
|
+
var refreshPostgresClient = async () => {
|
|
213
|
+
if (db["exulu"]) {
|
|
214
|
+
await db["exulu"].destroy();
|
|
215
|
+
db["exulu"] = void 0;
|
|
216
|
+
}
|
|
217
|
+
const { db: refreshed } = await postgresClient();
|
|
218
|
+
return {
|
|
219
|
+
db: refreshed
|
|
220
|
+
};
|
|
221
|
+
};
|
|
210
222
|
|
|
211
223
|
// src/registry/classes.ts
|
|
212
224
|
var import_knex5 = __toESM(require("pgvector/knex"), 1);
|
|
@@ -281,6 +293,15 @@ var mapType = (t, type, name, defaultValue, unique) => {
|
|
|
281
293
|
if (unique) t.unique(name);
|
|
282
294
|
return;
|
|
283
295
|
}
|
|
296
|
+
if (type === "markdown") {
|
|
297
|
+
if (defaultValue) {
|
|
298
|
+
t.text(name).defaultTo(defaultValue);
|
|
299
|
+
} else {
|
|
300
|
+
t.text(name);
|
|
301
|
+
}
|
|
302
|
+
if (unique) t.unique(name);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
284
305
|
if (type === "shortText") {
|
|
285
306
|
if (defaultValue) {
|
|
286
307
|
t.string(name, 100).defaultTo(defaultValue);
|
|
@@ -428,12 +449,11 @@ var ExuluEvalUtils = {
|
|
|
428
449
|
// src/registry/classes.ts
|
|
429
450
|
var import_crypto_js2 = __toESM(require("crypto-js"), 1);
|
|
430
451
|
var import_express = require("express");
|
|
431
|
-
var import_api = require("@opentelemetry/api");
|
|
432
452
|
|
|
433
453
|
// src/registry/utils/graphql.ts
|
|
434
454
|
var import_schema = require("@graphql-tools/schema");
|
|
435
455
|
var import_graphql_type_json = __toESM(require("graphql-type-json"), 1);
|
|
436
|
-
var
|
|
456
|
+
var import_graphql2 = require("graphql");
|
|
437
457
|
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
438
458
|
|
|
439
459
|
// src/auth/get-token.ts
|
|
@@ -484,7 +504,7 @@ var authentication = async ({
|
|
|
484
504
|
code: 200,
|
|
485
505
|
user: {
|
|
486
506
|
type: "api",
|
|
487
|
-
id:
|
|
507
|
+
id: 192837465,
|
|
488
508
|
email: "internal@exulu.com",
|
|
489
509
|
role: {
|
|
490
510
|
id: "internal",
|
|
@@ -805,7 +825,8 @@ var agentSessionsSchema = {
|
|
|
805
825
|
},
|
|
806
826
|
{
|
|
807
827
|
name: "project",
|
|
808
|
-
type: "uuid"
|
|
828
|
+
type: "uuid",
|
|
829
|
+
required: false
|
|
809
830
|
}
|
|
810
831
|
]
|
|
811
832
|
};
|
|
@@ -905,12 +926,13 @@ var projectsSchema = {
|
|
|
905
926
|
type: "text"
|
|
906
927
|
},
|
|
907
928
|
{
|
|
908
|
-
name: "
|
|
909
|
-
|
|
929
|
+
name: "project_items",
|
|
930
|
+
// array of items as global ids ('<context_id>/<item_id>')
|
|
931
|
+
type: "json"
|
|
910
932
|
},
|
|
911
933
|
{
|
|
912
|
-
name: "
|
|
913
|
-
type: "
|
|
934
|
+
name: "custom_instructions",
|
|
935
|
+
type: "longText"
|
|
914
936
|
}
|
|
915
937
|
]
|
|
916
938
|
};
|
|
@@ -930,20 +952,24 @@ var agentsSchema = {
|
|
|
930
952
|
name: "image",
|
|
931
953
|
type: "text"
|
|
932
954
|
},
|
|
955
|
+
{
|
|
956
|
+
name: "category",
|
|
957
|
+
type: "text"
|
|
958
|
+
},
|
|
933
959
|
{
|
|
934
960
|
name: "description",
|
|
935
961
|
type: "text"
|
|
936
962
|
},
|
|
937
963
|
{
|
|
938
|
-
name: "
|
|
964
|
+
name: "instructions",
|
|
939
965
|
type: "text"
|
|
940
966
|
},
|
|
941
967
|
{
|
|
942
|
-
name: "
|
|
968
|
+
name: "providerapikey",
|
|
943
969
|
type: "text"
|
|
944
970
|
},
|
|
945
971
|
{
|
|
946
|
-
name: "
|
|
972
|
+
name: "backend",
|
|
947
973
|
type: "text"
|
|
948
974
|
},
|
|
949
975
|
{
|
|
@@ -1277,18 +1303,22 @@ var rbacSchema = {
|
|
|
1277
1303
|
};
|
|
1278
1304
|
var addRBACfields = (schema) => {
|
|
1279
1305
|
if (schema.RBAC) {
|
|
1280
|
-
schema.fields.
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1306
|
+
if (!schema.fields.some((field) => field.name === "rights_mode")) {
|
|
1307
|
+
schema.fields.push({
|
|
1308
|
+
name: "rights_mode",
|
|
1309
|
+
type: "text",
|
|
1310
|
+
required: false,
|
|
1311
|
+
default: "private"
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
if (!schema.fields.some((field) => field.name === "created_by")) {
|
|
1315
|
+
schema.fields.push({
|
|
1316
|
+
name: "created_by",
|
|
1317
|
+
type: "number",
|
|
1318
|
+
required: true,
|
|
1319
|
+
default: 0
|
|
1320
|
+
});
|
|
1321
|
+
}
|
|
1292
1322
|
}
|
|
1293
1323
|
return schema;
|
|
1294
1324
|
};
|
|
@@ -1320,7 +1350,211 @@ var VectorMethodEnum = {
|
|
|
1320
1350
|
|
|
1321
1351
|
// src/registry/utils/graphql.ts
|
|
1322
1352
|
var import_knex4 = require("knex");
|
|
1323
|
-
|
|
1353
|
+
|
|
1354
|
+
// src/registry/rate-limiter.ts
|
|
1355
|
+
var rateLimiter = async (key, windowSeconds, limit, points) => {
|
|
1356
|
+
try {
|
|
1357
|
+
const { client: client2 } = await redisClient();
|
|
1358
|
+
if (!client2) {
|
|
1359
|
+
console.warn("[EXULU] Rate limiting disabled - Redis not available");
|
|
1360
|
+
return {
|
|
1361
|
+
status: true,
|
|
1362
|
+
retryAfter: null
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
const redisKey = `exulu/${key}`;
|
|
1366
|
+
const current = await client2.incrBy(redisKey, points);
|
|
1367
|
+
if (current === points) {
|
|
1368
|
+
await client2.expire(redisKey, windowSeconds);
|
|
1369
|
+
}
|
|
1370
|
+
if (current > limit) {
|
|
1371
|
+
const ttl = await client2.ttl(redisKey);
|
|
1372
|
+
return {
|
|
1373
|
+
status: false,
|
|
1374
|
+
retryAfter: ttl
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
return {
|
|
1378
|
+
status: true,
|
|
1379
|
+
retryAfter: null
|
|
1380
|
+
};
|
|
1381
|
+
} catch (error) {
|
|
1382
|
+
console.error("[EXULU] Rate limiting error:", error);
|
|
1383
|
+
return {
|
|
1384
|
+
status: true,
|
|
1385
|
+
retryAfter: null
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
|
|
1390
|
+
// src/registry/utils.ts
|
|
1391
|
+
var bullmq = {
|
|
1392
|
+
validate: (id, data) => {
|
|
1393
|
+
if (!data) {
|
|
1394
|
+
throw new Error(`Missing job data for job ${id}.`);
|
|
1395
|
+
}
|
|
1396
|
+
if (!data.type) {
|
|
1397
|
+
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
1398
|
+
}
|
|
1399
|
+
if (!data.inputs) {
|
|
1400
|
+
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
1401
|
+
}
|
|
1402
|
+
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
1403
|
+
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
|
|
1404
|
+
}
|
|
1405
|
+
if (!data.workflow && !data.embedder) {
|
|
1406
|
+
throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
var getEnabledTools = async (agentInstance, allExuluTools, disabledTools = [], agents, user) => {
|
|
1411
|
+
let enabledTools = [];
|
|
1412
|
+
if (agentInstance.tools) {
|
|
1413
|
+
const results = await Promise.all(agentInstance.tools.map(
|
|
1414
|
+
async ({ config, id, type }) => {
|
|
1415
|
+
let hydrated;
|
|
1416
|
+
if (type === "agent") {
|
|
1417
|
+
if (id === agentInstance.id) {
|
|
1418
|
+
return null;
|
|
1419
|
+
}
|
|
1420
|
+
const instance = await loadAgent(id);
|
|
1421
|
+
if (!instance) {
|
|
1422
|
+
throw new Error("Trying to load a tool of type 'agent', but the associated agent with id " + id + " was not found in the database.");
|
|
1423
|
+
}
|
|
1424
|
+
const backend = agents.find((a) => a.id === instance.backend);
|
|
1425
|
+
if (!backend) {
|
|
1426
|
+
throw new Error("Trying to load a tool of type 'agent', but the associated agent with id " + id + " does not have a backend set for it.");
|
|
1427
|
+
}
|
|
1428
|
+
const hasAccessToAgent = await checkRecordAccess(instance, "read", user);
|
|
1429
|
+
if (!hasAccessToAgent) {
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
hydrated = await backend.tool(instance.id, agents);
|
|
1433
|
+
} else {
|
|
1434
|
+
hydrated = allExuluTools.find((t) => t.id === id);
|
|
1435
|
+
}
|
|
1436
|
+
return hydrated;
|
|
1437
|
+
}
|
|
1438
|
+
));
|
|
1439
|
+
enabledTools = results.filter(Boolean);
|
|
1440
|
+
}
|
|
1441
|
+
console.log("[EXULU] available tools", enabledTools?.length);
|
|
1442
|
+
console.log("[EXULU] disabled tools", disabledTools?.length);
|
|
1443
|
+
enabledTools = enabledTools.filter((tool2) => !disabledTools.includes(tool2.id));
|
|
1444
|
+
return enabledTools;
|
|
1445
|
+
};
|
|
1446
|
+
var loadAgentCache = /* @__PURE__ */ new Map();
|
|
1447
|
+
var loadAgents = async () => {
|
|
1448
|
+
const { db: db3 } = await postgresClient();
|
|
1449
|
+
const agents = await db3.from("agents");
|
|
1450
|
+
for (const agent of agents) {
|
|
1451
|
+
const agentRbac = await RBACResolver(db3, "agent", agent.id, agent.rights_mode || "private");
|
|
1452
|
+
agent.RBAC = agentRbac;
|
|
1453
|
+
loadAgentCache.set(agent.id, {
|
|
1454
|
+
agent,
|
|
1455
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1456
|
+
// 1 minute
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
return agents;
|
|
1460
|
+
};
|
|
1461
|
+
var loadAgent = async (id) => {
|
|
1462
|
+
const cachedAgent = loadAgentCache.get(id);
|
|
1463
|
+
if (cachedAgent && cachedAgent.expiresAt > /* @__PURE__ */ new Date()) {
|
|
1464
|
+
return cachedAgent.agent;
|
|
1465
|
+
}
|
|
1466
|
+
const { db: db3 } = await postgresClient();
|
|
1467
|
+
const agentInstance = await db3.from("agents").where({
|
|
1468
|
+
id
|
|
1469
|
+
}).first();
|
|
1470
|
+
const agentRbac = await RBACResolver(db3, "agent", agentInstance.id, agentInstance.rights_mode || "private");
|
|
1471
|
+
agentInstance.RBAC = agentRbac;
|
|
1472
|
+
if (!agentInstance) {
|
|
1473
|
+
throw new Error("Agent instance not found.");
|
|
1474
|
+
}
|
|
1475
|
+
loadAgentCache.set(id, {
|
|
1476
|
+
agent: agentInstance,
|
|
1477
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1478
|
+
// 1 minute
|
|
1479
|
+
});
|
|
1480
|
+
return agentInstance;
|
|
1481
|
+
};
|
|
1482
|
+
var checkAgentRateLimit = async (agent) => {
|
|
1483
|
+
if (agent.rateLimit) {
|
|
1484
|
+
console.log("[EXULU] rate limiting agent.", agent.rateLimit);
|
|
1485
|
+
const limit = await rateLimiter(
|
|
1486
|
+
agent.rateLimit.name || agent.id,
|
|
1487
|
+
agent.rateLimit.rate_limit.time,
|
|
1488
|
+
agent.rateLimit.rate_limit.limit,
|
|
1489
|
+
1
|
|
1490
|
+
);
|
|
1491
|
+
if (!limit.status) {
|
|
1492
|
+
throw new Error("Rate limit exceeded.");
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
var checkRecordAccessCache = /* @__PURE__ */ new Map();
|
|
1497
|
+
var checkRecordAccess = async (record, request, user) => {
|
|
1498
|
+
const setRecordAccessCache = (hasAccess2) => {
|
|
1499
|
+
checkRecordAccessCache.set(`${record.id}-${request}-${user?.id}`, {
|
|
1500
|
+
hasAccess: hasAccess2,
|
|
1501
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1502
|
+
// 1 minute
|
|
1503
|
+
});
|
|
1504
|
+
};
|
|
1505
|
+
const cachedAccess = checkRecordAccessCache.get(`${record.id}-${request}-${user?.id}`);
|
|
1506
|
+
if (cachedAccess && cachedAccess.expiresAt > /* @__PURE__ */ new Date()) {
|
|
1507
|
+
return cachedAccess.hasAccess;
|
|
1508
|
+
}
|
|
1509
|
+
const isPublic = record.rights_mode === "public";
|
|
1510
|
+
const byUsers = record.rights_mode === "users";
|
|
1511
|
+
const byRoles = record.rights_mode === "roles";
|
|
1512
|
+
const isCreator = user ? record.created_by === user.id.toString() : false;
|
|
1513
|
+
const isAdmin = user ? user.super_admin : false;
|
|
1514
|
+
const isApi = user ? user.type === "api" : false;
|
|
1515
|
+
let hasAccess = "none";
|
|
1516
|
+
if (isPublic || isCreator || isAdmin || isApi) {
|
|
1517
|
+
setRecordAccessCache(true);
|
|
1518
|
+
return true;
|
|
1519
|
+
}
|
|
1520
|
+
if (byUsers) {
|
|
1521
|
+
if (!user) {
|
|
1522
|
+
setRecordAccessCache(false);
|
|
1523
|
+
return false;
|
|
1524
|
+
}
|
|
1525
|
+
console.log("record.RBAC?.users", record.RBAC?.users);
|
|
1526
|
+
console.log("user.id", user.id.toString());
|
|
1527
|
+
hasAccess = record.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
1528
|
+
if (!hasAccess || hasAccess === "none" || hasAccess !== request) {
|
|
1529
|
+
console.error(`Your current user ${user.id} does not have access to this record, current access type is: ${hasAccess}.`);
|
|
1530
|
+
setRecordAccessCache(false);
|
|
1531
|
+
return false;
|
|
1532
|
+
} else {
|
|
1533
|
+
setRecordAccessCache(true);
|
|
1534
|
+
return true;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (byRoles) {
|
|
1538
|
+
if (!user) {
|
|
1539
|
+
setRecordAccessCache(false);
|
|
1540
|
+
return false;
|
|
1541
|
+
}
|
|
1542
|
+
hasAccess = record.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
1543
|
+
if (!hasAccess || hasAccess === "none" || hasAccess !== request) {
|
|
1544
|
+
console.error(`Your current role ${user.role?.name} does not have access to this record, current access type is: ${hasAccess}.`);
|
|
1545
|
+
setRecordAccessCache(false);
|
|
1546
|
+
return false;
|
|
1547
|
+
} else {
|
|
1548
|
+
setRecordAccessCache(true);
|
|
1549
|
+
return true;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
setRecordAccessCache(false);
|
|
1553
|
+
return false;
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
// src/registry/utils/graphql.ts
|
|
1557
|
+
var GraphQLDate = new import_graphql2.GraphQLScalarType({
|
|
1324
1558
|
name: "Date",
|
|
1325
1559
|
description: "Date custom scalar type",
|
|
1326
1560
|
serialize(value) {
|
|
@@ -1345,10 +1579,10 @@ var GraphQLDate = new import_graphql.GraphQLScalarType({
|
|
|
1345
1579
|
return value;
|
|
1346
1580
|
},
|
|
1347
1581
|
parseLiteral(ast) {
|
|
1348
|
-
if (ast.kind ===
|
|
1582
|
+
if (ast.kind === import_graphql2.Kind.STRING) {
|
|
1349
1583
|
return new Date(ast.value);
|
|
1350
1584
|
}
|
|
1351
|
-
if (ast.kind ===
|
|
1585
|
+
if (ast.kind === import_graphql2.Kind.INT) {
|
|
1352
1586
|
return new Date(parseInt(ast.value, 10));
|
|
1353
1587
|
}
|
|
1354
1588
|
return null;
|
|
@@ -1360,6 +1594,7 @@ var map = (field) => {
|
|
|
1360
1594
|
case "text":
|
|
1361
1595
|
case "shortText":
|
|
1362
1596
|
case "longText":
|
|
1597
|
+
case "markdown":
|
|
1363
1598
|
case "code":
|
|
1364
1599
|
type = "String";
|
|
1365
1600
|
break;
|
|
@@ -1411,6 +1646,7 @@ ${enumValues}
|
|
|
1411
1646
|
fields.push(" rateLimit: RateLimiterRule");
|
|
1412
1647
|
fields.push(" streaming: Boolean");
|
|
1413
1648
|
fields.push(" capabilities: AgentCapabilities");
|
|
1649
|
+
fields.push(" maxContextLength: Int");
|
|
1414
1650
|
fields.push(" slug: String");
|
|
1415
1651
|
}
|
|
1416
1652
|
const rbacField = table.RBAC ? " RBAC: RBACData" : "";
|
|
@@ -1745,7 +1981,6 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1745
1981
|
input.id = db3.fn.uuid();
|
|
1746
1982
|
}
|
|
1747
1983
|
const columns = await db3(tableNamePlural).columnInfo();
|
|
1748
|
-
console.log("[EXULU] Columns", columns);
|
|
1749
1984
|
const insert = db3(tableNamePlural).insert({
|
|
1750
1985
|
...input,
|
|
1751
1986
|
...table.RBAC ? { rights_mode: "private" } : {}
|
|
@@ -1760,7 +1995,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1760
1995
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user.id, role: context.user.role?.id });
|
|
1761
1996
|
return {
|
|
1762
1997
|
// Filter result to only include requested fields
|
|
1763
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0] }),
|
|
1998
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user }),
|
|
1764
1999
|
job
|
|
1765
2000
|
};
|
|
1766
2001
|
},
|
|
@@ -1803,7 +2038,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1803
2038
|
}
|
|
1804
2039
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id });
|
|
1805
2040
|
return {
|
|
1806
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result }),
|
|
2041
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
|
|
1807
2042
|
job
|
|
1808
2043
|
};
|
|
1809
2044
|
},
|
|
@@ -1839,7 +2074,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1839
2074
|
const result = await db3.from(tableNamePlural).select(Object.keys(columns)).where({ id }).first();
|
|
1840
2075
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id });
|
|
1841
2076
|
return {
|
|
1842
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result }),
|
|
2077
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
|
|
1843
2078
|
job
|
|
1844
2079
|
};
|
|
1845
2080
|
},
|
|
@@ -1871,7 +2106,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1871
2106
|
}).del();
|
|
1872
2107
|
}
|
|
1873
2108
|
await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
|
|
1874
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2109
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
|
|
1875
2110
|
},
|
|
1876
2111
|
[`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
|
|
1877
2112
|
const { where } = args;
|
|
@@ -1895,7 +2130,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1895
2130
|
}
|
|
1896
2131
|
await db3(tableNamePlural).where(where).del();
|
|
1897
2132
|
await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
|
|
1898
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2133
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
|
|
1899
2134
|
}
|
|
1900
2135
|
};
|
|
1901
2136
|
if (table.type === "items") {
|
|
@@ -1975,7 +2210,6 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1975
2210
|
return mutations;
|
|
1976
2211
|
}
|
|
1977
2212
|
var applyAccessControl = (table, user, query) => {
|
|
1978
|
-
console.log("table", table);
|
|
1979
2213
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
1980
2214
|
if (!user.super_admin && table.name.plural === "jobs") {
|
|
1981
2215
|
query = query.where("created_by", user.id);
|
|
@@ -2002,7 +2236,6 @@ var applyAccessControl = (table, user, query) => {
|
|
|
2002
2236
|
});
|
|
2003
2237
|
});
|
|
2004
2238
|
if (user.role) {
|
|
2005
|
-
console.log("user.role", user.role);
|
|
2006
2239
|
this.orWhere(function() {
|
|
2007
2240
|
this.where("rights_mode", "roles").whereExists(function() {
|
|
2008
2241
|
this.select("*").from("rbac").whereRaw("rbac.target_resource_id = " + tableNamePlural + ".id").where("rbac.entity", table.name.singular).where("rbac.access_type", "Role").where("rbac.role_id", user.role.id);
|
|
@@ -2043,14 +2276,15 @@ var backendAgentFields = [
|
|
|
2043
2276
|
"slug",
|
|
2044
2277
|
"rateLimit",
|
|
2045
2278
|
"streaming",
|
|
2046
|
-
"capabilities"
|
|
2279
|
+
"capabilities",
|
|
2280
|
+
"maxContextLength"
|
|
2047
2281
|
];
|
|
2048
2282
|
var removeAgentFields = (requestedFields) => {
|
|
2049
2283
|
const filtered = requestedFields.filter((field) => !backendAgentFields.includes(field));
|
|
2050
2284
|
filtered.push("backend");
|
|
2051
2285
|
return filtered;
|
|
2052
2286
|
};
|
|
2053
|
-
var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
2287
|
+
var addAgentFields = async (requestedFields, agents, result, tools, user) => {
|
|
2054
2288
|
let backend = agents.find((a) => a.id === result?.backend);
|
|
2055
2289
|
if (requestedFields.includes("providerName")) {
|
|
2056
2290
|
result.providerName = backend?.providerName || "";
|
|
@@ -2065,13 +2299,39 @@ var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
|
2065
2299
|
result.rateLimit = backend?.rateLimit || "";
|
|
2066
2300
|
}
|
|
2067
2301
|
if (requestedFields.includes("tools")) {
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2302
|
+
if (result.tools) {
|
|
2303
|
+
result.tools = await Promise.all(result.tools.map(async (tool2) => {
|
|
2304
|
+
let hydrated;
|
|
2305
|
+
if (tool2.type === "agent") {
|
|
2306
|
+
if (tool2.id === result.id) {
|
|
2307
|
+
return null;
|
|
2308
|
+
}
|
|
2309
|
+
const instance = await loadAgent(tool2.id);
|
|
2310
|
+
if (!instance) {
|
|
2311
|
+
throw new Error("Trying to load a tool of type 'agent', but the associated agent with id " + tool2.id + " was not found in the database.");
|
|
2312
|
+
}
|
|
2313
|
+
const backend2 = agents.find((a) => a.id === instance.backend);
|
|
2314
|
+
if (!backend2) {
|
|
2315
|
+
throw new Error("Trying to load a tool of type 'agent', but the associated agent with id " + tool2.id + " does not have a backend set for it.");
|
|
2316
|
+
}
|
|
2317
|
+
const hasAccessToAgent = await checkRecordAccess(instance, "read", user);
|
|
2318
|
+
if (!hasAccessToAgent) {
|
|
2319
|
+
return null;
|
|
2320
|
+
}
|
|
2321
|
+
hydrated = await backend2.tool(instance.id, agents);
|
|
2322
|
+
} else {
|
|
2323
|
+
hydrated = tools.find((t) => t.id === tool2.id);
|
|
2324
|
+
}
|
|
2325
|
+
return {
|
|
2326
|
+
...tool2,
|
|
2327
|
+
name: hydrated?.name || "",
|
|
2328
|
+
description: hydrated?.description || ""
|
|
2329
|
+
};
|
|
2330
|
+
}));
|
|
2331
|
+
result.tools = result.tools.filter((tool2) => tool2 !== null);
|
|
2332
|
+
} else {
|
|
2333
|
+
result.tools = [];
|
|
2334
|
+
}
|
|
2075
2335
|
}
|
|
2076
2336
|
if (requestedFields.includes("streaming")) {
|
|
2077
2337
|
result.streaming = backend?.streaming || false;
|
|
@@ -2079,6 +2339,9 @@ var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
|
2079
2339
|
if (requestedFields.includes("capabilities")) {
|
|
2080
2340
|
result.capabilities = backend?.capabilities || [];
|
|
2081
2341
|
}
|
|
2342
|
+
if (requestedFields.includes("maxContextLength")) {
|
|
2343
|
+
result.maxContextLength = backend?.maxContextLength || 0;
|
|
2344
|
+
}
|
|
2082
2345
|
if (!requestedFields.includes("backend")) {
|
|
2083
2346
|
delete result.backend;
|
|
2084
2347
|
}
|
|
@@ -2189,7 +2452,8 @@ var finalizeRequestedFields = async ({
|
|
|
2189
2452
|
agents,
|
|
2190
2453
|
contexts,
|
|
2191
2454
|
tools,
|
|
2192
|
-
result
|
|
2455
|
+
result,
|
|
2456
|
+
user
|
|
2193
2457
|
}) => {
|
|
2194
2458
|
if (!result) {
|
|
2195
2459
|
return result;
|
|
@@ -2199,11 +2463,11 @@ var finalizeRequestedFields = async ({
|
|
|
2199
2463
|
}
|
|
2200
2464
|
if (Array.isArray(result)) {
|
|
2201
2465
|
result = result.map((item) => {
|
|
2202
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item });
|
|
2466
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item, user });
|
|
2203
2467
|
});
|
|
2204
2468
|
} else {
|
|
2205
2469
|
if (table.name.singular === "agent") {
|
|
2206
|
-
result = addAgentFields(requestedFields, agents, result, tools);
|
|
2470
|
+
result = await addAgentFields(requestedFields, agents, result, tools, user);
|
|
2207
2471
|
if (!requestedFields.includes("backend")) {
|
|
2208
2472
|
delete result.backend;
|
|
2209
2473
|
}
|
|
@@ -2250,7 +2514,6 @@ var applyFilters = (query, filters) => {
|
|
|
2250
2514
|
Object.entries(filter).forEach(([fieldName, operators]) => {
|
|
2251
2515
|
if (operators) {
|
|
2252
2516
|
if (operators.and !== void 0) {
|
|
2253
|
-
console.log("operators.and", operators.and);
|
|
2254
2517
|
operators.and.forEach((operator) => {
|
|
2255
2518
|
query = converOperatorToQuery(query, fieldName, operator);
|
|
2256
2519
|
});
|
|
@@ -2261,7 +2524,6 @@ var applyFilters = (query, filters) => {
|
|
|
2261
2524
|
});
|
|
2262
2525
|
}
|
|
2263
2526
|
query = converOperatorToQuery(query, fieldName, operators);
|
|
2264
|
-
console.log("query", query);
|
|
2265
2527
|
}
|
|
2266
2528
|
});
|
|
2267
2529
|
});
|
|
@@ -2284,7 +2546,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2284
2546
|
let query = db3.from(tableNamePlural).select(sanitizedFields).where({ id: args.id });
|
|
2285
2547
|
query = applyAccessControl(table, context.user, query);
|
|
2286
2548
|
let result = await query.first();
|
|
2287
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2549
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2288
2550
|
},
|
|
2289
2551
|
[`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
|
|
2290
2552
|
const { db: db3 } = context;
|
|
@@ -2293,7 +2555,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2293
2555
|
let query = db3.from(tableNamePlural).select(sanitizedFields).whereIn("id", args.ids);
|
|
2294
2556
|
query = applyAccessControl(table, context.user, query);
|
|
2295
2557
|
let result = await query;
|
|
2296
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2558
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2297
2559
|
},
|
|
2298
2560
|
[`${tableNameSingular}One`]: async (_, args, context, info) => {
|
|
2299
2561
|
const { filters = [], sort } = args;
|
|
@@ -2305,7 +2567,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2305
2567
|
query = applyAccessControl(table, context.user, query);
|
|
2306
2568
|
query = applySorting(query, sort);
|
|
2307
2569
|
let result = await query.first();
|
|
2308
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2570
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2309
2571
|
},
|
|
2310
2572
|
[`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
|
|
2311
2573
|
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
@@ -2316,7 +2578,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2316
2578
|
let countQuery = db3(tableNamePlural);
|
|
2317
2579
|
countQuery = applyFilters(countQuery, filters);
|
|
2318
2580
|
countQuery = applyAccessControl(table, context.user, countQuery);
|
|
2319
|
-
console.log("countQuery", countQuery);
|
|
2320
2581
|
const countResult = await countQuery.count("* as count");
|
|
2321
2582
|
const itemCount = Number(countResult[0]?.count || 0);
|
|
2322
2583
|
const pageCount = Math.ceil(itemCount / limit);
|
|
@@ -2341,7 +2602,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2341
2602
|
hasPreviousPage,
|
|
2342
2603
|
hasNextPage
|
|
2343
2604
|
},
|
|
2344
|
-
items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items })
|
|
2605
|
+
items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items, user: context.user })
|
|
2345
2606
|
};
|
|
2346
2607
|
},
|
|
2347
2608
|
// Add generic statistics query for all tables
|
|
@@ -2359,7 +2620,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2359
2620
|
query = query.count("* as count");
|
|
2360
2621
|
}
|
|
2361
2622
|
const results = await query;
|
|
2362
|
-
console.log("!!! results !!!", results);
|
|
2363
2623
|
return results.map((r) => ({
|
|
2364
2624
|
group: r[groupBy],
|
|
2365
2625
|
count: r.count ? Number(r.count) : 0
|
|
@@ -2368,7 +2628,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2368
2628
|
if (tableNamePlural === "tracking") {
|
|
2369
2629
|
query = query.sum("total as count");
|
|
2370
2630
|
const [{ count }] = await query.sum("total as count");
|
|
2371
|
-
console.log("!!! count !!!", count);
|
|
2372
2631
|
return [{
|
|
2373
2632
|
group: "total",
|
|
2374
2633
|
count: count ? Number(count) : 0
|
|
@@ -2453,7 +2712,6 @@ var vectorSearch = async ({
|
|
|
2453
2712
|
let countQuery = db3(mainTable);
|
|
2454
2713
|
countQuery = applyFilters(countQuery, filters);
|
|
2455
2714
|
countQuery = applyAccessControl(table, user, countQuery);
|
|
2456
|
-
console.log("countQuery", countQuery);
|
|
2457
2715
|
const columns = await db3(mainTable).columnInfo();
|
|
2458
2716
|
let itemsQuery = db3(mainTable).select(Object.keys(columns).map((column) => mainTable + "." + column));
|
|
2459
2717
|
itemsQuery = applyFilters(itemsQuery, filters);
|
|
@@ -2592,7 +2850,6 @@ var vectorSearch = async ({
|
|
|
2592
2850
|
];
|
|
2593
2851
|
items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
|
|
2594
2852
|
}
|
|
2595
|
-
console.log("items", items);
|
|
2596
2853
|
const seenSources = /* @__PURE__ */ new Map();
|
|
2597
2854
|
items = items.reduce((acc, item) => {
|
|
2598
2855
|
if (!seenSources.has(item.source)) {
|
|
@@ -2635,7 +2892,6 @@ var vectorSearch = async ({
|
|
|
2635
2892
|
}
|
|
2636
2893
|
return acc;
|
|
2637
2894
|
}, []);
|
|
2638
|
-
console.log("items", items);
|
|
2639
2895
|
items.forEach((item) => {
|
|
2640
2896
|
if (!item.chunks?.length) {
|
|
2641
2897
|
return;
|
|
@@ -2657,7 +2913,6 @@ var vectorSearch = async ({
|
|
|
2657
2913
|
item.averageRelevance = average;
|
|
2658
2914
|
item.totalRelevance = total;
|
|
2659
2915
|
} else if (method === "hybridSearch") {
|
|
2660
|
-
console.log("item.chunks", item.chunks);
|
|
2661
2916
|
const scores = item.chunks.map((c) => typeof c.hybrid_score === "number" ? c.hybrid_score * 10 + 1 : 0);
|
|
2662
2917
|
const total = scores.reduce((a, b) => a + b, 0);
|
|
2663
2918
|
const average = scores.length ? total / scores.length : 0;
|
|
@@ -2746,6 +3001,10 @@ var contextToTableDefinition = (context) => {
|
|
|
2746
3001
|
name: "textlength",
|
|
2747
3002
|
type: "number"
|
|
2748
3003
|
});
|
|
3004
|
+
definition.fields.push({
|
|
3005
|
+
name: "ttl",
|
|
3006
|
+
type: "text"
|
|
3007
|
+
});
|
|
2749
3008
|
definition.fields.push({
|
|
2750
3009
|
name: "embeddings_updated_at",
|
|
2751
3010
|
type: "date"
|
|
@@ -2789,7 +3048,7 @@ function createSDL(tables, contexts, agents, tools) {
|
|
|
2789
3048
|
}
|
|
2790
3049
|
});
|
|
2791
3050
|
tables = [...tables, ...contextSchemas];
|
|
2792
|
-
console.log("[EXULU] Creating SDL");
|
|
3051
|
+
console.log("[EXULU] Creating SDL.");
|
|
2793
3052
|
let typeDefs = `
|
|
2794
3053
|
scalar JSON
|
|
2795
3054
|
scalar Date
|
|
@@ -3030,8 +3289,19 @@ type PageInfo {
|
|
|
3030
3289
|
};
|
|
3031
3290
|
resolvers.Query["tools"] = async (_, args, context, info) => {
|
|
3032
3291
|
const requestedFields = getRequestedFields(info);
|
|
3292
|
+
const instances = await loadAgents();
|
|
3293
|
+
let agentTools = await Promise.all(
|
|
3294
|
+
instances.map(async (instance) => {
|
|
3295
|
+
const backend = agents.find((a) => a.id === instance.backend);
|
|
3296
|
+
if (!backend) {
|
|
3297
|
+
return null;
|
|
3298
|
+
}
|
|
3299
|
+
return await backend.tool(instance.id, agents);
|
|
3300
|
+
})
|
|
3301
|
+
);
|
|
3302
|
+
const filtered = agentTools.filter((tool2) => tool2 !== null);
|
|
3033
3303
|
return {
|
|
3034
|
-
items: tools.map((tool2) => {
|
|
3304
|
+
items: [...filtered, ...tools].map((tool2) => {
|
|
3035
3305
|
const object = {};
|
|
3036
3306
|
requestedFields.forEach((field) => {
|
|
3037
3307
|
object[field] = tool2[field];
|
|
@@ -3127,7 +3397,6 @@ type Tool {
|
|
|
3127
3397
|
|
|
3128
3398
|
enum EnumProviderType {
|
|
3129
3399
|
agent
|
|
3130
|
-
custom
|
|
3131
3400
|
}
|
|
3132
3401
|
|
|
3133
3402
|
type StatisticsResult {
|
|
@@ -3161,6 +3430,9 @@ var validateCreateOrRemoveSuperAdminPermission = async (tableNamePlural, input,
|
|
|
3161
3430
|
};
|
|
3162
3431
|
|
|
3163
3432
|
// src/registry/classes.ts
|
|
3433
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
3434
|
+
var import_node_crypto = require("crypto");
|
|
3435
|
+
var s3Client;
|
|
3164
3436
|
function sanitizeToolName(name) {
|
|
3165
3437
|
if (typeof name !== "string") return "";
|
|
3166
3438
|
let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
@@ -3170,17 +3442,18 @@ function sanitizeToolName(name) {
|
|
|
3170
3442
|
}
|
|
3171
3443
|
return sanitized;
|
|
3172
3444
|
}
|
|
3173
|
-
var convertToolsArrayToObject = (
|
|
3174
|
-
if (!
|
|
3175
|
-
|
|
3445
|
+
var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, filesContext2) => {
|
|
3446
|
+
if (!currentTools) return {};
|
|
3447
|
+
if (!allExuluTools) return {};
|
|
3448
|
+
const sanitizedTools = currentTools ? currentTools.map((tool2) => ({
|
|
3176
3449
|
...tool2,
|
|
3177
3450
|
name: sanitizeToolName(tool2.name)
|
|
3178
3451
|
})) : [];
|
|
3179
|
-
console.log("[EXULU] Sanitized tools", sanitizedTools);
|
|
3452
|
+
console.log("[EXULU] Sanitized tools", sanitizedTools.map((x) => x.name + " (" + x.id + ")"));
|
|
3180
3453
|
const askForConfirmation = {
|
|
3181
3454
|
description: "Ask the user for confirmation.",
|
|
3182
|
-
inputSchema:
|
|
3183
|
-
message:
|
|
3455
|
+
inputSchema: import_zod.z.object({
|
|
3456
|
+
message: import_zod.z.string().describe("The message to ask for confirmation.")
|
|
3184
3457
|
})
|
|
3185
3458
|
};
|
|
3186
3459
|
return {
|
|
@@ -3189,29 +3462,91 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) =>
|
|
|
3189
3462
|
...prev,
|
|
3190
3463
|
[cur.name]: {
|
|
3191
3464
|
...cur.tool,
|
|
3192
|
-
|
|
3465
|
+
async *execute(inputs, options) {
|
|
3193
3466
|
if (!cur.tool?.execute) {
|
|
3194
3467
|
console.error("[EXULU] Tool execute function is undefined.", cur.tool);
|
|
3195
3468
|
throw new Error("Tool execute function is undefined.");
|
|
3196
3469
|
}
|
|
3197
|
-
let config = configs?.find((config2) => config2.
|
|
3470
|
+
let config = configs?.find((config2) => config2.id === cur.id);
|
|
3198
3471
|
if (config) {
|
|
3199
3472
|
config = await hydrateVariables(config || []);
|
|
3200
3473
|
}
|
|
3474
|
+
let upload = void 0;
|
|
3475
|
+
if (exuluConfig?.fileUploads?.s3endpoint && exuluConfig?.fileUploads?.s3key && exuluConfig?.fileUploads?.s3secret && exuluConfig?.fileUploads?.s3Bucket && filesContext2) {
|
|
3476
|
+
s3Client ??= new import_client_s3.S3Client({
|
|
3477
|
+
region: exuluConfig?.fileUploads?.s3region,
|
|
3478
|
+
...exuluConfig?.fileUploads?.s3endpoint && {
|
|
3479
|
+
forcePathStyle: true,
|
|
3480
|
+
endpoint: exuluConfig?.fileUploads?.s3endpoint
|
|
3481
|
+
},
|
|
3482
|
+
credentials: {
|
|
3483
|
+
accessKeyId: exuluConfig?.fileUploads?.s3key ?? "",
|
|
3484
|
+
secretAccessKey: exuluConfig?.fileUploads?.s3secret ?? ""
|
|
3485
|
+
}
|
|
3486
|
+
});
|
|
3487
|
+
upload = async ({
|
|
3488
|
+
name,
|
|
3489
|
+
data,
|
|
3490
|
+
type,
|
|
3491
|
+
tags
|
|
3492
|
+
}) => {
|
|
3493
|
+
const mime = getMimeType(type);
|
|
3494
|
+
const key = `${user}/${generateS3Key(name)}${type}`;
|
|
3495
|
+
const command = new import_client_s3.PutObjectCommand({
|
|
3496
|
+
Bucket: exuluConfig?.fileUploads?.s3Bucket,
|
|
3497
|
+
Key: key,
|
|
3498
|
+
Body: data,
|
|
3499
|
+
ContentType: mime
|
|
3500
|
+
});
|
|
3501
|
+
try {
|
|
3502
|
+
const response2 = await s3Client.send(command);
|
|
3503
|
+
console.log(response2);
|
|
3504
|
+
const { item } = await filesContext2.createItem({
|
|
3505
|
+
name: `${name}${type}`,
|
|
3506
|
+
type: mime,
|
|
3507
|
+
rights_mode: "private",
|
|
3508
|
+
s3key: key,
|
|
3509
|
+
tags
|
|
3510
|
+
}, user?.id, user?.role?.id, false);
|
|
3511
|
+
return item;
|
|
3512
|
+
} catch (caught) {
|
|
3513
|
+
if (caught instanceof import_client_s3.S3ServiceException && caught.name === "EntityTooLarge") {
|
|
3514
|
+
console.error(
|
|
3515
|
+
`Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. The object was too large. To upload objects larger than 5GB, use the S3 console (160GB max) or the multipart upload API (5TB max).`
|
|
3516
|
+
);
|
|
3517
|
+
} else if (caught instanceof import_client_s3.S3ServiceException) {
|
|
3518
|
+
console.error(
|
|
3519
|
+
`Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. ${caught.name}: ${caught.message}`
|
|
3520
|
+
);
|
|
3521
|
+
} else {
|
|
3522
|
+
throw caught;
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
};
|
|
3526
|
+
}
|
|
3527
|
+
const contextsMap = contexts?.reduce((acc, curr) => {
|
|
3528
|
+
acc[curr.id] = curr;
|
|
3529
|
+
return acc;
|
|
3530
|
+
}, {});
|
|
3201
3531
|
console.log("[EXULU] Config", config);
|
|
3202
|
-
|
|
3532
|
+
const response = await cur.tool.execute({
|
|
3203
3533
|
...inputs,
|
|
3204
3534
|
// Convert config to object format if a config object
|
|
3205
3535
|
// is available, after we added the .value property
|
|
3206
3536
|
// by hydrating it from the variables table.
|
|
3207
|
-
|
|
3537
|
+
providerapikey,
|
|
3538
|
+
allExuluTools,
|
|
3539
|
+
currentTools,
|
|
3208
3540
|
user,
|
|
3209
|
-
|
|
3541
|
+
contexts: contextsMap,
|
|
3542
|
+
upload,
|
|
3210
3543
|
config: config ? config.config.reduce((acc, curr) => {
|
|
3211
3544
|
acc[curr.name] = curr.value;
|
|
3212
3545
|
return acc;
|
|
3213
3546
|
}, {}) : {}
|
|
3214
3547
|
}, options);
|
|
3548
|
+
yield response;
|
|
3549
|
+
return response;
|
|
3215
3550
|
}
|
|
3216
3551
|
}
|
|
3217
3552
|
}),
|
|
@@ -3246,6 +3581,18 @@ function generateSlug(name) {
|
|
|
3246
3581
|
const slug = lowercase.replace(/[\W_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3247
3582
|
return slug;
|
|
3248
3583
|
}
|
|
3584
|
+
function errorHandler(error) {
|
|
3585
|
+
if (error == null) {
|
|
3586
|
+
return "unknown error";
|
|
3587
|
+
}
|
|
3588
|
+
if (typeof error === "string") {
|
|
3589
|
+
return error;
|
|
3590
|
+
}
|
|
3591
|
+
if (error instanceof Error) {
|
|
3592
|
+
return error.message;
|
|
3593
|
+
}
|
|
3594
|
+
return JSON.stringify(error);
|
|
3595
|
+
}
|
|
3249
3596
|
var ExuluAgent2 = class {
|
|
3250
3597
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
3251
3598
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -3256,13 +3603,14 @@ var ExuluAgent2 = class {
|
|
|
3256
3603
|
slug = "";
|
|
3257
3604
|
type;
|
|
3258
3605
|
streaming = false;
|
|
3606
|
+
maxContextLength;
|
|
3259
3607
|
rateLimit;
|
|
3260
3608
|
config;
|
|
3261
3609
|
// private memory: Memory | undefined; // TODO do own implementation
|
|
3262
3610
|
evals;
|
|
3263
3611
|
model;
|
|
3264
3612
|
capabilities;
|
|
3265
|
-
constructor({ id, name, description, config, rateLimit, capabilities, type, evals }) {
|
|
3613
|
+
constructor({ id, name, description, config, rateLimit, capabilities, type, evals, maxContextLength }) {
|
|
3266
3614
|
this.id = id;
|
|
3267
3615
|
this.name = name;
|
|
3268
3616
|
this.evals = evals;
|
|
@@ -3270,6 +3618,7 @@ var ExuluAgent2 = class {
|
|
|
3270
3618
|
this.rateLimit = rateLimit;
|
|
3271
3619
|
this.config = config;
|
|
3272
3620
|
this.type = type;
|
|
3621
|
+
this.maxContextLength = maxContextLength;
|
|
3273
3622
|
this.capabilities = capabilities || {
|
|
3274
3623
|
text: false,
|
|
3275
3624
|
images: [],
|
|
@@ -3296,31 +3645,82 @@ var ExuluAgent2 = class {
|
|
|
3296
3645
|
}
|
|
3297
3646
|
// Exports the agent as a tool that can be used by another agent
|
|
3298
3647
|
// todo test this
|
|
3299
|
-
tool = () => {
|
|
3648
|
+
tool = async (instance, agents) => {
|
|
3649
|
+
const agentInstance = await loadAgent(instance);
|
|
3650
|
+
if (!agentInstance) {
|
|
3651
|
+
return null;
|
|
3652
|
+
}
|
|
3300
3653
|
return new ExuluTool2({
|
|
3301
|
-
id:
|
|
3302
|
-
name: `${
|
|
3654
|
+
id: agentInstance.id,
|
|
3655
|
+
name: `${agentInstance.name}`,
|
|
3303
3656
|
type: "agent",
|
|
3304
|
-
inputSchema:
|
|
3305
|
-
prompt:
|
|
3657
|
+
inputSchema: import_zod.z.object({
|
|
3658
|
+
prompt: import_zod.z.string().describe("The prompt (usually a question for the agent) to send to the agent."),
|
|
3659
|
+
information: import_zod.z.string().describe("A summary of relevant context / information from the current session")
|
|
3306
3660
|
}),
|
|
3307
|
-
description: `
|
|
3661
|
+
description: `This tool calls an AI agent named: ${agentInstance.name}. The agent does the following: ${agentInstance.description}.`,
|
|
3308
3662
|
config: [],
|
|
3309
|
-
execute: async ({ prompt,
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3663
|
+
execute: async ({ prompt, information, user, allExuluTools }) => {
|
|
3664
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
3665
|
+
if (!hasAccessToAgent) {
|
|
3666
|
+
throw new Error("You don't have access to this agent.");
|
|
3667
|
+
}
|
|
3668
|
+
let enabledTools = await getEnabledTools(agentInstance, allExuluTools, [], agents, user);
|
|
3669
|
+
const variableName = agentInstance.providerapikey;
|
|
3670
|
+
if (!variableName) {
|
|
3671
|
+
throw new Error("Provider API key variable not set for agent: " + agentInstance.name + " (" + agentInstance.id + ") being called as a tool.");
|
|
3672
|
+
}
|
|
3673
|
+
const { db: db3 } = await postgresClient();
|
|
3674
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
3675
|
+
if (!variable) {
|
|
3676
|
+
throw new Error("Provider API key variable not found for agent: " + agentInstance.name + " (" + agentInstance.id + ") being called as a tool.");
|
|
3677
|
+
}
|
|
3678
|
+
let providerapikey = variable.value;
|
|
3679
|
+
if (!variable.encrypted) {
|
|
3680
|
+
throw new Error("Provider API key variable not encrypted for agent: " + agentInstance.name + " (" + agentInstance.id + ") being called as a tool, for security reasons you are only allowed to use encrypted variables for provider API keys.");
|
|
3681
|
+
}
|
|
3682
|
+
if (variable.encrypted) {
|
|
3683
|
+
const bytes = import_crypto_js2.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
3684
|
+
providerapikey = bytes.toString(import_crypto_js2.default.enc.Utf8);
|
|
3685
|
+
}
|
|
3686
|
+
console.log("[EXULU] Enabled tools for agent '" + agentInstance.name + " (" + agentInstance.id + ") that is being called as a tool", enabledTools.map((x) => x.name + " (" + x.id + ")"));
|
|
3687
|
+
console.log("[EXULU] Prompt for agent '" + agentInstance.name + "' that is being called as a tool", prompt.slice(0, 100) + "...");
|
|
3688
|
+
console.log("[EXULU] Instructions for agent '" + agentInstance.name + "' that is being called as a tool", agentInstance.instructions?.slice(0, 100) + "...");
|
|
3689
|
+
const response = await this.generateSync({
|
|
3690
|
+
instructions: agentInstance.instructions,
|
|
3691
|
+
prompt: "The user has asked the following question: " + prompt + " and the following information is available: " + information,
|
|
3692
|
+
providerapikey,
|
|
3313
3693
|
user,
|
|
3314
|
-
|
|
3694
|
+
currentTools: enabledTools,
|
|
3695
|
+
allExuluTools,
|
|
3315
3696
|
statistics: {
|
|
3316
|
-
label:
|
|
3697
|
+
label: agentInstance.name,
|
|
3317
3698
|
trigger: "tool"
|
|
3318
3699
|
}
|
|
3319
3700
|
});
|
|
3701
|
+
return {
|
|
3702
|
+
result: response
|
|
3703
|
+
};
|
|
3320
3704
|
}
|
|
3321
3705
|
});
|
|
3322
3706
|
};
|
|
3323
|
-
generateSync = async ({
|
|
3707
|
+
generateSync = async ({
|
|
3708
|
+
prompt,
|
|
3709
|
+
user,
|
|
3710
|
+
session,
|
|
3711
|
+
message,
|
|
3712
|
+
currentTools,
|
|
3713
|
+
allExuluTools,
|
|
3714
|
+
statistics,
|
|
3715
|
+
toolConfigs,
|
|
3716
|
+
providerapikey,
|
|
3717
|
+
contexts,
|
|
3718
|
+
exuluConfig,
|
|
3719
|
+
filesContext: filesContext2,
|
|
3720
|
+
outputSchema,
|
|
3721
|
+
instructions
|
|
3722
|
+
}) => {
|
|
3723
|
+
console.log("[EXULU] Called generate sync for agent: " + this.name, "with prompt: " + prompt?.slice(0, 100) + "...");
|
|
3324
3724
|
if (!this.model) {
|
|
3325
3725
|
throw new Error("Model is required for streaming.");
|
|
3326
3726
|
}
|
|
@@ -3333,14 +3733,18 @@ var ExuluAgent2 = class {
|
|
|
3333
3733
|
if (!prompt && !message) {
|
|
3334
3734
|
throw new Error("Prompt or message is required for generating.");
|
|
3335
3735
|
}
|
|
3736
|
+
if (outputSchema && !prompt) {
|
|
3737
|
+
throw new Error("Prompt is required for generating with an output schema.");
|
|
3738
|
+
}
|
|
3336
3739
|
const model = this.model.create({
|
|
3337
|
-
apiKey:
|
|
3740
|
+
apiKey: providerapikey
|
|
3338
3741
|
});
|
|
3742
|
+
console.log("[EXULU] Model for agent: " + this.name, " created for generating sync.");
|
|
3339
3743
|
let messages = [];
|
|
3340
3744
|
if (message && session && user) {
|
|
3341
3745
|
const previousMessages = await getAgentMessages({
|
|
3342
3746
|
session,
|
|
3343
|
-
user,
|
|
3747
|
+
user: user.id,
|
|
3344
3748
|
limit: 50,
|
|
3345
3749
|
page: 1
|
|
3346
3750
|
});
|
|
@@ -3350,56 +3754,134 @@ var ExuluAgent2 = class {
|
|
|
3350
3754
|
messages: [...previousMessagesContent, message]
|
|
3351
3755
|
});
|
|
3352
3756
|
}
|
|
3353
|
-
console.log("[EXULU]
|
|
3354
|
-
|
|
3757
|
+
console.log("[EXULU] Message count for agent: " + this.name, "loaded for generating sync.", messages.length);
|
|
3758
|
+
const genericContext = "IMPORTANT: \n\n The current date is " + (/* @__PURE__ */ new Date()).toLocaleDateString() + " and the current time is " + (/* @__PURE__ */ new Date()).toLocaleTimeString() + ". If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume they are talking with the current date in mind as a reference.";
|
|
3759
|
+
let system = instructions || "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.";
|
|
3760
|
+
system += "\n\n" + genericContext;
|
|
3355
3761
|
if (prompt) {
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3762
|
+
let result = { object: null, text: "" };
|
|
3763
|
+
let tokens = 0;
|
|
3764
|
+
if (outputSchema) {
|
|
3765
|
+
const { object, usage } = await (0, import_ai.generateObject)({
|
|
3766
|
+
model,
|
|
3767
|
+
system,
|
|
3768
|
+
prompt,
|
|
3769
|
+
maxRetries: 3,
|
|
3770
|
+
schema: outputSchema
|
|
3771
|
+
});
|
|
3772
|
+
result.object = object;
|
|
3773
|
+
tokens = usage.totalTokens || 0;
|
|
3774
|
+
} else {
|
|
3775
|
+
console.log("[EXULU] Generating text for agent: " + this.name, "with prompt: " + prompt?.slice(0, 100) + "...");
|
|
3776
|
+
const { text, totalUsage } = await (0, import_ai.generateText)({
|
|
3777
|
+
model,
|
|
3778
|
+
system,
|
|
3779
|
+
prompt,
|
|
3780
|
+
maxRetries: 2,
|
|
3781
|
+
tools: convertToolsArrayToObject(
|
|
3782
|
+
currentTools,
|
|
3783
|
+
allExuluTools,
|
|
3784
|
+
toolConfigs,
|
|
3785
|
+
providerapikey,
|
|
3786
|
+
contexts,
|
|
3787
|
+
user,
|
|
3788
|
+
exuluConfig,
|
|
3789
|
+
filesContext2
|
|
3790
|
+
),
|
|
3791
|
+
stopWhen: [(0, import_ai.stepCountIs)(2)]
|
|
3374
3792
|
});
|
|
3793
|
+
result.text = text;
|
|
3794
|
+
tokens = totalUsage?.totalTokens || 0;
|
|
3375
3795
|
}
|
|
3376
|
-
|
|
3796
|
+
if (statistics) {
|
|
3797
|
+
await Promise.all([
|
|
3798
|
+
updateStatistic({
|
|
3799
|
+
name: "count",
|
|
3800
|
+
label: statistics.label,
|
|
3801
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3802
|
+
trigger: statistics.trigger,
|
|
3803
|
+
count: 1,
|
|
3804
|
+
user: user?.id,
|
|
3805
|
+
role: user?.role?.id
|
|
3806
|
+
}),
|
|
3807
|
+
...tokens ? [
|
|
3808
|
+
updateStatistic({
|
|
3809
|
+
name: "tokens",
|
|
3810
|
+
label: statistics.label,
|
|
3811
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3812
|
+
trigger: statistics.trigger,
|
|
3813
|
+
count: tokens
|
|
3814
|
+
})
|
|
3815
|
+
] : []
|
|
3816
|
+
]);
|
|
3817
|
+
}
|
|
3818
|
+
return result.text || result.object;
|
|
3377
3819
|
}
|
|
3378
3820
|
if (messages) {
|
|
3379
|
-
|
|
3821
|
+
console.log("[EXULU] Generating text for agent: " + this.name, "with messages: " + messages.length);
|
|
3822
|
+
const { text, totalUsage } = await (0, import_ai.generateText)({
|
|
3380
3823
|
model,
|
|
3381
3824
|
// Should be a LanguageModelV1
|
|
3382
|
-
system
|
|
3383
|
-
messages: (0, import_ai.convertToModelMessages)(messages
|
|
3825
|
+
system,
|
|
3826
|
+
messages: (0, import_ai.convertToModelMessages)(messages, {
|
|
3827
|
+
ignoreIncompleteToolCalls: true
|
|
3828
|
+
}),
|
|
3384
3829
|
maxRetries: 2,
|
|
3385
|
-
tools: convertToolsArrayToObject(
|
|
3386
|
-
|
|
3830
|
+
tools: convertToolsArrayToObject(
|
|
3831
|
+
currentTools,
|
|
3832
|
+
allExuluTools,
|
|
3833
|
+
toolConfigs,
|
|
3834
|
+
providerapikey,
|
|
3835
|
+
contexts,
|
|
3836
|
+
user,
|
|
3837
|
+
exuluConfig,
|
|
3838
|
+
filesContext2
|
|
3839
|
+
),
|
|
3840
|
+
stopWhen: [(0, import_ai.stepCountIs)(2)]
|
|
3387
3841
|
});
|
|
3388
3842
|
if (statistics) {
|
|
3389
|
-
await
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3843
|
+
await Promise.all([
|
|
3844
|
+
updateStatistic({
|
|
3845
|
+
name: "count",
|
|
3846
|
+
label: statistics.label,
|
|
3847
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3848
|
+
trigger: statistics.trigger,
|
|
3849
|
+
count: 1,
|
|
3850
|
+
user: user?.id,
|
|
3851
|
+
role: user?.role?.id
|
|
3852
|
+
}),
|
|
3853
|
+
...totalUsage?.totalTokens ? [
|
|
3854
|
+
updateStatistic({
|
|
3855
|
+
name: "tokens",
|
|
3856
|
+
label: statistics.label,
|
|
3857
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3858
|
+
trigger: statistics.trigger,
|
|
3859
|
+
count: totalUsage?.totalTokens,
|
|
3860
|
+
user: user?.id,
|
|
3861
|
+
role: user?.role?.id
|
|
3862
|
+
})
|
|
3863
|
+
] : []
|
|
3864
|
+
]);
|
|
3398
3865
|
}
|
|
3399
3866
|
return text;
|
|
3400
3867
|
}
|
|
3868
|
+
return "";
|
|
3401
3869
|
};
|
|
3402
|
-
generateStream = async ({
|
|
3870
|
+
generateStream = async ({
|
|
3871
|
+
express: express3,
|
|
3872
|
+
user,
|
|
3873
|
+
session,
|
|
3874
|
+
message,
|
|
3875
|
+
currentTools,
|
|
3876
|
+
allExuluTools,
|
|
3877
|
+
statistics,
|
|
3878
|
+
toolConfigs,
|
|
3879
|
+
providerapikey,
|
|
3880
|
+
contexts,
|
|
3881
|
+
exuluConfig,
|
|
3882
|
+
filesContext: filesContext2,
|
|
3883
|
+
instructions
|
|
3884
|
+
}) => {
|
|
3403
3885
|
if (!this.model) {
|
|
3404
3886
|
throw new Error("Model is required for streaming.");
|
|
3405
3887
|
}
|
|
@@ -3410,60 +3892,105 @@ var ExuluAgent2 = class {
|
|
|
3410
3892
|
throw new Error("Message is required for streaming.");
|
|
3411
3893
|
}
|
|
3412
3894
|
const model = this.model.create({
|
|
3413
|
-
apiKey:
|
|
3895
|
+
apiKey: providerapikey
|
|
3414
3896
|
});
|
|
3415
3897
|
let messages = [];
|
|
3416
3898
|
const previousMessages = await getAgentMessages({
|
|
3417
3899
|
session,
|
|
3418
|
-
user,
|
|
3900
|
+
user: user.id,
|
|
3419
3901
|
limit: 50,
|
|
3420
3902
|
page: 1
|
|
3421
3903
|
});
|
|
3422
|
-
const previousMessagesContent = previousMessages.map(
|
|
3904
|
+
const previousMessagesContent = previousMessages.map(
|
|
3905
|
+
(message2) => JSON.parse(message2.content)
|
|
3906
|
+
);
|
|
3423
3907
|
messages = await (0, import_ai.validateUIMessages)({
|
|
3424
3908
|
// append the new message to the previous messages:
|
|
3425
3909
|
messages: [...previousMessagesContent, message]
|
|
3426
3910
|
});
|
|
3911
|
+
const genericContext = "IMPORTANT: \n\n The current date is " + (/* @__PURE__ */ new Date()).toLocaleDateString() + " and the current time is " + (/* @__PURE__ */ new Date()).toLocaleTimeString() + ". If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume they are talking with the current date in mind as a reference.";
|
|
3912
|
+
let system = instructions || "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.";
|
|
3913
|
+
system += "\n\n" + genericContext;
|
|
3914
|
+
console.log("[EXULU] tools for agent: " + this.name, currentTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
3915
|
+
console.log("[EXULU] system", system.slice(0, 100) + "...");
|
|
3427
3916
|
const result = (0, import_ai.streamText)({
|
|
3428
3917
|
model,
|
|
3429
3918
|
// Should be a LanguageModelV1
|
|
3430
|
-
messages: (0, import_ai.convertToModelMessages)(messages
|
|
3431
|
-
|
|
3919
|
+
messages: (0, import_ai.convertToModelMessages)(messages, {
|
|
3920
|
+
ignoreIncompleteToolCalls: true
|
|
3921
|
+
}),
|
|
3922
|
+
// prepareStep could be used here to set the model for the first step or change other params
|
|
3923
|
+
system,
|
|
3432
3924
|
maxRetries: 2,
|
|
3433
|
-
tools: convertToolsArrayToObject(
|
|
3434
|
-
|
|
3435
|
-
|
|
3925
|
+
tools: convertToolsArrayToObject(
|
|
3926
|
+
currentTools,
|
|
3927
|
+
allExuluTools,
|
|
3928
|
+
toolConfigs,
|
|
3929
|
+
providerapikey,
|
|
3930
|
+
contexts,
|
|
3931
|
+
user,
|
|
3932
|
+
exuluConfig,
|
|
3933
|
+
filesContext2
|
|
3934
|
+
),
|
|
3935
|
+
onError: (error) => console.error("[EXULU] chat stream error.", error)
|
|
3936
|
+
// stopWhen: [stepCountIs(1)],
|
|
3436
3937
|
});
|
|
3437
3938
|
result.consumeStream();
|
|
3438
3939
|
result.pipeUIMessageStreamToResponse(express3.res, {
|
|
3940
|
+
messageMetadata: ({ part }) => {
|
|
3941
|
+
if (part.type === "finish") {
|
|
3942
|
+
return {
|
|
3943
|
+
totalTokens: part.totalUsage.totalTokens,
|
|
3944
|
+
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
3945
|
+
inputTokens: part.totalUsage.inputTokens,
|
|
3946
|
+
outputTokens: part.totalUsage.outputTokens,
|
|
3947
|
+
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
3948
|
+
};
|
|
3949
|
+
}
|
|
3950
|
+
},
|
|
3439
3951
|
originalMessages: messages,
|
|
3440
3952
|
sendReasoning: true,
|
|
3953
|
+
sendSources: true,
|
|
3954
|
+
onError: (error) => {
|
|
3955
|
+
console.error("[EXULU] chat response error.", error);
|
|
3956
|
+
return errorHandler(error);
|
|
3957
|
+
},
|
|
3441
3958
|
generateMessageId: (0, import_ai.createIdGenerator)({
|
|
3442
3959
|
prefix: "msg_",
|
|
3443
3960
|
size: 16
|
|
3444
3961
|
}),
|
|
3445
|
-
onFinish: async ({ messages: messages2 }) => {
|
|
3446
|
-
console.info(
|
|
3447
|
-
"[EXULU] chat stream finished.",
|
|
3448
|
-
messages2
|
|
3449
|
-
);
|
|
3962
|
+
onFinish: async ({ messages: messages2, isContinuation, isAborted, responseMessage }) => {
|
|
3450
3963
|
if (session) {
|
|
3451
3964
|
await saveChat({
|
|
3452
3965
|
session,
|
|
3453
|
-
user,
|
|
3454
|
-
messages: messages2
|
|
3966
|
+
user: user.id,
|
|
3967
|
+
messages: messages2.filter((x) => !previousMessagesContent.find((y) => y.id === x.id))
|
|
3455
3968
|
});
|
|
3456
3969
|
}
|
|
3970
|
+
const metadata = messages2[messages2.length - 1]?.metadata;
|
|
3457
3971
|
if (statistics) {
|
|
3458
|
-
await
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3972
|
+
await Promise.all([
|
|
3973
|
+
updateStatistic({
|
|
3974
|
+
name: "count",
|
|
3975
|
+
label: statistics.label,
|
|
3976
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3977
|
+
trigger: statistics.trigger,
|
|
3978
|
+
count: 1,
|
|
3979
|
+
user: user.id,
|
|
3980
|
+
role: user?.role?.id
|
|
3981
|
+
}),
|
|
3982
|
+
...metadata?.totalTokens ? [
|
|
3983
|
+
updateStatistic({
|
|
3984
|
+
name: "tokens",
|
|
3985
|
+
label: statistics.label,
|
|
3986
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3987
|
+
trigger: statistics.trigger,
|
|
3988
|
+
count: metadata?.totalTokens,
|
|
3989
|
+
user: user.id,
|
|
3990
|
+
role: user?.role?.id
|
|
3991
|
+
})
|
|
3992
|
+
] : []
|
|
3993
|
+
]);
|
|
3467
3994
|
}
|
|
3468
3995
|
}
|
|
3469
3996
|
});
|
|
@@ -3472,7 +3999,12 @@ var ExuluAgent2 = class {
|
|
|
3472
3999
|
};
|
|
3473
4000
|
var getAgentMessages = async ({ session, user, limit, page }) => {
|
|
3474
4001
|
const { db: db3 } = await postgresClient();
|
|
3475
|
-
|
|
4002
|
+
console.log("[EXULU] getting agent messages for session: " + session + " and user: " + user + " and page: " + page);
|
|
4003
|
+
const query = db3.from("agent_messages").where({ session, user }).limit(limit);
|
|
4004
|
+
if (page > 0) {
|
|
4005
|
+
query.offset((page - 1) * limit);
|
|
4006
|
+
}
|
|
4007
|
+
const messages = await query;
|
|
3476
4008
|
return messages;
|
|
3477
4009
|
};
|
|
3478
4010
|
var saveChat = async ({ session, user, messages }) => {
|
|
@@ -3582,31 +4114,31 @@ var ExuluEval = class {
|
|
|
3582
4114
|
throw new Error("Prompt is required for running an agent.");
|
|
3583
4115
|
}
|
|
3584
4116
|
const { db: db4 } = await postgresClient();
|
|
3585
|
-
const variableName = runner.agent.
|
|
4117
|
+
const variableName = runner.agent.providerapikey;
|
|
3586
4118
|
const variable = await db4.from("variables").where({ name: variableName }).first();
|
|
3587
4119
|
if (!variable) {
|
|
3588
|
-
throw new Error(`Provider API key for variable "${runner.agent.
|
|
4120
|
+
throw new Error(`Provider API key for variable "${runner.agent.providerapikey}" not found.`);
|
|
3589
4121
|
}
|
|
3590
|
-
let
|
|
4122
|
+
let providerapikey = variable.value;
|
|
3591
4123
|
if (!variable.encrypted) {
|
|
3592
|
-
throw new Error(`Provider API key for variable "${runner.agent.
|
|
4124
|
+
throw new Error(`Provider API key for variable "${runner.agent.providerapikey}" is not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys.`);
|
|
3593
4125
|
}
|
|
3594
4126
|
if (variable.encrypted) {
|
|
3595
4127
|
const bytes = import_crypto_js2.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
3596
|
-
|
|
4128
|
+
providerapikey = bytes.toString(import_crypto_js2.default.enc.Utf8);
|
|
3597
4129
|
}
|
|
3598
4130
|
const result = await runner.agent.generateSync({
|
|
3599
4131
|
prompt: data.prompt,
|
|
3600
|
-
|
|
4132
|
+
providerapikey
|
|
3601
4133
|
});
|
|
3602
4134
|
data.result = result;
|
|
3603
4135
|
}
|
|
3604
4136
|
const { object } = await (0, import_ai.generateObject)({
|
|
3605
4137
|
model,
|
|
3606
4138
|
maxRetries: 3,
|
|
3607
|
-
schema:
|
|
3608
|
-
correctnessScore:
|
|
3609
|
-
comment:
|
|
4139
|
+
schema: import_zod.z.object({
|
|
4140
|
+
correctnessScore: import_zod.z.number(),
|
|
4141
|
+
comment: import_zod.z.string()
|
|
3610
4142
|
}),
|
|
3611
4143
|
prompt: `You are checking if the below "actual_answers" contain the correct information as
|
|
3612
4144
|
presented in the "correct_answers" section to calculate the correctness score.
|
|
@@ -3677,7 +4209,7 @@ var ExuluTool2 = class {
|
|
|
3677
4209
|
this.type = type;
|
|
3678
4210
|
this.tool = (0, import_ai.tool)({
|
|
3679
4211
|
description,
|
|
3680
|
-
inputSchema: inputSchema ||
|
|
4212
|
+
inputSchema: inputSchema || import_zod.z.object({}),
|
|
3681
4213
|
execute: execute2
|
|
3682
4214
|
});
|
|
3683
4215
|
}
|
|
@@ -3756,10 +4288,6 @@ var ExuluContext = class {
|
|
|
3756
4288
|
label: statistics?.label || this.name,
|
|
3757
4289
|
trigger: statistics?.trigger || "agent"
|
|
3758
4290
|
}, user, role);
|
|
3759
|
-
const exists = await db3.schema.hasTable(getChunksTableName(this.id));
|
|
3760
|
-
if (!exists) {
|
|
3761
|
-
await this.createChunksTable();
|
|
3762
|
-
}
|
|
3763
4291
|
await db3.from(getChunksTableName(this.id)).where({ source }).delete();
|
|
3764
4292
|
await db3.from(getChunksTableName(this.id)).insert(chunks.map((chunk) => ({
|
|
3765
4293
|
source,
|
|
@@ -3776,26 +4304,120 @@ var ExuluContext = class {
|
|
|
3776
4304
|
job
|
|
3777
4305
|
};
|
|
3778
4306
|
};
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
4307
|
+
createItem = async (item, user, role, upsert) => {
|
|
4308
|
+
const { db: db3 } = await postgresClient();
|
|
4309
|
+
const mutation = db3.from(getTableName(
|
|
4310
|
+
this.id
|
|
4311
|
+
)).insert(
|
|
4312
|
+
{
|
|
4313
|
+
...item,
|
|
4314
|
+
tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
|
|
4315
|
+
}
|
|
4316
|
+
).returning("id");
|
|
4317
|
+
if (upsert) {
|
|
4318
|
+
mutation.onConflict().merge();
|
|
4319
|
+
}
|
|
4320
|
+
const results = await mutation;
|
|
4321
|
+
if (!results[0]) {
|
|
4322
|
+
throw new Error("Failed to create item.");
|
|
4323
|
+
}
|
|
4324
|
+
if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
|
|
4325
|
+
const { job } = await this.embeddings.generate.one({
|
|
4326
|
+
item: results[0],
|
|
3783
4327
|
user,
|
|
3784
4328
|
role,
|
|
3785
|
-
trigger
|
|
3786
|
-
})
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
4329
|
+
trigger: "api"
|
|
4330
|
+
});
|
|
4331
|
+
return {
|
|
4332
|
+
item: results[0],
|
|
4333
|
+
job
|
|
4334
|
+
};
|
|
4335
|
+
}
|
|
4336
|
+
return {
|
|
4337
|
+
item: results[0],
|
|
4338
|
+
job: void 0
|
|
4339
|
+
};
|
|
4340
|
+
};
|
|
4341
|
+
updateItem = async (item, user, role) => {
|
|
4342
|
+
const { db: db3 } = await postgresClient();
|
|
4343
|
+
const record = await db3.from(
|
|
4344
|
+
getTableName(this.id)
|
|
4345
|
+
).where(
|
|
4346
|
+
{ id: item.id }
|
|
4347
|
+
).first();
|
|
4348
|
+
if (!record) {
|
|
4349
|
+
throw new Error("Item not found.");
|
|
4350
|
+
}
|
|
4351
|
+
const mutation = db3.from(
|
|
4352
|
+
getTableName(this.id)
|
|
4353
|
+
).where(
|
|
4354
|
+
{ id: record.id }
|
|
4355
|
+
).update(
|
|
4356
|
+
{
|
|
4357
|
+
...item,
|
|
4358
|
+
tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
|
|
4359
|
+
}
|
|
4360
|
+
).returning("id");
|
|
4361
|
+
await mutation;
|
|
4362
|
+
if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
|
|
4363
|
+
const { job } = await this.embeddings.generate.one({
|
|
4364
|
+
item: record,
|
|
4365
|
+
// important we need to full record here with all fields
|
|
4366
|
+
user,
|
|
4367
|
+
role,
|
|
4368
|
+
trigger: "api"
|
|
4369
|
+
});
|
|
4370
|
+
return {
|
|
4371
|
+
item: record,
|
|
4372
|
+
job
|
|
4373
|
+
};
|
|
4374
|
+
}
|
|
4375
|
+
return {
|
|
4376
|
+
item: record,
|
|
4377
|
+
job: void 0
|
|
4378
|
+
};
|
|
4379
|
+
};
|
|
4380
|
+
deleteItem = async (item, user, role) => {
|
|
4381
|
+
if (!item.id) {
|
|
4382
|
+
throw new Error("Item id is required for deleting item.");
|
|
4383
|
+
}
|
|
4384
|
+
const { db: db3 } = await postgresClient();
|
|
4385
|
+
await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
|
|
4386
|
+
if (!this.embedder) {
|
|
4387
|
+
return {
|
|
4388
|
+
id: item.id,
|
|
4389
|
+
job: void 0
|
|
4390
|
+
};
|
|
4391
|
+
}
|
|
4392
|
+
const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
|
|
4393
|
+
if (chunks.length > 0) {
|
|
4394
|
+
await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
|
|
4395
|
+
}
|
|
4396
|
+
return {
|
|
4397
|
+
id: item.id,
|
|
4398
|
+
job: void 0
|
|
4399
|
+
};
|
|
4400
|
+
};
|
|
4401
|
+
embeddings = {
|
|
4402
|
+
generate: {
|
|
4403
|
+
one: async ({
|
|
4404
|
+
item,
|
|
4405
|
+
user,
|
|
4406
|
+
role,
|
|
4407
|
+
trigger
|
|
4408
|
+
}) => {
|
|
4409
|
+
console.log("[EXULU] Generating embeddings for item", item.id);
|
|
4410
|
+
if (!this.embedder) {
|
|
4411
|
+
throw new Error("Embedder is not set for this context.");
|
|
4412
|
+
}
|
|
4413
|
+
if (!item.id) {
|
|
4414
|
+
throw new Error("Item id is required for generating embeddings.");
|
|
4415
|
+
}
|
|
4416
|
+
if (this.embedder.queue?.name) {
|
|
4417
|
+
console.log("[EXULU] embedder is in queue mode, scheduling job.");
|
|
4418
|
+
const job = await bullmqDecorator({
|
|
4419
|
+
label: `${this.embedder.name}`,
|
|
4420
|
+
embedder: this.embedder.id,
|
|
3799
4421
|
context: this.id,
|
|
3800
4422
|
inputs: item,
|
|
3801
4423
|
item: item.id,
|
|
@@ -3843,301 +4465,6 @@ var ExuluContext = class {
|
|
|
3843
4465
|
}
|
|
3844
4466
|
}
|
|
3845
4467
|
};
|
|
3846
|
-
getItems = async ({
|
|
3847
|
-
statistics,
|
|
3848
|
-
limit,
|
|
3849
|
-
sort,
|
|
3850
|
-
order,
|
|
3851
|
-
page,
|
|
3852
|
-
name,
|
|
3853
|
-
user,
|
|
3854
|
-
role,
|
|
3855
|
-
archived,
|
|
3856
|
-
query,
|
|
3857
|
-
method
|
|
3858
|
-
}) => {
|
|
3859
|
-
if (!query && limit > 500) {
|
|
3860
|
-
throw new Error("Limit cannot be greater than 500.");
|
|
3861
|
-
}
|
|
3862
|
-
if (query && limit > 50) {
|
|
3863
|
-
throw new Error("Limit cannot be greater than 50 when using a vector search query.");
|
|
3864
|
-
}
|
|
3865
|
-
if (page < 1) page = 1;
|
|
3866
|
-
if (limit < 1) limit = 10;
|
|
3867
|
-
let offset = (page - 1) * limit;
|
|
3868
|
-
const mainTable = getTableName(this.id);
|
|
3869
|
-
const { db: db3 } = await postgresClient();
|
|
3870
|
-
const columns = await db3(mainTable).columnInfo();
|
|
3871
|
-
const totalQuery = db3.count("* as count").from(mainTable).first();
|
|
3872
|
-
const itemsQuery = db3.select(Object.keys(columns).map((column) => mainTable + "." + column)).from(mainTable).offset(offset).limit(limit);
|
|
3873
|
-
if (sort) {
|
|
3874
|
-
itemsQuery.orderBy(sort, order === "desc" ? "desc" : "asc");
|
|
3875
|
-
}
|
|
3876
|
-
if (typeof name === "string") {
|
|
3877
|
-
itemsQuery.whereILike("name", `%${name}%`);
|
|
3878
|
-
totalQuery.whereILike("name", `%${name}%`);
|
|
3879
|
-
}
|
|
3880
|
-
if (typeof archived === "boolean") {
|
|
3881
|
-
itemsQuery.where("archived", archived);
|
|
3882
|
-
totalQuery.where("archived", archived);
|
|
3883
|
-
}
|
|
3884
|
-
if (!query) {
|
|
3885
|
-
const total = await totalQuery;
|
|
3886
|
-
let items = await itemsQuery;
|
|
3887
|
-
const last = Math.ceil(total.count / limit);
|
|
3888
|
-
return {
|
|
3889
|
-
pagination: {
|
|
3890
|
-
totalCount: parseInt(total.count),
|
|
3891
|
-
currentPage: page,
|
|
3892
|
-
limit,
|
|
3893
|
-
from: offset,
|
|
3894
|
-
pageCount: last || 1,
|
|
3895
|
-
to: offset + items.length,
|
|
3896
|
-
lastPage: last || 1,
|
|
3897
|
-
nextPage: page + 1 > last ? null : page + 1,
|
|
3898
|
-
previousPage: page - 1 || null
|
|
3899
|
-
},
|
|
3900
|
-
filters: {
|
|
3901
|
-
archived,
|
|
3902
|
-
name,
|
|
3903
|
-
query
|
|
3904
|
-
},
|
|
3905
|
-
context: {
|
|
3906
|
-
name: this.name,
|
|
3907
|
-
id: this.id,
|
|
3908
|
-
embedder: this.embedder?.name || void 0
|
|
3909
|
-
},
|
|
3910
|
-
items
|
|
3911
|
-
};
|
|
3912
|
-
}
|
|
3913
|
-
if (typeof query === "string" && this.embedder) {
|
|
3914
|
-
if (!method) {
|
|
3915
|
-
method = "cosineDistance";
|
|
3916
|
-
}
|
|
3917
|
-
itemsQuery.limit(limit * 5);
|
|
3918
|
-
if (statistics) {
|
|
3919
|
-
await updateStatistic({
|
|
3920
|
-
name: "count",
|
|
3921
|
-
label: statistics.label,
|
|
3922
|
-
type: STATISTICS_TYPE_ENUM.CONTEXT_RETRIEVE,
|
|
3923
|
-
trigger: statistics.trigger,
|
|
3924
|
-
user,
|
|
3925
|
-
role
|
|
3926
|
-
});
|
|
3927
|
-
}
|
|
3928
|
-
if (this.queryRewriter) {
|
|
3929
|
-
query = await this.queryRewriter(query);
|
|
3930
|
-
}
|
|
3931
|
-
const chunksTable = getChunksTableName(this.id);
|
|
3932
|
-
itemsQuery.leftJoin(chunksTable, function() {
|
|
3933
|
-
this.on(chunksTable + ".source", "=", mainTable + ".id");
|
|
3934
|
-
});
|
|
3935
|
-
itemsQuery.select(chunksTable + ".id as chunk_id");
|
|
3936
|
-
itemsQuery.select(chunksTable + ".source");
|
|
3937
|
-
itemsQuery.select(chunksTable + ".content");
|
|
3938
|
-
itemsQuery.select(chunksTable + ".chunk_index");
|
|
3939
|
-
itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
|
|
3940
|
-
itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
|
|
3941
|
-
const { chunks } = await this.embedder.generateFromQuery(query, {
|
|
3942
|
-
label: this.name,
|
|
3943
|
-
trigger: "agent"
|
|
3944
|
-
}, user, role);
|
|
3945
|
-
if (!chunks?.[0]?.vector) {
|
|
3946
|
-
throw new Error("No vector generated for query.");
|
|
3947
|
-
}
|
|
3948
|
-
const vector = chunks[0].vector;
|
|
3949
|
-
const vectorStr = `ARRAY[${vector.join(",")}]`;
|
|
3950
|
-
const vectorExpr = `${vectorStr}::vector`;
|
|
3951
|
-
const language = this.configuration.language || "english";
|
|
3952
|
-
let items = [];
|
|
3953
|
-
switch (method) {
|
|
3954
|
-
case "tsvector":
|
|
3955
|
-
itemsQuery.select(db3.raw(
|
|
3956
|
-
`ts_rank(${chunksTable}.fts, websearch_to_tsquery(?, ?)) as fts_rank`,
|
|
3957
|
-
[language, query]
|
|
3958
|
-
)).whereRaw(
|
|
3959
|
-
`${chunksTable}.fts @@ websearch_to_tsquery(?, ?)`,
|
|
3960
|
-
[language, query]
|
|
3961
|
-
).orderByRaw(`fts_rank DESC`);
|
|
3962
|
-
items = await itemsQuery;
|
|
3963
|
-
break;
|
|
3964
|
-
case "cosineDistance":
|
|
3965
|
-
default:
|
|
3966
|
-
itemsQuery.whereNotNull(`${chunksTable}.embedding`);
|
|
3967
|
-
itemsQuery.select(
|
|
3968
|
-
db3.raw(`1 - (${chunksTable}.embedding <=> ${vectorExpr}) AS cosine_distance`)
|
|
3969
|
-
);
|
|
3970
|
-
itemsQuery.orderByRaw(
|
|
3971
|
-
`${chunksTable}.embedding <=> ${vectorExpr} ASC NULLS LAST`
|
|
3972
|
-
);
|
|
3973
|
-
items = await itemsQuery;
|
|
3974
|
-
break;
|
|
3975
|
-
case "hybridSearch":
|
|
3976
|
-
const matchCount = Math.min(limit * 5, 30);
|
|
3977
|
-
const fullTextWeight = 1;
|
|
3978
|
-
const semanticWeight = 1;
|
|
3979
|
-
const rrfK = 50;
|
|
3980
|
-
const hybridSQL = `
|
|
3981
|
-
WITH full_text AS (
|
|
3982
|
-
SELECT
|
|
3983
|
-
c.id,
|
|
3984
|
-
c.source,
|
|
3985
|
-
row_number() OVER (
|
|
3986
|
-
ORDER BY ts_rank_cd(c.fts, websearch_to_tsquery(?, ?)) DESC
|
|
3987
|
-
) AS rank_ix
|
|
3988
|
-
FROM ${chunksTable} c
|
|
3989
|
-
WHERE c.fts @@ websearch_to_tsquery(?, ?)
|
|
3990
|
-
ORDER BY rank_ix
|
|
3991
|
-
LIMIT LEAST(?, 30) * 2
|
|
3992
|
-
),
|
|
3993
|
-
semantic AS (
|
|
3994
|
-
SELECT
|
|
3995
|
-
c.id,
|
|
3996
|
-
c.source,
|
|
3997
|
-
row_number() OVER (
|
|
3998
|
-
ORDER BY c.embedding <=> ${vectorExpr} ASC
|
|
3999
|
-
) AS rank_ix
|
|
4000
|
-
FROM ${chunksTable} c
|
|
4001
|
-
WHERE c.embedding IS NOT NULL
|
|
4002
|
-
ORDER BY rank_ix
|
|
4003
|
-
LIMIT LEAST(?, 30) * 2
|
|
4004
|
-
)
|
|
4005
|
-
SELECT
|
|
4006
|
-
m.*,
|
|
4007
|
-
c.id AS chunk_id,
|
|
4008
|
-
c.source,
|
|
4009
|
-
c.content,
|
|
4010
|
-
c.chunk_index,
|
|
4011
|
-
c.created_at AS chunk_created_at,
|
|
4012
|
-
c.updated_at AS chunk_updated_at,
|
|
4013
|
-
|
|
4014
|
-
/* Per-signal scores for introspection */
|
|
4015
|
-
ts_rank(c.fts, websearch_to_tsquery(?, ?)) AS fts_rank,
|
|
4016
|
-
(1 - (c.embedding <=> ${vectorExpr})) AS cosine_distance,
|
|
4017
|
-
|
|
4018
|
-
/* Hybrid RRF score */
|
|
4019
|
-
(
|
|
4020
|
-
COALESCE(1.0 / (? + ft.rank_ix), 0.0) * ?
|
|
4021
|
-
+
|
|
4022
|
-
COALESCE(1.0 / (? + se.rank_ix), 0.0) * ?
|
|
4023
|
-
)::float AS hybrid_score
|
|
4024
|
-
|
|
4025
|
-
FROM full_text ft
|
|
4026
|
-
FULL OUTER JOIN semantic se
|
|
4027
|
-
ON ft.id = se.id
|
|
4028
|
-
JOIN ${chunksTable} c
|
|
4029
|
-
ON COALESCE(ft.id, se.id) = c.id
|
|
4030
|
-
JOIN ${mainTable} m
|
|
4031
|
-
ON m.id = c.source
|
|
4032
|
-
ORDER BY hybrid_score DESC
|
|
4033
|
-
LIMIT LEAST(?, 30)
|
|
4034
|
-
OFFSET 0
|
|
4035
|
-
`;
|
|
4036
|
-
const bindings = [
|
|
4037
|
-
// full_text: websearch_to_tsquery(lang, query) in rank and where
|
|
4038
|
-
language,
|
|
4039
|
-
query,
|
|
4040
|
-
language,
|
|
4041
|
-
query,
|
|
4042
|
-
matchCount,
|
|
4043
|
-
// full_text limit
|
|
4044
|
-
matchCount,
|
|
4045
|
-
// semantic limit
|
|
4046
|
-
// fts_rank (ts_rank) call
|
|
4047
|
-
language,
|
|
4048
|
-
query,
|
|
4049
|
-
// RRF fusion parameters
|
|
4050
|
-
rrfK,
|
|
4051
|
-
fullTextWeight,
|
|
4052
|
-
rrfK,
|
|
4053
|
-
semanticWeight,
|
|
4054
|
-
matchCount
|
|
4055
|
-
// final limit
|
|
4056
|
-
];
|
|
4057
|
-
items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
|
|
4058
|
-
}
|
|
4059
|
-
console.log("items", items);
|
|
4060
|
-
const seenSources = /* @__PURE__ */ new Map();
|
|
4061
|
-
items = items.reduce((acc, item) => {
|
|
4062
|
-
if (!seenSources.has(item.source)) {
|
|
4063
|
-
seenSources.set(item.source, {
|
|
4064
|
-
...Object.fromEntries(
|
|
4065
|
-
Object.keys(item).filter(
|
|
4066
|
-
(key) => key !== "cosine_distance" && // kept per chunk below
|
|
4067
|
-
key !== "fts_rank" && // kept per chunk below
|
|
4068
|
-
key !== "hybrid_score" && // we will compute per item below
|
|
4069
|
-
key !== "content" && key !== "source" && key !== "chunk_index" && key !== "chunk_id" && key !== "chunk_created_at" && key !== "chunk_updated_at"
|
|
4070
|
-
).map((key) => [key, item[key]])
|
|
4071
|
-
),
|
|
4072
|
-
chunks: [{
|
|
4073
|
-
content: item.content,
|
|
4074
|
-
chunk_index: item.chunk_index,
|
|
4075
|
-
...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
|
|
4076
|
-
...(method === "tsvector" || method === "hybridSearch") && { fts_rank: item.fts_rank },
|
|
4077
|
-
...method === "hybridSearch" && { hybrid_score: item.hybrid_score }
|
|
4078
|
-
}]
|
|
4079
|
-
});
|
|
4080
|
-
acc.push(seenSources.get(item.source));
|
|
4081
|
-
} else {
|
|
4082
|
-
seenSources.get(item.source).chunks.push({
|
|
4083
|
-
content: item.content,
|
|
4084
|
-
chunk_index: item.chunk_index,
|
|
4085
|
-
...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
|
|
4086
|
-
...(method === "tsvector" || method === "hybridSearch") && { fts_rank: item.fts_rank },
|
|
4087
|
-
...method === "hybridSearch" && { hybrid_score: item.hybrid_score }
|
|
4088
|
-
});
|
|
4089
|
-
}
|
|
4090
|
-
return acc;
|
|
4091
|
-
}, []);
|
|
4092
|
-
console.log("items", items);
|
|
4093
|
-
items.forEach((item) => {
|
|
4094
|
-
if (!item.chunks?.length) {
|
|
4095
|
-
return;
|
|
4096
|
-
}
|
|
4097
|
-
if (method === "tsvector") {
|
|
4098
|
-
const ranks = item.chunks.map((c) => typeof c.fts_rank === "number" ? c.fts_rank : 0);
|
|
4099
|
-
const total = ranks.reduce((a, b) => a + b, 0);
|
|
4100
|
-
const average = ranks.length ? total / ranks.length : 0;
|
|
4101
|
-
item.averageRelevance = average;
|
|
4102
|
-
item.totalRelevance = total;
|
|
4103
|
-
} else if (method === "cosineDistance") {
|
|
4104
|
-
let methodProperty = "cosine_distance";
|
|
4105
|
-
const average = item.chunks.reduce((acc, item2) => {
|
|
4106
|
-
return acc + item2[methodProperty];
|
|
4107
|
-
}, 0) / item.chunks.length;
|
|
4108
|
-
const total = item.chunks.reduce((acc, item2) => {
|
|
4109
|
-
return acc + item2[methodProperty];
|
|
4110
|
-
}, 0);
|
|
4111
|
-
item.averageRelevance = average;
|
|
4112
|
-
item.totalRelevance = total;
|
|
4113
|
-
} else if (method === "hybridSearch") {
|
|
4114
|
-
console.log("item.chunks", item.chunks);
|
|
4115
|
-
const scores = item.chunks.map((c) => typeof c.hybrid_score === "number" ? c.hybrid_score * 10 + 1 : 0);
|
|
4116
|
-
const total = scores.reduce((a, b) => a + b, 0);
|
|
4117
|
-
const average = scores.length ? total / scores.length : 0;
|
|
4118
|
-
item.averageRelevance = average;
|
|
4119
|
-
item.totalRelevance = total;
|
|
4120
|
-
}
|
|
4121
|
-
});
|
|
4122
|
-
if (this.resultReranker && query) {
|
|
4123
|
-
items = await this.resultReranker(items);
|
|
4124
|
-
}
|
|
4125
|
-
items = items.slice(0, limit);
|
|
4126
|
-
return {
|
|
4127
|
-
filters: {
|
|
4128
|
-
archived,
|
|
4129
|
-
name,
|
|
4130
|
-
query
|
|
4131
|
-
},
|
|
4132
|
-
context: {
|
|
4133
|
-
name: this.name,
|
|
4134
|
-
id: this.id,
|
|
4135
|
-
embedder: this.embedder.name
|
|
4136
|
-
},
|
|
4137
|
-
items
|
|
4138
|
-
};
|
|
4139
|
-
}
|
|
4140
|
-
};
|
|
4141
4468
|
createItemsTable = async () => {
|
|
4142
4469
|
const { db: db3 } = await postgresClient();
|
|
4143
4470
|
const tableName = getTableName(this.id);
|
|
@@ -4151,6 +4478,7 @@ var ExuluContext = class {
|
|
|
4151
4478
|
table.boolean("archived").defaultTo(false);
|
|
4152
4479
|
table.text("external_id");
|
|
4153
4480
|
table.text("created_by");
|
|
4481
|
+
table.text("ttl");
|
|
4154
4482
|
table.text("rights_mode").defaultTo(this.configuration?.defaultRightsMode ?? "private");
|
|
4155
4483
|
table.integer("textlength");
|
|
4156
4484
|
table.text("source");
|
|
@@ -4168,7 +4496,7 @@ var ExuluContext = class {
|
|
|
4168
4496
|
});
|
|
4169
4497
|
};
|
|
4170
4498
|
createChunksTable = async () => {
|
|
4171
|
-
const { db: db3 } = await
|
|
4499
|
+
const { db: db3 } = await refreshPostgresClient();
|
|
4172
4500
|
const tableName = getChunksTableName(this.id);
|
|
4173
4501
|
console.log("[EXULU] Creating table: " + tableName);
|
|
4174
4502
|
await db3.schema.createTable(tableName, (table) => {
|
|
@@ -4204,14 +4532,14 @@ var ExuluContext = class {
|
|
|
4204
4532
|
id: this.id,
|
|
4205
4533
|
name: `${this.name}`,
|
|
4206
4534
|
type: "context",
|
|
4207
|
-
inputSchema:
|
|
4208
|
-
query:
|
|
4535
|
+
inputSchema: import_zod.z.object({
|
|
4536
|
+
query: import_zod.z.string()
|
|
4209
4537
|
}),
|
|
4210
4538
|
config: [],
|
|
4211
4539
|
description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
|
|
4212
4540
|
execute: async ({ query, user, role }) => {
|
|
4213
4541
|
const { db: db3 } = await postgresClient();
|
|
4214
|
-
await vectorSearch({
|
|
4542
|
+
const result = await vectorSearch({
|
|
4215
4543
|
page: 1,
|
|
4216
4544
|
limit: 10,
|
|
4217
4545
|
query,
|
|
@@ -4224,6 +4552,9 @@ var ExuluContext = class {
|
|
|
4224
4552
|
sort: void 0,
|
|
4225
4553
|
trigger: "agent"
|
|
4226
4554
|
});
|
|
4555
|
+
return {
|
|
4556
|
+
items: result.items
|
|
4557
|
+
};
|
|
4227
4558
|
}
|
|
4228
4559
|
});
|
|
4229
4560
|
};
|
|
@@ -4239,7 +4570,6 @@ var updateStatistic = async (statistic) => {
|
|
|
4239
4570
|
type: statistic.type,
|
|
4240
4571
|
createdAt: currentDate
|
|
4241
4572
|
}).first();
|
|
4242
|
-
console.log("!!! existing !!!", existing);
|
|
4243
4573
|
if (!existing) {
|
|
4244
4574
|
await db3.from("tracking").insert({
|
|
4245
4575
|
name: statistic.name,
|
|
@@ -4261,6 +4591,53 @@ var updateStatistic = async (statistic) => {
|
|
|
4261
4591
|
});
|
|
4262
4592
|
}
|
|
4263
4593
|
};
|
|
4594
|
+
var generateS3Key = (filename) => `${(0, import_node_crypto.randomUUID)()}-${filename}`;
|
|
4595
|
+
var getMimeType = (type) => {
|
|
4596
|
+
switch (type) {
|
|
4597
|
+
case ".png":
|
|
4598
|
+
return "image/png";
|
|
4599
|
+
case ".jpg":
|
|
4600
|
+
return "image/jpg";
|
|
4601
|
+
case ".jpeg":
|
|
4602
|
+
return "image/jpeg";
|
|
4603
|
+
case ".gif":
|
|
4604
|
+
return "image/gif";
|
|
4605
|
+
case ".webp":
|
|
4606
|
+
return "image/webp";
|
|
4607
|
+
case ".pdf":
|
|
4608
|
+
return "application/pdf";
|
|
4609
|
+
case ".docx":
|
|
4610
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
4611
|
+
case ".xlsx":
|
|
4612
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
4613
|
+
case ".xls":
|
|
4614
|
+
return "application/vnd.ms-excel";
|
|
4615
|
+
case ".csv":
|
|
4616
|
+
return "text/csv";
|
|
4617
|
+
case ".pptx":
|
|
4618
|
+
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
4619
|
+
case ".ppt":
|
|
4620
|
+
return "application/vnd.ms-powerpoint";
|
|
4621
|
+
case ".m4a":
|
|
4622
|
+
return "audio/mp4";
|
|
4623
|
+
case ".mp4":
|
|
4624
|
+
return "audio/mp4";
|
|
4625
|
+
case ".mpeg":
|
|
4626
|
+
return "audio/mpeg";
|
|
4627
|
+
case ".mp3":
|
|
4628
|
+
return "audio/mp3";
|
|
4629
|
+
case ".wav":
|
|
4630
|
+
return "audio/wav";
|
|
4631
|
+
case ".txt":
|
|
4632
|
+
return "text/plain";
|
|
4633
|
+
case ".md":
|
|
4634
|
+
return "text/markdown";
|
|
4635
|
+
case ".json":
|
|
4636
|
+
return "application/json";
|
|
4637
|
+
default:
|
|
4638
|
+
return "";
|
|
4639
|
+
}
|
|
4640
|
+
};
|
|
4264
4641
|
|
|
4265
4642
|
// src/registry/index.ts
|
|
4266
4643
|
var import_express7 = require("express");
|
|
@@ -4268,42 +4645,6 @@ var import_express7 = require("express");
|
|
|
4268
4645
|
// src/registry/routes.ts
|
|
4269
4646
|
var import_express3 = require("express");
|
|
4270
4647
|
|
|
4271
|
-
// src/registry/rate-limiter.ts
|
|
4272
|
-
var rateLimiter = async (key, windowSeconds, limit, points) => {
|
|
4273
|
-
try {
|
|
4274
|
-
const { client: client2 } = await redisClient();
|
|
4275
|
-
if (!client2) {
|
|
4276
|
-
console.warn("[EXULU] Rate limiting disabled - Redis not available");
|
|
4277
|
-
return {
|
|
4278
|
-
status: true,
|
|
4279
|
-
retryAfter: null
|
|
4280
|
-
};
|
|
4281
|
-
}
|
|
4282
|
-
const redisKey = `exulu/${key}`;
|
|
4283
|
-
const current = await client2.incrBy(redisKey, points);
|
|
4284
|
-
if (current === points) {
|
|
4285
|
-
await client2.expire(redisKey, windowSeconds);
|
|
4286
|
-
}
|
|
4287
|
-
if (current > limit) {
|
|
4288
|
-
const ttl = await client2.ttl(redisKey);
|
|
4289
|
-
return {
|
|
4290
|
-
status: false,
|
|
4291
|
-
retryAfter: ttl
|
|
4292
|
-
};
|
|
4293
|
-
}
|
|
4294
|
-
return {
|
|
4295
|
-
status: true,
|
|
4296
|
-
retryAfter: null
|
|
4297
|
-
};
|
|
4298
|
-
} catch (error) {
|
|
4299
|
-
console.error("[EXULU] Rate limiting error:", error);
|
|
4300
|
-
return {
|
|
4301
|
-
status: true,
|
|
4302
|
-
retryAfter: null
|
|
4303
|
-
};
|
|
4304
|
-
}
|
|
4305
|
-
};
|
|
4306
|
-
|
|
4307
4648
|
// src/bullmq/queues.ts
|
|
4308
4649
|
var import_bullmq4 = require("bullmq");
|
|
4309
4650
|
var import_bullmq_otel = require("bullmq-otel");
|
|
@@ -4346,10 +4687,10 @@ var import_express5 = require("@as-integrations/express5");
|
|
|
4346
4687
|
|
|
4347
4688
|
// src/registry/uppy.ts
|
|
4348
4689
|
var import_express2 = require("express");
|
|
4349
|
-
var
|
|
4690
|
+
var import_client_s32 = require("@aws-sdk/client-s3");
|
|
4350
4691
|
var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
4351
4692
|
var import_client_sts = require("@aws-sdk/client-sts");
|
|
4352
|
-
var
|
|
4693
|
+
var import_node_crypto2 = require("crypto");
|
|
4353
4694
|
var createUppyRoutes = async (app, config) => {
|
|
4354
4695
|
const policy = {
|
|
4355
4696
|
Version: "2012-10-17",
|
|
@@ -4366,11 +4707,11 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4366
4707
|
}
|
|
4367
4708
|
]
|
|
4368
4709
|
};
|
|
4369
|
-
let
|
|
4710
|
+
let s3Client2;
|
|
4370
4711
|
let stsClient;
|
|
4371
4712
|
const expiresIn = 60 * 60 * 24 * 1;
|
|
4372
4713
|
function getS3Client() {
|
|
4373
|
-
|
|
4714
|
+
s3Client2 ??= new import_client_s32.S3Client({
|
|
4374
4715
|
region: config.fileUploads.s3region,
|
|
4375
4716
|
...config.fileUploads.s3endpoint && {
|
|
4376
4717
|
forcePathStyle: true,
|
|
@@ -4381,7 +4722,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4381
4722
|
secretAccessKey: config.fileUploads.s3secret
|
|
4382
4723
|
}
|
|
4383
4724
|
});
|
|
4384
|
-
return
|
|
4725
|
+
return s3Client2;
|
|
4385
4726
|
}
|
|
4386
4727
|
function getSTSClient() {
|
|
4387
4728
|
stsClient ??= new import_client_sts.STSClient({
|
|
@@ -4394,54 +4735,51 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4394
4735
|
});
|
|
4395
4736
|
return stsClient;
|
|
4396
4737
|
}
|
|
4397
|
-
app.
|
|
4398
|
-
req.accepts;
|
|
4738
|
+
app.delete("/s3/delete", async (req, res, next) => {
|
|
4399
4739
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
4740
|
+
const internalkey = req.headers["internal-key"] || null;
|
|
4741
|
+
const { db: db3 } = await postgresClient();
|
|
4400
4742
|
let authtoken = null;
|
|
4401
|
-
if (typeof apikey !== "string") {
|
|
4743
|
+
if (typeof apikey !== "string" && typeof internalkey !== "string") {
|
|
4402
4744
|
authtoken = await getToken(req.headers.authorization ?? "");
|
|
4403
4745
|
}
|
|
4404
|
-
const { db: db3 } = await postgresClient();
|
|
4405
4746
|
const authenticationResult = await authentication({
|
|
4406
4747
|
authtoken,
|
|
4407
4748
|
apikey,
|
|
4749
|
+
internalkey,
|
|
4408
4750
|
db: db3
|
|
4409
4751
|
});
|
|
4410
4752
|
if (!authenticationResult.user?.id) {
|
|
4411
4753
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4412
4754
|
return;
|
|
4413
4755
|
}
|
|
4414
|
-
const {
|
|
4415
|
-
if (typeof
|
|
4416
|
-
res.status(400).json({ error: "
|
|
4756
|
+
const { key } = req.query;
|
|
4757
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
4758
|
+
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4417
4759
|
return;
|
|
4418
4760
|
}
|
|
4419
|
-
|
|
4420
|
-
|
|
4761
|
+
const userPrefix = key.split("/")[0];
|
|
4762
|
+
console.log("userPrefix", userPrefix);
|
|
4763
|
+
console.log("authenticationResult.user.id", authenticationResult.user.id);
|
|
4764
|
+
if (!userPrefix) {
|
|
4765
|
+
res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
|
|
4421
4766
|
return;
|
|
4422
4767
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
Prefix: prefix,
|
|
4427
|
-
MaxKeys: 1e3
|
|
4428
|
-
// Adjust this value based on your needs
|
|
4429
|
-
});
|
|
4430
|
-
const data = await getS3Client().send(command);
|
|
4431
|
-
const files = data.Contents?.map((item) => ({
|
|
4432
|
-
key: item.Key,
|
|
4433
|
-
size: item.Size,
|
|
4434
|
-
lastModified: item.LastModified
|
|
4435
|
-
})) || [];
|
|
4436
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4437
|
-
res.status(200).json({
|
|
4438
|
-
files,
|
|
4439
|
-
isTruncated: data.IsTruncated,
|
|
4440
|
-
nextContinuationToken: data.NextContinuationToken
|
|
4441
|
-
});
|
|
4442
|
-
} catch (err) {
|
|
4443
|
-
next(err);
|
|
4768
|
+
if (userPrefix !== authenticationResult.user.id.toString()) {
|
|
4769
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4770
|
+
return;
|
|
4444
4771
|
}
|
|
4772
|
+
if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
|
|
4773
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4774
|
+
return;
|
|
4775
|
+
}
|
|
4776
|
+
const client2 = getS3Client();
|
|
4777
|
+
const command = new import_client_s32.DeleteObjectCommand({
|
|
4778
|
+
Bucket: config.fileUploads.s3Bucket,
|
|
4779
|
+
Key: key
|
|
4780
|
+
});
|
|
4781
|
+
await client2.send(command);
|
|
4782
|
+
res.json({ key });
|
|
4445
4783
|
});
|
|
4446
4784
|
app.get("/s3/download", async (req, res, next) => {
|
|
4447
4785
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
@@ -4466,14 +4804,23 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4466
4804
|
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4467
4805
|
return;
|
|
4468
4806
|
}
|
|
4469
|
-
|
|
4807
|
+
const userPrefix = key.split("/")[0];
|
|
4808
|
+
if (!userPrefix) {
|
|
4809
|
+
res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
|
|
4810
|
+
return;
|
|
4811
|
+
}
|
|
4812
|
+
if (userPrefix !== authenticationResult.user.id.toString()) {
|
|
4813
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4814
|
+
return;
|
|
4815
|
+
}
|
|
4816
|
+
if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
|
|
4470
4817
|
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4471
4818
|
return;
|
|
4472
4819
|
}
|
|
4473
4820
|
try {
|
|
4474
4821
|
const url = await (0, import_s3_request_presigner.getSignedUrl)(
|
|
4475
4822
|
getS3Client(),
|
|
4476
|
-
new
|
|
4823
|
+
new import_client_s32.GetObjectCommand({
|
|
4477
4824
|
Bucket: config.fileUploads.s3Bucket,
|
|
4478
4825
|
Key: key
|
|
4479
4826
|
}),
|
|
@@ -4516,7 +4863,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4516
4863
|
contentType: params.type
|
|
4517
4864
|
};
|
|
4518
4865
|
};
|
|
4519
|
-
const
|
|
4866
|
+
const generateS3Key2 = (filename) => `${(0, import_node_crypto2.randomUUID)()}-${filename}`;
|
|
4520
4867
|
const signOnServer = async (req, res, next) => {
|
|
4521
4868
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
4522
4869
|
const { db: db3 } = await postgresClient();
|
|
@@ -4535,16 +4882,11 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4535
4882
|
}
|
|
4536
4883
|
const { filename, contentType } = extractFileParameters(req);
|
|
4537
4884
|
validateFileParameters(filename, contentType);
|
|
4538
|
-
const key =
|
|
4539
|
-
let folder =
|
|
4540
|
-
if (authenticationResult.user.type === "api") {
|
|
4541
|
-
folder = `api/`;
|
|
4542
|
-
} else {
|
|
4543
|
-
folder = `${authenticationResult.user.id}/`;
|
|
4544
|
-
}
|
|
4885
|
+
const key = generateS3Key2(filename);
|
|
4886
|
+
let folder = `${authenticationResult.user.id}/`;
|
|
4545
4887
|
(0, import_s3_request_presigner.getSignedUrl)(
|
|
4546
4888
|
getS3Client(),
|
|
4547
|
-
new
|
|
4889
|
+
new import_client_s32.PutObjectCommand({
|
|
4548
4890
|
Bucket: config.fileUploads.s3Bucket,
|
|
4549
4891
|
Key: folder + key,
|
|
4550
4892
|
ContentType: contentType
|
|
@@ -4590,7 +4932,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4590
4932
|
if (typeof type !== "string") {
|
|
4591
4933
|
return res.status(400).json({ error: "s3: content type must be a string" });
|
|
4592
4934
|
}
|
|
4593
|
-
const key = `${(0,
|
|
4935
|
+
const key = `${(0, import_node_crypto2.randomUUID)()}-${filename}`;
|
|
4594
4936
|
let folder = "";
|
|
4595
4937
|
if (authenticationResult.user.type === "api") {
|
|
4596
4938
|
folder = `api/`;
|
|
@@ -4603,7 +4945,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4603
4945
|
ContentType: type,
|
|
4604
4946
|
Metadata: metadata
|
|
4605
4947
|
};
|
|
4606
|
-
const command = new
|
|
4948
|
+
const command = new import_client_s32.CreateMultipartUploadCommand(params);
|
|
4607
4949
|
return client2.send(command, (err, data) => {
|
|
4608
4950
|
if (err) {
|
|
4609
4951
|
next(err);
|
|
@@ -4629,7 +4971,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4629
4971
|
if (typeof key !== "string") {
|
|
4630
4972
|
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' });
|
|
4631
4973
|
}
|
|
4632
|
-
return (0, import_s3_request_presigner.getSignedUrl)(getS3Client(), new
|
|
4974
|
+
return (0, import_s3_request_presigner.getSignedUrl)(getS3Client(), new import_client_s32.UploadPartCommand({
|
|
4633
4975
|
Bucket: config.fileUploads.s3Bucket,
|
|
4634
4976
|
Key: key,
|
|
4635
4977
|
UploadId: uploadId,
|
|
@@ -4650,7 +4992,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4650
4992
|
}
|
|
4651
4993
|
const parts = [];
|
|
4652
4994
|
function listPartsPage(startAt) {
|
|
4653
|
-
client2.send(new
|
|
4995
|
+
client2.send(new import_client_s32.ListPartsCommand({
|
|
4654
4996
|
Bucket: config.fileUploads.s3Bucket,
|
|
4655
4997
|
Key: key,
|
|
4656
4998
|
UploadId: uploadId,
|
|
@@ -4684,7 +5026,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4684
5026
|
if (!Array.isArray(parts) || !parts.every(isValidPart)) {
|
|
4685
5027
|
return res.status(400).json({ error: "s3: `parts` must be an array of {ETag, PartNumber} objects." });
|
|
4686
5028
|
}
|
|
4687
|
-
return client2.send(new
|
|
5029
|
+
return client2.send(new import_client_s32.CompleteMultipartUploadCommand({
|
|
4688
5030
|
Bucket: config.fileUploads.s3Bucket,
|
|
4689
5031
|
Key: key,
|
|
4690
5032
|
UploadId: uploadId,
|
|
@@ -4710,7 +5052,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4710
5052
|
if (typeof key !== "string") {
|
|
4711
5053
|
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' });
|
|
4712
5054
|
}
|
|
4713
|
-
return client2.send(new
|
|
5055
|
+
return client2.send(new import_client_s32.AbortMultipartUploadCommand({
|
|
4714
5056
|
Bucket: config.fileUploads.s3Bucket,
|
|
4715
5057
|
Key: key,
|
|
4716
5058
|
UploadId: uploadId
|
|
@@ -4728,7 +5070,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4728
5070
|
};
|
|
4729
5071
|
|
|
4730
5072
|
// src/registry/routes.ts
|
|
4731
|
-
var
|
|
5073
|
+
var import_utils4 = require("@apollo/utils.keyvaluecache");
|
|
4732
5074
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
4733
5075
|
var import_crypto_js3 = __toESM(require("crypto-js"), 1);
|
|
4734
5076
|
|
|
@@ -4757,8 +5099,11 @@ var CLAUDE_MESSAGES = {
|
|
|
4757
5099
|
// src/registry/routes.ts
|
|
4758
5100
|
var import_openai = __toESM(require("openai"), 1);
|
|
4759
5101
|
var import_fs = __toESM(require("fs"), 1);
|
|
4760
|
-
var
|
|
4761
|
-
var
|
|
5102
|
+
var import_node_crypto3 = require("crypto");
|
|
5103
|
+
var import_api = require("@opentelemetry/api");
|
|
5104
|
+
var import_ai2 = require("ai");
|
|
5105
|
+
var import_zod2 = require("zod");
|
|
5106
|
+
var import_client_s33 = require("@aws-sdk/client-s3");
|
|
4762
5107
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
4763
5108
|
var global_queues = {
|
|
4764
5109
|
logs_cleaner: "logs-cleaner"
|
|
@@ -4808,8 +5153,7 @@ var createRecurringJobs = async () => {
|
|
|
4808
5153
|
);
|
|
4809
5154
|
return queue;
|
|
4810
5155
|
};
|
|
4811
|
-
var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
|
|
4812
|
-
console.log("============= agents =============", agents?.length);
|
|
5156
|
+
var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer, filesContext2) => {
|
|
4813
5157
|
var corsOptions = {
|
|
4814
5158
|
origin: "*",
|
|
4815
5159
|
exposedHeaders: "*",
|
|
@@ -4834,7 +5178,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4834
5178
|
if (redisServer.host?.length && redisServer.port?.length) {
|
|
4835
5179
|
await createRecurringJobs();
|
|
4836
5180
|
} else {
|
|
4837
|
-
console.log("
|
|
5181
|
+
console.log("[EXULU] no redis server configured, not setting up recurring jobs.");
|
|
4838
5182
|
}
|
|
4839
5183
|
const schema = createSDL([
|
|
4840
5184
|
usersSchema2(),
|
|
@@ -4849,9 +5193,9 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4849
5193
|
workflowTemplatesSchema2(),
|
|
4850
5194
|
statisticsSchema2(),
|
|
4851
5195
|
rbacSchema2()
|
|
4852
|
-
], contexts, agents, tools);
|
|
5196
|
+
], contexts ?? [], agents, tools);
|
|
4853
5197
|
const server = new import_server3.ApolloServer({
|
|
4854
|
-
cache: new
|
|
5198
|
+
cache: new import_utils4.InMemoryLRUCache(),
|
|
4855
5199
|
schema,
|
|
4856
5200
|
introspection: true
|
|
4857
5201
|
});
|
|
@@ -4907,7 +5251,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4907
5251
|
});
|
|
4908
5252
|
return;
|
|
4909
5253
|
}
|
|
4910
|
-
let
|
|
5254
|
+
let providerapikey = variable.value;
|
|
4911
5255
|
if (!variable.encrypted) {
|
|
4912
5256
|
res.status(400).json({
|
|
4913
5257
|
message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
|
|
@@ -4916,10 +5260,10 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4916
5260
|
}
|
|
4917
5261
|
if (variable.encrypted) {
|
|
4918
5262
|
const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
4919
|
-
|
|
5263
|
+
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
4920
5264
|
}
|
|
4921
5265
|
const openai = new import_openai.default({
|
|
4922
|
-
apiKey:
|
|
5266
|
+
apiKey: providerapikey
|
|
4923
5267
|
});
|
|
4924
5268
|
let style_reference = "";
|
|
4925
5269
|
if (style === "origami") {
|
|
@@ -4966,7 +5310,7 @@ Mood: friendly and intelligent.
|
|
|
4966
5310
|
return;
|
|
4967
5311
|
}
|
|
4968
5312
|
const image_bytes = Buffer.from(image_base64, "base64");
|
|
4969
|
-
const uuid = (0,
|
|
5313
|
+
const uuid = (0, import_node_crypto3.randomUUID)();
|
|
4970
5314
|
if (!import_fs.default.existsSync("public")) {
|
|
4971
5315
|
import_fs.default.mkdirSync("public");
|
|
4972
5316
|
}
|
|
@@ -4992,6 +5336,12 @@ Mood: friendly and intelligent.
|
|
|
4992
5336
|
const slug = agent.slug;
|
|
4993
5337
|
if (!slug) return;
|
|
4994
5338
|
app.post(slug + "/:instance", async (req, res) => {
|
|
5339
|
+
const headers = {
|
|
5340
|
+
stream: req.headers["stream"] === "true" || false,
|
|
5341
|
+
user: req.headers["user"] || null,
|
|
5342
|
+
session: req.headers["session"] || null
|
|
5343
|
+
};
|
|
5344
|
+
await checkAgentRateLimit(agent);
|
|
4995
5345
|
const instance = req.params.instance;
|
|
4996
5346
|
if (!instance) {
|
|
4997
5347
|
res.status(400).json({
|
|
@@ -5000,38 +5350,7 @@ Mood: friendly and intelligent.
|
|
|
5000
5350
|
return;
|
|
5001
5351
|
}
|
|
5002
5352
|
const { db: db3 } = await postgresClient();
|
|
5003
|
-
const agentInstance = await
|
|
5004
|
-
id: instance
|
|
5005
|
-
}).first();
|
|
5006
|
-
const agentRbac = await RBACResolver(db3, "agent", agentInstance.id, agentInstance.rights_mode || "private");
|
|
5007
|
-
agentInstance.RBAC = agentRbac;
|
|
5008
|
-
if (!agentInstance) {
|
|
5009
|
-
res.status(400).json({
|
|
5010
|
-
message: "Agent instance not found."
|
|
5011
|
-
});
|
|
5012
|
-
return;
|
|
5013
|
-
}
|
|
5014
|
-
if (agent.rateLimit) {
|
|
5015
|
-
console.log("[EXULU] rate limiting agent.", agent.rateLimit);
|
|
5016
|
-
const limit = await rateLimiter(
|
|
5017
|
-
agent.rateLimit.name || agent.id,
|
|
5018
|
-
agent.rateLimit.rate_limit.time,
|
|
5019
|
-
agent.rateLimit.rate_limit.limit,
|
|
5020
|
-
1
|
|
5021
|
-
);
|
|
5022
|
-
if (!limit.status) {
|
|
5023
|
-
res.status(429).json({
|
|
5024
|
-
message: "Rate limit exceeded.",
|
|
5025
|
-
retryAfter: limit.retryAfter
|
|
5026
|
-
});
|
|
5027
|
-
return;
|
|
5028
|
-
}
|
|
5029
|
-
}
|
|
5030
|
-
const headers = {
|
|
5031
|
-
stream: req.headers["stream"] === "true" || false,
|
|
5032
|
-
user: req.headers["user"] || null,
|
|
5033
|
-
session: req.headers["session"] || null
|
|
5034
|
-
};
|
|
5353
|
+
const agentInstance = await loadAgent(instance);
|
|
5035
5354
|
const requestValidationResult = requestValidators.agents(req);
|
|
5036
5355
|
if (requestValidationResult.error) {
|
|
5037
5356
|
res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
|
|
@@ -5043,85 +5362,24 @@ Mood: friendly and intelligent.
|
|
|
5043
5362
|
return;
|
|
5044
5363
|
}
|
|
5045
5364
|
const user = authenticationResult.user;
|
|
5046
|
-
const
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
const isAgentCreator = agentInstance.created_by === user.id;
|
|
5050
|
-
const isAdmin = user.super_admin;
|
|
5051
|
-
const isApi = user.type === "api";
|
|
5052
|
-
let hasAccessToAgent = "none";
|
|
5053
|
-
if (agentIsPublic || isAgentCreator || isAdmin || isApi) {
|
|
5054
|
-
hasAccessToAgent = "write";
|
|
5055
|
-
}
|
|
5056
|
-
if (agentByUsers) {
|
|
5057
|
-
hasAccessToAgent = agentInstance.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
5058
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none" || hasAccessToAgent === "read") {
|
|
5059
|
-
res.status(410).json({
|
|
5060
|
-
message: `Your current user ${user.id} does not have access to this agent.`
|
|
5061
|
-
});
|
|
5062
|
-
return;
|
|
5063
|
-
}
|
|
5064
|
-
}
|
|
5065
|
-
if (agentByRoles) {
|
|
5066
|
-
hasAccessToAgent = agentInstance.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
5067
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none" || hasAccessToAgent === "read") {
|
|
5068
|
-
res.status(410).json({
|
|
5069
|
-
message: `Your current role ${user.role?.name} does not have access to this agent.`
|
|
5070
|
-
});
|
|
5071
|
-
return;
|
|
5072
|
-
}
|
|
5073
|
-
}
|
|
5074
|
-
let hasAccessToSession = "none";
|
|
5075
|
-
;
|
|
5076
|
-
if (headers.session) {
|
|
5077
|
-
const session = await db3.from("agents").where({
|
|
5078
|
-
id: instance
|
|
5079
|
-
}).first();
|
|
5080
|
-
const sessionIsPublic = agentInstance.rights_mode === "public";
|
|
5081
|
-
const sessionByUsers = agentInstance.rights_mode === "users";
|
|
5082
|
-
const sessionByRoles = agentInstance.rights_mode === "roles";
|
|
5083
|
-
const isSessionCreator = agentInstance.created_by === user.id;
|
|
5084
|
-
const isAdmin2 = user.super_admin;
|
|
5085
|
-
const isApi2 = user.type === "api";
|
|
5086
|
-
if (sessionIsPublic || isSessionCreator || isAdmin2 || isApi2) {
|
|
5087
|
-
hasAccessToSession = "write";
|
|
5088
|
-
}
|
|
5089
|
-
if (sessionByUsers) {
|
|
5090
|
-
hasAccessToSession = session.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
5091
|
-
if (!hasAccessToSession || hasAccessToSession === "none" || hasAccessToSession === "read") {
|
|
5092
|
-
res.status(410).json({
|
|
5093
|
-
message: `Your current user ${user.id} does not have access to this session.`
|
|
5094
|
-
});
|
|
5095
|
-
return;
|
|
5096
|
-
}
|
|
5097
|
-
}
|
|
5098
|
-
if (sessionByRoles) {
|
|
5099
|
-
hasAccessToSession = session.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
5100
|
-
if (!hasAccessToSession || hasAccessToSession === "none" || hasAccessToSession === "read") {
|
|
5101
|
-
res.status(410).json({
|
|
5102
|
-
message: `Your current role ${user.role?.name} does not have access to this session.`
|
|
5103
|
-
});
|
|
5104
|
-
return;
|
|
5105
|
-
}
|
|
5106
|
-
}
|
|
5107
|
-
}
|
|
5108
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none") {
|
|
5109
|
-
res.status(410).json({
|
|
5365
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
5366
|
+
if (!hasAccessToAgent) {
|
|
5367
|
+
res.status(401).json({
|
|
5110
5368
|
message: "You don't have access to this agent."
|
|
5111
5369
|
});
|
|
5112
5370
|
return;
|
|
5113
5371
|
}
|
|
5114
|
-
if (
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
});
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5372
|
+
if (headers.session) {
|
|
5373
|
+
const session = await db3.from("agent_sessions").where({
|
|
5374
|
+
id: headers.session
|
|
5375
|
+
}).first();
|
|
5376
|
+
let hasAccessToSession = await checkRecordAccess(session, "write", user);
|
|
5377
|
+
if (!hasAccessToSession) {
|
|
5378
|
+
res.status(401).json({
|
|
5379
|
+
message: "You don't have access to this session."
|
|
5380
|
+
});
|
|
5381
|
+
return;
|
|
5382
|
+
}
|
|
5125
5383
|
}
|
|
5126
5384
|
if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
|
|
5127
5385
|
res.status(400).json({
|
|
@@ -5129,16 +5387,11 @@ Mood: friendly and intelligent.
|
|
|
5129
5387
|
});
|
|
5130
5388
|
return;
|
|
5131
5389
|
}
|
|
5132
|
-
console.log("[EXULU] agent tools", agentInstance.tools);
|
|
5133
|
-
let enabledTools = agentInstance.tools ? agentInstance.tools.map(
|
|
5134
|
-
({ config: config2, toolId }) => tools.find(({ id }) => id === toolId)
|
|
5135
|
-
).filter(Boolean) : [];
|
|
5136
|
-
console.log("[EXULU] available tools", enabledTools?.length);
|
|
5390
|
+
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
5137
5391
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
const variableName = agentInstance.providerApiKey;
|
|
5392
|
+
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
5393
|
+
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
5394
|
+
const variableName = agentInstance.providerapikey;
|
|
5142
5395
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5143
5396
|
if (!variable) {
|
|
5144
5397
|
res.status(400).json({
|
|
@@ -5146,7 +5399,7 @@ Mood: friendly and intelligent.
|
|
|
5146
5399
|
});
|
|
5147
5400
|
return;
|
|
5148
5401
|
}
|
|
5149
|
-
let
|
|
5402
|
+
let providerapikey = variable.value;
|
|
5150
5403
|
if (!variable.encrypted) {
|
|
5151
5404
|
res.status(400).json({
|
|
5152
5405
|
message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
|
|
@@ -5155,7 +5408,7 @@ Mood: friendly and intelligent.
|
|
|
5155
5408
|
}
|
|
5156
5409
|
if (variable.encrypted) {
|
|
5157
5410
|
const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
5158
|
-
|
|
5411
|
+
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
5159
5412
|
}
|
|
5160
5413
|
if (!!headers.stream) {
|
|
5161
5414
|
await agent.generateStream({
|
|
@@ -5163,13 +5416,17 @@ Mood: friendly and intelligent.
|
|
|
5163
5416
|
res,
|
|
5164
5417
|
req
|
|
5165
5418
|
},
|
|
5166
|
-
|
|
5167
|
-
|
|
5419
|
+
contexts,
|
|
5420
|
+
user,
|
|
5421
|
+
instructions: agentInstance.instructions,
|
|
5168
5422
|
session: headers.session,
|
|
5169
5423
|
message: req.body.message,
|
|
5170
|
-
|
|
5171
|
-
|
|
5424
|
+
currentTools: enabledTools,
|
|
5425
|
+
allExuluTools: tools,
|
|
5426
|
+
providerapikey,
|
|
5172
5427
|
toolConfigs: agentInstance.tools,
|
|
5428
|
+
exuluConfig: config,
|
|
5429
|
+
filesContext: filesContext2,
|
|
5173
5430
|
statistics: {
|
|
5174
5431
|
label: agent.name,
|
|
5175
5432
|
trigger: "agent"
|
|
@@ -5178,13 +5435,17 @@ Mood: friendly and intelligent.
|
|
|
5178
5435
|
return;
|
|
5179
5436
|
} else {
|
|
5180
5437
|
const response = await agent.generateSync({
|
|
5181
|
-
user
|
|
5438
|
+
user,
|
|
5439
|
+
instructions: agentInstance.instructions,
|
|
5182
5440
|
session: headers.session,
|
|
5183
|
-
role: user?.role?.id,
|
|
5184
5441
|
message: req.body.message,
|
|
5185
|
-
|
|
5186
|
-
|
|
5442
|
+
contexts,
|
|
5443
|
+
currentTools: enabledTools,
|
|
5444
|
+
allExuluTools: tools,
|
|
5445
|
+
providerapikey,
|
|
5446
|
+
exuluConfig: config,
|
|
5187
5447
|
toolConfigs: agentInstance.tools,
|
|
5448
|
+
filesContext: filesContext2,
|
|
5188
5449
|
statistics: {
|
|
5189
5450
|
label: agent.name,
|
|
5190
5451
|
trigger: "agent"
|
|
@@ -5202,8 +5463,9 @@ Mood: friendly and intelligent.
|
|
|
5202
5463
|
}
|
|
5203
5464
|
const TARGET_API = "https://api.anthropic.com";
|
|
5204
5465
|
app.use("/gateway/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
5205
|
-
|
|
5206
|
-
const
|
|
5466
|
+
console.log("[EXULU] Coding request!!!");
|
|
5467
|
+
const path2 = req.url;
|
|
5468
|
+
const url = `${TARGET_API}${path2}`;
|
|
5207
5469
|
try {
|
|
5208
5470
|
if (!req.body.tools) {
|
|
5209
5471
|
req.body.tools = [];
|
|
@@ -5214,6 +5476,7 @@ Mood: friendly and intelligent.
|
|
|
5214
5476
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
5215
5477
|
return;
|
|
5216
5478
|
}
|
|
5479
|
+
console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
|
|
5217
5480
|
const { db: db3 } = await postgresClient();
|
|
5218
5481
|
let query = db3("agents");
|
|
5219
5482
|
query.select("*");
|
|
@@ -5228,20 +5491,30 @@ Mood: friendly and intelligent.
|
|
|
5228
5491
|
res.end(Buffer.from(arrayBuffer));
|
|
5229
5492
|
return;
|
|
5230
5493
|
}
|
|
5231
|
-
console.log("[EXULU]
|
|
5494
|
+
console.log("[EXULU] Agent loaded", agent.name);
|
|
5495
|
+
const backend = agents.find((x) => x.id === agent.backend);
|
|
5496
|
+
if (!backend) {
|
|
5497
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(`
|
|
5498
|
+
\x1B[41m -- Agent ${agent.name} does not have a exulu backend setup, or the exulu backend that was assigned no longer exists. --
|
|
5499
|
+
\x1B[0m`);
|
|
5500
|
+
res.setHeader("Content-Type", "application/json");
|
|
5501
|
+
res.end(Buffer.from(arrayBuffer));
|
|
5502
|
+
return;
|
|
5503
|
+
}
|
|
5504
|
+
console.log("[EXULU] Backend loaded", backend.id);
|
|
5232
5505
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
5233
5506
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
|
|
5234
5507
|
res.setHeader("Content-Type", "application/json");
|
|
5235
5508
|
res.end(Buffer.from(arrayBuffer));
|
|
5236
5509
|
return;
|
|
5237
5510
|
}
|
|
5238
|
-
if (!agent.
|
|
5511
|
+
if (!agent.providerapikey) {
|
|
5239
5512
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.not_enabled);
|
|
5240
5513
|
res.setHeader("Content-Type", "application/json");
|
|
5241
5514
|
res.end(Buffer.from(arrayBuffer));
|
|
5242
5515
|
return;
|
|
5243
5516
|
}
|
|
5244
|
-
const variableName = agent.
|
|
5517
|
+
const variableName = agent.providerapikey;
|
|
5245
5518
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5246
5519
|
if (!variable) {
|
|
5247
5520
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.anthropic_token_variable_not_found);
|
|
@@ -5249,7 +5522,7 @@ Mood: friendly and intelligent.
|
|
|
5249
5522
|
res.end(Buffer.from(arrayBuffer));
|
|
5250
5523
|
return;
|
|
5251
5524
|
}
|
|
5252
|
-
let
|
|
5525
|
+
let providerapikey = variable.value;
|
|
5253
5526
|
if (!variable.encrypted) {
|
|
5254
5527
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.anthropic_token_variable_not_encrypted);
|
|
5255
5528
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -5258,54 +5531,90 @@ Mood: friendly and intelligent.
|
|
|
5258
5531
|
}
|
|
5259
5532
|
if (variable.encrypted) {
|
|
5260
5533
|
const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
5261
|
-
|
|
5534
|
+
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
5262
5535
|
}
|
|
5263
5536
|
const headers = {
|
|
5264
|
-
"x-api-key":
|
|
5537
|
+
"x-api-key": providerapikey,
|
|
5265
5538
|
"anthropic-version": "2023-06-01",
|
|
5266
5539
|
"content-type": req.headers["content-type"] || "application/json"
|
|
5267
5540
|
};
|
|
5268
5541
|
if (req.headers["accept"]) headers["accept"] = req.headers["accept"];
|
|
5269
5542
|
if (req.headers["user-agent"]) headers["user-agent"] = req.headers["user-agent"];
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
body: req.method !== "GET" ? JSON.stringify(req.body) : void 0
|
|
5543
|
+
console.log("agent", agent.name);
|
|
5544
|
+
const model = backend.model?.create({
|
|
5545
|
+
apiKey: providerapikey
|
|
5274
5546
|
});
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
trigger: "claude-code",
|
|
5280
|
-
count: 1,
|
|
5281
|
-
user: authenticationResult.user?.id,
|
|
5282
|
-
role: authenticationResult.user.role?.id
|
|
5283
|
-
});
|
|
5284
|
-
response.headers.forEach((value, key) => {
|
|
5285
|
-
res.setHeader(key, value);
|
|
5286
|
-
});
|
|
5287
|
-
res.status(response.status);
|
|
5288
|
-
const isStreaming = response.headers.get("content-type")?.includes("text/event-stream");
|
|
5289
|
-
if (isStreaming && !response?.body) {
|
|
5290
|
-
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_body);
|
|
5547
|
+
if (!model) {
|
|
5548
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(`
|
|
5549
|
+
\x1B[41m -- Could not create language model instance fro agent ${agent.name}. --
|
|
5550
|
+
\x1B[0m`);
|
|
5291
5551
|
res.setHeader("Content-Type", "application/json");
|
|
5292
5552
|
res.end(Buffer.from(arrayBuffer));
|
|
5293
5553
|
return;
|
|
5294
5554
|
}
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5555
|
+
const systemMessagesConcatenated = req.body.system.map((x) => x.text).join("\n\n\n");
|
|
5556
|
+
let messages = convertClaudeCodeMessagesToVercelAISdkMessages(
|
|
5557
|
+
req.body.messages
|
|
5558
|
+
);
|
|
5559
|
+
const tools2 = convertClaudeCodeToolsToVercelAISdkTools(
|
|
5560
|
+
req.body.tools
|
|
5561
|
+
);
|
|
5562
|
+
console.log("STREAMING TEXT");
|
|
5563
|
+
const result = (0, import_ai2.streamText)({
|
|
5564
|
+
model,
|
|
5565
|
+
// Should be a LanguageModelV1
|
|
5566
|
+
// TIP FOR DEBUGGING IF YOU RUN INTO ISSUES / ERRORS REGARDING THE MESSAGES FORMAT. STORE THE 'raw' VARIABLE TO A FILE (fs.write)
|
|
5567
|
+
// AND COPY THE CONTENT INTO THE messages: BELOW, TYPESCRIPT WILL TELL YOU WHAT IS WRONG WHICH IS USUALLY EASIER TO READ THAN THE
|
|
5568
|
+
// ERROR OUTPUT IN THE LOGS.
|
|
5569
|
+
messages,
|
|
5570
|
+
system: systemMessagesConcatenated || "",
|
|
5571
|
+
// prepareStep could be used here to set the model for the first step or change other params
|
|
5572
|
+
maxOutputTokens: req.body.max_tokens,
|
|
5573
|
+
temperature: req.body.temperature,
|
|
5574
|
+
maxRetries: 2,
|
|
5575
|
+
providerOptions: {
|
|
5576
|
+
metadata: {
|
|
5577
|
+
user_id: req.body.metadata?.user_id
|
|
5578
|
+
}
|
|
5579
|
+
},
|
|
5580
|
+
tools: tools2,
|
|
5581
|
+
onFinish: (data) => console.log("[EXULU] Finished stream"),
|
|
5582
|
+
onError: (error) => console.error("[EXULU] chat stream error.", error)
|
|
5583
|
+
// stopWhen: [stepCountIs(1)],
|
|
5584
|
+
});
|
|
5585
|
+
let allChunks = [];
|
|
5586
|
+
result.consumeStream();
|
|
5587
|
+
const responses = [];
|
|
5588
|
+
try {
|
|
5589
|
+
for await (const uiMessage of (0, import_ai2.readUIMessageStream)({
|
|
5590
|
+
stream: result.toUIMessageStream()
|
|
5591
|
+
})) {
|
|
5592
|
+
console.log("Streaming chunk:", uiMessage);
|
|
5593
|
+
const message = {
|
|
5594
|
+
type: "message",
|
|
5595
|
+
role: uiMessage.role,
|
|
5596
|
+
content: uiMessage.parts.map((part) => {
|
|
5597
|
+
if (part.type.includes("tool-")) {
|
|
5598
|
+
const type = part.type;
|
|
5599
|
+
part.type = "tool_use";
|
|
5600
|
+
part.name = type.replace("tool-", "");
|
|
5601
|
+
part.id = part.toolCallId;
|
|
5602
|
+
}
|
|
5603
|
+
return part;
|
|
5604
|
+
})
|
|
5605
|
+
};
|
|
5606
|
+
responses.push(message);
|
|
5607
|
+
console.log("Wrote message to response", message);
|
|
5303
5608
|
}
|
|
5304
|
-
|
|
5609
|
+
} catch (err) {
|
|
5610
|
+
console.error("Stream error:", err);
|
|
5611
|
+
} finally {
|
|
5612
|
+
const jsonString = JSON.stringify(responses[responses.length - 1]);
|
|
5613
|
+
const arrayBuffer = new TextEncoder().encode(jsonString).buffer;
|
|
5614
|
+
res.setHeader("Content-Type", "application/json");
|
|
5615
|
+
res.end(Buffer.from(arrayBuffer));
|
|
5305
5616
|
return;
|
|
5306
5617
|
}
|
|
5307
|
-
const data = await response.arrayBuffer();
|
|
5308
|
-
res.end(Buffer.from(data));
|
|
5309
5618
|
} catch (error) {
|
|
5310
5619
|
console.error("[PROXY] Manual proxy error:", error);
|
|
5311
5620
|
if (!res.headersSent) {
|
|
@@ -5320,6 +5629,97 @@ Mood: friendly and intelligent.
|
|
|
5320
5629
|
app.use(import_express4.default.static("public"));
|
|
5321
5630
|
return app;
|
|
5322
5631
|
};
|
|
5632
|
+
var convertClaudeCodeToolsToVercelAISdkTools = (tools) => {
|
|
5633
|
+
const result = {};
|
|
5634
|
+
for (const tool2 of tools) {
|
|
5635
|
+
const mySchema = (0, import_ai2.jsonSchema)(tool2.input_schema);
|
|
5636
|
+
tools[tool2.name] = {
|
|
5637
|
+
id: tool2.name,
|
|
5638
|
+
name: tool2.name,
|
|
5639
|
+
description: tool2.description,
|
|
5640
|
+
inputSchema: mySchema
|
|
5641
|
+
};
|
|
5642
|
+
}
|
|
5643
|
+
return result;
|
|
5644
|
+
};
|
|
5645
|
+
var convertClaudeCodeMessagesToVercelAISdkMessages = (messages) => {
|
|
5646
|
+
let raw = messages.map((msg) => {
|
|
5647
|
+
if (!msg.role) {
|
|
5648
|
+
msg.role = "assistant";
|
|
5649
|
+
}
|
|
5650
|
+
delete msg.id;
|
|
5651
|
+
if (!Array.isArray(msg.content)) {
|
|
5652
|
+
return {
|
|
5653
|
+
role: msg.role,
|
|
5654
|
+
content: msg.content
|
|
5655
|
+
};
|
|
5656
|
+
}
|
|
5657
|
+
if (msg.content.some((part) => part.type === "tool_result")) {
|
|
5658
|
+
msg.role = "tool";
|
|
5659
|
+
}
|
|
5660
|
+
let parts = msg.content.map((part) => {
|
|
5661
|
+
if (part.type === "step-start") {
|
|
5662
|
+
return void 0;
|
|
5663
|
+
}
|
|
5664
|
+
if (part.type === "reasoning") {
|
|
5665
|
+
const content = part.text?.length > 1 ? part.text : part.content;
|
|
5666
|
+
return {
|
|
5667
|
+
type: "reasoning",
|
|
5668
|
+
text: content || "No reasoning content provided"
|
|
5669
|
+
};
|
|
5670
|
+
}
|
|
5671
|
+
if (part.type === "tool_use") {
|
|
5672
|
+
part.type = "tool-call";
|
|
5673
|
+
}
|
|
5674
|
+
if (part.type === "tool_result") {
|
|
5675
|
+
part.type = "tool-result";
|
|
5676
|
+
part.output = {
|
|
5677
|
+
type: "text",
|
|
5678
|
+
value: part.text || part.content
|
|
5679
|
+
};
|
|
5680
|
+
part.text = null;
|
|
5681
|
+
part.content = null;
|
|
5682
|
+
if (!part.name && part.tool_use_id) {
|
|
5683
|
+
const allParts = raw.map((x) => x.content).flat();
|
|
5684
|
+
const result = allParts.find((x) => {
|
|
5685
|
+
return x.toolCallId === part.tool_use_id && x.name;
|
|
5686
|
+
});
|
|
5687
|
+
console.log("FIND RESULT!!!!", result);
|
|
5688
|
+
if (result) {
|
|
5689
|
+
part.name = result.name;
|
|
5690
|
+
} else {
|
|
5691
|
+
part.name = "...";
|
|
5692
|
+
}
|
|
5693
|
+
}
|
|
5694
|
+
}
|
|
5695
|
+
if (part.tool_use_id) {
|
|
5696
|
+
part.toolCallId = part.tool_use_id;
|
|
5697
|
+
delete part.tool_use_id;
|
|
5698
|
+
}
|
|
5699
|
+
return {
|
|
5700
|
+
type: part.type,
|
|
5701
|
+
...part.text || part.content ? { text: part.text || part.content } : {},
|
|
5702
|
+
...part.toolCallId ? { toolCallId: part.toolCallId } : {},
|
|
5703
|
+
...part.name ? { toolName: part.name } : {},
|
|
5704
|
+
...part.input ? { input: part.input } : {},
|
|
5705
|
+
...part.output ? { output: part.output } : {},
|
|
5706
|
+
...part.cache_control?.type ? {
|
|
5707
|
+
providerOptions: {
|
|
5708
|
+
anthropic: { cacheControl: { type: part.cache_control?.type } }
|
|
5709
|
+
}
|
|
5710
|
+
} : {}
|
|
5711
|
+
};
|
|
5712
|
+
});
|
|
5713
|
+
parts = parts.filter((part) => part !== void 0);
|
|
5714
|
+
return {
|
|
5715
|
+
role: msg.role,
|
|
5716
|
+
id: msg.id,
|
|
5717
|
+
content: parts
|
|
5718
|
+
};
|
|
5719
|
+
});
|
|
5720
|
+
raw = raw.filter((msg) => msg !== void 0);
|
|
5721
|
+
return raw;
|
|
5722
|
+
};
|
|
5323
5723
|
var createCustomAnthropicStreamingMessage = (message) => {
|
|
5324
5724
|
const responseData = {
|
|
5325
5725
|
type: "message",
|
|
@@ -5338,32 +5738,9 @@ var createCustomAnthropicStreamingMessage = (message) => {
|
|
|
5338
5738
|
// src/registry/workers.ts
|
|
5339
5739
|
var import_ioredis = __toESM(require("ioredis"), 1);
|
|
5340
5740
|
var import_bullmq5 = require("bullmq");
|
|
5341
|
-
|
|
5342
|
-
// src/registry/utils.ts
|
|
5343
|
-
var bullmq = {
|
|
5344
|
-
validate: (id, data) => {
|
|
5345
|
-
if (!data) {
|
|
5346
|
-
throw new Error(`Missing job data for job ${id}.`);
|
|
5347
|
-
}
|
|
5348
|
-
if (!data.type) {
|
|
5349
|
-
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
5350
|
-
}
|
|
5351
|
-
if (!data.inputs) {
|
|
5352
|
-
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
5353
|
-
}
|
|
5354
|
-
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
5355
|
-
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
|
|
5356
|
-
}
|
|
5357
|
-
if (!data.workflow && !data.embedder) {
|
|
5358
|
-
throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
|
|
5359
|
-
}
|
|
5360
|
-
}
|
|
5361
|
-
};
|
|
5362
|
-
|
|
5363
|
-
// src/registry/workers.ts
|
|
5364
|
-
var fs3 = __toESM(require("fs"), 1);
|
|
5741
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
5365
5742
|
var import_path = __toESM(require("path"), 1);
|
|
5366
|
-
var
|
|
5743
|
+
var import_api2 = require("@opentelemetry/api");
|
|
5367
5744
|
var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
|
|
5368
5745
|
var redisConnection;
|
|
5369
5746
|
var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
|
|
@@ -5441,16 +5818,16 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
5441
5818
|
global_queues.logs_cleaner,
|
|
5442
5819
|
async (job) => {
|
|
5443
5820
|
console.log(`[EXULU] recurring job ${job.id}.`);
|
|
5444
|
-
const folder =
|
|
5821
|
+
const folder = fs2.readdirSync(logsDir);
|
|
5445
5822
|
const files = folder.filter((file) => file.endsWith(".log"));
|
|
5446
5823
|
const now = /* @__PURE__ */ new Date();
|
|
5447
5824
|
const daysToKeep = job.data.ttld;
|
|
5448
5825
|
const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
|
|
5449
5826
|
files.forEach((file) => {
|
|
5450
5827
|
const filePath = import_path.default.join(logsDir, file);
|
|
5451
|
-
const fileStats =
|
|
5828
|
+
const fileStats = fs2.statSync(filePath);
|
|
5452
5829
|
if (fileStats.mtime < dateToKeep) {
|
|
5453
|
-
|
|
5830
|
+
fs2.unlinkSync(filePath);
|
|
5454
5831
|
}
|
|
5455
5832
|
});
|
|
5456
5833
|
},
|
|
@@ -5470,12 +5847,12 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
5470
5847
|
|
|
5471
5848
|
// src/mcp/index.ts
|
|
5472
5849
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5473
|
-
var
|
|
5850
|
+
var import_node_crypto4 = require("crypto");
|
|
5474
5851
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
5475
5852
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
5476
5853
|
var import_zod3 = require("zod");
|
|
5477
5854
|
var import_express6 = require("express");
|
|
5478
|
-
var
|
|
5855
|
+
var import_api3 = require("@opentelemetry/api");
|
|
5479
5856
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
5480
5857
|
var ExuluMCP = class {
|
|
5481
5858
|
server;
|
|
@@ -5552,7 +5929,6 @@ ${code}`
|
|
|
5552
5929
|
if (!this.server) {
|
|
5553
5930
|
throw new Error("MCP server not initialized.");
|
|
5554
5931
|
}
|
|
5555
|
-
console.log("[EXULU] Wiring up MCP server routes to express app.");
|
|
5556
5932
|
this.express.post("/mcp", async (req, res) => {
|
|
5557
5933
|
if (!this.server) {
|
|
5558
5934
|
throw new Error("MCP server not initialized.");
|
|
@@ -5563,7 +5939,7 @@ ${code}`
|
|
|
5563
5939
|
transport = this.transports[sessionId];
|
|
5564
5940
|
} else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
|
|
5565
5941
|
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
5566
|
-
sessionIdGenerator: () => (0,
|
|
5942
|
+
sessionIdGenerator: () => (0, import_node_crypto4.randomUUID)(),
|
|
5567
5943
|
onsessioninitialized: (sessionId2) => {
|
|
5568
5944
|
this.transports[sessionId2] = transport;
|
|
5569
5945
|
}
|
|
@@ -5605,35 +5981,49 @@ ${code}`
|
|
|
5605
5981
|
// src/registry/index.ts
|
|
5606
5982
|
var import_express8 = __toESM(require("express"), 1);
|
|
5607
5983
|
|
|
5608
|
-
// src/templates/agents/claude-
|
|
5609
|
-
var
|
|
5984
|
+
// src/templates/agents/claude-sonnet-4.ts
|
|
5985
|
+
var import_anthropic = require("@ai-sdk/anthropic");
|
|
5986
|
+
var claudeSonnet4Agent = new ExuluAgent2({
|
|
5610
5987
|
id: `claude_code_agent`,
|
|
5611
5988
|
name: `Claude Code Agent`,
|
|
5612
5989
|
description: `Claude Code agent, enabling the creation of multiple Claude Code Agent instances with different configurations (rate limits, functions, etc).`,
|
|
5613
|
-
type: "
|
|
5990
|
+
type: "agent",
|
|
5991
|
+
config: {
|
|
5992
|
+
name: `Default Claude Code agent`,
|
|
5993
|
+
instructions: "You are a coding assistant.",
|
|
5994
|
+
model: {
|
|
5995
|
+
create: ({ apiKey }) => {
|
|
5996
|
+
const anthropic = (0, import_anthropic.createAnthropic)({
|
|
5997
|
+
apiKey
|
|
5998
|
+
});
|
|
5999
|
+
return anthropic.languageModel("claude-sonnet-4-20250514");
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
5614
6003
|
});
|
|
5615
6004
|
|
|
5616
6005
|
// src/templates/agents/claude-opus-4.ts
|
|
5617
|
-
var
|
|
5618
|
-
var
|
|
6006
|
+
var import_anthropic2 = require("@ai-sdk/anthropic");
|
|
6007
|
+
var claudeOpus4Agent = new ExuluAgent2({
|
|
5619
6008
|
id: `default_claude_4_opus_agent`,
|
|
5620
|
-
name: `Default Claude 4 Opus
|
|
5621
|
-
description: `Basic agent
|
|
6009
|
+
name: `Default Claude 4 Opus anthropic provider`,
|
|
6010
|
+
description: `Basic agent claude 4 opus agent you can use to chat with.`,
|
|
5622
6011
|
type: "agent",
|
|
5623
6012
|
capabilities: {
|
|
5624
6013
|
text: true,
|
|
5625
6014
|
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
5626
|
-
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt"],
|
|
6015
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
5627
6016
|
audio: [],
|
|
5628
6017
|
video: []
|
|
5629
6018
|
},
|
|
5630
6019
|
evals: [],
|
|
6020
|
+
maxContextLength: 2e5,
|
|
5631
6021
|
config: {
|
|
5632
6022
|
name: `Default agent`,
|
|
5633
6023
|
instructions: "You are a helpful assistant.",
|
|
5634
6024
|
model: {
|
|
5635
6025
|
create: ({ apiKey }) => {
|
|
5636
|
-
const anthropic = (0,
|
|
6026
|
+
const anthropic = (0, import_anthropic2.createAnthropic)({
|
|
5637
6027
|
apiKey
|
|
5638
6028
|
});
|
|
5639
6029
|
return anthropic.languageModel("claude-4-opus-20250514");
|
|
@@ -5649,8 +6039,79 @@ var defaultAgent = new ExuluAgent2({
|
|
|
5649
6039
|
}
|
|
5650
6040
|
});
|
|
5651
6041
|
|
|
6042
|
+
// src/templates/agents/gpt-5.ts
|
|
6043
|
+
var import_openai2 = require("@ai-sdk/openai");
|
|
6044
|
+
var gpt5MiniAgent = new ExuluAgent2({
|
|
6045
|
+
id: `default_gpt_5_mini_agent`,
|
|
6046
|
+
name: `Default GPT 5 Mini OpenAI provider`,
|
|
6047
|
+
description: `Basic agent gpt 5 mini agent you can use to chat with.`,
|
|
6048
|
+
type: "agent",
|
|
6049
|
+
capabilities: {
|
|
6050
|
+
text: true,
|
|
6051
|
+
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
6052
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
6053
|
+
audio: [],
|
|
6054
|
+
video: []
|
|
6055
|
+
},
|
|
6056
|
+
evals: [],
|
|
6057
|
+
maxContextLength: 128e3,
|
|
6058
|
+
config: {
|
|
6059
|
+
name: `Default agent`,
|
|
6060
|
+
instructions: "You are a helpful assistant.",
|
|
6061
|
+
model: {
|
|
6062
|
+
create: ({ apiKey }) => {
|
|
6063
|
+
const openai = (0, import_openai2.createOpenAI)({
|
|
6064
|
+
apiKey
|
|
6065
|
+
});
|
|
6066
|
+
return openai.languageModel("gpt-5-mini");
|
|
6067
|
+
}
|
|
6068
|
+
// todo add a field of type string that adds a dropdown list from which the user can select the model
|
|
6069
|
+
// todo for each model, check which provider is used, and require the admin to add one or multiple
|
|
6070
|
+
// API keys for the provider (which we can then auto-rotate).
|
|
6071
|
+
// todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
|
|
6072
|
+
// and allow him/her to decide if the rate limit is per user or per agent.
|
|
6073
|
+
// todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
|
|
6074
|
+
// and stores the logs into the pre-defined storage.
|
|
6075
|
+
}
|
|
6076
|
+
}
|
|
6077
|
+
});
|
|
6078
|
+
var gpt5agent = new ExuluAgent2({
|
|
6079
|
+
id: `default_gpt_5_agent`,
|
|
6080
|
+
name: `Default GPT 5 OpenAI provider`,
|
|
6081
|
+
description: `Basic agent gpt 5 agent you can use to chat with.`,
|
|
6082
|
+
type: "agent",
|
|
6083
|
+
capabilities: {
|
|
6084
|
+
text: true,
|
|
6085
|
+
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
6086
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
6087
|
+
audio: [],
|
|
6088
|
+
video: []
|
|
6089
|
+
},
|
|
6090
|
+
evals: [],
|
|
6091
|
+
maxContextLength: 128e3,
|
|
6092
|
+
config: {
|
|
6093
|
+
name: `Default agent`,
|
|
6094
|
+
instructions: "You are a helpful assistant.",
|
|
6095
|
+
model: {
|
|
6096
|
+
create: ({ apiKey }) => {
|
|
6097
|
+
const openai = (0, import_openai2.createOpenAI)({
|
|
6098
|
+
apiKey
|
|
6099
|
+
});
|
|
6100
|
+
return openai.languageModel("gpt-5");
|
|
6101
|
+
}
|
|
6102
|
+
// todo add a field of type string that adds a dropdown list from which the user can select the model
|
|
6103
|
+
// todo for each model, check which provider is used, and require the admin to add one or multiple
|
|
6104
|
+
// API keys for the provider (which we can then auto-rotate).
|
|
6105
|
+
// todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
|
|
6106
|
+
// and allow him/her to decide if the rate limit is per user or per agent.
|
|
6107
|
+
// todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
|
|
6108
|
+
// and stores the logs into the pre-defined storage.
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
});
|
|
6112
|
+
|
|
5652
6113
|
// src/registry/index.ts
|
|
5653
|
-
var
|
|
6114
|
+
var import_api4 = require("@opentelemetry/api");
|
|
5654
6115
|
|
|
5655
6116
|
// src/registry/logger.ts
|
|
5656
6117
|
var import_winston_transport = require("@opentelemetry/winston-transport");
|
|
@@ -5702,21 +6163,21 @@ var codeStandardsContext = new ExuluContext({
|
|
|
5702
6163
|
active: true
|
|
5703
6164
|
});
|
|
5704
6165
|
|
|
5705
|
-
// src/templates/contexts/
|
|
5706
|
-
var
|
|
5707
|
-
id: "
|
|
5708
|
-
name: "
|
|
5709
|
-
description: "
|
|
6166
|
+
// src/templates/contexts/outputs.ts
|
|
6167
|
+
var outputsContext = new ExuluContext({
|
|
6168
|
+
id: "outputs_default_context",
|
|
6169
|
+
name: "Outputs",
|
|
6170
|
+
description: "Outputs from agent sessions that you have saved for re-used later.",
|
|
5710
6171
|
configuration: {
|
|
5711
|
-
defaultRightsMode: "
|
|
6172
|
+
defaultRightsMode: "private",
|
|
6173
|
+
calculateVectors: "manual"
|
|
5712
6174
|
},
|
|
5713
|
-
fields: [
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
}],
|
|
6175
|
+
fields: [
|
|
6176
|
+
{
|
|
6177
|
+
name: "content",
|
|
6178
|
+
type: "longText"
|
|
6179
|
+
}
|
|
6180
|
+
],
|
|
5720
6181
|
active: true
|
|
5721
6182
|
});
|
|
5722
6183
|
|
|
@@ -5734,14 +6195,6 @@ var filesContext = new ExuluContext({
|
|
|
5734
6195
|
name: "type",
|
|
5735
6196
|
type: "text"
|
|
5736
6197
|
},
|
|
5737
|
-
{
|
|
5738
|
-
name: "s3bucket",
|
|
5739
|
-
type: "text"
|
|
5740
|
-
},
|
|
5741
|
-
{
|
|
5742
|
-
name: "s3region",
|
|
5743
|
-
type: "text"
|
|
5744
|
-
},
|
|
5745
6198
|
{
|
|
5746
6199
|
name: "url",
|
|
5747
6200
|
type: "text"
|
|
@@ -5751,10 +6204,6 @@ var filesContext = new ExuluContext({
|
|
|
5751
6204
|
// ID of the file in S3 storage
|
|
5752
6205
|
type: "text"
|
|
5753
6206
|
},
|
|
5754
|
-
{
|
|
5755
|
-
name: "s3endpoint",
|
|
5756
|
-
type: "text"
|
|
5757
|
-
},
|
|
5758
6207
|
{
|
|
5759
6208
|
name: "content",
|
|
5760
6209
|
type: "longText"
|
|
@@ -5765,7 +6214,6 @@ var filesContext = new ExuluContext({
|
|
|
5765
6214
|
|
|
5766
6215
|
// src/registry/index.ts
|
|
5767
6216
|
var isValidPostgresName = (id) => {
|
|
5768
|
-
console.log("[EXULU] validating context id.", id);
|
|
5769
6217
|
const regex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
5770
6218
|
const isValid = regex.test(id);
|
|
5771
6219
|
const length = id.length;
|
|
@@ -5785,22 +6233,25 @@ var ExuluApp = class {
|
|
|
5785
6233
|
create = async ({ contexts, agents, config, tools }) => {
|
|
5786
6234
|
this._contexts = {
|
|
5787
6235
|
...contexts,
|
|
5788
|
-
projectsContext,
|
|
5789
6236
|
codeStandardsContext,
|
|
5790
|
-
filesContext
|
|
6237
|
+
filesContext,
|
|
6238
|
+
outputsContext
|
|
5791
6239
|
};
|
|
5792
6240
|
this._agents = [
|
|
5793
|
-
|
|
5794
|
-
|
|
6241
|
+
claudeSonnet4Agent,
|
|
6242
|
+
claudeOpus4Agent,
|
|
6243
|
+
gpt5MiniAgent,
|
|
6244
|
+
gpt5agent,
|
|
5795
6245
|
...agents ?? []
|
|
5796
6246
|
];
|
|
5797
6247
|
this._config = config;
|
|
5798
6248
|
this._tools = [
|
|
5799
6249
|
...tools ?? [],
|
|
5800
6250
|
// Add contexts as tools
|
|
5801
|
-
...Object.values(contexts || {}).map((context) => context.tool())
|
|
5802
|
-
//
|
|
5803
|
-
|
|
6251
|
+
...Object.values(contexts || {}).map((context) => context.tool())
|
|
6252
|
+
// Because agents are stored in the database, we add those as tools
|
|
6253
|
+
// at request time, not during ExuluApp initialization. We add them
|
|
6254
|
+
// in the grahql tools resolver.
|
|
5804
6255
|
];
|
|
5805
6256
|
const checks = [
|
|
5806
6257
|
...Object.keys(this._contexts || {}).map((x) => ({
|
|
@@ -5893,8 +6344,7 @@ var ExuluApp = class {
|
|
|
5893
6344
|
create: async () => {
|
|
5894
6345
|
let tracer;
|
|
5895
6346
|
if (this._config?.telemetry?.enabled) {
|
|
5896
|
-
|
|
5897
|
-
tracer = import_api5.trace.getTracer("exulu", "1.0.0");
|
|
6347
|
+
tracer = import_api4.trace.getTracer("exulu", "1.0.0");
|
|
5898
6348
|
}
|
|
5899
6349
|
const logger = logger_default({
|
|
5900
6350
|
enableOtel: this._config?.workers?.telemetry?.enabled ?? false
|
|
@@ -5918,8 +6368,7 @@ var ExuluApp = class {
|
|
|
5918
6368
|
const app = this._expressApp;
|
|
5919
6369
|
let tracer;
|
|
5920
6370
|
if (this._config?.telemetry?.enabled) {
|
|
5921
|
-
|
|
5922
|
-
tracer = import_api5.trace.getTracer("exulu", "1.0.0");
|
|
6371
|
+
tracer = import_api4.trace.getTracer("exulu", "1.0.0");
|
|
5923
6372
|
}
|
|
5924
6373
|
const logger = logger_default({
|
|
5925
6374
|
enableOtel: this._config?.telemetry?.enabled ?? false
|
|
@@ -5931,7 +6380,8 @@ var ExuluApp = class {
|
|
|
5931
6380
|
this._tools,
|
|
5932
6381
|
Object.values(this._contexts ?? {}),
|
|
5933
6382
|
this._config,
|
|
5934
|
-
tracer
|
|
6383
|
+
tracer,
|
|
6384
|
+
filesContext
|
|
5935
6385
|
);
|
|
5936
6386
|
if (this._config?.MCP.enabled) {
|
|
5937
6387
|
const mcp = new ExuluMCP();
|
|
@@ -6211,7 +6661,7 @@ var RecursiveRules = class _RecursiveRules {
|
|
|
6211
6661
|
* @param {string} path - The path to the recipe.
|
|
6212
6662
|
* @returns {Promise<RecursiveRules>} The RecursiveRules object.
|
|
6213
6663
|
*/
|
|
6214
|
-
static async fromRecipe(name = "default", lang = "en",
|
|
6664
|
+
static async fromRecipe(name = "default", lang = "en", path2) {
|
|
6215
6665
|
throw new Error("Not implemented");
|
|
6216
6666
|
}
|
|
6217
6667
|
};
|
|
@@ -7200,7 +7650,20 @@ var generateApiKey = async (name, email) => {
|
|
|
7200
7650
|
};
|
|
7201
7651
|
|
|
7202
7652
|
// src/postgres/init-db.ts
|
|
7203
|
-
var {
|
|
7653
|
+
var {
|
|
7654
|
+
agentsSchema: agentsSchema3,
|
|
7655
|
+
evalResultsSchema: evalResultsSchema3,
|
|
7656
|
+
jobsSchema: jobsSchema3,
|
|
7657
|
+
agentSessionsSchema: agentSessionsSchema3,
|
|
7658
|
+
agentMessagesSchema: agentMessagesSchema3,
|
|
7659
|
+
rolesSchema: rolesSchema3,
|
|
7660
|
+
usersSchema: usersSchema3,
|
|
7661
|
+
statisticsSchema: statisticsSchema3,
|
|
7662
|
+
variablesSchema: variablesSchema3,
|
|
7663
|
+
workflowTemplatesSchema: workflowTemplatesSchema3,
|
|
7664
|
+
rbacSchema: rbacSchema3,
|
|
7665
|
+
projectsSchema: projectsSchema3
|
|
7666
|
+
} = coreSchemas.get();
|
|
7204
7667
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
7205
7668
|
for (const field of fields) {
|
|
7206
7669
|
const { type, name, default: defaultValue, unique } = field;
|
|
@@ -7257,6 +7720,7 @@ var up = async function(knex) {
|
|
|
7257
7720
|
}
|
|
7258
7721
|
};
|
|
7259
7722
|
for (const schema of schemas) {
|
|
7723
|
+
console.log(`[EXULU] Creating ${schema.name.plural} table.`, schema.fields);
|
|
7260
7724
|
await createTable(schema);
|
|
7261
7725
|
}
|
|
7262
7726
|
if (!await knex.schema.hasTable("verification_token")) {
|
|
@@ -7413,6 +7877,9 @@ var create = ({
|
|
|
7413
7877
|
return sdk;
|
|
7414
7878
|
};
|
|
7415
7879
|
|
|
7880
|
+
// src/index.ts
|
|
7881
|
+
var import_crypto_js4 = __toESM(require("crypto-js"), 1);
|
|
7882
|
+
|
|
7416
7883
|
// types/enums/jobs.ts
|
|
7417
7884
|
var JOB_STATUS_ENUM = {
|
|
7418
7885
|
completed: "completed",
|
|
@@ -7431,6 +7898,113 @@ var ExuluJobs = {
|
|
|
7431
7898
|
validate: validateJob
|
|
7432
7899
|
}
|
|
7433
7900
|
};
|
|
7901
|
+
var ExuluDefaultContexts = {
|
|
7902
|
+
files: filesContext,
|
|
7903
|
+
codeStandards: codeStandardsContext,
|
|
7904
|
+
outputs: outputsContext
|
|
7905
|
+
};
|
|
7906
|
+
var ExuluDefaultAgents = {
|
|
7907
|
+
anthropic: {
|
|
7908
|
+
opus4: claudeOpus4Agent,
|
|
7909
|
+
sonnet4: claudeSonnet4Agent
|
|
7910
|
+
},
|
|
7911
|
+
openai: {
|
|
7912
|
+
gpt5Mini: gpt5MiniAgent,
|
|
7913
|
+
gpt5: gpt5agent
|
|
7914
|
+
}
|
|
7915
|
+
};
|
|
7916
|
+
var ExuluVariables = {
|
|
7917
|
+
get: async (name) => {
|
|
7918
|
+
const { db: db3 } = await postgresClient();
|
|
7919
|
+
let variable = await db3.from("variables").where({ name }).first();
|
|
7920
|
+
if (!variable) {
|
|
7921
|
+
throw new Error(`Variable ${name} not found.`);
|
|
7922
|
+
}
|
|
7923
|
+
if (variable.encrypted) {
|
|
7924
|
+
const bytes = import_crypto_js4.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7925
|
+
variable.value = bytes.toString(import_crypto_js4.default.enc.Utf8);
|
|
7926
|
+
}
|
|
7927
|
+
return variable.value;
|
|
7928
|
+
}
|
|
7929
|
+
};
|
|
7930
|
+
var ExuluUtils = {
|
|
7931
|
+
batch: async ({
|
|
7932
|
+
fn,
|
|
7933
|
+
size,
|
|
7934
|
+
inputs,
|
|
7935
|
+
delay,
|
|
7936
|
+
retries
|
|
7937
|
+
}) => {
|
|
7938
|
+
if (!size) {
|
|
7939
|
+
size = 10;
|
|
7940
|
+
}
|
|
7941
|
+
if (!inputs) {
|
|
7942
|
+
throw new Error("Inputs are required.");
|
|
7943
|
+
}
|
|
7944
|
+
if (!delay) {
|
|
7945
|
+
delay = 0;
|
|
7946
|
+
}
|
|
7947
|
+
let results = [];
|
|
7948
|
+
let lastBatchTime = 0;
|
|
7949
|
+
for (let start = 0; start < inputs.length; start += size) {
|
|
7950
|
+
const currentTime = Date.now();
|
|
7951
|
+
const timeSinceLastBatch = currentTime - lastBatchTime;
|
|
7952
|
+
if (timeSinceLastBatch < delay * 1e3) {
|
|
7953
|
+
console.log("[EXULU] Utils function, waiting for", delay - timeSinceLastBatch, "seconds");
|
|
7954
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1e3 - timeSinceLastBatch));
|
|
7955
|
+
}
|
|
7956
|
+
lastBatchTime = Date.now();
|
|
7957
|
+
console.log(`[EXULU] Utils function, processing batch ${start / size + 1} of ${Math.ceil(inputs.length / size)} (${Math.min(start + 1, inputs.length)}-${Math.min(start + size, inputs.length)} of ${inputs.length})`);
|
|
7958
|
+
const end = start + size > inputs.length ? inputs.length : start + size;
|
|
7959
|
+
const slicedResults = await Promise.all(inputs.slice(start, end).map((data, i) => {
|
|
7960
|
+
if (retries?.max) {
|
|
7961
|
+
return ExuluUtils.retry({
|
|
7962
|
+
fn: async () => {
|
|
7963
|
+
return await fn(data);
|
|
7964
|
+
},
|
|
7965
|
+
retries: retries.max,
|
|
7966
|
+
delays: retries.delays
|
|
7967
|
+
});
|
|
7968
|
+
} else {
|
|
7969
|
+
return fn(data);
|
|
7970
|
+
}
|
|
7971
|
+
}));
|
|
7972
|
+
results = [
|
|
7973
|
+
...results,
|
|
7974
|
+
...slicedResults
|
|
7975
|
+
];
|
|
7976
|
+
}
|
|
7977
|
+
return results;
|
|
7978
|
+
},
|
|
7979
|
+
retry: async ({
|
|
7980
|
+
fn,
|
|
7981
|
+
retries,
|
|
7982
|
+
delays
|
|
7983
|
+
}) => {
|
|
7984
|
+
if (!retries) {
|
|
7985
|
+
retries = 3;
|
|
7986
|
+
}
|
|
7987
|
+
if (!delays) {
|
|
7988
|
+
delays = [1e3, 5e3, 1e4];
|
|
7989
|
+
}
|
|
7990
|
+
for (let i = 0; i < retries; i++) {
|
|
7991
|
+
try {
|
|
7992
|
+
return await fn();
|
|
7993
|
+
} catch (error) {
|
|
7994
|
+
console.error(`[EXULU] Util function, retry attempt ${i + 1} failed:`, error);
|
|
7995
|
+
if (i >= retries - 1) {
|
|
7996
|
+
throw error;
|
|
7997
|
+
}
|
|
7998
|
+
if (!delays[i]) {
|
|
7999
|
+
delays[i] = delays[delays.length - 1] || 1e4;
|
|
8000
|
+
}
|
|
8001
|
+
const delay = delays && delays[i] ? delays[i] : 1e4;
|
|
8002
|
+
console.log(`[EXULU] Util function, retrying in ${delay / 1e3} seconds...`);
|
|
8003
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
8004
|
+
}
|
|
8005
|
+
}
|
|
8006
|
+
}
|
|
8007
|
+
};
|
|
7434
8008
|
var ExuluOtel = {
|
|
7435
8009
|
create: ({
|
|
7436
8010
|
SIGNOZ_ACCESS_TOKEN,
|
|
@@ -7474,11 +8048,15 @@ var ExuluChunkers = {
|
|
|
7474
8048
|
ExuluAuthentication,
|
|
7475
8049
|
ExuluChunkers,
|
|
7476
8050
|
ExuluContext,
|
|
8051
|
+
ExuluDefaultAgents,
|
|
8052
|
+
ExuluDefaultContexts,
|
|
7477
8053
|
ExuluEmbedder,
|
|
7478
8054
|
ExuluEval,
|
|
7479
8055
|
ExuluJobs,
|
|
7480
8056
|
ExuluOtel,
|
|
7481
8057
|
ExuluQueues,
|
|
7482
8058
|
ExuluTool,
|
|
8059
|
+
ExuluUtils,
|
|
8060
|
+
ExuluVariables,
|
|
7483
8061
|
db
|
|
7484
8062
|
});
|