@exulu/backend 1.23.3 → 1.25.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 +1358 -843
- package/dist/index.d.cts +191 -176
- package/dist/index.d.ts +191 -176
- package/dist/index.js +1340 -825
- package/package.json +7 -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,15 +159,18 @@ 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
|
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
console.error("[EXULU] Error while checking to ensure the database exists, this could be if the user running the server does not have database admin rights, it is fine to ignore this if you are sure the database exists.", error);
|
|
173
|
+
return;
|
|
169
174
|
} finally {
|
|
170
175
|
await defaultKnex.destroy();
|
|
171
176
|
}
|
|
@@ -173,7 +178,7 @@ async function ensureDatabaseExists() {
|
|
|
173
178
|
async function postgresClient() {
|
|
174
179
|
if (!db["exulu"]) {
|
|
175
180
|
try {
|
|
176
|
-
console.log(
|
|
181
|
+
console.log(`[EXULU] Connecting to ${dbName} database.`);
|
|
177
182
|
console.log("[EXULU] POSTGRES_DB_HOST:", process.env.POSTGRES_DB_HOST);
|
|
178
183
|
console.log("[EXULU] POSTGRES_DB_PORT:", process.env.POSTGRES_DB_PORT);
|
|
179
184
|
console.log("[EXULU] POSTGRES_DB_USER:", process.env.POSTGRES_DB_USER);
|
|
@@ -181,7 +186,7 @@ async function postgresClient() {
|
|
|
181
186
|
console.log("[EXULU] POSTGRES_DB_SSL:", process.env.POSTGRES_DB_SSL);
|
|
182
187
|
console.log("[EXULU] Database exists checked:", databaseExistsChecked);
|
|
183
188
|
if (!databaseExistsChecked) {
|
|
184
|
-
console.log(
|
|
189
|
+
console.log(`[EXULU] Ensuring ${dbName} database exists...`);
|
|
185
190
|
await ensureDatabaseExists();
|
|
186
191
|
databaseExistsChecked = true;
|
|
187
192
|
}
|
|
@@ -191,12 +196,16 @@ async function postgresClient() {
|
|
|
191
196
|
host: process.env.POSTGRES_DB_HOST,
|
|
192
197
|
port: parseInt(process.env.POSTGRES_DB_PORT || "5432"),
|
|
193
198
|
user: process.env.POSTGRES_DB_USER,
|
|
194
|
-
database:
|
|
199
|
+
database: dbName,
|
|
195
200
|
password: process.env.POSTGRES_DB_PASSWORD,
|
|
196
201
|
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
|
|
197
202
|
}
|
|
198
203
|
});
|
|
199
|
-
|
|
204
|
+
try {
|
|
205
|
+
await knex.schema.createExtensionIfNotExists("vector");
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.error("[EXULU] Error creating vector extension, this might be fine if you already activated the extension and the 'user' running this script does not have higher level database permissions.", error);
|
|
208
|
+
}
|
|
200
209
|
db["exulu"] = knex;
|
|
201
210
|
} catch (error) {
|
|
202
211
|
console.error("[EXULU] Error initializing exulu database.", error);
|
|
@@ -207,6 +216,16 @@ async function postgresClient() {
|
|
|
207
216
|
db: db["exulu"]
|
|
208
217
|
};
|
|
209
218
|
}
|
|
219
|
+
var refreshPostgresClient = async () => {
|
|
220
|
+
if (db["exulu"]) {
|
|
221
|
+
await db["exulu"].destroy();
|
|
222
|
+
db["exulu"] = void 0;
|
|
223
|
+
}
|
|
224
|
+
const { db: refreshed } = await postgresClient();
|
|
225
|
+
return {
|
|
226
|
+
db: refreshed
|
|
227
|
+
};
|
|
228
|
+
};
|
|
210
229
|
|
|
211
230
|
// src/registry/classes.ts
|
|
212
231
|
var import_knex5 = __toESM(require("pgvector/knex"), 1);
|
|
@@ -281,6 +300,15 @@ var mapType = (t, type, name, defaultValue, unique) => {
|
|
|
281
300
|
if (unique) t.unique(name);
|
|
282
301
|
return;
|
|
283
302
|
}
|
|
303
|
+
if (type === "markdown") {
|
|
304
|
+
if (defaultValue) {
|
|
305
|
+
t.text(name).defaultTo(defaultValue);
|
|
306
|
+
} else {
|
|
307
|
+
t.text(name);
|
|
308
|
+
}
|
|
309
|
+
if (unique) t.unique(name);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
284
312
|
if (type === "shortText") {
|
|
285
313
|
if (defaultValue) {
|
|
286
314
|
t.string(name, 100).defaultTo(defaultValue);
|
|
@@ -428,12 +456,11 @@ var ExuluEvalUtils = {
|
|
|
428
456
|
// src/registry/classes.ts
|
|
429
457
|
var import_crypto_js2 = __toESM(require("crypto-js"), 1);
|
|
430
458
|
var import_express = require("express");
|
|
431
|
-
var import_api = require("@opentelemetry/api");
|
|
432
459
|
|
|
433
460
|
// src/registry/utils/graphql.ts
|
|
434
461
|
var import_schema = require("@graphql-tools/schema");
|
|
435
462
|
var import_graphql_type_json = __toESM(require("graphql-type-json"), 1);
|
|
436
|
-
var
|
|
463
|
+
var import_graphql2 = require("graphql");
|
|
437
464
|
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
438
465
|
|
|
439
466
|
// src/auth/get-token.ts
|
|
@@ -441,7 +468,7 @@ var import_jose = require("jose");
|
|
|
441
468
|
var getToken = async (authHeader) => {
|
|
442
469
|
const token = authHeader.split(" ")[1];
|
|
443
470
|
if (!token) {
|
|
444
|
-
throw new Error("No token provided");
|
|
471
|
+
throw new Error("No token provided for user authentication in headers.");
|
|
445
472
|
}
|
|
446
473
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
447
474
|
throw new Error("No NEXTAUTH_SECRET provided");
|
|
@@ -484,7 +511,7 @@ var authentication = async ({
|
|
|
484
511
|
code: 200,
|
|
485
512
|
user: {
|
|
486
513
|
type: "api",
|
|
487
|
-
id:
|
|
514
|
+
id: 192837465,
|
|
488
515
|
email: "internal@exulu.com",
|
|
489
516
|
role: {
|
|
490
517
|
id: "internal",
|
|
@@ -805,7 +832,8 @@ var agentSessionsSchema = {
|
|
|
805
832
|
},
|
|
806
833
|
{
|
|
807
834
|
name: "project",
|
|
808
|
-
type: "uuid"
|
|
835
|
+
type: "uuid",
|
|
836
|
+
required: false
|
|
809
837
|
}
|
|
810
838
|
]
|
|
811
839
|
};
|
|
@@ -905,12 +933,13 @@ var projectsSchema = {
|
|
|
905
933
|
type: "text"
|
|
906
934
|
},
|
|
907
935
|
{
|
|
908
|
-
name: "
|
|
909
|
-
|
|
936
|
+
name: "project_items",
|
|
937
|
+
// array of items as global ids ('<context_id>/<item_id>')
|
|
938
|
+
type: "json"
|
|
910
939
|
},
|
|
911
940
|
{
|
|
912
|
-
name: "
|
|
913
|
-
type: "
|
|
941
|
+
name: "custom_instructions",
|
|
942
|
+
type: "longText"
|
|
914
943
|
}
|
|
915
944
|
]
|
|
916
945
|
};
|
|
@@ -930,20 +959,24 @@ var agentsSchema = {
|
|
|
930
959
|
name: "image",
|
|
931
960
|
type: "text"
|
|
932
961
|
},
|
|
962
|
+
{
|
|
963
|
+
name: "category",
|
|
964
|
+
type: "text"
|
|
965
|
+
},
|
|
933
966
|
{
|
|
934
967
|
name: "description",
|
|
935
968
|
type: "text"
|
|
936
969
|
},
|
|
937
970
|
{
|
|
938
|
-
name: "
|
|
971
|
+
name: "instructions",
|
|
939
972
|
type: "text"
|
|
940
973
|
},
|
|
941
974
|
{
|
|
942
|
-
name: "
|
|
975
|
+
name: "providerapikey",
|
|
943
976
|
type: "text"
|
|
944
977
|
},
|
|
945
978
|
{
|
|
946
|
-
name: "
|
|
979
|
+
name: "backend",
|
|
947
980
|
type: "text"
|
|
948
981
|
},
|
|
949
982
|
{
|
|
@@ -1277,18 +1310,22 @@ var rbacSchema = {
|
|
|
1277
1310
|
};
|
|
1278
1311
|
var addRBACfields = (schema) => {
|
|
1279
1312
|
if (schema.RBAC) {
|
|
1280
|
-
schema.fields.
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1313
|
+
if (!schema.fields.some((field) => field.name === "rights_mode")) {
|
|
1314
|
+
schema.fields.push({
|
|
1315
|
+
name: "rights_mode",
|
|
1316
|
+
type: "text",
|
|
1317
|
+
required: false,
|
|
1318
|
+
default: "private"
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
if (!schema.fields.some((field) => field.name === "created_by")) {
|
|
1322
|
+
schema.fields.push({
|
|
1323
|
+
name: "created_by",
|
|
1324
|
+
type: "number",
|
|
1325
|
+
required: true,
|
|
1326
|
+
default: 0
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1292
1329
|
}
|
|
1293
1330
|
return schema;
|
|
1294
1331
|
};
|
|
@@ -1320,7 +1357,211 @@ var VectorMethodEnum = {
|
|
|
1320
1357
|
|
|
1321
1358
|
// src/registry/utils/graphql.ts
|
|
1322
1359
|
var import_knex4 = require("knex");
|
|
1323
|
-
|
|
1360
|
+
|
|
1361
|
+
// src/registry/rate-limiter.ts
|
|
1362
|
+
var rateLimiter = async (key, windowSeconds, limit, points) => {
|
|
1363
|
+
try {
|
|
1364
|
+
const { client: client2 } = await redisClient();
|
|
1365
|
+
if (!client2) {
|
|
1366
|
+
console.warn("[EXULU] Rate limiting disabled - Redis not available");
|
|
1367
|
+
return {
|
|
1368
|
+
status: true,
|
|
1369
|
+
retryAfter: null
|
|
1370
|
+
};
|
|
1371
|
+
}
|
|
1372
|
+
const redisKey = `exulu/${key}`;
|
|
1373
|
+
const current = await client2.incrBy(redisKey, points);
|
|
1374
|
+
if (current === points) {
|
|
1375
|
+
await client2.expire(redisKey, windowSeconds);
|
|
1376
|
+
}
|
|
1377
|
+
if (current > limit) {
|
|
1378
|
+
const ttl = await client2.ttl(redisKey);
|
|
1379
|
+
return {
|
|
1380
|
+
status: false,
|
|
1381
|
+
retryAfter: ttl
|
|
1382
|
+
};
|
|
1383
|
+
}
|
|
1384
|
+
return {
|
|
1385
|
+
status: true,
|
|
1386
|
+
retryAfter: null
|
|
1387
|
+
};
|
|
1388
|
+
} catch (error) {
|
|
1389
|
+
console.error("[EXULU] Rate limiting error:", error);
|
|
1390
|
+
return {
|
|
1391
|
+
status: true,
|
|
1392
|
+
retryAfter: null
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
};
|
|
1396
|
+
|
|
1397
|
+
// src/registry/utils.ts
|
|
1398
|
+
var bullmq = {
|
|
1399
|
+
validate: (id, data) => {
|
|
1400
|
+
if (!data) {
|
|
1401
|
+
throw new Error(`Missing job data for job ${id}.`);
|
|
1402
|
+
}
|
|
1403
|
+
if (!data.type) {
|
|
1404
|
+
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
1405
|
+
}
|
|
1406
|
+
if (!data.inputs) {
|
|
1407
|
+
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
1408
|
+
}
|
|
1409
|
+
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
1410
|
+
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
|
|
1411
|
+
}
|
|
1412
|
+
if (!data.workflow && !data.embedder) {
|
|
1413
|
+
throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
var getEnabledTools = async (agentInstance, allExuluTools, disabledTools = [], agents, user) => {
|
|
1418
|
+
let enabledTools = [];
|
|
1419
|
+
if (agentInstance.tools) {
|
|
1420
|
+
const results = await Promise.all(agentInstance.tools.map(
|
|
1421
|
+
async ({ config, id, type }) => {
|
|
1422
|
+
let hydrated;
|
|
1423
|
+
if (type === "agent") {
|
|
1424
|
+
if (id === agentInstance.id) {
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
const instance = await loadAgent(id);
|
|
1428
|
+
if (!instance) {
|
|
1429
|
+
throw new Error("Trying to load a tool of type 'agent', but the associated agent with id " + id + " was not found in the database.");
|
|
1430
|
+
}
|
|
1431
|
+
const backend = agents.find((a) => a.id === instance.backend);
|
|
1432
|
+
if (!backend) {
|
|
1433
|
+
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.");
|
|
1434
|
+
}
|
|
1435
|
+
const hasAccessToAgent = await checkRecordAccess(instance, "read", user);
|
|
1436
|
+
if (!hasAccessToAgent) {
|
|
1437
|
+
return null;
|
|
1438
|
+
}
|
|
1439
|
+
hydrated = await backend.tool(instance.id, agents);
|
|
1440
|
+
} else {
|
|
1441
|
+
hydrated = allExuluTools.find((t) => t.id === id);
|
|
1442
|
+
}
|
|
1443
|
+
return hydrated;
|
|
1444
|
+
}
|
|
1445
|
+
));
|
|
1446
|
+
enabledTools = results.filter(Boolean);
|
|
1447
|
+
}
|
|
1448
|
+
console.log("[EXULU] available tools", enabledTools?.length);
|
|
1449
|
+
console.log("[EXULU] disabled tools", disabledTools?.length);
|
|
1450
|
+
enabledTools = enabledTools.filter((tool2) => !disabledTools.includes(tool2.id));
|
|
1451
|
+
return enabledTools;
|
|
1452
|
+
};
|
|
1453
|
+
var loadAgentCache = /* @__PURE__ */ new Map();
|
|
1454
|
+
var loadAgents = async () => {
|
|
1455
|
+
const { db: db3 } = await postgresClient();
|
|
1456
|
+
const agents = await db3.from("agents");
|
|
1457
|
+
for (const agent of agents) {
|
|
1458
|
+
const agentRbac = await RBACResolver(db3, "agent", agent.id, agent.rights_mode || "private");
|
|
1459
|
+
agent.RBAC = agentRbac;
|
|
1460
|
+
loadAgentCache.set(agent.id, {
|
|
1461
|
+
agent,
|
|
1462
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1463
|
+
// 1 minute
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
return agents;
|
|
1467
|
+
};
|
|
1468
|
+
var loadAgent = async (id) => {
|
|
1469
|
+
const cachedAgent = loadAgentCache.get(id);
|
|
1470
|
+
if (cachedAgent && cachedAgent.expiresAt > /* @__PURE__ */ new Date()) {
|
|
1471
|
+
return cachedAgent.agent;
|
|
1472
|
+
}
|
|
1473
|
+
const { db: db3 } = await postgresClient();
|
|
1474
|
+
const agentInstance = await db3.from("agents").where({
|
|
1475
|
+
id
|
|
1476
|
+
}).first();
|
|
1477
|
+
const agentRbac = await RBACResolver(db3, "agent", agentInstance.id, agentInstance.rights_mode || "private");
|
|
1478
|
+
agentInstance.RBAC = agentRbac;
|
|
1479
|
+
if (!agentInstance) {
|
|
1480
|
+
throw new Error("Agent instance not found.");
|
|
1481
|
+
}
|
|
1482
|
+
loadAgentCache.set(id, {
|
|
1483
|
+
agent: agentInstance,
|
|
1484
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1485
|
+
// 1 minute
|
|
1486
|
+
});
|
|
1487
|
+
return agentInstance;
|
|
1488
|
+
};
|
|
1489
|
+
var checkAgentRateLimit = async (agent) => {
|
|
1490
|
+
if (agent.rateLimit) {
|
|
1491
|
+
console.log("[EXULU] rate limiting agent.", agent.rateLimit);
|
|
1492
|
+
const limit = await rateLimiter(
|
|
1493
|
+
agent.rateLimit.name || agent.id,
|
|
1494
|
+
agent.rateLimit.rate_limit.time,
|
|
1495
|
+
agent.rateLimit.rate_limit.limit,
|
|
1496
|
+
1
|
|
1497
|
+
);
|
|
1498
|
+
if (!limit.status) {
|
|
1499
|
+
throw new Error("Rate limit exceeded.");
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1503
|
+
var checkRecordAccessCache = /* @__PURE__ */ new Map();
|
|
1504
|
+
var checkRecordAccess = async (record, request, user) => {
|
|
1505
|
+
const setRecordAccessCache = (hasAccess2) => {
|
|
1506
|
+
checkRecordAccessCache.set(`${record.id}-${request}-${user?.id}`, {
|
|
1507
|
+
hasAccess: hasAccess2,
|
|
1508
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1509
|
+
// 1 minute
|
|
1510
|
+
});
|
|
1511
|
+
};
|
|
1512
|
+
const cachedAccess = checkRecordAccessCache.get(`${record.id}-${request}-${user?.id}`);
|
|
1513
|
+
if (cachedAccess && cachedAccess.expiresAt > /* @__PURE__ */ new Date()) {
|
|
1514
|
+
return cachedAccess.hasAccess;
|
|
1515
|
+
}
|
|
1516
|
+
const isPublic = record.rights_mode === "public";
|
|
1517
|
+
const byUsers = record.rights_mode === "users";
|
|
1518
|
+
const byRoles = record.rights_mode === "roles";
|
|
1519
|
+
const isCreator = user ? record.created_by === user.id.toString() : false;
|
|
1520
|
+
const isAdmin = user ? user.super_admin : false;
|
|
1521
|
+
const isApi = user ? user.type === "api" : false;
|
|
1522
|
+
let hasAccess = "none";
|
|
1523
|
+
if (isPublic || isCreator || isAdmin || isApi) {
|
|
1524
|
+
setRecordAccessCache(true);
|
|
1525
|
+
return true;
|
|
1526
|
+
}
|
|
1527
|
+
if (byUsers) {
|
|
1528
|
+
if (!user) {
|
|
1529
|
+
setRecordAccessCache(false);
|
|
1530
|
+
return false;
|
|
1531
|
+
}
|
|
1532
|
+
console.log("record.RBAC?.users", record.RBAC?.users);
|
|
1533
|
+
console.log("user.id", user.id.toString());
|
|
1534
|
+
hasAccess = record.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
1535
|
+
if (!hasAccess || hasAccess === "none" || hasAccess !== request) {
|
|
1536
|
+
console.error(`Your current user ${user.id} does not have access to this record, current access type is: ${hasAccess}.`);
|
|
1537
|
+
setRecordAccessCache(false);
|
|
1538
|
+
return false;
|
|
1539
|
+
} else {
|
|
1540
|
+
setRecordAccessCache(true);
|
|
1541
|
+
return true;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
if (byRoles) {
|
|
1545
|
+
if (!user) {
|
|
1546
|
+
setRecordAccessCache(false);
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
hasAccess = record.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
1550
|
+
if (!hasAccess || hasAccess === "none" || hasAccess !== request) {
|
|
1551
|
+
console.error(`Your current role ${user.role?.name} does not have access to this record, current access type is: ${hasAccess}.`);
|
|
1552
|
+
setRecordAccessCache(false);
|
|
1553
|
+
return false;
|
|
1554
|
+
} else {
|
|
1555
|
+
setRecordAccessCache(true);
|
|
1556
|
+
return true;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
setRecordAccessCache(false);
|
|
1560
|
+
return false;
|
|
1561
|
+
};
|
|
1562
|
+
|
|
1563
|
+
// src/registry/utils/graphql.ts
|
|
1564
|
+
var GraphQLDate = new import_graphql2.GraphQLScalarType({
|
|
1324
1565
|
name: "Date",
|
|
1325
1566
|
description: "Date custom scalar type",
|
|
1326
1567
|
serialize(value) {
|
|
@@ -1345,10 +1586,10 @@ var GraphQLDate = new import_graphql.GraphQLScalarType({
|
|
|
1345
1586
|
return value;
|
|
1346
1587
|
},
|
|
1347
1588
|
parseLiteral(ast) {
|
|
1348
|
-
if (ast.kind ===
|
|
1589
|
+
if (ast.kind === import_graphql2.Kind.STRING) {
|
|
1349
1590
|
return new Date(ast.value);
|
|
1350
1591
|
}
|
|
1351
|
-
if (ast.kind ===
|
|
1592
|
+
if (ast.kind === import_graphql2.Kind.INT) {
|
|
1352
1593
|
return new Date(parseInt(ast.value, 10));
|
|
1353
1594
|
}
|
|
1354
1595
|
return null;
|
|
@@ -1360,6 +1601,7 @@ var map = (field) => {
|
|
|
1360
1601
|
case "text":
|
|
1361
1602
|
case "shortText":
|
|
1362
1603
|
case "longText":
|
|
1604
|
+
case "markdown":
|
|
1363
1605
|
case "code":
|
|
1364
1606
|
type = "String";
|
|
1365
1607
|
break;
|
|
@@ -1411,6 +1653,7 @@ ${enumValues}
|
|
|
1411
1653
|
fields.push(" rateLimit: RateLimiterRule");
|
|
1412
1654
|
fields.push(" streaming: Boolean");
|
|
1413
1655
|
fields.push(" capabilities: AgentCapabilities");
|
|
1656
|
+
fields.push(" maxContextLength: Int");
|
|
1414
1657
|
fields.push(" slug: String");
|
|
1415
1658
|
}
|
|
1416
1659
|
const rbacField = table.RBAC ? " RBAC: RBACData" : "";
|
|
@@ -1745,7 +1988,6 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1745
1988
|
input.id = db3.fn.uuid();
|
|
1746
1989
|
}
|
|
1747
1990
|
const columns = await db3(tableNamePlural).columnInfo();
|
|
1748
|
-
console.log("[EXULU] Columns", columns);
|
|
1749
1991
|
const insert = db3(tableNamePlural).insert({
|
|
1750
1992
|
...input,
|
|
1751
1993
|
...table.RBAC ? { rights_mode: "private" } : {}
|
|
@@ -1760,7 +2002,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1760
2002
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user.id, role: context.user.role?.id });
|
|
1761
2003
|
return {
|
|
1762
2004
|
// Filter result to only include requested fields
|
|
1763
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0] }),
|
|
2005
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user }),
|
|
1764
2006
|
job
|
|
1765
2007
|
};
|
|
1766
2008
|
},
|
|
@@ -1803,7 +2045,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1803
2045
|
}
|
|
1804
2046
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id });
|
|
1805
2047
|
return {
|
|
1806
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result }),
|
|
2048
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
|
|
1807
2049
|
job
|
|
1808
2050
|
};
|
|
1809
2051
|
},
|
|
@@ -1839,7 +2081,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1839
2081
|
const result = await db3.from(tableNamePlural).select(Object.keys(columns)).where({ id }).first();
|
|
1840
2082
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id });
|
|
1841
2083
|
return {
|
|
1842
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result }),
|
|
2084
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
|
|
1843
2085
|
job
|
|
1844
2086
|
};
|
|
1845
2087
|
},
|
|
@@ -1871,7 +2113,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1871
2113
|
}).del();
|
|
1872
2114
|
}
|
|
1873
2115
|
await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
|
|
1874
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2116
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
|
|
1875
2117
|
},
|
|
1876
2118
|
[`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
|
|
1877
2119
|
const { where } = args;
|
|
@@ -1895,7 +2137,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1895
2137
|
}
|
|
1896
2138
|
await db3(tableNamePlural).where(where).del();
|
|
1897
2139
|
await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
|
|
1898
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2140
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
|
|
1899
2141
|
}
|
|
1900
2142
|
};
|
|
1901
2143
|
if (table.type === "items") {
|
|
@@ -1975,7 +2217,6 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1975
2217
|
return mutations;
|
|
1976
2218
|
}
|
|
1977
2219
|
var applyAccessControl = (table, user, query) => {
|
|
1978
|
-
console.log("table", table);
|
|
1979
2220
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
1980
2221
|
if (!user.super_admin && table.name.plural === "jobs") {
|
|
1981
2222
|
query = query.where("created_by", user.id);
|
|
@@ -2002,7 +2243,6 @@ var applyAccessControl = (table, user, query) => {
|
|
|
2002
2243
|
});
|
|
2003
2244
|
});
|
|
2004
2245
|
if (user.role) {
|
|
2005
|
-
console.log("user.role", user.role);
|
|
2006
2246
|
this.orWhere(function() {
|
|
2007
2247
|
this.where("rights_mode", "roles").whereExists(function() {
|
|
2008
2248
|
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 +2283,15 @@ var backendAgentFields = [
|
|
|
2043
2283
|
"slug",
|
|
2044
2284
|
"rateLimit",
|
|
2045
2285
|
"streaming",
|
|
2046
|
-
"capabilities"
|
|
2286
|
+
"capabilities",
|
|
2287
|
+
"maxContextLength"
|
|
2047
2288
|
];
|
|
2048
2289
|
var removeAgentFields = (requestedFields) => {
|
|
2049
2290
|
const filtered = requestedFields.filter((field) => !backendAgentFields.includes(field));
|
|
2050
2291
|
filtered.push("backend");
|
|
2051
2292
|
return filtered;
|
|
2052
2293
|
};
|
|
2053
|
-
var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
2294
|
+
var addAgentFields = async (requestedFields, agents, result, tools, user) => {
|
|
2054
2295
|
let backend = agents.find((a) => a.id === result?.backend);
|
|
2055
2296
|
if (requestedFields.includes("providerName")) {
|
|
2056
2297
|
result.providerName = backend?.providerName || "";
|
|
@@ -2065,13 +2306,39 @@ var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
|
2065
2306
|
result.rateLimit = backend?.rateLimit || "";
|
|
2066
2307
|
}
|
|
2067
2308
|
if (requestedFields.includes("tools")) {
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2309
|
+
if (result.tools) {
|
|
2310
|
+
result.tools = await Promise.all(result.tools.map(async (tool2) => {
|
|
2311
|
+
let hydrated;
|
|
2312
|
+
if (tool2.type === "agent") {
|
|
2313
|
+
if (tool2.id === result.id) {
|
|
2314
|
+
return null;
|
|
2315
|
+
}
|
|
2316
|
+
const instance = await loadAgent(tool2.id);
|
|
2317
|
+
if (!instance) {
|
|
2318
|
+
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.");
|
|
2319
|
+
}
|
|
2320
|
+
const backend2 = agents.find((a) => a.id === instance.backend);
|
|
2321
|
+
if (!backend2) {
|
|
2322
|
+
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.");
|
|
2323
|
+
}
|
|
2324
|
+
const hasAccessToAgent = await checkRecordAccess(instance, "read", user);
|
|
2325
|
+
if (!hasAccessToAgent) {
|
|
2326
|
+
return null;
|
|
2327
|
+
}
|
|
2328
|
+
hydrated = await backend2.tool(instance.id, agents);
|
|
2329
|
+
} else {
|
|
2330
|
+
hydrated = tools.find((t) => t.id === tool2.id);
|
|
2331
|
+
}
|
|
2332
|
+
return {
|
|
2333
|
+
...tool2,
|
|
2334
|
+
name: hydrated?.name || "",
|
|
2335
|
+
description: hydrated?.description || ""
|
|
2336
|
+
};
|
|
2337
|
+
}));
|
|
2338
|
+
result.tools = result.tools.filter((tool2) => tool2 !== null);
|
|
2339
|
+
} else {
|
|
2340
|
+
result.tools = [];
|
|
2341
|
+
}
|
|
2075
2342
|
}
|
|
2076
2343
|
if (requestedFields.includes("streaming")) {
|
|
2077
2344
|
result.streaming = backend?.streaming || false;
|
|
@@ -2079,6 +2346,9 @@ var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
|
2079
2346
|
if (requestedFields.includes("capabilities")) {
|
|
2080
2347
|
result.capabilities = backend?.capabilities || [];
|
|
2081
2348
|
}
|
|
2349
|
+
if (requestedFields.includes("maxContextLength")) {
|
|
2350
|
+
result.maxContextLength = backend?.maxContextLength || 0;
|
|
2351
|
+
}
|
|
2082
2352
|
if (!requestedFields.includes("backend")) {
|
|
2083
2353
|
delete result.backend;
|
|
2084
2354
|
}
|
|
@@ -2128,7 +2398,11 @@ var postprocessUpdate = async ({
|
|
|
2128
2398
|
const { db: db3 } = await postgresClient();
|
|
2129
2399
|
console.log("[EXULU] Deleting chunks for item", result.id);
|
|
2130
2400
|
await db3.from(getChunksTableName(context.id)).where({ source: result.id }).delete();
|
|
2401
|
+
console.log("[EXULU] Deleted chunks for item", result.id);
|
|
2402
|
+
console.log("[EXULU] Embedder", context.embedder);
|
|
2403
|
+
console.log("[EXULU] Configuration", context.configuration);
|
|
2131
2404
|
if (context.embedder && (context.configuration.calculateVectors === "onUpdate" || context.configuration.calculateVectors === "always")) {
|
|
2405
|
+
console.log("[EXULU] Generating embeddings for item", result.id);
|
|
2132
2406
|
const { job } = await context.embeddings.generate.one({
|
|
2133
2407
|
item: result,
|
|
2134
2408
|
user,
|
|
@@ -2189,7 +2463,8 @@ var finalizeRequestedFields = async ({
|
|
|
2189
2463
|
agents,
|
|
2190
2464
|
contexts,
|
|
2191
2465
|
tools,
|
|
2192
|
-
result
|
|
2466
|
+
result,
|
|
2467
|
+
user
|
|
2193
2468
|
}) => {
|
|
2194
2469
|
if (!result) {
|
|
2195
2470
|
return result;
|
|
@@ -2199,11 +2474,11 @@ var finalizeRequestedFields = async ({
|
|
|
2199
2474
|
}
|
|
2200
2475
|
if (Array.isArray(result)) {
|
|
2201
2476
|
result = result.map((item) => {
|
|
2202
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item });
|
|
2477
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item, user });
|
|
2203
2478
|
});
|
|
2204
2479
|
} else {
|
|
2205
2480
|
if (table.name.singular === "agent") {
|
|
2206
|
-
result = addAgentFields(requestedFields, agents, result, tools);
|
|
2481
|
+
result = await addAgentFields(requestedFields, agents, result, tools, user);
|
|
2207
2482
|
if (!requestedFields.includes("backend")) {
|
|
2208
2483
|
delete result.backend;
|
|
2209
2484
|
}
|
|
@@ -2250,7 +2525,6 @@ var applyFilters = (query, filters) => {
|
|
|
2250
2525
|
Object.entries(filter).forEach(([fieldName, operators]) => {
|
|
2251
2526
|
if (operators) {
|
|
2252
2527
|
if (operators.and !== void 0) {
|
|
2253
|
-
console.log("operators.and", operators.and);
|
|
2254
2528
|
operators.and.forEach((operator) => {
|
|
2255
2529
|
query = converOperatorToQuery(query, fieldName, operator);
|
|
2256
2530
|
});
|
|
@@ -2261,7 +2535,6 @@ var applyFilters = (query, filters) => {
|
|
|
2261
2535
|
});
|
|
2262
2536
|
}
|
|
2263
2537
|
query = converOperatorToQuery(query, fieldName, operators);
|
|
2264
|
-
console.log("query", query);
|
|
2265
2538
|
}
|
|
2266
2539
|
});
|
|
2267
2540
|
});
|
|
@@ -2284,7 +2557,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2284
2557
|
let query = db3.from(tableNamePlural).select(sanitizedFields).where({ id: args.id });
|
|
2285
2558
|
query = applyAccessControl(table, context.user, query);
|
|
2286
2559
|
let result = await query.first();
|
|
2287
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2560
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2288
2561
|
},
|
|
2289
2562
|
[`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
|
|
2290
2563
|
const { db: db3 } = context;
|
|
@@ -2293,7 +2566,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2293
2566
|
let query = db3.from(tableNamePlural).select(sanitizedFields).whereIn("id", args.ids);
|
|
2294
2567
|
query = applyAccessControl(table, context.user, query);
|
|
2295
2568
|
let result = await query;
|
|
2296
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2569
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2297
2570
|
},
|
|
2298
2571
|
[`${tableNameSingular}One`]: async (_, args, context, info) => {
|
|
2299
2572
|
const { filters = [], sort } = args;
|
|
@@ -2305,7 +2578,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2305
2578
|
query = applyAccessControl(table, context.user, query);
|
|
2306
2579
|
query = applySorting(query, sort);
|
|
2307
2580
|
let result = await query.first();
|
|
2308
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2581
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2309
2582
|
},
|
|
2310
2583
|
[`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
|
|
2311
2584
|
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
@@ -2316,7 +2589,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2316
2589
|
let countQuery = db3(tableNamePlural);
|
|
2317
2590
|
countQuery = applyFilters(countQuery, filters);
|
|
2318
2591
|
countQuery = applyAccessControl(table, context.user, countQuery);
|
|
2319
|
-
console.log("countQuery", countQuery);
|
|
2320
2592
|
const countResult = await countQuery.count("* as count");
|
|
2321
2593
|
const itemCount = Number(countResult[0]?.count || 0);
|
|
2322
2594
|
const pageCount = Math.ceil(itemCount / limit);
|
|
@@ -2341,7 +2613,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2341
2613
|
hasPreviousPage,
|
|
2342
2614
|
hasNextPage
|
|
2343
2615
|
},
|
|
2344
|
-
items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items })
|
|
2616
|
+
items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items, user: context.user })
|
|
2345
2617
|
};
|
|
2346
2618
|
},
|
|
2347
2619
|
// Add generic statistics query for all tables
|
|
@@ -2359,7 +2631,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2359
2631
|
query = query.count("* as count");
|
|
2360
2632
|
}
|
|
2361
2633
|
const results = await query;
|
|
2362
|
-
console.log("!!! results !!!", results);
|
|
2363
2634
|
return results.map((r) => ({
|
|
2364
2635
|
group: r[groupBy],
|
|
2365
2636
|
count: r.count ? Number(r.count) : 0
|
|
@@ -2368,7 +2639,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2368
2639
|
if (tableNamePlural === "tracking") {
|
|
2369
2640
|
query = query.sum("total as count");
|
|
2370
2641
|
const [{ count }] = await query.sum("total as count");
|
|
2371
|
-
console.log("!!! count !!!", count);
|
|
2372
2642
|
return [{
|
|
2373
2643
|
group: "total",
|
|
2374
2644
|
count: count ? Number(count) : 0
|
|
@@ -2453,7 +2723,6 @@ var vectorSearch = async ({
|
|
|
2453
2723
|
let countQuery = db3(mainTable);
|
|
2454
2724
|
countQuery = applyFilters(countQuery, filters);
|
|
2455
2725
|
countQuery = applyAccessControl(table, user, countQuery);
|
|
2456
|
-
console.log("countQuery", countQuery);
|
|
2457
2726
|
const columns = await db3(mainTable).columnInfo();
|
|
2458
2727
|
let itemsQuery = db3(mainTable).select(Object.keys(columns).map((column) => mainTable + "." + column));
|
|
2459
2728
|
itemsQuery = applyFilters(itemsQuery, filters);
|
|
@@ -2592,7 +2861,6 @@ var vectorSearch = async ({
|
|
|
2592
2861
|
];
|
|
2593
2862
|
items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
|
|
2594
2863
|
}
|
|
2595
|
-
console.log("items", items);
|
|
2596
2864
|
const seenSources = /* @__PURE__ */ new Map();
|
|
2597
2865
|
items = items.reduce((acc, item) => {
|
|
2598
2866
|
if (!seenSources.has(item.source)) {
|
|
@@ -2635,7 +2903,6 @@ var vectorSearch = async ({
|
|
|
2635
2903
|
}
|
|
2636
2904
|
return acc;
|
|
2637
2905
|
}, []);
|
|
2638
|
-
console.log("items", items);
|
|
2639
2906
|
items.forEach((item) => {
|
|
2640
2907
|
if (!item.chunks?.length) {
|
|
2641
2908
|
return;
|
|
@@ -2657,7 +2924,6 @@ var vectorSearch = async ({
|
|
|
2657
2924
|
item.averageRelevance = average;
|
|
2658
2925
|
item.totalRelevance = total;
|
|
2659
2926
|
} else if (method === "hybridSearch") {
|
|
2660
|
-
console.log("item.chunks", item.chunks);
|
|
2661
2927
|
const scores = item.chunks.map((c) => typeof c.hybrid_score === "number" ? c.hybrid_score * 10 + 1 : 0);
|
|
2662
2928
|
const total = scores.reduce((a, b) => a + b, 0);
|
|
2663
2929
|
const average = scores.length ? total / scores.length : 0;
|
|
@@ -2746,6 +3012,10 @@ var contextToTableDefinition = (context) => {
|
|
|
2746
3012
|
name: "textlength",
|
|
2747
3013
|
type: "number"
|
|
2748
3014
|
});
|
|
3015
|
+
definition.fields.push({
|
|
3016
|
+
name: "ttl",
|
|
3017
|
+
type: "text"
|
|
3018
|
+
});
|
|
2749
3019
|
definition.fields.push({
|
|
2750
3020
|
name: "embeddings_updated_at",
|
|
2751
3021
|
type: "date"
|
|
@@ -3030,8 +3300,19 @@ type PageInfo {
|
|
|
3030
3300
|
};
|
|
3031
3301
|
resolvers.Query["tools"] = async (_, args, context, info) => {
|
|
3032
3302
|
const requestedFields = getRequestedFields(info);
|
|
3303
|
+
const instances = await loadAgents();
|
|
3304
|
+
let agentTools = await Promise.all(
|
|
3305
|
+
instances.map(async (instance) => {
|
|
3306
|
+
const backend = agents.find((a) => a.id === instance.backend);
|
|
3307
|
+
if (!backend) {
|
|
3308
|
+
return null;
|
|
3309
|
+
}
|
|
3310
|
+
return await backend.tool(instance.id, agents);
|
|
3311
|
+
})
|
|
3312
|
+
);
|
|
3313
|
+
const filtered = agentTools.filter((tool2) => tool2 !== null);
|
|
3033
3314
|
return {
|
|
3034
|
-
items: tools.map((tool2) => {
|
|
3315
|
+
items: [...filtered, ...tools].map((tool2) => {
|
|
3035
3316
|
const object = {};
|
|
3036
3317
|
requestedFields.forEach((field) => {
|
|
3037
3318
|
object[field] = tool2[field];
|
|
@@ -3127,7 +3408,6 @@ type Tool {
|
|
|
3127
3408
|
|
|
3128
3409
|
enum EnumProviderType {
|
|
3129
3410
|
agent
|
|
3130
|
-
custom
|
|
3131
3411
|
}
|
|
3132
3412
|
|
|
3133
3413
|
type StatisticsResult {
|
|
@@ -3161,6 +3441,9 @@ var validateCreateOrRemoveSuperAdminPermission = async (tableNamePlural, input,
|
|
|
3161
3441
|
};
|
|
3162
3442
|
|
|
3163
3443
|
// src/registry/classes.ts
|
|
3444
|
+
var import_client_s3 = require("@aws-sdk/client-s3");
|
|
3445
|
+
var import_node_crypto = require("crypto");
|
|
3446
|
+
var s3Client;
|
|
3164
3447
|
function sanitizeToolName(name) {
|
|
3165
3448
|
if (typeof name !== "string") return "";
|
|
3166
3449
|
let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
@@ -3170,17 +3453,18 @@ function sanitizeToolName(name) {
|
|
|
3170
3453
|
}
|
|
3171
3454
|
return sanitized;
|
|
3172
3455
|
}
|
|
3173
|
-
var convertToolsArrayToObject = (
|
|
3174
|
-
if (!
|
|
3175
|
-
|
|
3456
|
+
var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, filesContext2) => {
|
|
3457
|
+
if (!currentTools) return {};
|
|
3458
|
+
if (!allExuluTools) return {};
|
|
3459
|
+
const sanitizedTools = currentTools ? currentTools.map((tool2) => ({
|
|
3176
3460
|
...tool2,
|
|
3177
3461
|
name: sanitizeToolName(tool2.name)
|
|
3178
3462
|
})) : [];
|
|
3179
|
-
console.log("[EXULU] Sanitized tools", sanitizedTools);
|
|
3463
|
+
console.log("[EXULU] Sanitized tools", sanitizedTools.map((x) => x.name + " (" + x.id + ")"));
|
|
3180
3464
|
const askForConfirmation = {
|
|
3181
3465
|
description: "Ask the user for confirmation.",
|
|
3182
|
-
inputSchema:
|
|
3183
|
-
message:
|
|
3466
|
+
inputSchema: import_zod.z.object({
|
|
3467
|
+
message: import_zod.z.string().describe("The message to ask for confirmation.")
|
|
3184
3468
|
})
|
|
3185
3469
|
};
|
|
3186
3470
|
return {
|
|
@@ -3189,29 +3473,91 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) =>
|
|
|
3189
3473
|
...prev,
|
|
3190
3474
|
[cur.name]: {
|
|
3191
3475
|
...cur.tool,
|
|
3192
|
-
|
|
3476
|
+
async *execute(inputs, options) {
|
|
3193
3477
|
if (!cur.tool?.execute) {
|
|
3194
3478
|
console.error("[EXULU] Tool execute function is undefined.", cur.tool);
|
|
3195
3479
|
throw new Error("Tool execute function is undefined.");
|
|
3196
3480
|
}
|
|
3197
|
-
let config = configs?.find((config2) => config2.
|
|
3481
|
+
let config = configs?.find((config2) => config2.id === cur.id);
|
|
3198
3482
|
if (config) {
|
|
3199
3483
|
config = await hydrateVariables(config || []);
|
|
3200
3484
|
}
|
|
3485
|
+
let upload = void 0;
|
|
3486
|
+
if (exuluConfig?.fileUploads?.s3endpoint && exuluConfig?.fileUploads?.s3key && exuluConfig?.fileUploads?.s3secret && exuluConfig?.fileUploads?.s3Bucket && filesContext2) {
|
|
3487
|
+
s3Client ??= new import_client_s3.S3Client({
|
|
3488
|
+
region: exuluConfig?.fileUploads?.s3region,
|
|
3489
|
+
...exuluConfig?.fileUploads?.s3endpoint && {
|
|
3490
|
+
forcePathStyle: true,
|
|
3491
|
+
endpoint: exuluConfig?.fileUploads?.s3endpoint
|
|
3492
|
+
},
|
|
3493
|
+
credentials: {
|
|
3494
|
+
accessKeyId: exuluConfig?.fileUploads?.s3key ?? "",
|
|
3495
|
+
secretAccessKey: exuluConfig?.fileUploads?.s3secret ?? ""
|
|
3496
|
+
}
|
|
3497
|
+
});
|
|
3498
|
+
upload = async ({
|
|
3499
|
+
name,
|
|
3500
|
+
data,
|
|
3501
|
+
type,
|
|
3502
|
+
tags
|
|
3503
|
+
}) => {
|
|
3504
|
+
const mime = getMimeType(type);
|
|
3505
|
+
const key = `${user}/${generateS3Key(name)}${type}`;
|
|
3506
|
+
const command = new import_client_s3.PutObjectCommand({
|
|
3507
|
+
Bucket: exuluConfig?.fileUploads?.s3Bucket,
|
|
3508
|
+
Key: key,
|
|
3509
|
+
Body: data,
|
|
3510
|
+
ContentType: mime
|
|
3511
|
+
});
|
|
3512
|
+
try {
|
|
3513
|
+
const response2 = await s3Client.send(command);
|
|
3514
|
+
console.log(response2);
|
|
3515
|
+
const { item } = await filesContext2.createItem({
|
|
3516
|
+
name: `${name}${type}`,
|
|
3517
|
+
type: mime,
|
|
3518
|
+
rights_mode: "private",
|
|
3519
|
+
s3key: key,
|
|
3520
|
+
tags
|
|
3521
|
+
}, user?.id, user?.role?.id, false);
|
|
3522
|
+
return item;
|
|
3523
|
+
} catch (caught) {
|
|
3524
|
+
if (caught instanceof import_client_s3.S3ServiceException && caught.name === "EntityTooLarge") {
|
|
3525
|
+
console.error(
|
|
3526
|
+
`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).`
|
|
3527
|
+
);
|
|
3528
|
+
} else if (caught instanceof import_client_s3.S3ServiceException) {
|
|
3529
|
+
console.error(
|
|
3530
|
+
`Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. ${caught.name}: ${caught.message}`
|
|
3531
|
+
);
|
|
3532
|
+
} else {
|
|
3533
|
+
throw caught;
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
};
|
|
3537
|
+
}
|
|
3538
|
+
const contextsMap = contexts?.reduce((acc, curr) => {
|
|
3539
|
+
acc[curr.id] = curr;
|
|
3540
|
+
return acc;
|
|
3541
|
+
}, {});
|
|
3201
3542
|
console.log("[EXULU] Config", config);
|
|
3202
|
-
|
|
3543
|
+
const response = await cur.tool.execute({
|
|
3203
3544
|
...inputs,
|
|
3204
3545
|
// Convert config to object format if a config object
|
|
3205
3546
|
// is available, after we added the .value property
|
|
3206
3547
|
// by hydrating it from the variables table.
|
|
3207
|
-
|
|
3548
|
+
providerapikey,
|
|
3549
|
+
allExuluTools,
|
|
3550
|
+
currentTools,
|
|
3208
3551
|
user,
|
|
3209
|
-
|
|
3552
|
+
contexts: contextsMap,
|
|
3553
|
+
upload,
|
|
3210
3554
|
config: config ? config.config.reduce((acc, curr) => {
|
|
3211
3555
|
acc[curr.name] = curr.value;
|
|
3212
3556
|
return acc;
|
|
3213
3557
|
}, {}) : {}
|
|
3214
3558
|
}, options);
|
|
3559
|
+
yield response;
|
|
3560
|
+
return response;
|
|
3215
3561
|
}
|
|
3216
3562
|
}
|
|
3217
3563
|
}),
|
|
@@ -3246,6 +3592,18 @@ function generateSlug(name) {
|
|
|
3246
3592
|
const slug = lowercase.replace(/[\W_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3247
3593
|
return slug;
|
|
3248
3594
|
}
|
|
3595
|
+
function errorHandler(error) {
|
|
3596
|
+
if (error == null) {
|
|
3597
|
+
return "unknown error";
|
|
3598
|
+
}
|
|
3599
|
+
if (typeof error === "string") {
|
|
3600
|
+
return error;
|
|
3601
|
+
}
|
|
3602
|
+
if (error instanceof Error) {
|
|
3603
|
+
return error.message;
|
|
3604
|
+
}
|
|
3605
|
+
return JSON.stringify(error);
|
|
3606
|
+
}
|
|
3249
3607
|
var ExuluAgent2 = class {
|
|
3250
3608
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
3251
3609
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -3256,13 +3614,14 @@ var ExuluAgent2 = class {
|
|
|
3256
3614
|
slug = "";
|
|
3257
3615
|
type;
|
|
3258
3616
|
streaming = false;
|
|
3617
|
+
maxContextLength;
|
|
3259
3618
|
rateLimit;
|
|
3260
3619
|
config;
|
|
3261
3620
|
// private memory: Memory | undefined; // TODO do own implementation
|
|
3262
3621
|
evals;
|
|
3263
3622
|
model;
|
|
3264
3623
|
capabilities;
|
|
3265
|
-
constructor({ id, name, description, config, rateLimit, capabilities, type, evals }) {
|
|
3624
|
+
constructor({ id, name, description, config, rateLimit, capabilities, type, evals, maxContextLength }) {
|
|
3266
3625
|
this.id = id;
|
|
3267
3626
|
this.name = name;
|
|
3268
3627
|
this.evals = evals;
|
|
@@ -3270,6 +3629,7 @@ var ExuluAgent2 = class {
|
|
|
3270
3629
|
this.rateLimit = rateLimit;
|
|
3271
3630
|
this.config = config;
|
|
3272
3631
|
this.type = type;
|
|
3632
|
+
this.maxContextLength = maxContextLength;
|
|
3273
3633
|
this.capabilities = capabilities || {
|
|
3274
3634
|
text: false,
|
|
3275
3635
|
images: [],
|
|
@@ -3296,31 +3656,82 @@ var ExuluAgent2 = class {
|
|
|
3296
3656
|
}
|
|
3297
3657
|
// Exports the agent as a tool that can be used by another agent
|
|
3298
3658
|
// todo test this
|
|
3299
|
-
tool = () => {
|
|
3659
|
+
tool = async (instance, agents) => {
|
|
3660
|
+
const agentInstance = await loadAgent(instance);
|
|
3661
|
+
if (!agentInstance) {
|
|
3662
|
+
return null;
|
|
3663
|
+
}
|
|
3300
3664
|
return new ExuluTool2({
|
|
3301
|
-
id:
|
|
3302
|
-
name: `${
|
|
3665
|
+
id: agentInstance.id,
|
|
3666
|
+
name: `${agentInstance.name}`,
|
|
3303
3667
|
type: "agent",
|
|
3304
|
-
inputSchema:
|
|
3305
|
-
prompt:
|
|
3668
|
+
inputSchema: import_zod.z.object({
|
|
3669
|
+
prompt: import_zod.z.string().describe("The prompt (usually a question for the agent) to send to the agent."),
|
|
3670
|
+
information: import_zod.z.string().describe("A summary of relevant context / information from the current session")
|
|
3306
3671
|
}),
|
|
3307
|
-
description: `
|
|
3672
|
+
description: `This tool calls an AI agent named: ${agentInstance.name}. The agent does the following: ${agentInstance.description}.`,
|
|
3308
3673
|
config: [],
|
|
3309
|
-
execute: async ({ prompt,
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3674
|
+
execute: async ({ prompt, information, user, allExuluTools }) => {
|
|
3675
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
3676
|
+
if (!hasAccessToAgent) {
|
|
3677
|
+
throw new Error("You don't have access to this agent.");
|
|
3678
|
+
}
|
|
3679
|
+
let enabledTools = await getEnabledTools(agentInstance, allExuluTools, [], agents, user);
|
|
3680
|
+
const variableName = agentInstance.providerapikey;
|
|
3681
|
+
if (!variableName) {
|
|
3682
|
+
throw new Error("Provider API key variable not set for agent: " + agentInstance.name + " (" + agentInstance.id + ") being called as a tool.");
|
|
3683
|
+
}
|
|
3684
|
+
const { db: db3 } = await postgresClient();
|
|
3685
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
3686
|
+
if (!variable) {
|
|
3687
|
+
throw new Error("Provider API key variable not found for agent: " + agentInstance.name + " (" + agentInstance.id + ") being called as a tool.");
|
|
3688
|
+
}
|
|
3689
|
+
let providerapikey = variable.value;
|
|
3690
|
+
if (!variable.encrypted) {
|
|
3691
|
+
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.");
|
|
3692
|
+
}
|
|
3693
|
+
if (variable.encrypted) {
|
|
3694
|
+
const bytes = import_crypto_js2.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
3695
|
+
providerapikey = bytes.toString(import_crypto_js2.default.enc.Utf8);
|
|
3696
|
+
}
|
|
3697
|
+
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 + ")"));
|
|
3698
|
+
console.log("[EXULU] Prompt for agent '" + agentInstance.name + "' that is being called as a tool", prompt.slice(0, 100) + "...");
|
|
3699
|
+
console.log("[EXULU] Instructions for agent '" + agentInstance.name + "' that is being called as a tool", agentInstance.instructions?.slice(0, 100) + "...");
|
|
3700
|
+
const response = await this.generateSync({
|
|
3701
|
+
instructions: agentInstance.instructions,
|
|
3702
|
+
prompt: "The user has asked the following question: " + prompt + " and the following information is available: " + information,
|
|
3703
|
+
providerapikey,
|
|
3313
3704
|
user,
|
|
3314
|
-
|
|
3705
|
+
currentTools: enabledTools,
|
|
3706
|
+
allExuluTools,
|
|
3315
3707
|
statistics: {
|
|
3316
|
-
label:
|
|
3708
|
+
label: agentInstance.name,
|
|
3317
3709
|
trigger: "tool"
|
|
3318
3710
|
}
|
|
3319
3711
|
});
|
|
3712
|
+
return {
|
|
3713
|
+
result: response
|
|
3714
|
+
};
|
|
3320
3715
|
}
|
|
3321
3716
|
});
|
|
3322
3717
|
};
|
|
3323
|
-
generateSync = async ({
|
|
3718
|
+
generateSync = async ({
|
|
3719
|
+
prompt,
|
|
3720
|
+
user,
|
|
3721
|
+
session,
|
|
3722
|
+
message,
|
|
3723
|
+
currentTools,
|
|
3724
|
+
allExuluTools,
|
|
3725
|
+
statistics,
|
|
3726
|
+
toolConfigs,
|
|
3727
|
+
providerapikey,
|
|
3728
|
+
contexts,
|
|
3729
|
+
exuluConfig,
|
|
3730
|
+
filesContext: filesContext2,
|
|
3731
|
+
outputSchema,
|
|
3732
|
+
instructions
|
|
3733
|
+
}) => {
|
|
3734
|
+
console.log("[EXULU] Called generate sync for agent: " + this.name, "with prompt: " + prompt?.slice(0, 100) + "...");
|
|
3324
3735
|
if (!this.model) {
|
|
3325
3736
|
throw new Error("Model is required for streaming.");
|
|
3326
3737
|
}
|
|
@@ -3333,14 +3744,18 @@ var ExuluAgent2 = class {
|
|
|
3333
3744
|
if (!prompt && !message) {
|
|
3334
3745
|
throw new Error("Prompt or message is required for generating.");
|
|
3335
3746
|
}
|
|
3747
|
+
if (outputSchema && !prompt) {
|
|
3748
|
+
throw new Error("Prompt is required for generating with an output schema.");
|
|
3749
|
+
}
|
|
3336
3750
|
const model = this.model.create({
|
|
3337
|
-
apiKey:
|
|
3751
|
+
apiKey: providerapikey
|
|
3338
3752
|
});
|
|
3753
|
+
console.log("[EXULU] Model for agent: " + this.name, " created for generating sync.");
|
|
3339
3754
|
let messages = [];
|
|
3340
3755
|
if (message && session && user) {
|
|
3341
3756
|
const previousMessages = await getAgentMessages({
|
|
3342
3757
|
session,
|
|
3343
|
-
user,
|
|
3758
|
+
user: user.id,
|
|
3344
3759
|
limit: 50,
|
|
3345
3760
|
page: 1
|
|
3346
3761
|
});
|
|
@@ -3350,56 +3765,134 @@ var ExuluAgent2 = class {
|
|
|
3350
3765
|
messages: [...previousMessagesContent, message]
|
|
3351
3766
|
});
|
|
3352
3767
|
}
|
|
3353
|
-
console.log("[EXULU]
|
|
3354
|
-
|
|
3768
|
+
console.log("[EXULU] Message count for agent: " + this.name, "loaded for generating sync.", messages.length);
|
|
3769
|
+
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.";
|
|
3770
|
+
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.";
|
|
3771
|
+
system += "\n\n" + genericContext;
|
|
3355
3772
|
if (prompt) {
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3773
|
+
let result = { object: null, text: "" };
|
|
3774
|
+
let tokens = 0;
|
|
3775
|
+
if (outputSchema) {
|
|
3776
|
+
const { object, usage } = await (0, import_ai.generateObject)({
|
|
3777
|
+
model,
|
|
3778
|
+
system,
|
|
3779
|
+
prompt,
|
|
3780
|
+
maxRetries: 3,
|
|
3781
|
+
schema: outputSchema
|
|
3782
|
+
});
|
|
3783
|
+
result.object = object;
|
|
3784
|
+
tokens = usage.totalTokens || 0;
|
|
3785
|
+
} else {
|
|
3786
|
+
console.log("[EXULU] Generating text for agent: " + this.name, "with prompt: " + prompt?.slice(0, 100) + "...");
|
|
3787
|
+
const { text, totalUsage } = await (0, import_ai.generateText)({
|
|
3788
|
+
model,
|
|
3789
|
+
system,
|
|
3790
|
+
prompt,
|
|
3791
|
+
maxRetries: 2,
|
|
3792
|
+
tools: convertToolsArrayToObject(
|
|
3793
|
+
currentTools,
|
|
3794
|
+
allExuluTools,
|
|
3795
|
+
toolConfigs,
|
|
3796
|
+
providerapikey,
|
|
3797
|
+
contexts,
|
|
3798
|
+
user,
|
|
3799
|
+
exuluConfig,
|
|
3800
|
+
filesContext2
|
|
3801
|
+
),
|
|
3802
|
+
stopWhen: [(0, import_ai.stepCountIs)(2)]
|
|
3374
3803
|
});
|
|
3804
|
+
result.text = text;
|
|
3805
|
+
tokens = totalUsage?.totalTokens || 0;
|
|
3375
3806
|
}
|
|
3376
|
-
|
|
3807
|
+
if (statistics) {
|
|
3808
|
+
await Promise.all([
|
|
3809
|
+
updateStatistic({
|
|
3810
|
+
name: "count",
|
|
3811
|
+
label: statistics.label,
|
|
3812
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3813
|
+
trigger: statistics.trigger,
|
|
3814
|
+
count: 1,
|
|
3815
|
+
user: user?.id,
|
|
3816
|
+
role: user?.role?.id
|
|
3817
|
+
}),
|
|
3818
|
+
...tokens ? [
|
|
3819
|
+
updateStatistic({
|
|
3820
|
+
name: "tokens",
|
|
3821
|
+
label: statistics.label,
|
|
3822
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3823
|
+
trigger: statistics.trigger,
|
|
3824
|
+
count: tokens
|
|
3825
|
+
})
|
|
3826
|
+
] : []
|
|
3827
|
+
]);
|
|
3828
|
+
}
|
|
3829
|
+
return result.text || result.object;
|
|
3377
3830
|
}
|
|
3378
3831
|
if (messages) {
|
|
3379
|
-
|
|
3832
|
+
console.log("[EXULU] Generating text for agent: " + this.name, "with messages: " + messages.length);
|
|
3833
|
+
const { text, totalUsage } = await (0, import_ai.generateText)({
|
|
3380
3834
|
model,
|
|
3381
3835
|
// Should be a LanguageModelV1
|
|
3382
|
-
system
|
|
3383
|
-
messages: (0, import_ai.convertToModelMessages)(messages
|
|
3836
|
+
system,
|
|
3837
|
+
messages: (0, import_ai.convertToModelMessages)(messages, {
|
|
3838
|
+
ignoreIncompleteToolCalls: true
|
|
3839
|
+
}),
|
|
3384
3840
|
maxRetries: 2,
|
|
3385
|
-
tools: convertToolsArrayToObject(
|
|
3386
|
-
|
|
3841
|
+
tools: convertToolsArrayToObject(
|
|
3842
|
+
currentTools,
|
|
3843
|
+
allExuluTools,
|
|
3844
|
+
toolConfigs,
|
|
3845
|
+
providerapikey,
|
|
3846
|
+
contexts,
|
|
3847
|
+
user,
|
|
3848
|
+
exuluConfig,
|
|
3849
|
+
filesContext2
|
|
3850
|
+
),
|
|
3851
|
+
stopWhen: [(0, import_ai.stepCountIs)(2)]
|
|
3387
3852
|
});
|
|
3388
3853
|
if (statistics) {
|
|
3389
|
-
await
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3854
|
+
await Promise.all([
|
|
3855
|
+
updateStatistic({
|
|
3856
|
+
name: "count",
|
|
3857
|
+
label: statistics.label,
|
|
3858
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3859
|
+
trigger: statistics.trigger,
|
|
3860
|
+
count: 1,
|
|
3861
|
+
user: user?.id,
|
|
3862
|
+
role: user?.role?.id
|
|
3863
|
+
}),
|
|
3864
|
+
...totalUsage?.totalTokens ? [
|
|
3865
|
+
updateStatistic({
|
|
3866
|
+
name: "tokens",
|
|
3867
|
+
label: statistics.label,
|
|
3868
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3869
|
+
trigger: statistics.trigger,
|
|
3870
|
+
count: totalUsage?.totalTokens,
|
|
3871
|
+
user: user?.id,
|
|
3872
|
+
role: user?.role?.id
|
|
3873
|
+
})
|
|
3874
|
+
] : []
|
|
3875
|
+
]);
|
|
3398
3876
|
}
|
|
3399
3877
|
return text;
|
|
3400
3878
|
}
|
|
3879
|
+
return "";
|
|
3401
3880
|
};
|
|
3402
|
-
generateStream = async ({
|
|
3881
|
+
generateStream = async ({
|
|
3882
|
+
express: express3,
|
|
3883
|
+
user,
|
|
3884
|
+
session,
|
|
3885
|
+
message,
|
|
3886
|
+
currentTools,
|
|
3887
|
+
allExuluTools,
|
|
3888
|
+
statistics,
|
|
3889
|
+
toolConfigs,
|
|
3890
|
+
providerapikey,
|
|
3891
|
+
contexts,
|
|
3892
|
+
exuluConfig,
|
|
3893
|
+
filesContext: filesContext2,
|
|
3894
|
+
instructions
|
|
3895
|
+
}) => {
|
|
3403
3896
|
if (!this.model) {
|
|
3404
3897
|
throw new Error("Model is required for streaming.");
|
|
3405
3898
|
}
|
|
@@ -3410,60 +3903,110 @@ var ExuluAgent2 = class {
|
|
|
3410
3903
|
throw new Error("Message is required for streaming.");
|
|
3411
3904
|
}
|
|
3412
3905
|
const model = this.model.create({
|
|
3413
|
-
apiKey:
|
|
3906
|
+
apiKey: providerapikey
|
|
3414
3907
|
});
|
|
3415
3908
|
let messages = [];
|
|
3416
3909
|
const previousMessages = await getAgentMessages({
|
|
3417
3910
|
session,
|
|
3418
|
-
user,
|
|
3911
|
+
user: user.id,
|
|
3419
3912
|
limit: 50,
|
|
3420
3913
|
page: 1
|
|
3421
3914
|
});
|
|
3422
|
-
const previousMessagesContent = previousMessages.map(
|
|
3915
|
+
const previousMessagesContent = previousMessages.map(
|
|
3916
|
+
(message2) => JSON.parse(message2.content)
|
|
3917
|
+
);
|
|
3423
3918
|
messages = await (0, import_ai.validateUIMessages)({
|
|
3424
3919
|
// append the new message to the previous messages:
|
|
3425
3920
|
messages: [...previousMessagesContent, message]
|
|
3426
3921
|
});
|
|
3922
|
+
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.";
|
|
3923
|
+
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.";
|
|
3924
|
+
system += "\n\n" + genericContext;
|
|
3925
|
+
console.log("[EXULU] tools for agent: " + this.name, currentTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
3926
|
+
console.log("[EXULU] system", system.slice(0, 100) + "...");
|
|
3427
3927
|
const result = (0, import_ai.streamText)({
|
|
3428
3928
|
model,
|
|
3429
3929
|
// Should be a LanguageModelV1
|
|
3430
|
-
messages: (0, import_ai.convertToModelMessages)(messages
|
|
3431
|
-
|
|
3930
|
+
messages: (0, import_ai.convertToModelMessages)(messages, {
|
|
3931
|
+
ignoreIncompleteToolCalls: true
|
|
3932
|
+
}),
|
|
3933
|
+
// prepareStep could be used here to set the model for the first step or change other params
|
|
3934
|
+
system,
|
|
3432
3935
|
maxRetries: 2,
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3936
|
+
providerOptions: {
|
|
3937
|
+
openai: {
|
|
3938
|
+
reasoningSummary: "auto"
|
|
3939
|
+
}
|
|
3940
|
+
},
|
|
3941
|
+
tools: convertToolsArrayToObject(
|
|
3942
|
+
currentTools,
|
|
3943
|
+
allExuluTools,
|
|
3944
|
+
toolConfigs,
|
|
3945
|
+
providerapikey,
|
|
3946
|
+
contexts,
|
|
3947
|
+
user,
|
|
3948
|
+
exuluConfig,
|
|
3949
|
+
filesContext2
|
|
3950
|
+
),
|
|
3951
|
+
onError: (error) => console.error("[EXULU] chat stream error.", error)
|
|
3952
|
+
// stopWhen: [stepCountIs(1)],
|
|
3436
3953
|
});
|
|
3437
3954
|
result.consumeStream();
|
|
3438
3955
|
result.pipeUIMessageStreamToResponse(express3.res, {
|
|
3956
|
+
messageMetadata: ({ part }) => {
|
|
3957
|
+
if (part.type === "finish") {
|
|
3958
|
+
return {
|
|
3959
|
+
totalTokens: part.totalUsage.totalTokens,
|
|
3960
|
+
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
3961
|
+
inputTokens: part.totalUsage.inputTokens,
|
|
3962
|
+
outputTokens: part.totalUsage.outputTokens,
|
|
3963
|
+
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
},
|
|
3439
3967
|
originalMessages: messages,
|
|
3440
3968
|
sendReasoning: true,
|
|
3969
|
+
sendSources: true,
|
|
3970
|
+
onError: (error) => {
|
|
3971
|
+
console.error("[EXULU] chat response error.", error);
|
|
3972
|
+
return errorHandler(error);
|
|
3973
|
+
},
|
|
3441
3974
|
generateMessageId: (0, import_ai.createIdGenerator)({
|
|
3442
3975
|
prefix: "msg_",
|
|
3443
3976
|
size: 16
|
|
3444
3977
|
}),
|
|
3445
|
-
onFinish: async ({ messages: messages2 }) => {
|
|
3446
|
-
console.info(
|
|
3447
|
-
"[EXULU] chat stream finished.",
|
|
3448
|
-
messages2
|
|
3449
|
-
);
|
|
3978
|
+
onFinish: async ({ messages: messages2, isContinuation, isAborted, responseMessage }) => {
|
|
3450
3979
|
if (session) {
|
|
3451
3980
|
await saveChat({
|
|
3452
3981
|
session,
|
|
3453
|
-
user,
|
|
3454
|
-
messages: messages2
|
|
3982
|
+
user: user.id,
|
|
3983
|
+
messages: messages2.filter((x) => !previousMessagesContent.find((y) => y.id === x.id))
|
|
3455
3984
|
});
|
|
3456
3985
|
}
|
|
3986
|
+
const metadata = messages2[messages2.length - 1]?.metadata;
|
|
3457
3987
|
if (statistics) {
|
|
3458
|
-
await
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3988
|
+
await Promise.all([
|
|
3989
|
+
updateStatistic({
|
|
3990
|
+
name: "count",
|
|
3991
|
+
label: statistics.label,
|
|
3992
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3993
|
+
trigger: statistics.trigger,
|
|
3994
|
+
count: 1,
|
|
3995
|
+
user: user.id,
|
|
3996
|
+
role: user?.role?.id
|
|
3997
|
+
}),
|
|
3998
|
+
...metadata?.totalTokens ? [
|
|
3999
|
+
updateStatistic({
|
|
4000
|
+
name: "tokens",
|
|
4001
|
+
label: statistics.label,
|
|
4002
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
4003
|
+
trigger: statistics.trigger,
|
|
4004
|
+
count: metadata?.totalTokens,
|
|
4005
|
+
user: user.id,
|
|
4006
|
+
role: user?.role?.id
|
|
4007
|
+
})
|
|
4008
|
+
] : []
|
|
4009
|
+
]);
|
|
3467
4010
|
}
|
|
3468
4011
|
}
|
|
3469
4012
|
});
|
|
@@ -3472,7 +4015,12 @@ var ExuluAgent2 = class {
|
|
|
3472
4015
|
};
|
|
3473
4016
|
var getAgentMessages = async ({ session, user, limit, page }) => {
|
|
3474
4017
|
const { db: db3 } = await postgresClient();
|
|
3475
|
-
|
|
4018
|
+
console.log("[EXULU] getting agent messages for session: " + session + " and user: " + user + " and page: " + page);
|
|
4019
|
+
const query = db3.from("agent_messages").where({ session, user }).limit(limit);
|
|
4020
|
+
if (page > 0) {
|
|
4021
|
+
query.offset((page - 1) * limit);
|
|
4022
|
+
}
|
|
4023
|
+
const messages = await query;
|
|
3476
4024
|
return messages;
|
|
3477
4025
|
};
|
|
3478
4026
|
var saveChat = async ({ session, user, messages }) => {
|
|
@@ -3582,31 +4130,31 @@ var ExuluEval = class {
|
|
|
3582
4130
|
throw new Error("Prompt is required for running an agent.");
|
|
3583
4131
|
}
|
|
3584
4132
|
const { db: db4 } = await postgresClient();
|
|
3585
|
-
const variableName = runner.agent.
|
|
4133
|
+
const variableName = runner.agent.providerapikey;
|
|
3586
4134
|
const variable = await db4.from("variables").where({ name: variableName }).first();
|
|
3587
4135
|
if (!variable) {
|
|
3588
|
-
throw new Error(`Provider API key for variable "${runner.agent.
|
|
4136
|
+
throw new Error(`Provider API key for variable "${runner.agent.providerapikey}" not found.`);
|
|
3589
4137
|
}
|
|
3590
|
-
let
|
|
4138
|
+
let providerapikey = variable.value;
|
|
3591
4139
|
if (!variable.encrypted) {
|
|
3592
|
-
throw new Error(`Provider API key for variable "${runner.agent.
|
|
4140
|
+
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
4141
|
}
|
|
3594
4142
|
if (variable.encrypted) {
|
|
3595
4143
|
const bytes = import_crypto_js2.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
3596
|
-
|
|
4144
|
+
providerapikey = bytes.toString(import_crypto_js2.default.enc.Utf8);
|
|
3597
4145
|
}
|
|
3598
4146
|
const result = await runner.agent.generateSync({
|
|
3599
4147
|
prompt: data.prompt,
|
|
3600
|
-
|
|
4148
|
+
providerapikey
|
|
3601
4149
|
});
|
|
3602
4150
|
data.result = result;
|
|
3603
4151
|
}
|
|
3604
4152
|
const { object } = await (0, import_ai.generateObject)({
|
|
3605
4153
|
model,
|
|
3606
4154
|
maxRetries: 3,
|
|
3607
|
-
schema:
|
|
3608
|
-
correctnessScore:
|
|
3609
|
-
comment:
|
|
4155
|
+
schema: import_zod.z.object({
|
|
4156
|
+
correctnessScore: import_zod.z.number(),
|
|
4157
|
+
comment: import_zod.z.string()
|
|
3610
4158
|
}),
|
|
3611
4159
|
prompt: `You are checking if the below "actual_answers" contain the correct information as
|
|
3612
4160
|
presented in the "correct_answers" section to calculate the correctness score.
|
|
@@ -3677,7 +4225,7 @@ var ExuluTool2 = class {
|
|
|
3677
4225
|
this.type = type;
|
|
3678
4226
|
this.tool = (0, import_ai.tool)({
|
|
3679
4227
|
description,
|
|
3680
|
-
inputSchema: inputSchema ||
|
|
4228
|
+
inputSchema: inputSchema || import_zod.z.object({}),
|
|
3681
4229
|
execute: execute2
|
|
3682
4230
|
});
|
|
3683
4231
|
}
|
|
@@ -3756,10 +4304,6 @@ var ExuluContext = class {
|
|
|
3756
4304
|
label: statistics?.label || this.name,
|
|
3757
4305
|
trigger: statistics?.trigger || "agent"
|
|
3758
4306
|
}, user, role);
|
|
3759
|
-
const exists = await db3.schema.hasTable(getChunksTableName(this.id));
|
|
3760
|
-
if (!exists) {
|
|
3761
|
-
await this.createChunksTable();
|
|
3762
|
-
}
|
|
3763
4307
|
await db3.from(getChunksTableName(this.id)).where({ source }).delete();
|
|
3764
4308
|
await db3.from(getChunksTableName(this.id)).insert(chunks.map((chunk) => ({
|
|
3765
4309
|
source,
|
|
@@ -3776,21 +4320,115 @@ var ExuluContext = class {
|
|
|
3776
4320
|
job
|
|
3777
4321
|
};
|
|
3778
4322
|
};
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
4323
|
+
createItem = async (item, user, role, upsert) => {
|
|
4324
|
+
const { db: db3 } = await postgresClient();
|
|
4325
|
+
const mutation = db3.from(getTableName(
|
|
4326
|
+
this.id
|
|
4327
|
+
)).insert(
|
|
4328
|
+
{
|
|
4329
|
+
...item,
|
|
4330
|
+
tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
|
|
4331
|
+
}
|
|
4332
|
+
).returning("id");
|
|
4333
|
+
if (upsert) {
|
|
4334
|
+
mutation.onConflict().merge();
|
|
4335
|
+
}
|
|
4336
|
+
const results = await mutation;
|
|
4337
|
+
if (!results[0]) {
|
|
4338
|
+
throw new Error("Failed to create item.");
|
|
4339
|
+
}
|
|
4340
|
+
if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
|
|
4341
|
+
const { job } = await this.embeddings.generate.one({
|
|
4342
|
+
item: results[0],
|
|
3783
4343
|
user,
|
|
3784
4344
|
role,
|
|
3785
|
-
trigger
|
|
3786
|
-
})
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
4345
|
+
trigger: "api"
|
|
4346
|
+
});
|
|
4347
|
+
return {
|
|
4348
|
+
item: results[0],
|
|
4349
|
+
job
|
|
4350
|
+
};
|
|
4351
|
+
}
|
|
4352
|
+
return {
|
|
4353
|
+
item: results[0],
|
|
4354
|
+
job: void 0
|
|
4355
|
+
};
|
|
4356
|
+
};
|
|
4357
|
+
updateItem = async (item, user, role) => {
|
|
4358
|
+
const { db: db3 } = await postgresClient();
|
|
4359
|
+
const record = await db3.from(
|
|
4360
|
+
getTableName(this.id)
|
|
4361
|
+
).where(
|
|
4362
|
+
{ id: item.id }
|
|
4363
|
+
).first();
|
|
4364
|
+
if (!record) {
|
|
4365
|
+
throw new Error("Item not found.");
|
|
4366
|
+
}
|
|
4367
|
+
const mutation = db3.from(
|
|
4368
|
+
getTableName(this.id)
|
|
4369
|
+
).where(
|
|
4370
|
+
{ id: record.id }
|
|
4371
|
+
).update(
|
|
4372
|
+
{
|
|
4373
|
+
...item,
|
|
4374
|
+
tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
|
|
4375
|
+
}
|
|
4376
|
+
).returning("id");
|
|
4377
|
+
await mutation;
|
|
4378
|
+
if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
|
|
4379
|
+
const { job } = await this.embeddings.generate.one({
|
|
4380
|
+
item: record,
|
|
4381
|
+
// important we need to full record here with all fields
|
|
4382
|
+
user,
|
|
4383
|
+
role,
|
|
4384
|
+
trigger: "api"
|
|
4385
|
+
});
|
|
4386
|
+
return {
|
|
4387
|
+
item: record,
|
|
4388
|
+
job
|
|
4389
|
+
};
|
|
4390
|
+
}
|
|
4391
|
+
return {
|
|
4392
|
+
item: record,
|
|
4393
|
+
job: void 0
|
|
4394
|
+
};
|
|
4395
|
+
};
|
|
4396
|
+
deleteItem = async (item, user, role) => {
|
|
4397
|
+
if (!item.id) {
|
|
4398
|
+
throw new Error("Item id is required for deleting item.");
|
|
4399
|
+
}
|
|
4400
|
+
const { db: db3 } = await postgresClient();
|
|
4401
|
+
await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
|
|
4402
|
+
if (!this.embedder) {
|
|
4403
|
+
return {
|
|
4404
|
+
id: item.id,
|
|
4405
|
+
job: void 0
|
|
4406
|
+
};
|
|
4407
|
+
}
|
|
4408
|
+
const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
|
|
4409
|
+
if (chunks.length > 0) {
|
|
4410
|
+
await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
|
|
4411
|
+
}
|
|
4412
|
+
return {
|
|
4413
|
+
id: item.id,
|
|
4414
|
+
job: void 0
|
|
4415
|
+
};
|
|
4416
|
+
};
|
|
4417
|
+
embeddings = {
|
|
4418
|
+
generate: {
|
|
4419
|
+
one: async ({
|
|
4420
|
+
item,
|
|
4421
|
+
user,
|
|
4422
|
+
role,
|
|
4423
|
+
trigger
|
|
4424
|
+
}) => {
|
|
4425
|
+
console.log("[EXULU] Generating embeddings for item", item.id);
|
|
4426
|
+
if (!this.embedder) {
|
|
4427
|
+
throw new Error("Embedder is not set for this context.");
|
|
4428
|
+
}
|
|
4429
|
+
if (!item.id) {
|
|
4430
|
+
throw new Error("Item id is required for generating embeddings.");
|
|
4431
|
+
}
|
|
3794
4432
|
if (this.embedder.queue?.name) {
|
|
3795
4433
|
console.log("[EXULU] embedder is in queue mode, scheduling job.");
|
|
3796
4434
|
const job = await bullmqDecorator({
|
|
@@ -3843,301 +4481,6 @@ var ExuluContext = class {
|
|
|
3843
4481
|
}
|
|
3844
4482
|
}
|
|
3845
4483
|
};
|
|
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
4484
|
createItemsTable = async () => {
|
|
4142
4485
|
const { db: db3 } = await postgresClient();
|
|
4143
4486
|
const tableName = getTableName(this.id);
|
|
@@ -4151,6 +4494,7 @@ var ExuluContext = class {
|
|
|
4151
4494
|
table.boolean("archived").defaultTo(false);
|
|
4152
4495
|
table.text("external_id");
|
|
4153
4496
|
table.text("created_by");
|
|
4497
|
+
table.text("ttl");
|
|
4154
4498
|
table.text("rights_mode").defaultTo(this.configuration?.defaultRightsMode ?? "private");
|
|
4155
4499
|
table.integer("textlength");
|
|
4156
4500
|
table.text("source");
|
|
@@ -4168,7 +4512,7 @@ var ExuluContext = class {
|
|
|
4168
4512
|
});
|
|
4169
4513
|
};
|
|
4170
4514
|
createChunksTable = async () => {
|
|
4171
|
-
const { db: db3 } = await
|
|
4515
|
+
const { db: db3 } = await refreshPostgresClient();
|
|
4172
4516
|
const tableName = getChunksTableName(this.id);
|
|
4173
4517
|
console.log("[EXULU] Creating table: " + tableName);
|
|
4174
4518
|
await db3.schema.createTable(tableName, (table) => {
|
|
@@ -4193,8 +4537,8 @@ var ExuluContext = class {
|
|
|
4193
4537
|
CREATE INDEX IF NOT EXISTS ${tableName}_embedding_hnsw_cosine
|
|
4194
4538
|
ON ${tableName}
|
|
4195
4539
|
USING hnsw (embedding vector_cosine_ops)
|
|
4196
|
-
WHERE embedding IS NOT NULL
|
|
4197
4540
|
WITH (m = 16, ef_construction = 64)
|
|
4541
|
+
WHERE embedding IS NOT NULL
|
|
4198
4542
|
`);
|
|
4199
4543
|
return;
|
|
4200
4544
|
};
|
|
@@ -4204,14 +4548,14 @@ var ExuluContext = class {
|
|
|
4204
4548
|
id: this.id,
|
|
4205
4549
|
name: `${this.name}`,
|
|
4206
4550
|
type: "context",
|
|
4207
|
-
inputSchema:
|
|
4208
|
-
query:
|
|
4551
|
+
inputSchema: import_zod.z.object({
|
|
4552
|
+
query: import_zod.z.string()
|
|
4209
4553
|
}),
|
|
4210
4554
|
config: [],
|
|
4211
4555
|
description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
|
|
4212
4556
|
execute: async ({ query, user, role }) => {
|
|
4213
4557
|
const { db: db3 } = await postgresClient();
|
|
4214
|
-
await vectorSearch({
|
|
4558
|
+
const result = await vectorSearch({
|
|
4215
4559
|
page: 1,
|
|
4216
4560
|
limit: 10,
|
|
4217
4561
|
query,
|
|
@@ -4224,6 +4568,9 @@ var ExuluContext = class {
|
|
|
4224
4568
|
sort: void 0,
|
|
4225
4569
|
trigger: "agent"
|
|
4226
4570
|
});
|
|
4571
|
+
return {
|
|
4572
|
+
items: result.items
|
|
4573
|
+
};
|
|
4227
4574
|
}
|
|
4228
4575
|
});
|
|
4229
4576
|
};
|
|
@@ -4239,7 +4586,6 @@ var updateStatistic = async (statistic) => {
|
|
|
4239
4586
|
type: statistic.type,
|
|
4240
4587
|
createdAt: currentDate
|
|
4241
4588
|
}).first();
|
|
4242
|
-
console.log("!!! existing !!!", existing);
|
|
4243
4589
|
if (!existing) {
|
|
4244
4590
|
await db3.from("tracking").insert({
|
|
4245
4591
|
name: statistic.name,
|
|
@@ -4261,6 +4607,53 @@ var updateStatistic = async (statistic) => {
|
|
|
4261
4607
|
});
|
|
4262
4608
|
}
|
|
4263
4609
|
};
|
|
4610
|
+
var generateS3Key = (filename) => `${(0, import_node_crypto.randomUUID)()}-${filename}`;
|
|
4611
|
+
var getMimeType = (type) => {
|
|
4612
|
+
switch (type) {
|
|
4613
|
+
case ".png":
|
|
4614
|
+
return "image/png";
|
|
4615
|
+
case ".jpg":
|
|
4616
|
+
return "image/jpg";
|
|
4617
|
+
case ".jpeg":
|
|
4618
|
+
return "image/jpeg";
|
|
4619
|
+
case ".gif":
|
|
4620
|
+
return "image/gif";
|
|
4621
|
+
case ".webp":
|
|
4622
|
+
return "image/webp";
|
|
4623
|
+
case ".pdf":
|
|
4624
|
+
return "application/pdf";
|
|
4625
|
+
case ".docx":
|
|
4626
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
4627
|
+
case ".xlsx":
|
|
4628
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
4629
|
+
case ".xls":
|
|
4630
|
+
return "application/vnd.ms-excel";
|
|
4631
|
+
case ".csv":
|
|
4632
|
+
return "text/csv";
|
|
4633
|
+
case ".pptx":
|
|
4634
|
+
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
4635
|
+
case ".ppt":
|
|
4636
|
+
return "application/vnd.ms-powerpoint";
|
|
4637
|
+
case ".m4a":
|
|
4638
|
+
return "audio/mp4";
|
|
4639
|
+
case ".mp4":
|
|
4640
|
+
return "audio/mp4";
|
|
4641
|
+
case ".mpeg":
|
|
4642
|
+
return "audio/mpeg";
|
|
4643
|
+
case ".mp3":
|
|
4644
|
+
return "audio/mp3";
|
|
4645
|
+
case ".wav":
|
|
4646
|
+
return "audio/wav";
|
|
4647
|
+
case ".txt":
|
|
4648
|
+
return "text/plain";
|
|
4649
|
+
case ".md":
|
|
4650
|
+
return "text/markdown";
|
|
4651
|
+
case ".json":
|
|
4652
|
+
return "application/json";
|
|
4653
|
+
default:
|
|
4654
|
+
return "";
|
|
4655
|
+
}
|
|
4656
|
+
};
|
|
4264
4657
|
|
|
4265
4658
|
// src/registry/index.ts
|
|
4266
4659
|
var import_express7 = require("express");
|
|
@@ -4268,42 +4661,6 @@ var import_express7 = require("express");
|
|
|
4268
4661
|
// src/registry/routes.ts
|
|
4269
4662
|
var import_express3 = require("express");
|
|
4270
4663
|
|
|
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
4664
|
// src/bullmq/queues.ts
|
|
4308
4665
|
var import_bullmq4 = require("bullmq");
|
|
4309
4666
|
var import_bullmq_otel = require("bullmq-otel");
|
|
@@ -4346,10 +4703,10 @@ var import_express5 = require("@as-integrations/express5");
|
|
|
4346
4703
|
|
|
4347
4704
|
// src/registry/uppy.ts
|
|
4348
4705
|
var import_express2 = require("express");
|
|
4349
|
-
var
|
|
4706
|
+
var import_client_s32 = require("@aws-sdk/client-s3");
|
|
4350
4707
|
var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
4351
4708
|
var import_client_sts = require("@aws-sdk/client-sts");
|
|
4352
|
-
var
|
|
4709
|
+
var import_node_crypto2 = require("crypto");
|
|
4353
4710
|
var createUppyRoutes = async (app, config) => {
|
|
4354
4711
|
const policy = {
|
|
4355
4712
|
Version: "2012-10-17",
|
|
@@ -4366,11 +4723,11 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4366
4723
|
}
|
|
4367
4724
|
]
|
|
4368
4725
|
};
|
|
4369
|
-
let
|
|
4726
|
+
let s3Client2;
|
|
4370
4727
|
let stsClient;
|
|
4371
4728
|
const expiresIn = 60 * 60 * 24 * 1;
|
|
4372
4729
|
function getS3Client() {
|
|
4373
|
-
|
|
4730
|
+
s3Client2 ??= new import_client_s32.S3Client({
|
|
4374
4731
|
region: config.fileUploads.s3region,
|
|
4375
4732
|
...config.fileUploads.s3endpoint && {
|
|
4376
4733
|
forcePathStyle: true,
|
|
@@ -4381,7 +4738,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4381
4738
|
secretAccessKey: config.fileUploads.s3secret
|
|
4382
4739
|
}
|
|
4383
4740
|
});
|
|
4384
|
-
return
|
|
4741
|
+
return s3Client2;
|
|
4385
4742
|
}
|
|
4386
4743
|
function getSTSClient() {
|
|
4387
4744
|
stsClient ??= new import_client_sts.STSClient({
|
|
@@ -4394,54 +4751,51 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4394
4751
|
});
|
|
4395
4752
|
return stsClient;
|
|
4396
4753
|
}
|
|
4397
|
-
app.
|
|
4398
|
-
req.accepts;
|
|
4754
|
+
app.delete("/s3/delete", async (req, res, next) => {
|
|
4399
4755
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
4756
|
+
const internalkey = req.headers["internal-key"] || null;
|
|
4757
|
+
const { db: db3 } = await postgresClient();
|
|
4400
4758
|
let authtoken = null;
|
|
4401
|
-
if (typeof apikey !== "string") {
|
|
4759
|
+
if (typeof apikey !== "string" && typeof internalkey !== "string") {
|
|
4402
4760
|
authtoken = await getToken(req.headers.authorization ?? "");
|
|
4403
4761
|
}
|
|
4404
|
-
const { db: db3 } = await postgresClient();
|
|
4405
4762
|
const authenticationResult = await authentication({
|
|
4406
4763
|
authtoken,
|
|
4407
4764
|
apikey,
|
|
4765
|
+
internalkey,
|
|
4408
4766
|
db: db3
|
|
4409
4767
|
});
|
|
4410
4768
|
if (!authenticationResult.user?.id) {
|
|
4411
4769
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4412
4770
|
return;
|
|
4413
4771
|
}
|
|
4414
|
-
const {
|
|
4415
|
-
if (typeof
|
|
4416
|
-
res.status(400).json({ error: "
|
|
4772
|
+
const { key } = req.query;
|
|
4773
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
4774
|
+
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4417
4775
|
return;
|
|
4418
4776
|
}
|
|
4419
|
-
|
|
4420
|
-
|
|
4777
|
+
const userPrefix = key.split("/")[0];
|
|
4778
|
+
console.log("userPrefix", userPrefix);
|
|
4779
|
+
console.log("authenticationResult.user.id", authenticationResult.user.id);
|
|
4780
|
+
if (!userPrefix) {
|
|
4781
|
+
res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
|
|
4421
4782
|
return;
|
|
4422
4783
|
}
|
|
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);
|
|
4784
|
+
if (userPrefix !== authenticationResult.user.id.toString()) {
|
|
4785
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4786
|
+
return;
|
|
4444
4787
|
}
|
|
4788
|
+
if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
|
|
4789
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4790
|
+
return;
|
|
4791
|
+
}
|
|
4792
|
+
const client2 = getS3Client();
|
|
4793
|
+
const command = new import_client_s32.DeleteObjectCommand({
|
|
4794
|
+
Bucket: config.fileUploads.s3Bucket,
|
|
4795
|
+
Key: key
|
|
4796
|
+
});
|
|
4797
|
+
await client2.send(command);
|
|
4798
|
+
res.json({ key });
|
|
4445
4799
|
});
|
|
4446
4800
|
app.get("/s3/download", async (req, res, next) => {
|
|
4447
4801
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
@@ -4466,14 +4820,23 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4466
4820
|
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4467
4821
|
return;
|
|
4468
4822
|
}
|
|
4469
|
-
|
|
4823
|
+
const userPrefix = key.split("/")[0];
|
|
4824
|
+
if (!userPrefix) {
|
|
4825
|
+
res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4828
|
+
if (userPrefix !== authenticationResult.user.id.toString()) {
|
|
4829
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4832
|
+
if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
|
|
4470
4833
|
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4471
4834
|
return;
|
|
4472
4835
|
}
|
|
4473
4836
|
try {
|
|
4474
4837
|
const url = await (0, import_s3_request_presigner.getSignedUrl)(
|
|
4475
4838
|
getS3Client(),
|
|
4476
|
-
new
|
|
4839
|
+
new import_client_s32.GetObjectCommand({
|
|
4477
4840
|
Bucket: config.fileUploads.s3Bucket,
|
|
4478
4841
|
Key: key
|
|
4479
4842
|
}),
|
|
@@ -4516,7 +4879,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4516
4879
|
contentType: params.type
|
|
4517
4880
|
};
|
|
4518
4881
|
};
|
|
4519
|
-
const
|
|
4882
|
+
const generateS3Key2 = (filename) => `${(0, import_node_crypto2.randomUUID)()}-${filename}`;
|
|
4520
4883
|
const signOnServer = async (req, res, next) => {
|
|
4521
4884
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
4522
4885
|
const { db: db3 } = await postgresClient();
|
|
@@ -4535,16 +4898,11 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4535
4898
|
}
|
|
4536
4899
|
const { filename, contentType } = extractFileParameters(req);
|
|
4537
4900
|
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
|
-
}
|
|
4901
|
+
const key = generateS3Key2(filename);
|
|
4902
|
+
let folder = `${authenticationResult.user.id}/`;
|
|
4545
4903
|
(0, import_s3_request_presigner.getSignedUrl)(
|
|
4546
4904
|
getS3Client(),
|
|
4547
|
-
new
|
|
4905
|
+
new import_client_s32.PutObjectCommand({
|
|
4548
4906
|
Bucket: config.fileUploads.s3Bucket,
|
|
4549
4907
|
Key: folder + key,
|
|
4550
4908
|
ContentType: contentType
|
|
@@ -4590,7 +4948,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4590
4948
|
if (typeof type !== "string") {
|
|
4591
4949
|
return res.status(400).json({ error: "s3: content type must be a string" });
|
|
4592
4950
|
}
|
|
4593
|
-
const key = `${(0,
|
|
4951
|
+
const key = `${(0, import_node_crypto2.randomUUID)()}-${filename}`;
|
|
4594
4952
|
let folder = "";
|
|
4595
4953
|
if (authenticationResult.user.type === "api") {
|
|
4596
4954
|
folder = `api/`;
|
|
@@ -4603,7 +4961,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4603
4961
|
ContentType: type,
|
|
4604
4962
|
Metadata: metadata
|
|
4605
4963
|
};
|
|
4606
|
-
const command = new
|
|
4964
|
+
const command = new import_client_s32.CreateMultipartUploadCommand(params);
|
|
4607
4965
|
return client2.send(command, (err, data) => {
|
|
4608
4966
|
if (err) {
|
|
4609
4967
|
next(err);
|
|
@@ -4629,7 +4987,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4629
4987
|
if (typeof key !== "string") {
|
|
4630
4988
|
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' });
|
|
4631
4989
|
}
|
|
4632
|
-
return (0, import_s3_request_presigner.getSignedUrl)(getS3Client(), new
|
|
4990
|
+
return (0, import_s3_request_presigner.getSignedUrl)(getS3Client(), new import_client_s32.UploadPartCommand({
|
|
4633
4991
|
Bucket: config.fileUploads.s3Bucket,
|
|
4634
4992
|
Key: key,
|
|
4635
4993
|
UploadId: uploadId,
|
|
@@ -4650,7 +5008,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4650
5008
|
}
|
|
4651
5009
|
const parts = [];
|
|
4652
5010
|
function listPartsPage(startAt) {
|
|
4653
|
-
client2.send(new
|
|
5011
|
+
client2.send(new import_client_s32.ListPartsCommand({
|
|
4654
5012
|
Bucket: config.fileUploads.s3Bucket,
|
|
4655
5013
|
Key: key,
|
|
4656
5014
|
UploadId: uploadId,
|
|
@@ -4684,7 +5042,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4684
5042
|
if (!Array.isArray(parts) || !parts.every(isValidPart)) {
|
|
4685
5043
|
return res.status(400).json({ error: "s3: `parts` must be an array of {ETag, PartNumber} objects." });
|
|
4686
5044
|
}
|
|
4687
|
-
return client2.send(new
|
|
5045
|
+
return client2.send(new import_client_s32.CompleteMultipartUploadCommand({
|
|
4688
5046
|
Bucket: config.fileUploads.s3Bucket,
|
|
4689
5047
|
Key: key,
|
|
4690
5048
|
UploadId: uploadId,
|
|
@@ -4710,7 +5068,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4710
5068
|
if (typeof key !== "string") {
|
|
4711
5069
|
return res.status(400).json({ error: 's3: the object key must be passed as a query parameter. For example: "?key=abc.jpg"' });
|
|
4712
5070
|
}
|
|
4713
|
-
return client2.send(new
|
|
5071
|
+
return client2.send(new import_client_s32.AbortMultipartUploadCommand({
|
|
4714
5072
|
Bucket: config.fileUploads.s3Bucket,
|
|
4715
5073
|
Key: key,
|
|
4716
5074
|
UploadId: uploadId
|
|
@@ -4728,9 +5086,16 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4728
5086
|
};
|
|
4729
5087
|
|
|
4730
5088
|
// src/registry/routes.ts
|
|
4731
|
-
var
|
|
5089
|
+
var import_utils4 = require("@apollo/utils.keyvaluecache");
|
|
4732
5090
|
var import_body_parser = __toESM(require("body-parser"), 1);
|
|
4733
5091
|
var import_crypto_js3 = __toESM(require("crypto-js"), 1);
|
|
5092
|
+
var import_openai = __toESM(require("openai"), 1);
|
|
5093
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
5094
|
+
var import_node_crypto3 = require("crypto");
|
|
5095
|
+
var import_api = require("@opentelemetry/api");
|
|
5096
|
+
var import_ai2 = require("ai");
|
|
5097
|
+
var import_express_http_proxy = __toESM(require("express-http-proxy"), 1);
|
|
5098
|
+
var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
4734
5099
|
|
|
4735
5100
|
// src/registry/utils/claude-messages.ts
|
|
4736
5101
|
var CLAUDE_MESSAGES = {
|
|
@@ -4755,10 +5120,6 @@ var CLAUDE_MESSAGES = {
|
|
|
4755
5120
|
};
|
|
4756
5121
|
|
|
4757
5122
|
// src/registry/routes.ts
|
|
4758
|
-
var import_openai = __toESM(require("openai"), 1);
|
|
4759
|
-
var import_fs = __toESM(require("fs"), 1);
|
|
4760
|
-
var import_node_crypto2 = require("crypto");
|
|
4761
|
-
var import_api2 = require("@opentelemetry/api");
|
|
4762
5123
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
4763
5124
|
var global_queues = {
|
|
4764
5125
|
logs_cleaner: "logs-cleaner"
|
|
@@ -4808,7 +5169,7 @@ var createRecurringJobs = async () => {
|
|
|
4808
5169
|
);
|
|
4809
5170
|
return queue;
|
|
4810
5171
|
};
|
|
4811
|
-
var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
|
|
5172
|
+
var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer, filesContext2) => {
|
|
4812
5173
|
var corsOptions = {
|
|
4813
5174
|
origin: "*",
|
|
4814
5175
|
exposedHeaders: "*",
|
|
@@ -4833,7 +5194,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4833
5194
|
if (redisServer.host?.length && redisServer.port?.length) {
|
|
4834
5195
|
await createRecurringJobs();
|
|
4835
5196
|
} else {
|
|
4836
|
-
console.log("[
|
|
5197
|
+
console.log("[EXULU] no redis server configured, not setting up recurring jobs.");
|
|
4837
5198
|
}
|
|
4838
5199
|
const schema = createSDL([
|
|
4839
5200
|
usersSchema2(),
|
|
@@ -4848,9 +5209,9 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4848
5209
|
workflowTemplatesSchema2(),
|
|
4849
5210
|
statisticsSchema2(),
|
|
4850
5211
|
rbacSchema2()
|
|
4851
|
-
], contexts, agents, tools);
|
|
5212
|
+
], contexts ?? [], agents, tools);
|
|
4852
5213
|
const server = new import_server3.ApolloServer({
|
|
4853
|
-
cache: new
|
|
5214
|
+
cache: new import_utils4.InMemoryLRUCache(),
|
|
4854
5215
|
schema,
|
|
4855
5216
|
introspection: true
|
|
4856
5217
|
});
|
|
@@ -4906,7 +5267,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4906
5267
|
});
|
|
4907
5268
|
return;
|
|
4908
5269
|
}
|
|
4909
|
-
let
|
|
5270
|
+
let providerapikey = variable.value;
|
|
4910
5271
|
if (!variable.encrypted) {
|
|
4911
5272
|
res.status(400).json({
|
|
4912
5273
|
message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
|
|
@@ -4915,10 +5276,10 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4915
5276
|
}
|
|
4916
5277
|
if (variable.encrypted) {
|
|
4917
5278
|
const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
4918
|
-
|
|
5279
|
+
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
4919
5280
|
}
|
|
4920
5281
|
const openai = new import_openai.default({
|
|
4921
|
-
apiKey:
|
|
5282
|
+
apiKey: providerapikey
|
|
4922
5283
|
});
|
|
4923
5284
|
let style_reference = "";
|
|
4924
5285
|
if (style === "origami") {
|
|
@@ -4965,7 +5326,7 @@ Mood: friendly and intelligent.
|
|
|
4965
5326
|
return;
|
|
4966
5327
|
}
|
|
4967
5328
|
const image_bytes = Buffer.from(image_base64, "base64");
|
|
4968
|
-
const uuid = (0,
|
|
5329
|
+
const uuid = (0, import_node_crypto3.randomUUID)();
|
|
4969
5330
|
if (!import_fs.default.existsSync("public")) {
|
|
4970
5331
|
import_fs.default.mkdirSync("public");
|
|
4971
5332
|
}
|
|
@@ -4991,6 +5352,12 @@ Mood: friendly and intelligent.
|
|
|
4991
5352
|
const slug = agent.slug;
|
|
4992
5353
|
if (!slug) return;
|
|
4993
5354
|
app.post(slug + "/:instance", async (req, res) => {
|
|
5355
|
+
const headers = {
|
|
5356
|
+
stream: req.headers["stream"] === "true" || false,
|
|
5357
|
+
user: req.headers["user"] || null,
|
|
5358
|
+
session: req.headers["session"] || null
|
|
5359
|
+
};
|
|
5360
|
+
await checkAgentRateLimit(agent);
|
|
4994
5361
|
const instance = req.params.instance;
|
|
4995
5362
|
if (!instance) {
|
|
4996
5363
|
res.status(400).json({
|
|
@@ -4999,38 +5366,7 @@ Mood: friendly and intelligent.
|
|
|
4999
5366
|
return;
|
|
5000
5367
|
}
|
|
5001
5368
|
const { db: db3 } = await postgresClient();
|
|
5002
|
-
const agentInstance = await
|
|
5003
|
-
id: instance
|
|
5004
|
-
}).first();
|
|
5005
|
-
const agentRbac = await RBACResolver(db3, "agent", agentInstance.id, agentInstance.rights_mode || "private");
|
|
5006
|
-
agentInstance.RBAC = agentRbac;
|
|
5007
|
-
if (!agentInstance) {
|
|
5008
|
-
res.status(400).json({
|
|
5009
|
-
message: "Agent instance not found."
|
|
5010
|
-
});
|
|
5011
|
-
return;
|
|
5012
|
-
}
|
|
5013
|
-
if (agent.rateLimit) {
|
|
5014
|
-
console.log("[EXULU] rate limiting agent.", agent.rateLimit);
|
|
5015
|
-
const limit = await rateLimiter(
|
|
5016
|
-
agent.rateLimit.name || agent.id,
|
|
5017
|
-
agent.rateLimit.rate_limit.time,
|
|
5018
|
-
agent.rateLimit.rate_limit.limit,
|
|
5019
|
-
1
|
|
5020
|
-
);
|
|
5021
|
-
if (!limit.status) {
|
|
5022
|
-
res.status(429).json({
|
|
5023
|
-
message: "Rate limit exceeded.",
|
|
5024
|
-
retryAfter: limit.retryAfter
|
|
5025
|
-
});
|
|
5026
|
-
return;
|
|
5027
|
-
}
|
|
5028
|
-
}
|
|
5029
|
-
const headers = {
|
|
5030
|
-
stream: req.headers["stream"] === "true" || false,
|
|
5031
|
-
user: req.headers["user"] || null,
|
|
5032
|
-
session: req.headers["session"] || null
|
|
5033
|
-
};
|
|
5369
|
+
const agentInstance = await loadAgent(instance);
|
|
5034
5370
|
const requestValidationResult = requestValidators.agents(req);
|
|
5035
5371
|
if (requestValidationResult.error) {
|
|
5036
5372
|
res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
|
|
@@ -5042,85 +5378,24 @@ Mood: friendly and intelligent.
|
|
|
5042
5378
|
return;
|
|
5043
5379
|
}
|
|
5044
5380
|
const user = authenticationResult.user;
|
|
5045
|
-
const
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
const isAgentCreator = agentInstance.created_by === user.id;
|
|
5049
|
-
const isAdmin = user.super_admin;
|
|
5050
|
-
const isApi = user.type === "api";
|
|
5051
|
-
let hasAccessToAgent = "none";
|
|
5052
|
-
if (agentIsPublic || isAgentCreator || isAdmin || isApi) {
|
|
5053
|
-
hasAccessToAgent = "write";
|
|
5054
|
-
}
|
|
5055
|
-
if (agentByUsers) {
|
|
5056
|
-
hasAccessToAgent = agentInstance.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
5057
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none" || hasAccessToAgent === "read") {
|
|
5058
|
-
res.status(410).json({
|
|
5059
|
-
message: `Your current user ${user.id} does not have access to this agent.`
|
|
5060
|
-
});
|
|
5061
|
-
return;
|
|
5062
|
-
}
|
|
5063
|
-
}
|
|
5064
|
-
if (agentByRoles) {
|
|
5065
|
-
hasAccessToAgent = agentInstance.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
5066
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none" || hasAccessToAgent === "read") {
|
|
5067
|
-
res.status(410).json({
|
|
5068
|
-
message: `Your current role ${user.role?.name} does not have access to this agent.`
|
|
5069
|
-
});
|
|
5070
|
-
return;
|
|
5071
|
-
}
|
|
5072
|
-
}
|
|
5073
|
-
let hasAccessToSession = "none";
|
|
5074
|
-
;
|
|
5075
|
-
if (headers.session) {
|
|
5076
|
-
const session = await db3.from("agents").where({
|
|
5077
|
-
id: instance
|
|
5078
|
-
}).first();
|
|
5079
|
-
const sessionIsPublic = agentInstance.rights_mode === "public";
|
|
5080
|
-
const sessionByUsers = agentInstance.rights_mode === "users";
|
|
5081
|
-
const sessionByRoles = agentInstance.rights_mode === "roles";
|
|
5082
|
-
const isSessionCreator = agentInstance.created_by === user.id;
|
|
5083
|
-
const isAdmin2 = user.super_admin;
|
|
5084
|
-
const isApi2 = user.type === "api";
|
|
5085
|
-
if (sessionIsPublic || isSessionCreator || isAdmin2 || isApi2) {
|
|
5086
|
-
hasAccessToSession = "write";
|
|
5087
|
-
}
|
|
5088
|
-
if (sessionByUsers) {
|
|
5089
|
-
hasAccessToSession = session.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
5090
|
-
if (!hasAccessToSession || hasAccessToSession === "none" || hasAccessToSession === "read") {
|
|
5091
|
-
res.status(410).json({
|
|
5092
|
-
message: `Your current user ${user.id} does not have access to this session.`
|
|
5093
|
-
});
|
|
5094
|
-
return;
|
|
5095
|
-
}
|
|
5096
|
-
}
|
|
5097
|
-
if (sessionByRoles) {
|
|
5098
|
-
hasAccessToSession = session.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
5099
|
-
if (!hasAccessToSession || hasAccessToSession === "none" || hasAccessToSession === "read") {
|
|
5100
|
-
res.status(410).json({
|
|
5101
|
-
message: `Your current role ${user.role?.name} does not have access to this session.`
|
|
5102
|
-
});
|
|
5103
|
-
return;
|
|
5104
|
-
}
|
|
5105
|
-
}
|
|
5106
|
-
}
|
|
5107
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none") {
|
|
5108
|
-
res.status(410).json({
|
|
5381
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
5382
|
+
if (!hasAccessToAgent) {
|
|
5383
|
+
res.status(401).json({
|
|
5109
5384
|
message: "You don't have access to this agent."
|
|
5110
5385
|
});
|
|
5111
5386
|
return;
|
|
5112
5387
|
}
|
|
5113
|
-
if (
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
});
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5388
|
+
if (headers.session) {
|
|
5389
|
+
const session = await db3.from("agent_sessions").where({
|
|
5390
|
+
id: headers.session
|
|
5391
|
+
}).first();
|
|
5392
|
+
let hasAccessToSession = await checkRecordAccess(session, "write", user);
|
|
5393
|
+
if (!hasAccessToSession) {
|
|
5394
|
+
res.status(401).json({
|
|
5395
|
+
message: "You don't have access to this session."
|
|
5396
|
+
});
|
|
5397
|
+
return;
|
|
5398
|
+
}
|
|
5124
5399
|
}
|
|
5125
5400
|
if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
|
|
5126
5401
|
res.status(400).json({
|
|
@@ -5128,16 +5403,11 @@ Mood: friendly and intelligent.
|
|
|
5128
5403
|
});
|
|
5129
5404
|
return;
|
|
5130
5405
|
}
|
|
5131
|
-
console.log("[EXULU] agent tools", agentInstance.tools);
|
|
5132
|
-
let enabledTools = agentInstance.tools ? agentInstance.tools.map(
|
|
5133
|
-
({ config: config2, toolId }) => tools.find(({ id }) => id === toolId)
|
|
5134
|
-
).filter(Boolean) : [];
|
|
5135
|
-
console.log("[EXULU] available tools", enabledTools?.length);
|
|
5406
|
+
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
5136
5407
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
const variableName = agentInstance.providerApiKey;
|
|
5408
|
+
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
5409
|
+
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
5410
|
+
const variableName = agentInstance.providerapikey;
|
|
5141
5411
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5142
5412
|
if (!variable) {
|
|
5143
5413
|
res.status(400).json({
|
|
@@ -5145,7 +5415,7 @@ Mood: friendly and intelligent.
|
|
|
5145
5415
|
});
|
|
5146
5416
|
return;
|
|
5147
5417
|
}
|
|
5148
|
-
let
|
|
5418
|
+
let providerapikey = variable.value;
|
|
5149
5419
|
if (!variable.encrypted) {
|
|
5150
5420
|
res.status(400).json({
|
|
5151
5421
|
message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
|
|
@@ -5154,7 +5424,7 @@ Mood: friendly and intelligent.
|
|
|
5154
5424
|
}
|
|
5155
5425
|
if (variable.encrypted) {
|
|
5156
5426
|
const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
5157
|
-
|
|
5427
|
+
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
5158
5428
|
}
|
|
5159
5429
|
if (!!headers.stream) {
|
|
5160
5430
|
await agent.generateStream({
|
|
@@ -5162,13 +5432,17 @@ Mood: friendly and intelligent.
|
|
|
5162
5432
|
res,
|
|
5163
5433
|
req
|
|
5164
5434
|
},
|
|
5165
|
-
|
|
5166
|
-
|
|
5435
|
+
contexts,
|
|
5436
|
+
user,
|
|
5437
|
+
instructions: agentInstance.instructions,
|
|
5167
5438
|
session: headers.session,
|
|
5168
5439
|
message: req.body.message,
|
|
5169
|
-
|
|
5170
|
-
|
|
5440
|
+
currentTools: enabledTools,
|
|
5441
|
+
allExuluTools: tools,
|
|
5442
|
+
providerapikey,
|
|
5171
5443
|
toolConfigs: agentInstance.tools,
|
|
5444
|
+
exuluConfig: config,
|
|
5445
|
+
filesContext: filesContext2,
|
|
5172
5446
|
statistics: {
|
|
5173
5447
|
label: agent.name,
|
|
5174
5448
|
trigger: "agent"
|
|
@@ -5177,13 +5451,17 @@ Mood: friendly and intelligent.
|
|
|
5177
5451
|
return;
|
|
5178
5452
|
} else {
|
|
5179
5453
|
const response = await agent.generateSync({
|
|
5180
|
-
user
|
|
5454
|
+
user,
|
|
5455
|
+
instructions: agentInstance.instructions,
|
|
5181
5456
|
session: headers.session,
|
|
5182
|
-
role: user?.role?.id,
|
|
5183
5457
|
message: req.body.message,
|
|
5184
|
-
|
|
5185
|
-
|
|
5458
|
+
contexts,
|
|
5459
|
+
currentTools: enabledTools,
|
|
5460
|
+
allExuluTools: tools,
|
|
5461
|
+
providerapikey,
|
|
5462
|
+
exuluConfig: config,
|
|
5186
5463
|
toolConfigs: agentInstance.tools,
|
|
5464
|
+
filesContext: filesContext2,
|
|
5187
5465
|
statistics: {
|
|
5188
5466
|
label: agent.name,
|
|
5189
5467
|
trigger: "agent"
|
|
@@ -5199,10 +5477,92 @@ Mood: friendly and intelligent.
|
|
|
5199
5477
|
} else {
|
|
5200
5478
|
console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
|
|
5201
5479
|
}
|
|
5202
|
-
|
|
5480
|
+
app.use("/xxx/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), (0, import_express_http_proxy.default)(
|
|
5481
|
+
(req, res, next) => {
|
|
5482
|
+
return "https://api.anthropic.com";
|
|
5483
|
+
},
|
|
5484
|
+
{
|
|
5485
|
+
limit: "50mb",
|
|
5486
|
+
memoizeHost: false,
|
|
5487
|
+
preserveHostHdr: true,
|
|
5488
|
+
secure: false,
|
|
5489
|
+
reqAsBuffer: true,
|
|
5490
|
+
proxyReqBodyDecorator: function(bodyContent, srcReq) {
|
|
5491
|
+
return bodyContent;
|
|
5492
|
+
},
|
|
5493
|
+
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
|
|
5494
|
+
console.log("[EXULU] Proxy response!", proxyResData);
|
|
5495
|
+
proxyResData = proxyResData.toString();
|
|
5496
|
+
console.log("[EXULU] Proxy response string!", proxyResData);
|
|
5497
|
+
return proxyResData;
|
|
5498
|
+
},
|
|
5499
|
+
proxyReqPathResolver: (req) => {
|
|
5500
|
+
const prefix = `/gateway/anthropic/${req.params.id}`;
|
|
5501
|
+
let path2 = req.url.startsWith(prefix) ? req.url.slice(prefix.length) : req.url;
|
|
5502
|
+
if (!path2.startsWith("/")) path2 = "/" + path2;
|
|
5503
|
+
console.log("[EXULU] Provider path:", path2);
|
|
5504
|
+
return path2;
|
|
5505
|
+
},
|
|
5506
|
+
proxyReqOptDecorator: function(proxyReqOpts, srcReq) {
|
|
5507
|
+
return new Promise(async (resolve, reject) => {
|
|
5508
|
+
try {
|
|
5509
|
+
const authenticationResult = await requestValidators.authenticate(srcReq);
|
|
5510
|
+
if (!authenticationResult.user?.id) {
|
|
5511
|
+
console.log("[EXULU] failed authentication result", authenticationResult);
|
|
5512
|
+
reject(authenticationResult.message);
|
|
5513
|
+
return;
|
|
5514
|
+
}
|
|
5515
|
+
console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
|
|
5516
|
+
const { db: db3 } = await postgresClient();
|
|
5517
|
+
let query = db3("agents");
|
|
5518
|
+
query.select("*");
|
|
5519
|
+
query = applyAccessControl(agentsSchema2(), authenticationResult.user, query);
|
|
5520
|
+
query.where({ id: srcReq.params.id });
|
|
5521
|
+
const agent = await query.first();
|
|
5522
|
+
if (!agent) {
|
|
5523
|
+
reject(new Error("Agent with id " + srcReq.params.id + " not found."));
|
|
5524
|
+
return;
|
|
5525
|
+
}
|
|
5526
|
+
console.log("[EXULU] Agent loaded", agent.name);
|
|
5527
|
+
const backend = agents.find((x) => x.id === agent.backend);
|
|
5528
|
+
if (!process.env.NEXTAUTH_SECRET) {
|
|
5529
|
+
reject(new Error("Missing NEXTAUTH_SECRET"));
|
|
5530
|
+
return;
|
|
5531
|
+
}
|
|
5532
|
+
if (!agent.providerapikey) {
|
|
5533
|
+
reject(new Error("API Key not set for agent"));
|
|
5534
|
+
return;
|
|
5535
|
+
}
|
|
5536
|
+
const variableName = agent.providerapikey;
|
|
5537
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5538
|
+
console.log("[EXULU] Variable loaded", variable);
|
|
5539
|
+
let providerapikey = variable.value;
|
|
5540
|
+
if (!variable.encrypted) {
|
|
5541
|
+
reject(new Error("API Key not encrypted for agent"));
|
|
5542
|
+
return;
|
|
5543
|
+
}
|
|
5544
|
+
if (variable.encrypted) {
|
|
5545
|
+
const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
5546
|
+
providerapikey = bytes.toString(import_crypto_js3.default.enc.Utf8);
|
|
5547
|
+
}
|
|
5548
|
+
console.log("[EXULU] Provider API key", providerapikey);
|
|
5549
|
+
proxyReqOpts.headers["x-api-key"] = providerapikey;
|
|
5550
|
+
proxyReqOpts.rejectUnauthorized = false;
|
|
5551
|
+
delete proxyReqOpts.headers["provider"];
|
|
5552
|
+
const url = new URL("https://api.anthropic.com");
|
|
5553
|
+
proxyReqOpts.headers["host"] = url.host;
|
|
5554
|
+
proxyReqOpts.headers["anthropic-version"] = "2023-06-01";
|
|
5555
|
+
console.log("[EXULU] Proxy request headers", proxyReqOpts.headers);
|
|
5556
|
+
resolve(proxyReqOpts);
|
|
5557
|
+
} catch (error) {
|
|
5558
|
+
console.error("[EXULU] Proxy error", error);
|
|
5559
|
+
reject(error);
|
|
5560
|
+
}
|
|
5561
|
+
});
|
|
5562
|
+
}
|
|
5563
|
+
}
|
|
5564
|
+
));
|
|
5203
5565
|
app.use("/gateway/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
5204
|
-
const path3 = req.url;
|
|
5205
|
-
const url = `${TARGET_API}${path3}`;
|
|
5206
5566
|
try {
|
|
5207
5567
|
if (!req.body.tools) {
|
|
5208
5568
|
req.body.tools = [];
|
|
@@ -5234,13 +5594,13 @@ Mood: friendly and intelligent.
|
|
|
5234
5594
|
res.end(Buffer.from(arrayBuffer));
|
|
5235
5595
|
return;
|
|
5236
5596
|
}
|
|
5237
|
-
if (!agent.
|
|
5597
|
+
if (!agent.providerapikey) {
|
|
5238
5598
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.not_enabled);
|
|
5239
5599
|
res.setHeader("Content-Type", "application/json");
|
|
5240
5600
|
res.end(Buffer.from(arrayBuffer));
|
|
5241
5601
|
return;
|
|
5242
5602
|
}
|
|
5243
|
-
const variableName = agent.
|
|
5603
|
+
const variableName = agent.providerapikey;
|
|
5244
5604
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5245
5605
|
if (!variable) {
|
|
5246
5606
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.anthropic_token_variable_not_found);
|
|
@@ -5266,45 +5626,21 @@ Mood: friendly and intelligent.
|
|
|
5266
5626
|
};
|
|
5267
5627
|
if (req.headers["accept"]) headers["accept"] = req.headers["accept"];
|
|
5268
5628
|
if (req.headers["user-agent"]) headers["user-agent"] = req.headers["user-agent"];
|
|
5269
|
-
const
|
|
5270
|
-
|
|
5271
|
-
headers,
|
|
5272
|
-
body: req.method !== "GET" ? JSON.stringify(req.body) : void 0
|
|
5629
|
+
const client2 = new import_sdk.default({
|
|
5630
|
+
apiKey: anthropicApiKey
|
|
5273
5631
|
});
|
|
5274
|
-
await
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
5278
|
-
trigger: "claude-code",
|
|
5279
|
-
count: 1,
|
|
5280
|
-
user: authenticationResult.user?.id,
|
|
5281
|
-
role: authenticationResult.user.role?.id
|
|
5282
|
-
});
|
|
5283
|
-
response.headers.forEach((value, key) => {
|
|
5284
|
-
res.setHeader(key, value);
|
|
5285
|
-
});
|
|
5286
|
-
res.status(response.status);
|
|
5287
|
-
const isStreaming = response.headers.get("content-type")?.includes("text/event-stream");
|
|
5288
|
-
if (isStreaming && !response?.body) {
|
|
5289
|
-
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_body);
|
|
5290
|
-
res.setHeader("Content-Type", "application/json");
|
|
5291
|
-
res.end(Buffer.from(arrayBuffer));
|
|
5292
|
-
return;
|
|
5293
|
-
}
|
|
5294
|
-
if (isStreaming) {
|
|
5295
|
-
const reader = response.body.getReader();
|
|
5296
|
-
const decoder = new TextDecoder();
|
|
5297
|
-
while (true) {
|
|
5298
|
-
const { done, value } = await reader.read();
|
|
5299
|
-
if (done) break;
|
|
5300
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
5301
|
-
res.write(chunk);
|
|
5632
|
+
for await (const event of client2.messages.stream(req.body)) {
|
|
5633
|
+
console.log("[EXULU] Event", event);
|
|
5634
|
+
if (event.message?.usage) {
|
|
5302
5635
|
}
|
|
5303
|
-
|
|
5304
|
-
|
|
5636
|
+
const msg = `event: ${event.type}
|
|
5637
|
+
data: ${JSON.stringify(event)}
|
|
5638
|
+
|
|
5639
|
+
`;
|
|
5640
|
+
res.write(msg);
|
|
5305
5641
|
}
|
|
5306
|
-
|
|
5307
|
-
res.end(
|
|
5642
|
+
res.write("event: done\ndata: [DONE]\n\n");
|
|
5643
|
+
res.end();
|
|
5308
5644
|
} catch (error) {
|
|
5309
5645
|
console.error("[PROXY] Manual proxy error:", error);
|
|
5310
5646
|
if (!res.headersSent) {
|
|
@@ -5337,32 +5673,9 @@ var createCustomAnthropicStreamingMessage = (message) => {
|
|
|
5337
5673
|
// src/registry/workers.ts
|
|
5338
5674
|
var import_ioredis = __toESM(require("ioredis"), 1);
|
|
5339
5675
|
var import_bullmq5 = require("bullmq");
|
|
5340
|
-
|
|
5341
|
-
// src/registry/utils.ts
|
|
5342
|
-
var bullmq = {
|
|
5343
|
-
validate: (id, data) => {
|
|
5344
|
-
if (!data) {
|
|
5345
|
-
throw new Error(`Missing job data for job ${id}.`);
|
|
5346
|
-
}
|
|
5347
|
-
if (!data.type) {
|
|
5348
|
-
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
5349
|
-
}
|
|
5350
|
-
if (!data.inputs) {
|
|
5351
|
-
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
5352
|
-
}
|
|
5353
|
-
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
5354
|
-
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
|
|
5355
|
-
}
|
|
5356
|
-
if (!data.workflow && !data.embedder) {
|
|
5357
|
-
throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
|
|
5358
|
-
}
|
|
5359
|
-
}
|
|
5360
|
-
};
|
|
5361
|
-
|
|
5362
|
-
// src/registry/workers.ts
|
|
5363
|
-
var fs3 = __toESM(require("fs"), 1);
|
|
5676
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
5364
5677
|
var import_path = __toESM(require("path"), 1);
|
|
5365
|
-
var
|
|
5678
|
+
var import_api2 = require("@opentelemetry/api");
|
|
5366
5679
|
var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
|
|
5367
5680
|
var redisConnection;
|
|
5368
5681
|
var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
|
|
@@ -5404,8 +5717,6 @@ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
|
|
|
5404
5717
|
}, data.role, bullmqJob.id);
|
|
5405
5718
|
return result;
|
|
5406
5719
|
}
|
|
5407
|
-
if (bullmqJob.data.type === "workflow") {
|
|
5408
|
-
}
|
|
5409
5720
|
} catch (error) {
|
|
5410
5721
|
await db3.from("jobs").where({ redis: bullmqJob.id }).update({
|
|
5411
5722
|
status: "failed",
|
|
@@ -5440,16 +5751,16 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
5440
5751
|
global_queues.logs_cleaner,
|
|
5441
5752
|
async (job) => {
|
|
5442
5753
|
console.log(`[EXULU] recurring job ${job.id}.`);
|
|
5443
|
-
const folder =
|
|
5754
|
+
const folder = fs2.readdirSync(logsDir);
|
|
5444
5755
|
const files = folder.filter((file) => file.endsWith(".log"));
|
|
5445
5756
|
const now = /* @__PURE__ */ new Date();
|
|
5446
5757
|
const daysToKeep = job.data.ttld;
|
|
5447
5758
|
const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
|
|
5448
5759
|
files.forEach((file) => {
|
|
5449
5760
|
const filePath = import_path.default.join(logsDir, file);
|
|
5450
|
-
const fileStats =
|
|
5761
|
+
const fileStats = fs2.statSync(filePath);
|
|
5451
5762
|
if (fileStats.mtime < dateToKeep) {
|
|
5452
|
-
|
|
5763
|
+
fs2.unlinkSync(filePath);
|
|
5453
5764
|
}
|
|
5454
5765
|
});
|
|
5455
5766
|
},
|
|
@@ -5469,12 +5780,12 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
5469
5780
|
|
|
5470
5781
|
// src/mcp/index.ts
|
|
5471
5782
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
5472
|
-
var
|
|
5783
|
+
var import_node_crypto4 = require("crypto");
|
|
5473
5784
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
5474
5785
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
5475
|
-
var
|
|
5786
|
+
var import_zod2 = require("zod");
|
|
5476
5787
|
var import_express6 = require("express");
|
|
5477
|
-
var
|
|
5788
|
+
var import_api3 = require("@opentelemetry/api");
|
|
5478
5789
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
5479
5790
|
var ExuluMCP = class {
|
|
5480
5791
|
server;
|
|
@@ -5529,7 +5840,7 @@ var ExuluMCP = class {
|
|
|
5529
5840
|
{
|
|
5530
5841
|
title: "Code Review",
|
|
5531
5842
|
description: "Review code for best practices and potential issues",
|
|
5532
|
-
argsSchema: { code:
|
|
5843
|
+
argsSchema: { code: import_zod2.z.string() }
|
|
5533
5844
|
},
|
|
5534
5845
|
({ code }) => ({
|
|
5535
5846
|
messages: [{
|
|
@@ -5551,7 +5862,6 @@ ${code}`
|
|
|
5551
5862
|
if (!this.server) {
|
|
5552
5863
|
throw new Error("MCP server not initialized.");
|
|
5553
5864
|
}
|
|
5554
|
-
console.log("[EXULU] Wiring up MCP server routes to express app.");
|
|
5555
5865
|
this.express.post("/mcp", async (req, res) => {
|
|
5556
5866
|
if (!this.server) {
|
|
5557
5867
|
throw new Error("MCP server not initialized.");
|
|
@@ -5562,7 +5872,7 @@ ${code}`
|
|
|
5562
5872
|
transport = this.transports[sessionId];
|
|
5563
5873
|
} else if (!sessionId && (0, import_types.isInitializeRequest)(req.body)) {
|
|
5564
5874
|
transport = new import_streamableHttp.StreamableHTTPServerTransport({
|
|
5565
|
-
sessionIdGenerator: () => (0,
|
|
5875
|
+
sessionIdGenerator: () => (0, import_node_crypto4.randomUUID)(),
|
|
5566
5876
|
onsessioninitialized: (sessionId2) => {
|
|
5567
5877
|
this.transports[sessionId2] = transport;
|
|
5568
5878
|
}
|
|
@@ -5604,35 +5914,49 @@ ${code}`
|
|
|
5604
5914
|
// src/registry/index.ts
|
|
5605
5915
|
var import_express8 = __toESM(require("express"), 1);
|
|
5606
5916
|
|
|
5607
|
-
// src/templates/agents/claude-
|
|
5608
|
-
var
|
|
5917
|
+
// src/templates/agents/claude-sonnet-4.ts
|
|
5918
|
+
var import_anthropic = require("@ai-sdk/anthropic");
|
|
5919
|
+
var claudeSonnet4Agent = new ExuluAgent2({
|
|
5609
5920
|
id: `claude_code_agent`,
|
|
5610
5921
|
name: `Claude Code Agent`,
|
|
5611
5922
|
description: `Claude Code agent, enabling the creation of multiple Claude Code Agent instances with different configurations (rate limits, functions, etc).`,
|
|
5612
|
-
type: "
|
|
5923
|
+
type: "agent",
|
|
5924
|
+
config: {
|
|
5925
|
+
name: `Default Claude Code agent`,
|
|
5926
|
+
instructions: "You are a coding assistant.",
|
|
5927
|
+
model: {
|
|
5928
|
+
create: ({ apiKey }) => {
|
|
5929
|
+
const anthropic = (0, import_anthropic.createAnthropic)({
|
|
5930
|
+
apiKey
|
|
5931
|
+
});
|
|
5932
|
+
return anthropic.languageModel("claude-sonnet-4-20250514");
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
}
|
|
5613
5936
|
});
|
|
5614
5937
|
|
|
5615
5938
|
// src/templates/agents/claude-opus-4.ts
|
|
5616
|
-
var
|
|
5617
|
-
var
|
|
5939
|
+
var import_anthropic2 = require("@ai-sdk/anthropic");
|
|
5940
|
+
var claudeOpus4Agent = new ExuluAgent2({
|
|
5618
5941
|
id: `default_claude_4_opus_agent`,
|
|
5619
|
-
name: `Default Claude 4 Opus
|
|
5620
|
-
description: `Basic agent
|
|
5942
|
+
name: `Default Claude 4 Opus anthropic provider`,
|
|
5943
|
+
description: `Basic agent claude 4 opus agent you can use to chat with.`,
|
|
5621
5944
|
type: "agent",
|
|
5622
5945
|
capabilities: {
|
|
5623
5946
|
text: true,
|
|
5624
5947
|
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
5625
|
-
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt"],
|
|
5948
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
5626
5949
|
audio: [],
|
|
5627
5950
|
video: []
|
|
5628
5951
|
},
|
|
5629
5952
|
evals: [],
|
|
5953
|
+
maxContextLength: 2e5,
|
|
5630
5954
|
config: {
|
|
5631
5955
|
name: `Default agent`,
|
|
5632
5956
|
instructions: "You are a helpful assistant.",
|
|
5633
5957
|
model: {
|
|
5634
5958
|
create: ({ apiKey }) => {
|
|
5635
|
-
const anthropic = (0,
|
|
5959
|
+
const anthropic = (0, import_anthropic2.createAnthropic)({
|
|
5636
5960
|
apiKey
|
|
5637
5961
|
});
|
|
5638
5962
|
return anthropic.languageModel("claude-4-opus-20250514");
|
|
@@ -5648,8 +5972,79 @@ var defaultAgent = new ExuluAgent2({
|
|
|
5648
5972
|
}
|
|
5649
5973
|
});
|
|
5650
5974
|
|
|
5975
|
+
// src/templates/agents/gpt-5.ts
|
|
5976
|
+
var import_openai2 = require("@ai-sdk/openai");
|
|
5977
|
+
var gpt5MiniAgent = new ExuluAgent2({
|
|
5978
|
+
id: `default_gpt_5_mini_agent`,
|
|
5979
|
+
name: `Default GPT 5 Mini OpenAI provider`,
|
|
5980
|
+
description: `Basic agent gpt 5 mini agent you can use to chat with.`,
|
|
5981
|
+
type: "agent",
|
|
5982
|
+
capabilities: {
|
|
5983
|
+
text: true,
|
|
5984
|
+
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
5985
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
5986
|
+
audio: [],
|
|
5987
|
+
video: []
|
|
5988
|
+
},
|
|
5989
|
+
evals: [],
|
|
5990
|
+
maxContextLength: 128e3,
|
|
5991
|
+
config: {
|
|
5992
|
+
name: `Default agent`,
|
|
5993
|
+
instructions: "You are a helpful assistant.",
|
|
5994
|
+
model: {
|
|
5995
|
+
create: ({ apiKey }) => {
|
|
5996
|
+
const openai = (0, import_openai2.createOpenAI)({
|
|
5997
|
+
apiKey
|
|
5998
|
+
});
|
|
5999
|
+
return openai.languageModel("gpt-5-mini");
|
|
6000
|
+
}
|
|
6001
|
+
// todo add a field of type string that adds a dropdown list from which the user can select the model
|
|
6002
|
+
// todo for each model, check which provider is used, and require the admin to add one or multiple
|
|
6003
|
+
// API keys for the provider (which we can then auto-rotate).
|
|
6004
|
+
// todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
|
|
6005
|
+
// and allow him/her to decide if the rate limit is per user or per agent.
|
|
6006
|
+
// todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
|
|
6007
|
+
// and stores the logs into the pre-defined storage.
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
});
|
|
6011
|
+
var gpt5agent = new ExuluAgent2({
|
|
6012
|
+
id: `default_gpt_5_agent`,
|
|
6013
|
+
name: `Default GPT 5 OpenAI provider`,
|
|
6014
|
+
description: `Basic agent gpt 5 agent you can use to chat with.`,
|
|
6015
|
+
type: "agent",
|
|
6016
|
+
capabilities: {
|
|
6017
|
+
text: true,
|
|
6018
|
+
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
6019
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
6020
|
+
audio: [],
|
|
6021
|
+
video: []
|
|
6022
|
+
},
|
|
6023
|
+
evals: [],
|
|
6024
|
+
maxContextLength: 128e3,
|
|
6025
|
+
config: {
|
|
6026
|
+
name: `Default agent`,
|
|
6027
|
+
instructions: "You are a helpful assistant.",
|
|
6028
|
+
model: {
|
|
6029
|
+
create: ({ apiKey }) => {
|
|
6030
|
+
const openai = (0, import_openai2.createOpenAI)({
|
|
6031
|
+
apiKey
|
|
6032
|
+
});
|
|
6033
|
+
return openai.languageModel("gpt-5");
|
|
6034
|
+
}
|
|
6035
|
+
// todo add a field of type string that adds a dropdown list from which the user can select the model
|
|
6036
|
+
// todo for each model, check which provider is used, and require the admin to add one or multiple
|
|
6037
|
+
// API keys for the provider (which we can then auto-rotate).
|
|
6038
|
+
// todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
|
|
6039
|
+
// and allow him/her to decide if the rate limit is per user or per agent.
|
|
6040
|
+
// todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
|
|
6041
|
+
// and stores the logs into the pre-defined storage.
|
|
6042
|
+
}
|
|
6043
|
+
}
|
|
6044
|
+
});
|
|
6045
|
+
|
|
5651
6046
|
// src/registry/index.ts
|
|
5652
|
-
var
|
|
6047
|
+
var import_api4 = require("@opentelemetry/api");
|
|
5653
6048
|
|
|
5654
6049
|
// src/registry/logger.ts
|
|
5655
6050
|
var import_winston_transport = require("@opentelemetry/winston-transport");
|
|
@@ -5701,21 +6096,21 @@ var codeStandardsContext = new ExuluContext({
|
|
|
5701
6096
|
active: true
|
|
5702
6097
|
});
|
|
5703
6098
|
|
|
5704
|
-
// src/templates/contexts/
|
|
5705
|
-
var
|
|
5706
|
-
id: "
|
|
5707
|
-
name: "
|
|
5708
|
-
description: "
|
|
6099
|
+
// src/templates/contexts/outputs.ts
|
|
6100
|
+
var outputsContext = new ExuluContext({
|
|
6101
|
+
id: "outputs_default_context",
|
|
6102
|
+
name: "Outputs",
|
|
6103
|
+
description: "Outputs from agent sessions that you have saved for re-used later.",
|
|
5709
6104
|
configuration: {
|
|
5710
|
-
defaultRightsMode: "
|
|
6105
|
+
defaultRightsMode: "private",
|
|
6106
|
+
calculateVectors: "manual"
|
|
5711
6107
|
},
|
|
5712
|
-
fields: [
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
}],
|
|
6108
|
+
fields: [
|
|
6109
|
+
{
|
|
6110
|
+
name: "content",
|
|
6111
|
+
type: "longText"
|
|
6112
|
+
}
|
|
6113
|
+
],
|
|
5719
6114
|
active: true
|
|
5720
6115
|
});
|
|
5721
6116
|
|
|
@@ -5733,14 +6128,6 @@ var filesContext = new ExuluContext({
|
|
|
5733
6128
|
name: "type",
|
|
5734
6129
|
type: "text"
|
|
5735
6130
|
},
|
|
5736
|
-
{
|
|
5737
|
-
name: "s3bucket",
|
|
5738
|
-
type: "text"
|
|
5739
|
-
},
|
|
5740
|
-
{
|
|
5741
|
-
name: "s3region",
|
|
5742
|
-
type: "text"
|
|
5743
|
-
},
|
|
5744
6131
|
{
|
|
5745
6132
|
name: "url",
|
|
5746
6133
|
type: "text"
|
|
@@ -5750,10 +6137,6 @@ var filesContext = new ExuluContext({
|
|
|
5750
6137
|
// ID of the file in S3 storage
|
|
5751
6138
|
type: "text"
|
|
5752
6139
|
},
|
|
5753
|
-
{
|
|
5754
|
-
name: "s3endpoint",
|
|
5755
|
-
type: "text"
|
|
5756
|
-
},
|
|
5757
6140
|
{
|
|
5758
6141
|
name: "content",
|
|
5759
6142
|
type: "longText"
|
|
@@ -5783,22 +6166,25 @@ var ExuluApp = class {
|
|
|
5783
6166
|
create = async ({ contexts, agents, config, tools }) => {
|
|
5784
6167
|
this._contexts = {
|
|
5785
6168
|
...contexts,
|
|
5786
|
-
projectsContext,
|
|
5787
6169
|
codeStandardsContext,
|
|
5788
|
-
filesContext
|
|
6170
|
+
filesContext,
|
|
6171
|
+
outputsContext
|
|
5789
6172
|
};
|
|
5790
6173
|
this._agents = [
|
|
5791
|
-
|
|
5792
|
-
|
|
6174
|
+
claudeSonnet4Agent,
|
|
6175
|
+
claudeOpus4Agent,
|
|
6176
|
+
gpt5MiniAgent,
|
|
6177
|
+
gpt5agent,
|
|
5793
6178
|
...agents ?? []
|
|
5794
6179
|
];
|
|
5795
6180
|
this._config = config;
|
|
5796
6181
|
this._tools = [
|
|
5797
6182
|
...tools ?? [],
|
|
5798
6183
|
// Add contexts as tools
|
|
5799
|
-
...Object.values(contexts || {}).map((context) => context.tool())
|
|
5800
|
-
//
|
|
5801
|
-
|
|
6184
|
+
...Object.values(contexts || {}).map((context) => context.tool())
|
|
6185
|
+
// Because agents are stored in the database, we add those as tools
|
|
6186
|
+
// at request time, not during ExuluApp initialization. We add them
|
|
6187
|
+
// in the grahql tools resolver.
|
|
5802
6188
|
];
|
|
5803
6189
|
const checks = [
|
|
5804
6190
|
...Object.keys(this._contexts || {}).map((x) => ({
|
|
@@ -5891,7 +6277,7 @@ var ExuluApp = class {
|
|
|
5891
6277
|
create: async () => {
|
|
5892
6278
|
let tracer;
|
|
5893
6279
|
if (this._config?.telemetry?.enabled) {
|
|
5894
|
-
tracer =
|
|
6280
|
+
tracer = import_api4.trace.getTracer("exulu", "1.0.0");
|
|
5895
6281
|
}
|
|
5896
6282
|
const logger = logger_default({
|
|
5897
6283
|
enableOtel: this._config?.workers?.telemetry?.enabled ?? false
|
|
@@ -5915,7 +6301,7 @@ var ExuluApp = class {
|
|
|
5915
6301
|
const app = this._expressApp;
|
|
5916
6302
|
let tracer;
|
|
5917
6303
|
if (this._config?.telemetry?.enabled) {
|
|
5918
|
-
tracer =
|
|
6304
|
+
tracer = import_api4.trace.getTracer("exulu", "1.0.0");
|
|
5919
6305
|
}
|
|
5920
6306
|
const logger = logger_default({
|
|
5921
6307
|
enableOtel: this._config?.telemetry?.enabled ?? false
|
|
@@ -5927,7 +6313,8 @@ var ExuluApp = class {
|
|
|
5927
6313
|
this._tools,
|
|
5928
6314
|
Object.values(this._contexts ?? {}),
|
|
5929
6315
|
this._config,
|
|
5930
|
-
tracer
|
|
6316
|
+
tracer,
|
|
6317
|
+
filesContext
|
|
5931
6318
|
);
|
|
5932
6319
|
if (this._config?.MCP.enabled) {
|
|
5933
6320
|
const mcp = new ExuluMCP();
|
|
@@ -6207,7 +6594,7 @@ var RecursiveRules = class _RecursiveRules {
|
|
|
6207
6594
|
* @param {string} path - The path to the recipe.
|
|
6208
6595
|
* @returns {Promise<RecursiveRules>} The RecursiveRules object.
|
|
6209
6596
|
*/
|
|
6210
|
-
static async fromRecipe(name = "default", lang = "en",
|
|
6597
|
+
static async fromRecipe(name = "default", lang = "en", path2) {
|
|
6211
6598
|
throw new Error("Not implemented");
|
|
6212
6599
|
}
|
|
6213
6600
|
};
|
|
@@ -7196,7 +7583,20 @@ var generateApiKey = async (name, email) => {
|
|
|
7196
7583
|
};
|
|
7197
7584
|
|
|
7198
7585
|
// src/postgres/init-db.ts
|
|
7199
|
-
var {
|
|
7586
|
+
var {
|
|
7587
|
+
agentsSchema: agentsSchema3,
|
|
7588
|
+
evalResultsSchema: evalResultsSchema3,
|
|
7589
|
+
jobsSchema: jobsSchema3,
|
|
7590
|
+
agentSessionsSchema: agentSessionsSchema3,
|
|
7591
|
+
agentMessagesSchema: agentMessagesSchema3,
|
|
7592
|
+
rolesSchema: rolesSchema3,
|
|
7593
|
+
usersSchema: usersSchema3,
|
|
7594
|
+
statisticsSchema: statisticsSchema3,
|
|
7595
|
+
variablesSchema: variablesSchema3,
|
|
7596
|
+
workflowTemplatesSchema: workflowTemplatesSchema3,
|
|
7597
|
+
rbacSchema: rbacSchema3,
|
|
7598
|
+
projectsSchema: projectsSchema3
|
|
7599
|
+
} = coreSchemas.get();
|
|
7200
7600
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
7201
7601
|
for (const field of fields) {
|
|
7202
7602
|
const { type, name, default: defaultValue, unique } = field;
|
|
@@ -7253,6 +7653,7 @@ var up = async function(knex) {
|
|
|
7253
7653
|
}
|
|
7254
7654
|
};
|
|
7255
7655
|
for (const schema of schemas) {
|
|
7656
|
+
console.log(`[EXULU] Creating ${schema.name.plural} table.`, schema.fields);
|
|
7256
7657
|
await createTable(schema);
|
|
7257
7658
|
}
|
|
7258
7659
|
if (!await knex.schema.hasTable("verification_token")) {
|
|
@@ -7409,6 +7810,9 @@ var create = ({
|
|
|
7409
7810
|
return sdk;
|
|
7410
7811
|
};
|
|
7411
7812
|
|
|
7813
|
+
// src/index.ts
|
|
7814
|
+
var import_crypto_js4 = __toESM(require("crypto-js"), 1);
|
|
7815
|
+
|
|
7412
7816
|
// types/enums/jobs.ts
|
|
7413
7817
|
var JOB_STATUS_ENUM = {
|
|
7414
7818
|
completed: "completed",
|
|
@@ -7427,6 +7831,113 @@ var ExuluJobs = {
|
|
|
7427
7831
|
validate: validateJob
|
|
7428
7832
|
}
|
|
7429
7833
|
};
|
|
7834
|
+
var ExuluDefaultContexts = {
|
|
7835
|
+
files: filesContext,
|
|
7836
|
+
codeStandards: codeStandardsContext,
|
|
7837
|
+
outputs: outputsContext
|
|
7838
|
+
};
|
|
7839
|
+
var ExuluDefaultAgents = {
|
|
7840
|
+
anthropic: {
|
|
7841
|
+
opus4: claudeOpus4Agent,
|
|
7842
|
+
sonnet4: claudeSonnet4Agent
|
|
7843
|
+
},
|
|
7844
|
+
openai: {
|
|
7845
|
+
gpt5Mini: gpt5MiniAgent,
|
|
7846
|
+
gpt5: gpt5agent
|
|
7847
|
+
}
|
|
7848
|
+
};
|
|
7849
|
+
var ExuluVariables = {
|
|
7850
|
+
get: async (name) => {
|
|
7851
|
+
const { db: db3 } = await postgresClient();
|
|
7852
|
+
let variable = await db3.from("variables").where({ name }).first();
|
|
7853
|
+
if (!variable) {
|
|
7854
|
+
throw new Error(`Variable ${name} not found.`);
|
|
7855
|
+
}
|
|
7856
|
+
if (variable.encrypted) {
|
|
7857
|
+
const bytes = import_crypto_js4.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7858
|
+
variable.value = bytes.toString(import_crypto_js4.default.enc.Utf8);
|
|
7859
|
+
}
|
|
7860
|
+
return variable.value;
|
|
7861
|
+
}
|
|
7862
|
+
};
|
|
7863
|
+
var ExuluUtils = {
|
|
7864
|
+
batch: async ({
|
|
7865
|
+
fn,
|
|
7866
|
+
size,
|
|
7867
|
+
inputs,
|
|
7868
|
+
delay,
|
|
7869
|
+
retries
|
|
7870
|
+
}) => {
|
|
7871
|
+
if (!size) {
|
|
7872
|
+
size = 10;
|
|
7873
|
+
}
|
|
7874
|
+
if (!inputs) {
|
|
7875
|
+
throw new Error("Inputs are required.");
|
|
7876
|
+
}
|
|
7877
|
+
if (!delay) {
|
|
7878
|
+
delay = 0;
|
|
7879
|
+
}
|
|
7880
|
+
let results = [];
|
|
7881
|
+
let lastBatchTime = 0;
|
|
7882
|
+
for (let start = 0; start < inputs.length; start += size) {
|
|
7883
|
+
const currentTime = Date.now();
|
|
7884
|
+
const timeSinceLastBatch = currentTime - lastBatchTime;
|
|
7885
|
+
if (timeSinceLastBatch < delay * 1e3) {
|
|
7886
|
+
console.log("[EXULU] Utils function, waiting for", delay - timeSinceLastBatch, "seconds");
|
|
7887
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1e3 - timeSinceLastBatch));
|
|
7888
|
+
}
|
|
7889
|
+
lastBatchTime = Date.now();
|
|
7890
|
+
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})`);
|
|
7891
|
+
const end = start + size > inputs.length ? inputs.length : start + size;
|
|
7892
|
+
const slicedResults = await Promise.all(inputs.slice(start, end).map((data, i) => {
|
|
7893
|
+
if (retries?.max) {
|
|
7894
|
+
return ExuluUtils.retry({
|
|
7895
|
+
fn: async () => {
|
|
7896
|
+
return await fn(data);
|
|
7897
|
+
},
|
|
7898
|
+
retries: retries.max,
|
|
7899
|
+
delays: retries.delays
|
|
7900
|
+
});
|
|
7901
|
+
} else {
|
|
7902
|
+
return fn(data);
|
|
7903
|
+
}
|
|
7904
|
+
}));
|
|
7905
|
+
results = [
|
|
7906
|
+
...results,
|
|
7907
|
+
...slicedResults
|
|
7908
|
+
];
|
|
7909
|
+
}
|
|
7910
|
+
return results;
|
|
7911
|
+
},
|
|
7912
|
+
retry: async ({
|
|
7913
|
+
fn,
|
|
7914
|
+
retries,
|
|
7915
|
+
delays
|
|
7916
|
+
}) => {
|
|
7917
|
+
if (!retries) {
|
|
7918
|
+
retries = 3;
|
|
7919
|
+
}
|
|
7920
|
+
if (!delays) {
|
|
7921
|
+
delays = [1e3, 5e3, 1e4];
|
|
7922
|
+
}
|
|
7923
|
+
for (let i = 0; i < retries; i++) {
|
|
7924
|
+
try {
|
|
7925
|
+
return await fn();
|
|
7926
|
+
} catch (error) {
|
|
7927
|
+
console.error(`[EXULU] Util function, retry attempt ${i + 1} failed:`, error);
|
|
7928
|
+
if (i >= retries - 1) {
|
|
7929
|
+
throw error;
|
|
7930
|
+
}
|
|
7931
|
+
if (!delays[i]) {
|
|
7932
|
+
delays[i] = delays[delays.length - 1] || 1e4;
|
|
7933
|
+
}
|
|
7934
|
+
const delay = delays && delays[i] ? delays[i] : 1e4;
|
|
7935
|
+
console.log(`[EXULU] Util function, retrying in ${delay / 1e3} seconds...`);
|
|
7936
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
7937
|
+
}
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
};
|
|
7430
7941
|
var ExuluOtel = {
|
|
7431
7942
|
create: ({
|
|
7432
7943
|
SIGNOZ_ACCESS_TOKEN,
|
|
@@ -7470,11 +7981,15 @@ var ExuluChunkers = {
|
|
|
7470
7981
|
ExuluAuthentication,
|
|
7471
7982
|
ExuluChunkers,
|
|
7472
7983
|
ExuluContext,
|
|
7984
|
+
ExuluDefaultAgents,
|
|
7985
|
+
ExuluDefaultContexts,
|
|
7473
7986
|
ExuluEmbedder,
|
|
7474
7987
|
ExuluEval,
|
|
7475
7988
|
ExuluJobs,
|
|
7476
7989
|
ExuluOtel,
|
|
7477
7990
|
ExuluQueues,
|
|
7478
7991
|
ExuluTool,
|
|
7992
|
+
ExuluUtils,
|
|
7993
|
+
ExuluVariables,
|
|
7479
7994
|
db
|
|
7480
7995
|
});
|