@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.js
CHANGED
|
@@ -63,11 +63,8 @@ var validateJob = (job) => {
|
|
|
63
63
|
};
|
|
64
64
|
|
|
65
65
|
// src/registry/classes.ts
|
|
66
|
-
import "zod";
|
|
67
66
|
import "bullmq";
|
|
68
67
|
import { z } from "zod";
|
|
69
|
-
import "fs";
|
|
70
|
-
import "path";
|
|
71
68
|
import { convertToModelMessages, createIdGenerator, generateObject, generateText, streamText, tool, validateUIMessages, stepCountIs } from "ai";
|
|
72
69
|
|
|
73
70
|
// types/enums/statistics.ts
|
|
@@ -94,8 +91,9 @@ import "knex";
|
|
|
94
91
|
import "pgvector/knex";
|
|
95
92
|
var db = {};
|
|
96
93
|
var databaseExistsChecked = false;
|
|
94
|
+
var dbName = process.env.POSTGRES_DB_NAME || "exulu";
|
|
97
95
|
async function ensureDatabaseExists() {
|
|
98
|
-
console.log(
|
|
96
|
+
console.log(`[EXULU] Ensuring ${dbName} database exists...`);
|
|
99
97
|
const defaultKnex = Knex({
|
|
100
98
|
client: "pg",
|
|
101
99
|
connection: {
|
|
@@ -110,15 +108,18 @@ async function ensureDatabaseExists() {
|
|
|
110
108
|
});
|
|
111
109
|
try {
|
|
112
110
|
const result = await defaultKnex.raw(`
|
|
113
|
-
SELECT 1 FROM pg_database WHERE datname = '
|
|
111
|
+
SELECT 1 FROM pg_database WHERE datname = '${dbName}'
|
|
114
112
|
`);
|
|
115
113
|
if (result.rows.length === 0) {
|
|
116
|
-
console.log(
|
|
117
|
-
await defaultKnex.raw(`CREATE DATABASE
|
|
118
|
-
console.log(
|
|
114
|
+
console.log(`[EXULU] Database '${dbName}' does not exist. Creating it...`);
|
|
115
|
+
await defaultKnex.raw(`CREATE DATABASE ${dbName}`);
|
|
116
|
+
console.log(`[EXULU] Database '${dbName}' created successfully.`);
|
|
119
117
|
} else {
|
|
120
|
-
console.log(
|
|
118
|
+
console.log(`[EXULU] Database '${dbName}' already exists.`);
|
|
121
119
|
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
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);
|
|
122
|
+
return;
|
|
122
123
|
} finally {
|
|
123
124
|
await defaultKnex.destroy();
|
|
124
125
|
}
|
|
@@ -126,7 +127,7 @@ async function ensureDatabaseExists() {
|
|
|
126
127
|
async function postgresClient() {
|
|
127
128
|
if (!db["exulu"]) {
|
|
128
129
|
try {
|
|
129
|
-
console.log(
|
|
130
|
+
console.log(`[EXULU] Connecting to ${dbName} database.`);
|
|
130
131
|
console.log("[EXULU] POSTGRES_DB_HOST:", process.env.POSTGRES_DB_HOST);
|
|
131
132
|
console.log("[EXULU] POSTGRES_DB_PORT:", process.env.POSTGRES_DB_PORT);
|
|
132
133
|
console.log("[EXULU] POSTGRES_DB_USER:", process.env.POSTGRES_DB_USER);
|
|
@@ -134,7 +135,7 @@ async function postgresClient() {
|
|
|
134
135
|
console.log("[EXULU] POSTGRES_DB_SSL:", process.env.POSTGRES_DB_SSL);
|
|
135
136
|
console.log("[EXULU] Database exists checked:", databaseExistsChecked);
|
|
136
137
|
if (!databaseExistsChecked) {
|
|
137
|
-
console.log(
|
|
138
|
+
console.log(`[EXULU] Ensuring ${dbName} database exists...`);
|
|
138
139
|
await ensureDatabaseExists();
|
|
139
140
|
databaseExistsChecked = true;
|
|
140
141
|
}
|
|
@@ -144,12 +145,16 @@ async function postgresClient() {
|
|
|
144
145
|
host: process.env.POSTGRES_DB_HOST,
|
|
145
146
|
port: parseInt(process.env.POSTGRES_DB_PORT || "5432"),
|
|
146
147
|
user: process.env.POSTGRES_DB_USER,
|
|
147
|
-
database:
|
|
148
|
+
database: dbName,
|
|
148
149
|
password: process.env.POSTGRES_DB_PASSWORD,
|
|
149
150
|
ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
|
|
150
151
|
}
|
|
151
152
|
});
|
|
152
|
-
|
|
153
|
+
try {
|
|
154
|
+
await knex.schema.createExtensionIfNotExists("vector");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
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);
|
|
157
|
+
}
|
|
153
158
|
db["exulu"] = knex;
|
|
154
159
|
} catch (error) {
|
|
155
160
|
console.error("[EXULU] Error initializing exulu database.", error);
|
|
@@ -160,6 +165,16 @@ async function postgresClient() {
|
|
|
160
165
|
db: db["exulu"]
|
|
161
166
|
};
|
|
162
167
|
}
|
|
168
|
+
var refreshPostgresClient = async () => {
|
|
169
|
+
if (db["exulu"]) {
|
|
170
|
+
await db["exulu"].destroy();
|
|
171
|
+
db["exulu"] = void 0;
|
|
172
|
+
}
|
|
173
|
+
const { db: refreshed } = await postgresClient();
|
|
174
|
+
return {
|
|
175
|
+
db: refreshed
|
|
176
|
+
};
|
|
177
|
+
};
|
|
163
178
|
|
|
164
179
|
// src/registry/classes.ts
|
|
165
180
|
import pgvector2 from "pgvector/knex";
|
|
@@ -234,6 +249,15 @@ var mapType = (t, type, name, defaultValue, unique) => {
|
|
|
234
249
|
if (unique) t.unique(name);
|
|
235
250
|
return;
|
|
236
251
|
}
|
|
252
|
+
if (type === "markdown") {
|
|
253
|
+
if (defaultValue) {
|
|
254
|
+
t.text(name).defaultTo(defaultValue);
|
|
255
|
+
} else {
|
|
256
|
+
t.text(name);
|
|
257
|
+
}
|
|
258
|
+
if (unique) t.unique(name);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
237
261
|
if (type === "shortText") {
|
|
238
262
|
if (defaultValue) {
|
|
239
263
|
t.string(name, 100).defaultTo(defaultValue);
|
|
@@ -381,7 +405,6 @@ var ExuluEvalUtils = {
|
|
|
381
405
|
// src/registry/classes.ts
|
|
382
406
|
import CryptoJS2 from "crypto-js";
|
|
383
407
|
import "express";
|
|
384
|
-
import "@opentelemetry/api";
|
|
385
408
|
|
|
386
409
|
// src/registry/utils/graphql.ts
|
|
387
410
|
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
@@ -394,7 +417,7 @@ import { jwtVerify, importJWK } from "jose";
|
|
|
394
417
|
var getToken = async (authHeader) => {
|
|
395
418
|
const token = authHeader.split(" ")[1];
|
|
396
419
|
if (!token) {
|
|
397
|
-
throw new Error("No token provided");
|
|
420
|
+
throw new Error("No token provided for user authentication in headers.");
|
|
398
421
|
}
|
|
399
422
|
if (!process.env.NEXTAUTH_SECRET) {
|
|
400
423
|
throw new Error("No NEXTAUTH_SECRET provided");
|
|
@@ -437,7 +460,7 @@ var authentication = async ({
|
|
|
437
460
|
code: 200,
|
|
438
461
|
user: {
|
|
439
462
|
type: "api",
|
|
440
|
-
id:
|
|
463
|
+
id: 192837465,
|
|
441
464
|
email: "internal@exulu.com",
|
|
442
465
|
role: {
|
|
443
466
|
id: "internal",
|
|
@@ -758,7 +781,8 @@ var agentSessionsSchema = {
|
|
|
758
781
|
},
|
|
759
782
|
{
|
|
760
783
|
name: "project",
|
|
761
|
-
type: "uuid"
|
|
784
|
+
type: "uuid",
|
|
785
|
+
required: false
|
|
762
786
|
}
|
|
763
787
|
]
|
|
764
788
|
};
|
|
@@ -858,12 +882,13 @@ var projectsSchema = {
|
|
|
858
882
|
type: "text"
|
|
859
883
|
},
|
|
860
884
|
{
|
|
861
|
-
name: "
|
|
862
|
-
|
|
885
|
+
name: "project_items",
|
|
886
|
+
// array of items as global ids ('<context_id>/<item_id>')
|
|
887
|
+
type: "json"
|
|
863
888
|
},
|
|
864
889
|
{
|
|
865
|
-
name: "
|
|
866
|
-
type: "
|
|
890
|
+
name: "custom_instructions",
|
|
891
|
+
type: "longText"
|
|
867
892
|
}
|
|
868
893
|
]
|
|
869
894
|
};
|
|
@@ -883,20 +908,24 @@ var agentsSchema = {
|
|
|
883
908
|
name: "image",
|
|
884
909
|
type: "text"
|
|
885
910
|
},
|
|
911
|
+
{
|
|
912
|
+
name: "category",
|
|
913
|
+
type: "text"
|
|
914
|
+
},
|
|
886
915
|
{
|
|
887
916
|
name: "description",
|
|
888
917
|
type: "text"
|
|
889
918
|
},
|
|
890
919
|
{
|
|
891
|
-
name: "
|
|
920
|
+
name: "instructions",
|
|
892
921
|
type: "text"
|
|
893
922
|
},
|
|
894
923
|
{
|
|
895
|
-
name: "
|
|
924
|
+
name: "providerapikey",
|
|
896
925
|
type: "text"
|
|
897
926
|
},
|
|
898
927
|
{
|
|
899
|
-
name: "
|
|
928
|
+
name: "backend",
|
|
900
929
|
type: "text"
|
|
901
930
|
},
|
|
902
931
|
{
|
|
@@ -1230,18 +1259,22 @@ var rbacSchema = {
|
|
|
1230
1259
|
};
|
|
1231
1260
|
var addRBACfields = (schema) => {
|
|
1232
1261
|
if (schema.RBAC) {
|
|
1233
|
-
schema.fields.
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1262
|
+
if (!schema.fields.some((field) => field.name === "rights_mode")) {
|
|
1263
|
+
schema.fields.push({
|
|
1264
|
+
name: "rights_mode",
|
|
1265
|
+
type: "text",
|
|
1266
|
+
required: false,
|
|
1267
|
+
default: "private"
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
if (!schema.fields.some((field) => field.name === "created_by")) {
|
|
1271
|
+
schema.fields.push({
|
|
1272
|
+
name: "created_by",
|
|
1273
|
+
type: "number",
|
|
1274
|
+
required: true,
|
|
1275
|
+
default: 0
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1245
1278
|
}
|
|
1246
1279
|
return schema;
|
|
1247
1280
|
};
|
|
@@ -1273,6 +1306,210 @@ var VectorMethodEnum = {
|
|
|
1273
1306
|
|
|
1274
1307
|
// src/registry/utils/graphql.ts
|
|
1275
1308
|
import "knex";
|
|
1309
|
+
|
|
1310
|
+
// src/registry/rate-limiter.ts
|
|
1311
|
+
var rateLimiter = async (key, windowSeconds, limit, points) => {
|
|
1312
|
+
try {
|
|
1313
|
+
const { client: client2 } = await redisClient();
|
|
1314
|
+
if (!client2) {
|
|
1315
|
+
console.warn("[EXULU] Rate limiting disabled - Redis not available");
|
|
1316
|
+
return {
|
|
1317
|
+
status: true,
|
|
1318
|
+
retryAfter: null
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
const redisKey = `exulu/${key}`;
|
|
1322
|
+
const current = await client2.incrBy(redisKey, points);
|
|
1323
|
+
if (current === points) {
|
|
1324
|
+
await client2.expire(redisKey, windowSeconds);
|
|
1325
|
+
}
|
|
1326
|
+
if (current > limit) {
|
|
1327
|
+
const ttl = await client2.ttl(redisKey);
|
|
1328
|
+
return {
|
|
1329
|
+
status: false,
|
|
1330
|
+
retryAfter: ttl
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
return {
|
|
1334
|
+
status: true,
|
|
1335
|
+
retryAfter: null
|
|
1336
|
+
};
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
console.error("[EXULU] Rate limiting error:", error);
|
|
1339
|
+
return {
|
|
1340
|
+
status: true,
|
|
1341
|
+
retryAfter: null
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
// src/registry/utils.ts
|
|
1347
|
+
var bullmq = {
|
|
1348
|
+
validate: (id, data) => {
|
|
1349
|
+
if (!data) {
|
|
1350
|
+
throw new Error(`Missing job data for job ${id}.`);
|
|
1351
|
+
}
|
|
1352
|
+
if (!data.type) {
|
|
1353
|
+
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
1354
|
+
}
|
|
1355
|
+
if (!data.inputs) {
|
|
1356
|
+
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
1357
|
+
}
|
|
1358
|
+
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
1359
|
+
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
|
|
1360
|
+
}
|
|
1361
|
+
if (!data.workflow && !data.embedder) {
|
|
1362
|
+
throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
var getEnabledTools = async (agentInstance, allExuluTools, disabledTools = [], agents, user) => {
|
|
1367
|
+
let enabledTools = [];
|
|
1368
|
+
if (agentInstance.tools) {
|
|
1369
|
+
const results = await Promise.all(agentInstance.tools.map(
|
|
1370
|
+
async ({ config, id, type }) => {
|
|
1371
|
+
let hydrated;
|
|
1372
|
+
if (type === "agent") {
|
|
1373
|
+
if (id === agentInstance.id) {
|
|
1374
|
+
return null;
|
|
1375
|
+
}
|
|
1376
|
+
const instance = await loadAgent(id);
|
|
1377
|
+
if (!instance) {
|
|
1378
|
+
throw new Error("Trying to load a tool of type 'agent', but the associated agent with id " + id + " was not found in the database.");
|
|
1379
|
+
}
|
|
1380
|
+
const backend = agents.find((a) => a.id === instance.backend);
|
|
1381
|
+
if (!backend) {
|
|
1382
|
+
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.");
|
|
1383
|
+
}
|
|
1384
|
+
const hasAccessToAgent = await checkRecordAccess(instance, "read", user);
|
|
1385
|
+
if (!hasAccessToAgent) {
|
|
1386
|
+
return null;
|
|
1387
|
+
}
|
|
1388
|
+
hydrated = await backend.tool(instance.id, agents);
|
|
1389
|
+
} else {
|
|
1390
|
+
hydrated = allExuluTools.find((t) => t.id === id);
|
|
1391
|
+
}
|
|
1392
|
+
return hydrated;
|
|
1393
|
+
}
|
|
1394
|
+
));
|
|
1395
|
+
enabledTools = results.filter(Boolean);
|
|
1396
|
+
}
|
|
1397
|
+
console.log("[EXULU] available tools", enabledTools?.length);
|
|
1398
|
+
console.log("[EXULU] disabled tools", disabledTools?.length);
|
|
1399
|
+
enabledTools = enabledTools.filter((tool2) => !disabledTools.includes(tool2.id));
|
|
1400
|
+
return enabledTools;
|
|
1401
|
+
};
|
|
1402
|
+
var loadAgentCache = /* @__PURE__ */ new Map();
|
|
1403
|
+
var loadAgents = async () => {
|
|
1404
|
+
const { db: db3 } = await postgresClient();
|
|
1405
|
+
const agents = await db3.from("agents");
|
|
1406
|
+
for (const agent of agents) {
|
|
1407
|
+
const agentRbac = await RBACResolver(db3, "agent", agent.id, agent.rights_mode || "private");
|
|
1408
|
+
agent.RBAC = agentRbac;
|
|
1409
|
+
loadAgentCache.set(agent.id, {
|
|
1410
|
+
agent,
|
|
1411
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1412
|
+
// 1 minute
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
return agents;
|
|
1416
|
+
};
|
|
1417
|
+
var loadAgent = async (id) => {
|
|
1418
|
+
const cachedAgent = loadAgentCache.get(id);
|
|
1419
|
+
if (cachedAgent && cachedAgent.expiresAt > /* @__PURE__ */ new Date()) {
|
|
1420
|
+
return cachedAgent.agent;
|
|
1421
|
+
}
|
|
1422
|
+
const { db: db3 } = await postgresClient();
|
|
1423
|
+
const agentInstance = await db3.from("agents").where({
|
|
1424
|
+
id
|
|
1425
|
+
}).first();
|
|
1426
|
+
const agentRbac = await RBACResolver(db3, "agent", agentInstance.id, agentInstance.rights_mode || "private");
|
|
1427
|
+
agentInstance.RBAC = agentRbac;
|
|
1428
|
+
if (!agentInstance) {
|
|
1429
|
+
throw new Error("Agent instance not found.");
|
|
1430
|
+
}
|
|
1431
|
+
loadAgentCache.set(id, {
|
|
1432
|
+
agent: agentInstance,
|
|
1433
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1434
|
+
// 1 minute
|
|
1435
|
+
});
|
|
1436
|
+
return agentInstance;
|
|
1437
|
+
};
|
|
1438
|
+
var checkAgentRateLimit = async (agent) => {
|
|
1439
|
+
if (agent.rateLimit) {
|
|
1440
|
+
console.log("[EXULU] rate limiting agent.", agent.rateLimit);
|
|
1441
|
+
const limit = await rateLimiter(
|
|
1442
|
+
agent.rateLimit.name || agent.id,
|
|
1443
|
+
agent.rateLimit.rate_limit.time,
|
|
1444
|
+
agent.rateLimit.rate_limit.limit,
|
|
1445
|
+
1
|
|
1446
|
+
);
|
|
1447
|
+
if (!limit.status) {
|
|
1448
|
+
throw new Error("Rate limit exceeded.");
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
var checkRecordAccessCache = /* @__PURE__ */ new Map();
|
|
1453
|
+
var checkRecordAccess = async (record, request, user) => {
|
|
1454
|
+
const setRecordAccessCache = (hasAccess2) => {
|
|
1455
|
+
checkRecordAccessCache.set(`${record.id}-${request}-${user?.id}`, {
|
|
1456
|
+
hasAccess: hasAccess2,
|
|
1457
|
+
expiresAt: new Date(Date.now() + 1e3 * 60 * 1)
|
|
1458
|
+
// 1 minute
|
|
1459
|
+
});
|
|
1460
|
+
};
|
|
1461
|
+
const cachedAccess = checkRecordAccessCache.get(`${record.id}-${request}-${user?.id}`);
|
|
1462
|
+
if (cachedAccess && cachedAccess.expiresAt > /* @__PURE__ */ new Date()) {
|
|
1463
|
+
return cachedAccess.hasAccess;
|
|
1464
|
+
}
|
|
1465
|
+
const isPublic = record.rights_mode === "public";
|
|
1466
|
+
const byUsers = record.rights_mode === "users";
|
|
1467
|
+
const byRoles = record.rights_mode === "roles";
|
|
1468
|
+
const isCreator = user ? record.created_by === user.id.toString() : false;
|
|
1469
|
+
const isAdmin = user ? user.super_admin : false;
|
|
1470
|
+
const isApi = user ? user.type === "api" : false;
|
|
1471
|
+
let hasAccess = "none";
|
|
1472
|
+
if (isPublic || isCreator || isAdmin || isApi) {
|
|
1473
|
+
setRecordAccessCache(true);
|
|
1474
|
+
return true;
|
|
1475
|
+
}
|
|
1476
|
+
if (byUsers) {
|
|
1477
|
+
if (!user) {
|
|
1478
|
+
setRecordAccessCache(false);
|
|
1479
|
+
return false;
|
|
1480
|
+
}
|
|
1481
|
+
console.log("record.RBAC?.users", record.RBAC?.users);
|
|
1482
|
+
console.log("user.id", user.id.toString());
|
|
1483
|
+
hasAccess = record.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
1484
|
+
if (!hasAccess || hasAccess === "none" || hasAccess !== request) {
|
|
1485
|
+
console.error(`Your current user ${user.id} does not have access to this record, current access type is: ${hasAccess}.`);
|
|
1486
|
+
setRecordAccessCache(false);
|
|
1487
|
+
return false;
|
|
1488
|
+
} else {
|
|
1489
|
+
setRecordAccessCache(true);
|
|
1490
|
+
return true;
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (byRoles) {
|
|
1494
|
+
if (!user) {
|
|
1495
|
+
setRecordAccessCache(false);
|
|
1496
|
+
return false;
|
|
1497
|
+
}
|
|
1498
|
+
hasAccess = record.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
1499
|
+
if (!hasAccess || hasAccess === "none" || hasAccess !== request) {
|
|
1500
|
+
console.error(`Your current role ${user.role?.name} does not have access to this record, current access type is: ${hasAccess}.`);
|
|
1501
|
+
setRecordAccessCache(false);
|
|
1502
|
+
return false;
|
|
1503
|
+
} else {
|
|
1504
|
+
setRecordAccessCache(true);
|
|
1505
|
+
return true;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
setRecordAccessCache(false);
|
|
1509
|
+
return false;
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
// src/registry/utils/graphql.ts
|
|
1276
1513
|
var GraphQLDate = new GraphQLScalarType({
|
|
1277
1514
|
name: "Date",
|
|
1278
1515
|
description: "Date custom scalar type",
|
|
@@ -1313,6 +1550,7 @@ var map = (field) => {
|
|
|
1313
1550
|
case "text":
|
|
1314
1551
|
case "shortText":
|
|
1315
1552
|
case "longText":
|
|
1553
|
+
case "markdown":
|
|
1316
1554
|
case "code":
|
|
1317
1555
|
type = "String";
|
|
1318
1556
|
break;
|
|
@@ -1364,6 +1602,7 @@ ${enumValues}
|
|
|
1364
1602
|
fields.push(" rateLimit: RateLimiterRule");
|
|
1365
1603
|
fields.push(" streaming: Boolean");
|
|
1366
1604
|
fields.push(" capabilities: AgentCapabilities");
|
|
1605
|
+
fields.push(" maxContextLength: Int");
|
|
1367
1606
|
fields.push(" slug: String");
|
|
1368
1607
|
}
|
|
1369
1608
|
const rbacField = table.RBAC ? " RBAC: RBACData" : "";
|
|
@@ -1698,7 +1937,6 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1698
1937
|
input.id = db3.fn.uuid();
|
|
1699
1938
|
}
|
|
1700
1939
|
const columns = await db3(tableNamePlural).columnInfo();
|
|
1701
|
-
console.log("[EXULU] Columns", columns);
|
|
1702
1940
|
const insert = db3(tableNamePlural).insert({
|
|
1703
1941
|
...input,
|
|
1704
1942
|
...table.RBAC ? { rights_mode: "private" } : {}
|
|
@@ -1713,7 +1951,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1713
1951
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user.id, role: context.user.role?.id });
|
|
1714
1952
|
return {
|
|
1715
1953
|
// Filter result to only include requested fields
|
|
1716
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0] }),
|
|
1954
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user }),
|
|
1717
1955
|
job
|
|
1718
1956
|
};
|
|
1719
1957
|
},
|
|
@@ -1756,7 +1994,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1756
1994
|
}
|
|
1757
1995
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id });
|
|
1758
1996
|
return {
|
|
1759
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result }),
|
|
1997
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
|
|
1760
1998
|
job
|
|
1761
1999
|
};
|
|
1762
2000
|
},
|
|
@@ -1792,7 +2030,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1792
2030
|
const result = await db3.from(tableNamePlural).select(Object.keys(columns)).where({ id }).first();
|
|
1793
2031
|
const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id });
|
|
1794
2032
|
return {
|
|
1795
|
-
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result }),
|
|
2033
|
+
item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
|
|
1796
2034
|
job
|
|
1797
2035
|
};
|
|
1798
2036
|
},
|
|
@@ -1824,7 +2062,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1824
2062
|
}).del();
|
|
1825
2063
|
}
|
|
1826
2064
|
await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
|
|
1827
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2065
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
|
|
1828
2066
|
},
|
|
1829
2067
|
[`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
|
|
1830
2068
|
const { where } = args;
|
|
@@ -1848,7 +2086,7 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1848
2086
|
}
|
|
1849
2087
|
await db3(tableNamePlural).where(where).del();
|
|
1850
2088
|
await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
|
|
1851
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2089
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
|
|
1852
2090
|
}
|
|
1853
2091
|
};
|
|
1854
2092
|
if (table.type === "items") {
|
|
@@ -1928,7 +2166,6 @@ function createMutations(table, agents, contexts, tools) {
|
|
|
1928
2166
|
return mutations;
|
|
1929
2167
|
}
|
|
1930
2168
|
var applyAccessControl = (table, user, query) => {
|
|
1931
|
-
console.log("table", table);
|
|
1932
2169
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
1933
2170
|
if (!user.super_admin && table.name.plural === "jobs") {
|
|
1934
2171
|
query = query.where("created_by", user.id);
|
|
@@ -1955,7 +2192,6 @@ var applyAccessControl = (table, user, query) => {
|
|
|
1955
2192
|
});
|
|
1956
2193
|
});
|
|
1957
2194
|
if (user.role) {
|
|
1958
|
-
console.log("user.role", user.role);
|
|
1959
2195
|
this.orWhere(function() {
|
|
1960
2196
|
this.where("rights_mode", "roles").whereExists(function() {
|
|
1961
2197
|
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);
|
|
@@ -1996,14 +2232,15 @@ var backendAgentFields = [
|
|
|
1996
2232
|
"slug",
|
|
1997
2233
|
"rateLimit",
|
|
1998
2234
|
"streaming",
|
|
1999
|
-
"capabilities"
|
|
2235
|
+
"capabilities",
|
|
2236
|
+
"maxContextLength"
|
|
2000
2237
|
];
|
|
2001
2238
|
var removeAgentFields = (requestedFields) => {
|
|
2002
2239
|
const filtered = requestedFields.filter((field) => !backendAgentFields.includes(field));
|
|
2003
2240
|
filtered.push("backend");
|
|
2004
2241
|
return filtered;
|
|
2005
2242
|
};
|
|
2006
|
-
var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
2243
|
+
var addAgentFields = async (requestedFields, agents, result, tools, user) => {
|
|
2007
2244
|
let backend = agents.find((a) => a.id === result?.backend);
|
|
2008
2245
|
if (requestedFields.includes("providerName")) {
|
|
2009
2246
|
result.providerName = backend?.providerName || "";
|
|
@@ -2018,13 +2255,39 @@ var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
|
2018
2255
|
result.rateLimit = backend?.rateLimit || "";
|
|
2019
2256
|
}
|
|
2020
2257
|
if (requestedFields.includes("tools")) {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2258
|
+
if (result.tools) {
|
|
2259
|
+
result.tools = await Promise.all(result.tools.map(async (tool2) => {
|
|
2260
|
+
let hydrated;
|
|
2261
|
+
if (tool2.type === "agent") {
|
|
2262
|
+
if (tool2.id === result.id) {
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
const instance = await loadAgent(tool2.id);
|
|
2266
|
+
if (!instance) {
|
|
2267
|
+
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.");
|
|
2268
|
+
}
|
|
2269
|
+
const backend2 = agents.find((a) => a.id === instance.backend);
|
|
2270
|
+
if (!backend2) {
|
|
2271
|
+
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.");
|
|
2272
|
+
}
|
|
2273
|
+
const hasAccessToAgent = await checkRecordAccess(instance, "read", user);
|
|
2274
|
+
if (!hasAccessToAgent) {
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
hydrated = await backend2.tool(instance.id, agents);
|
|
2278
|
+
} else {
|
|
2279
|
+
hydrated = tools.find((t) => t.id === tool2.id);
|
|
2280
|
+
}
|
|
2281
|
+
return {
|
|
2282
|
+
...tool2,
|
|
2283
|
+
name: hydrated?.name || "",
|
|
2284
|
+
description: hydrated?.description || ""
|
|
2285
|
+
};
|
|
2286
|
+
}));
|
|
2287
|
+
result.tools = result.tools.filter((tool2) => tool2 !== null);
|
|
2288
|
+
} else {
|
|
2289
|
+
result.tools = [];
|
|
2290
|
+
}
|
|
2028
2291
|
}
|
|
2029
2292
|
if (requestedFields.includes("streaming")) {
|
|
2030
2293
|
result.streaming = backend?.streaming || false;
|
|
@@ -2032,6 +2295,9 @@ var addAgentFields = (requestedFields, agents, result, tools) => {
|
|
|
2032
2295
|
if (requestedFields.includes("capabilities")) {
|
|
2033
2296
|
result.capabilities = backend?.capabilities || [];
|
|
2034
2297
|
}
|
|
2298
|
+
if (requestedFields.includes("maxContextLength")) {
|
|
2299
|
+
result.maxContextLength = backend?.maxContextLength || 0;
|
|
2300
|
+
}
|
|
2035
2301
|
if (!requestedFields.includes("backend")) {
|
|
2036
2302
|
delete result.backend;
|
|
2037
2303
|
}
|
|
@@ -2081,7 +2347,11 @@ var postprocessUpdate = async ({
|
|
|
2081
2347
|
const { db: db3 } = await postgresClient();
|
|
2082
2348
|
console.log("[EXULU] Deleting chunks for item", result.id);
|
|
2083
2349
|
await db3.from(getChunksTableName(context.id)).where({ source: result.id }).delete();
|
|
2350
|
+
console.log("[EXULU] Deleted chunks for item", result.id);
|
|
2351
|
+
console.log("[EXULU] Embedder", context.embedder);
|
|
2352
|
+
console.log("[EXULU] Configuration", context.configuration);
|
|
2084
2353
|
if (context.embedder && (context.configuration.calculateVectors === "onUpdate" || context.configuration.calculateVectors === "always")) {
|
|
2354
|
+
console.log("[EXULU] Generating embeddings for item", result.id);
|
|
2085
2355
|
const { job } = await context.embeddings.generate.one({
|
|
2086
2356
|
item: result,
|
|
2087
2357
|
user,
|
|
@@ -2142,7 +2412,8 @@ var finalizeRequestedFields = async ({
|
|
|
2142
2412
|
agents,
|
|
2143
2413
|
contexts,
|
|
2144
2414
|
tools,
|
|
2145
|
-
result
|
|
2415
|
+
result,
|
|
2416
|
+
user
|
|
2146
2417
|
}) => {
|
|
2147
2418
|
if (!result) {
|
|
2148
2419
|
return result;
|
|
@@ -2152,11 +2423,11 @@ var finalizeRequestedFields = async ({
|
|
|
2152
2423
|
}
|
|
2153
2424
|
if (Array.isArray(result)) {
|
|
2154
2425
|
result = result.map((item) => {
|
|
2155
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item });
|
|
2426
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item, user });
|
|
2156
2427
|
});
|
|
2157
2428
|
} else {
|
|
2158
2429
|
if (table.name.singular === "agent") {
|
|
2159
|
-
result = addAgentFields(requestedFields, agents, result, tools);
|
|
2430
|
+
result = await addAgentFields(requestedFields, agents, result, tools, user);
|
|
2160
2431
|
if (!requestedFields.includes("backend")) {
|
|
2161
2432
|
delete result.backend;
|
|
2162
2433
|
}
|
|
@@ -2203,7 +2474,6 @@ var applyFilters = (query, filters) => {
|
|
|
2203
2474
|
Object.entries(filter).forEach(([fieldName, operators]) => {
|
|
2204
2475
|
if (operators) {
|
|
2205
2476
|
if (operators.and !== void 0) {
|
|
2206
|
-
console.log("operators.and", operators.and);
|
|
2207
2477
|
operators.and.forEach((operator) => {
|
|
2208
2478
|
query = converOperatorToQuery(query, fieldName, operator);
|
|
2209
2479
|
});
|
|
@@ -2214,7 +2484,6 @@ var applyFilters = (query, filters) => {
|
|
|
2214
2484
|
});
|
|
2215
2485
|
}
|
|
2216
2486
|
query = converOperatorToQuery(query, fieldName, operators);
|
|
2217
|
-
console.log("query", query);
|
|
2218
2487
|
}
|
|
2219
2488
|
});
|
|
2220
2489
|
});
|
|
@@ -2237,7 +2506,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2237
2506
|
let query = db3.from(tableNamePlural).select(sanitizedFields).where({ id: args.id });
|
|
2238
2507
|
query = applyAccessControl(table, context.user, query);
|
|
2239
2508
|
let result = await query.first();
|
|
2240
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2509
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2241
2510
|
},
|
|
2242
2511
|
[`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
|
|
2243
2512
|
const { db: db3 } = context;
|
|
@@ -2246,7 +2515,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2246
2515
|
let query = db3.from(tableNamePlural).select(sanitizedFields).whereIn("id", args.ids);
|
|
2247
2516
|
query = applyAccessControl(table, context.user, query);
|
|
2248
2517
|
let result = await query;
|
|
2249
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2518
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2250
2519
|
},
|
|
2251
2520
|
[`${tableNameSingular}One`]: async (_, args, context, info) => {
|
|
2252
2521
|
const { filters = [], sort } = args;
|
|
@@ -2258,7 +2527,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2258
2527
|
query = applyAccessControl(table, context.user, query);
|
|
2259
2528
|
query = applySorting(query, sort);
|
|
2260
2529
|
let result = await query.first();
|
|
2261
|
-
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
|
|
2530
|
+
return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
|
|
2262
2531
|
},
|
|
2263
2532
|
[`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
|
|
2264
2533
|
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
@@ -2269,7 +2538,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2269
2538
|
let countQuery = db3(tableNamePlural);
|
|
2270
2539
|
countQuery = applyFilters(countQuery, filters);
|
|
2271
2540
|
countQuery = applyAccessControl(table, context.user, countQuery);
|
|
2272
|
-
console.log("countQuery", countQuery);
|
|
2273
2541
|
const countResult = await countQuery.count("* as count");
|
|
2274
2542
|
const itemCount = Number(countResult[0]?.count || 0);
|
|
2275
2543
|
const pageCount = Math.ceil(itemCount / limit);
|
|
@@ -2294,7 +2562,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2294
2562
|
hasPreviousPage,
|
|
2295
2563
|
hasNextPage
|
|
2296
2564
|
},
|
|
2297
|
-
items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items })
|
|
2565
|
+
items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items, user: context.user })
|
|
2298
2566
|
};
|
|
2299
2567
|
},
|
|
2300
2568
|
// Add generic statistics query for all tables
|
|
@@ -2312,7 +2580,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2312
2580
|
query = query.count("* as count");
|
|
2313
2581
|
}
|
|
2314
2582
|
const results = await query;
|
|
2315
|
-
console.log("!!! results !!!", results);
|
|
2316
2583
|
return results.map((r) => ({
|
|
2317
2584
|
group: r[groupBy],
|
|
2318
2585
|
count: r.count ? Number(r.count) : 0
|
|
@@ -2321,7 +2588,6 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
2321
2588
|
if (tableNamePlural === "tracking") {
|
|
2322
2589
|
query = query.sum("total as count");
|
|
2323
2590
|
const [{ count }] = await query.sum("total as count");
|
|
2324
|
-
console.log("!!! count !!!", count);
|
|
2325
2591
|
return [{
|
|
2326
2592
|
group: "total",
|
|
2327
2593
|
count: count ? Number(count) : 0
|
|
@@ -2406,7 +2672,6 @@ var vectorSearch = async ({
|
|
|
2406
2672
|
let countQuery = db3(mainTable);
|
|
2407
2673
|
countQuery = applyFilters(countQuery, filters);
|
|
2408
2674
|
countQuery = applyAccessControl(table, user, countQuery);
|
|
2409
|
-
console.log("countQuery", countQuery);
|
|
2410
2675
|
const columns = await db3(mainTable).columnInfo();
|
|
2411
2676
|
let itemsQuery = db3(mainTable).select(Object.keys(columns).map((column) => mainTable + "." + column));
|
|
2412
2677
|
itemsQuery = applyFilters(itemsQuery, filters);
|
|
@@ -2545,7 +2810,6 @@ var vectorSearch = async ({
|
|
|
2545
2810
|
];
|
|
2546
2811
|
items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
|
|
2547
2812
|
}
|
|
2548
|
-
console.log("items", items);
|
|
2549
2813
|
const seenSources = /* @__PURE__ */ new Map();
|
|
2550
2814
|
items = items.reduce((acc, item) => {
|
|
2551
2815
|
if (!seenSources.has(item.source)) {
|
|
@@ -2588,7 +2852,6 @@ var vectorSearch = async ({
|
|
|
2588
2852
|
}
|
|
2589
2853
|
return acc;
|
|
2590
2854
|
}, []);
|
|
2591
|
-
console.log("items", items);
|
|
2592
2855
|
items.forEach((item) => {
|
|
2593
2856
|
if (!item.chunks?.length) {
|
|
2594
2857
|
return;
|
|
@@ -2610,7 +2873,6 @@ var vectorSearch = async ({
|
|
|
2610
2873
|
item.averageRelevance = average;
|
|
2611
2874
|
item.totalRelevance = total;
|
|
2612
2875
|
} else if (method === "hybridSearch") {
|
|
2613
|
-
console.log("item.chunks", item.chunks);
|
|
2614
2876
|
const scores = item.chunks.map((c) => typeof c.hybrid_score === "number" ? c.hybrid_score * 10 + 1 : 0);
|
|
2615
2877
|
const total = scores.reduce((a, b) => a + b, 0);
|
|
2616
2878
|
const average = scores.length ? total / scores.length : 0;
|
|
@@ -2699,6 +2961,10 @@ var contextToTableDefinition = (context) => {
|
|
|
2699
2961
|
name: "textlength",
|
|
2700
2962
|
type: "number"
|
|
2701
2963
|
});
|
|
2964
|
+
definition.fields.push({
|
|
2965
|
+
name: "ttl",
|
|
2966
|
+
type: "text"
|
|
2967
|
+
});
|
|
2702
2968
|
definition.fields.push({
|
|
2703
2969
|
name: "embeddings_updated_at",
|
|
2704
2970
|
type: "date"
|
|
@@ -2983,8 +3249,19 @@ type PageInfo {
|
|
|
2983
3249
|
};
|
|
2984
3250
|
resolvers.Query["tools"] = async (_, args, context, info) => {
|
|
2985
3251
|
const requestedFields = getRequestedFields(info);
|
|
3252
|
+
const instances = await loadAgents();
|
|
3253
|
+
let agentTools = await Promise.all(
|
|
3254
|
+
instances.map(async (instance) => {
|
|
3255
|
+
const backend = agents.find((a) => a.id === instance.backend);
|
|
3256
|
+
if (!backend) {
|
|
3257
|
+
return null;
|
|
3258
|
+
}
|
|
3259
|
+
return await backend.tool(instance.id, agents);
|
|
3260
|
+
})
|
|
3261
|
+
);
|
|
3262
|
+
const filtered = agentTools.filter((tool2) => tool2 !== null);
|
|
2986
3263
|
return {
|
|
2987
|
-
items: tools.map((tool2) => {
|
|
3264
|
+
items: [...filtered, ...tools].map((tool2) => {
|
|
2988
3265
|
const object = {};
|
|
2989
3266
|
requestedFields.forEach((field) => {
|
|
2990
3267
|
object[field] = tool2[field];
|
|
@@ -3080,7 +3357,6 @@ type Tool {
|
|
|
3080
3357
|
|
|
3081
3358
|
enum EnumProviderType {
|
|
3082
3359
|
agent
|
|
3083
|
-
custom
|
|
3084
3360
|
}
|
|
3085
3361
|
|
|
3086
3362
|
type StatisticsResult {
|
|
@@ -3114,6 +3390,13 @@ var validateCreateOrRemoveSuperAdminPermission = async (tableNamePlural, input,
|
|
|
3114
3390
|
};
|
|
3115
3391
|
|
|
3116
3392
|
// src/registry/classes.ts
|
|
3393
|
+
import {
|
|
3394
|
+
PutObjectCommand,
|
|
3395
|
+
S3Client,
|
|
3396
|
+
S3ServiceException
|
|
3397
|
+
} from "@aws-sdk/client-s3";
|
|
3398
|
+
import { randomUUID } from "crypto";
|
|
3399
|
+
var s3Client;
|
|
3117
3400
|
function sanitizeToolName(name) {
|
|
3118
3401
|
if (typeof name !== "string") return "";
|
|
3119
3402
|
let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
@@ -3123,13 +3406,14 @@ function sanitizeToolName(name) {
|
|
|
3123
3406
|
}
|
|
3124
3407
|
return sanitized;
|
|
3125
3408
|
}
|
|
3126
|
-
var convertToolsArrayToObject = (
|
|
3127
|
-
if (!
|
|
3128
|
-
|
|
3409
|
+
var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, filesContext2) => {
|
|
3410
|
+
if (!currentTools) return {};
|
|
3411
|
+
if (!allExuluTools) return {};
|
|
3412
|
+
const sanitizedTools = currentTools ? currentTools.map((tool2) => ({
|
|
3129
3413
|
...tool2,
|
|
3130
3414
|
name: sanitizeToolName(tool2.name)
|
|
3131
3415
|
})) : [];
|
|
3132
|
-
console.log("[EXULU] Sanitized tools", sanitizedTools);
|
|
3416
|
+
console.log("[EXULU] Sanitized tools", sanitizedTools.map((x) => x.name + " (" + x.id + ")"));
|
|
3133
3417
|
const askForConfirmation = {
|
|
3134
3418
|
description: "Ask the user for confirmation.",
|
|
3135
3419
|
inputSchema: z.object({
|
|
@@ -3142,29 +3426,91 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) =>
|
|
|
3142
3426
|
...prev,
|
|
3143
3427
|
[cur.name]: {
|
|
3144
3428
|
...cur.tool,
|
|
3145
|
-
|
|
3429
|
+
async *execute(inputs, options) {
|
|
3146
3430
|
if (!cur.tool?.execute) {
|
|
3147
3431
|
console.error("[EXULU] Tool execute function is undefined.", cur.tool);
|
|
3148
3432
|
throw new Error("Tool execute function is undefined.");
|
|
3149
3433
|
}
|
|
3150
|
-
let config = configs?.find((config2) => config2.
|
|
3434
|
+
let config = configs?.find((config2) => config2.id === cur.id);
|
|
3151
3435
|
if (config) {
|
|
3152
3436
|
config = await hydrateVariables(config || []);
|
|
3153
3437
|
}
|
|
3438
|
+
let upload = void 0;
|
|
3439
|
+
if (exuluConfig?.fileUploads?.s3endpoint && exuluConfig?.fileUploads?.s3key && exuluConfig?.fileUploads?.s3secret && exuluConfig?.fileUploads?.s3Bucket && filesContext2) {
|
|
3440
|
+
s3Client ??= new S3Client({
|
|
3441
|
+
region: exuluConfig?.fileUploads?.s3region,
|
|
3442
|
+
...exuluConfig?.fileUploads?.s3endpoint && {
|
|
3443
|
+
forcePathStyle: true,
|
|
3444
|
+
endpoint: exuluConfig?.fileUploads?.s3endpoint
|
|
3445
|
+
},
|
|
3446
|
+
credentials: {
|
|
3447
|
+
accessKeyId: exuluConfig?.fileUploads?.s3key ?? "",
|
|
3448
|
+
secretAccessKey: exuluConfig?.fileUploads?.s3secret ?? ""
|
|
3449
|
+
}
|
|
3450
|
+
});
|
|
3451
|
+
upload = async ({
|
|
3452
|
+
name,
|
|
3453
|
+
data,
|
|
3454
|
+
type,
|
|
3455
|
+
tags
|
|
3456
|
+
}) => {
|
|
3457
|
+
const mime = getMimeType(type);
|
|
3458
|
+
const key = `${user}/${generateS3Key(name)}${type}`;
|
|
3459
|
+
const command = new PutObjectCommand({
|
|
3460
|
+
Bucket: exuluConfig?.fileUploads?.s3Bucket,
|
|
3461
|
+
Key: key,
|
|
3462
|
+
Body: data,
|
|
3463
|
+
ContentType: mime
|
|
3464
|
+
});
|
|
3465
|
+
try {
|
|
3466
|
+
const response2 = await s3Client.send(command);
|
|
3467
|
+
console.log(response2);
|
|
3468
|
+
const { item } = await filesContext2.createItem({
|
|
3469
|
+
name: `${name}${type}`,
|
|
3470
|
+
type: mime,
|
|
3471
|
+
rights_mode: "private",
|
|
3472
|
+
s3key: key,
|
|
3473
|
+
tags
|
|
3474
|
+
}, user?.id, user?.role?.id, false);
|
|
3475
|
+
return item;
|
|
3476
|
+
} catch (caught) {
|
|
3477
|
+
if (caught instanceof S3ServiceException && caught.name === "EntityTooLarge") {
|
|
3478
|
+
console.error(
|
|
3479
|
+
`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).`
|
|
3480
|
+
);
|
|
3481
|
+
} else if (caught instanceof S3ServiceException) {
|
|
3482
|
+
console.error(
|
|
3483
|
+
`Error from S3 while uploading object to ${exuluConfig?.fileUploads?.s3Bucket}. ${caught.name}: ${caught.message}`
|
|
3484
|
+
);
|
|
3485
|
+
} else {
|
|
3486
|
+
throw caught;
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
};
|
|
3490
|
+
}
|
|
3491
|
+
const contextsMap = contexts?.reduce((acc, curr) => {
|
|
3492
|
+
acc[curr.id] = curr;
|
|
3493
|
+
return acc;
|
|
3494
|
+
}, {});
|
|
3154
3495
|
console.log("[EXULU] Config", config);
|
|
3155
|
-
|
|
3496
|
+
const response = await cur.tool.execute({
|
|
3156
3497
|
...inputs,
|
|
3157
3498
|
// Convert config to object format if a config object
|
|
3158
3499
|
// is available, after we added the .value property
|
|
3159
3500
|
// by hydrating it from the variables table.
|
|
3160
|
-
|
|
3501
|
+
providerapikey,
|
|
3502
|
+
allExuluTools,
|
|
3503
|
+
currentTools,
|
|
3161
3504
|
user,
|
|
3162
|
-
|
|
3505
|
+
contexts: contextsMap,
|
|
3506
|
+
upload,
|
|
3163
3507
|
config: config ? config.config.reduce((acc, curr) => {
|
|
3164
3508
|
acc[curr.name] = curr.value;
|
|
3165
3509
|
return acc;
|
|
3166
3510
|
}, {}) : {}
|
|
3167
3511
|
}, options);
|
|
3512
|
+
yield response;
|
|
3513
|
+
return response;
|
|
3168
3514
|
}
|
|
3169
3515
|
}
|
|
3170
3516
|
}),
|
|
@@ -3199,6 +3545,18 @@ function generateSlug(name) {
|
|
|
3199
3545
|
const slug = lowercase.replace(/[\W_]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3200
3546
|
return slug;
|
|
3201
3547
|
}
|
|
3548
|
+
function errorHandler(error) {
|
|
3549
|
+
if (error == null) {
|
|
3550
|
+
return "unknown error";
|
|
3551
|
+
}
|
|
3552
|
+
if (typeof error === "string") {
|
|
3553
|
+
return error;
|
|
3554
|
+
}
|
|
3555
|
+
if (error instanceof Error) {
|
|
3556
|
+
return error.message;
|
|
3557
|
+
}
|
|
3558
|
+
return JSON.stringify(error);
|
|
3559
|
+
}
|
|
3202
3560
|
var ExuluAgent2 = class {
|
|
3203
3561
|
// Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
|
|
3204
3562
|
// underscores and be a max length of 80 characters and at least 5 characters long.
|
|
@@ -3209,13 +3567,14 @@ var ExuluAgent2 = class {
|
|
|
3209
3567
|
slug = "";
|
|
3210
3568
|
type;
|
|
3211
3569
|
streaming = false;
|
|
3570
|
+
maxContextLength;
|
|
3212
3571
|
rateLimit;
|
|
3213
3572
|
config;
|
|
3214
3573
|
// private memory: Memory | undefined; // TODO do own implementation
|
|
3215
3574
|
evals;
|
|
3216
3575
|
model;
|
|
3217
3576
|
capabilities;
|
|
3218
|
-
constructor({ id, name, description, config, rateLimit, capabilities, type, evals }) {
|
|
3577
|
+
constructor({ id, name, description, config, rateLimit, capabilities, type, evals, maxContextLength }) {
|
|
3219
3578
|
this.id = id;
|
|
3220
3579
|
this.name = name;
|
|
3221
3580
|
this.evals = evals;
|
|
@@ -3223,6 +3582,7 @@ var ExuluAgent2 = class {
|
|
|
3223
3582
|
this.rateLimit = rateLimit;
|
|
3224
3583
|
this.config = config;
|
|
3225
3584
|
this.type = type;
|
|
3585
|
+
this.maxContextLength = maxContextLength;
|
|
3226
3586
|
this.capabilities = capabilities || {
|
|
3227
3587
|
text: false,
|
|
3228
3588
|
images: [],
|
|
@@ -3249,31 +3609,82 @@ var ExuluAgent2 = class {
|
|
|
3249
3609
|
}
|
|
3250
3610
|
// Exports the agent as a tool that can be used by another agent
|
|
3251
3611
|
// todo test this
|
|
3252
|
-
tool = () => {
|
|
3612
|
+
tool = async (instance, agents) => {
|
|
3613
|
+
const agentInstance = await loadAgent(instance);
|
|
3614
|
+
if (!agentInstance) {
|
|
3615
|
+
return null;
|
|
3616
|
+
}
|
|
3253
3617
|
return new ExuluTool2({
|
|
3254
|
-
id:
|
|
3255
|
-
name: `${
|
|
3618
|
+
id: agentInstance.id,
|
|
3619
|
+
name: `${agentInstance.name}`,
|
|
3256
3620
|
type: "agent",
|
|
3257
3621
|
inputSchema: z.object({
|
|
3258
|
-
prompt: z.string()
|
|
3622
|
+
prompt: z.string().describe("The prompt (usually a question for the agent) to send to the agent."),
|
|
3623
|
+
information: z.string().describe("A summary of relevant context / information from the current session")
|
|
3259
3624
|
}),
|
|
3260
|
-
description: `
|
|
3625
|
+
description: `This tool calls an AI agent named: ${agentInstance.name}. The agent does the following: ${agentInstance.description}.`,
|
|
3261
3626
|
config: [],
|
|
3262
|
-
execute: async ({ prompt,
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3627
|
+
execute: async ({ prompt, information, user, allExuluTools }) => {
|
|
3628
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
3629
|
+
if (!hasAccessToAgent) {
|
|
3630
|
+
throw new Error("You don't have access to this agent.");
|
|
3631
|
+
}
|
|
3632
|
+
let enabledTools = await getEnabledTools(agentInstance, allExuluTools, [], agents, user);
|
|
3633
|
+
const variableName = agentInstance.providerapikey;
|
|
3634
|
+
if (!variableName) {
|
|
3635
|
+
throw new Error("Provider API key variable not set for agent: " + agentInstance.name + " (" + agentInstance.id + ") being called as a tool.");
|
|
3636
|
+
}
|
|
3637
|
+
const { db: db3 } = await postgresClient();
|
|
3638
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
3639
|
+
if (!variable) {
|
|
3640
|
+
throw new Error("Provider API key variable not found for agent: " + agentInstance.name + " (" + agentInstance.id + ") being called as a tool.");
|
|
3641
|
+
}
|
|
3642
|
+
let providerapikey = variable.value;
|
|
3643
|
+
if (!variable.encrypted) {
|
|
3644
|
+
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.");
|
|
3645
|
+
}
|
|
3646
|
+
if (variable.encrypted) {
|
|
3647
|
+
const bytes = CryptoJS2.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
3648
|
+
providerapikey = bytes.toString(CryptoJS2.enc.Utf8);
|
|
3649
|
+
}
|
|
3650
|
+
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 + ")"));
|
|
3651
|
+
console.log("[EXULU] Prompt for agent '" + agentInstance.name + "' that is being called as a tool", prompt.slice(0, 100) + "...");
|
|
3652
|
+
console.log("[EXULU] Instructions for agent '" + agentInstance.name + "' that is being called as a tool", agentInstance.instructions?.slice(0, 100) + "...");
|
|
3653
|
+
const response = await this.generateSync({
|
|
3654
|
+
instructions: agentInstance.instructions,
|
|
3655
|
+
prompt: "The user has asked the following question: " + prompt + " and the following information is available: " + information,
|
|
3656
|
+
providerapikey,
|
|
3266
3657
|
user,
|
|
3267
|
-
|
|
3658
|
+
currentTools: enabledTools,
|
|
3659
|
+
allExuluTools,
|
|
3268
3660
|
statistics: {
|
|
3269
|
-
label:
|
|
3661
|
+
label: agentInstance.name,
|
|
3270
3662
|
trigger: "tool"
|
|
3271
3663
|
}
|
|
3272
3664
|
});
|
|
3665
|
+
return {
|
|
3666
|
+
result: response
|
|
3667
|
+
};
|
|
3273
3668
|
}
|
|
3274
3669
|
});
|
|
3275
3670
|
};
|
|
3276
|
-
generateSync = async ({
|
|
3671
|
+
generateSync = async ({
|
|
3672
|
+
prompt,
|
|
3673
|
+
user,
|
|
3674
|
+
session,
|
|
3675
|
+
message,
|
|
3676
|
+
currentTools,
|
|
3677
|
+
allExuluTools,
|
|
3678
|
+
statistics,
|
|
3679
|
+
toolConfigs,
|
|
3680
|
+
providerapikey,
|
|
3681
|
+
contexts,
|
|
3682
|
+
exuluConfig,
|
|
3683
|
+
filesContext: filesContext2,
|
|
3684
|
+
outputSchema,
|
|
3685
|
+
instructions
|
|
3686
|
+
}) => {
|
|
3687
|
+
console.log("[EXULU] Called generate sync for agent: " + this.name, "with prompt: " + prompt?.slice(0, 100) + "...");
|
|
3277
3688
|
if (!this.model) {
|
|
3278
3689
|
throw new Error("Model is required for streaming.");
|
|
3279
3690
|
}
|
|
@@ -3286,14 +3697,18 @@ var ExuluAgent2 = class {
|
|
|
3286
3697
|
if (!prompt && !message) {
|
|
3287
3698
|
throw new Error("Prompt or message is required for generating.");
|
|
3288
3699
|
}
|
|
3700
|
+
if (outputSchema && !prompt) {
|
|
3701
|
+
throw new Error("Prompt is required for generating with an output schema.");
|
|
3702
|
+
}
|
|
3289
3703
|
const model = this.model.create({
|
|
3290
|
-
apiKey:
|
|
3704
|
+
apiKey: providerapikey
|
|
3291
3705
|
});
|
|
3706
|
+
console.log("[EXULU] Model for agent: " + this.name, " created for generating sync.");
|
|
3292
3707
|
let messages = [];
|
|
3293
3708
|
if (message && session && user) {
|
|
3294
3709
|
const previousMessages = await getAgentMessages({
|
|
3295
3710
|
session,
|
|
3296
|
-
user,
|
|
3711
|
+
user: user.id,
|
|
3297
3712
|
limit: 50,
|
|
3298
3713
|
page: 1
|
|
3299
3714
|
});
|
|
@@ -3303,56 +3718,134 @@ var ExuluAgent2 = class {
|
|
|
3303
3718
|
messages: [...previousMessagesContent, message]
|
|
3304
3719
|
});
|
|
3305
3720
|
}
|
|
3306
|
-
console.log("[EXULU]
|
|
3307
|
-
|
|
3721
|
+
console.log("[EXULU] Message count for agent: " + this.name, "loaded for generating sync.", messages.length);
|
|
3722
|
+
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.";
|
|
3723
|
+
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.";
|
|
3724
|
+
system += "\n\n" + genericContext;
|
|
3308
3725
|
if (prompt) {
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3726
|
+
let result = { object: null, text: "" };
|
|
3727
|
+
let tokens = 0;
|
|
3728
|
+
if (outputSchema) {
|
|
3729
|
+
const { object, usage } = await generateObject({
|
|
3730
|
+
model,
|
|
3731
|
+
system,
|
|
3732
|
+
prompt,
|
|
3733
|
+
maxRetries: 3,
|
|
3734
|
+
schema: outputSchema
|
|
3735
|
+
});
|
|
3736
|
+
result.object = object;
|
|
3737
|
+
tokens = usage.totalTokens || 0;
|
|
3738
|
+
} else {
|
|
3739
|
+
console.log("[EXULU] Generating text for agent: " + this.name, "with prompt: " + prompt?.slice(0, 100) + "...");
|
|
3740
|
+
const { text, totalUsage } = await generateText({
|
|
3741
|
+
model,
|
|
3742
|
+
system,
|
|
3743
|
+
prompt,
|
|
3744
|
+
maxRetries: 2,
|
|
3745
|
+
tools: convertToolsArrayToObject(
|
|
3746
|
+
currentTools,
|
|
3747
|
+
allExuluTools,
|
|
3748
|
+
toolConfigs,
|
|
3749
|
+
providerapikey,
|
|
3750
|
+
contexts,
|
|
3751
|
+
user,
|
|
3752
|
+
exuluConfig,
|
|
3753
|
+
filesContext2
|
|
3754
|
+
),
|
|
3755
|
+
stopWhen: [stepCountIs(2)]
|
|
3327
3756
|
});
|
|
3757
|
+
result.text = text;
|
|
3758
|
+
tokens = totalUsage?.totalTokens || 0;
|
|
3328
3759
|
}
|
|
3329
|
-
|
|
3760
|
+
if (statistics) {
|
|
3761
|
+
await Promise.all([
|
|
3762
|
+
updateStatistic({
|
|
3763
|
+
name: "count",
|
|
3764
|
+
label: statistics.label,
|
|
3765
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3766
|
+
trigger: statistics.trigger,
|
|
3767
|
+
count: 1,
|
|
3768
|
+
user: user?.id,
|
|
3769
|
+
role: user?.role?.id
|
|
3770
|
+
}),
|
|
3771
|
+
...tokens ? [
|
|
3772
|
+
updateStatistic({
|
|
3773
|
+
name: "tokens",
|
|
3774
|
+
label: statistics.label,
|
|
3775
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3776
|
+
trigger: statistics.trigger,
|
|
3777
|
+
count: tokens
|
|
3778
|
+
})
|
|
3779
|
+
] : []
|
|
3780
|
+
]);
|
|
3781
|
+
}
|
|
3782
|
+
return result.text || result.object;
|
|
3330
3783
|
}
|
|
3331
3784
|
if (messages) {
|
|
3332
|
-
|
|
3785
|
+
console.log("[EXULU] Generating text for agent: " + this.name, "with messages: " + messages.length);
|
|
3786
|
+
const { text, totalUsage } = await generateText({
|
|
3333
3787
|
model,
|
|
3334
3788
|
// Should be a LanguageModelV1
|
|
3335
|
-
system
|
|
3336
|
-
messages: convertToModelMessages(messages
|
|
3789
|
+
system,
|
|
3790
|
+
messages: convertToModelMessages(messages, {
|
|
3791
|
+
ignoreIncompleteToolCalls: true
|
|
3792
|
+
}),
|
|
3337
3793
|
maxRetries: 2,
|
|
3338
|
-
tools: convertToolsArrayToObject(
|
|
3339
|
-
|
|
3794
|
+
tools: convertToolsArrayToObject(
|
|
3795
|
+
currentTools,
|
|
3796
|
+
allExuluTools,
|
|
3797
|
+
toolConfigs,
|
|
3798
|
+
providerapikey,
|
|
3799
|
+
contexts,
|
|
3800
|
+
user,
|
|
3801
|
+
exuluConfig,
|
|
3802
|
+
filesContext2
|
|
3803
|
+
),
|
|
3804
|
+
stopWhen: [stepCountIs(2)]
|
|
3340
3805
|
});
|
|
3341
3806
|
if (statistics) {
|
|
3342
|
-
await
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3807
|
+
await Promise.all([
|
|
3808
|
+
updateStatistic({
|
|
3809
|
+
name: "count",
|
|
3810
|
+
label: statistics.label,
|
|
3811
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3812
|
+
trigger: statistics.trigger,
|
|
3813
|
+
count: 1,
|
|
3814
|
+
user: user?.id,
|
|
3815
|
+
role: user?.role?.id
|
|
3816
|
+
}),
|
|
3817
|
+
...totalUsage?.totalTokens ? [
|
|
3818
|
+
updateStatistic({
|
|
3819
|
+
name: "tokens",
|
|
3820
|
+
label: statistics.label,
|
|
3821
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3822
|
+
trigger: statistics.trigger,
|
|
3823
|
+
count: totalUsage?.totalTokens,
|
|
3824
|
+
user: user?.id,
|
|
3825
|
+
role: user?.role?.id
|
|
3826
|
+
})
|
|
3827
|
+
] : []
|
|
3828
|
+
]);
|
|
3351
3829
|
}
|
|
3352
3830
|
return text;
|
|
3353
3831
|
}
|
|
3832
|
+
return "";
|
|
3354
3833
|
};
|
|
3355
|
-
generateStream = async ({
|
|
3834
|
+
generateStream = async ({
|
|
3835
|
+
express: express3,
|
|
3836
|
+
user,
|
|
3837
|
+
session,
|
|
3838
|
+
message,
|
|
3839
|
+
currentTools,
|
|
3840
|
+
allExuluTools,
|
|
3841
|
+
statistics,
|
|
3842
|
+
toolConfigs,
|
|
3843
|
+
providerapikey,
|
|
3844
|
+
contexts,
|
|
3845
|
+
exuluConfig,
|
|
3846
|
+
filesContext: filesContext2,
|
|
3847
|
+
instructions
|
|
3848
|
+
}) => {
|
|
3356
3849
|
if (!this.model) {
|
|
3357
3850
|
throw new Error("Model is required for streaming.");
|
|
3358
3851
|
}
|
|
@@ -3363,60 +3856,110 @@ var ExuluAgent2 = class {
|
|
|
3363
3856
|
throw new Error("Message is required for streaming.");
|
|
3364
3857
|
}
|
|
3365
3858
|
const model = this.model.create({
|
|
3366
|
-
apiKey:
|
|
3859
|
+
apiKey: providerapikey
|
|
3367
3860
|
});
|
|
3368
3861
|
let messages = [];
|
|
3369
3862
|
const previousMessages = await getAgentMessages({
|
|
3370
3863
|
session,
|
|
3371
|
-
user,
|
|
3864
|
+
user: user.id,
|
|
3372
3865
|
limit: 50,
|
|
3373
3866
|
page: 1
|
|
3374
3867
|
});
|
|
3375
|
-
const previousMessagesContent = previousMessages.map(
|
|
3868
|
+
const previousMessagesContent = previousMessages.map(
|
|
3869
|
+
(message2) => JSON.parse(message2.content)
|
|
3870
|
+
);
|
|
3376
3871
|
messages = await validateUIMessages({
|
|
3377
3872
|
// append the new message to the previous messages:
|
|
3378
3873
|
messages: [...previousMessagesContent, message]
|
|
3379
3874
|
});
|
|
3875
|
+
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.";
|
|
3876
|
+
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.";
|
|
3877
|
+
system += "\n\n" + genericContext;
|
|
3878
|
+
console.log("[EXULU] tools for agent: " + this.name, currentTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
3879
|
+
console.log("[EXULU] system", system.slice(0, 100) + "...");
|
|
3380
3880
|
const result = streamText({
|
|
3381
3881
|
model,
|
|
3382
3882
|
// Should be a LanguageModelV1
|
|
3383
|
-
messages: convertToModelMessages(messages
|
|
3384
|
-
|
|
3883
|
+
messages: convertToModelMessages(messages, {
|
|
3884
|
+
ignoreIncompleteToolCalls: true
|
|
3885
|
+
}),
|
|
3886
|
+
// prepareStep could be used here to set the model for the first step or change other params
|
|
3887
|
+
system,
|
|
3385
3888
|
maxRetries: 2,
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3889
|
+
providerOptions: {
|
|
3890
|
+
openai: {
|
|
3891
|
+
reasoningSummary: "auto"
|
|
3892
|
+
}
|
|
3893
|
+
},
|
|
3894
|
+
tools: convertToolsArrayToObject(
|
|
3895
|
+
currentTools,
|
|
3896
|
+
allExuluTools,
|
|
3897
|
+
toolConfigs,
|
|
3898
|
+
providerapikey,
|
|
3899
|
+
contexts,
|
|
3900
|
+
user,
|
|
3901
|
+
exuluConfig,
|
|
3902
|
+
filesContext2
|
|
3903
|
+
),
|
|
3904
|
+
onError: (error) => console.error("[EXULU] chat stream error.", error)
|
|
3905
|
+
// stopWhen: [stepCountIs(1)],
|
|
3389
3906
|
});
|
|
3390
3907
|
result.consumeStream();
|
|
3391
3908
|
result.pipeUIMessageStreamToResponse(express3.res, {
|
|
3909
|
+
messageMetadata: ({ part }) => {
|
|
3910
|
+
if (part.type === "finish") {
|
|
3911
|
+
return {
|
|
3912
|
+
totalTokens: part.totalUsage.totalTokens,
|
|
3913
|
+
reasoningTokens: part.totalUsage.reasoningTokens,
|
|
3914
|
+
inputTokens: part.totalUsage.inputTokens,
|
|
3915
|
+
outputTokens: part.totalUsage.outputTokens,
|
|
3916
|
+
cachedInputTokens: part.totalUsage.cachedInputTokens
|
|
3917
|
+
};
|
|
3918
|
+
}
|
|
3919
|
+
},
|
|
3392
3920
|
originalMessages: messages,
|
|
3393
3921
|
sendReasoning: true,
|
|
3922
|
+
sendSources: true,
|
|
3923
|
+
onError: (error) => {
|
|
3924
|
+
console.error("[EXULU] chat response error.", error);
|
|
3925
|
+
return errorHandler(error);
|
|
3926
|
+
},
|
|
3394
3927
|
generateMessageId: createIdGenerator({
|
|
3395
3928
|
prefix: "msg_",
|
|
3396
3929
|
size: 16
|
|
3397
3930
|
}),
|
|
3398
|
-
onFinish: async ({ messages: messages2 }) => {
|
|
3399
|
-
console.info(
|
|
3400
|
-
"[EXULU] chat stream finished.",
|
|
3401
|
-
messages2
|
|
3402
|
-
);
|
|
3931
|
+
onFinish: async ({ messages: messages2, isContinuation, isAborted, responseMessage }) => {
|
|
3403
3932
|
if (session) {
|
|
3404
3933
|
await saveChat({
|
|
3405
3934
|
session,
|
|
3406
|
-
user,
|
|
3407
|
-
messages: messages2
|
|
3935
|
+
user: user.id,
|
|
3936
|
+
messages: messages2.filter((x) => !previousMessagesContent.find((y) => y.id === x.id))
|
|
3408
3937
|
});
|
|
3409
3938
|
}
|
|
3939
|
+
const metadata = messages2[messages2.length - 1]?.metadata;
|
|
3410
3940
|
if (statistics) {
|
|
3411
|
-
await
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3941
|
+
await Promise.all([
|
|
3942
|
+
updateStatistic({
|
|
3943
|
+
name: "count",
|
|
3944
|
+
label: statistics.label,
|
|
3945
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3946
|
+
trigger: statistics.trigger,
|
|
3947
|
+
count: 1,
|
|
3948
|
+
user: user.id,
|
|
3949
|
+
role: user?.role?.id
|
|
3950
|
+
}),
|
|
3951
|
+
...metadata?.totalTokens ? [
|
|
3952
|
+
updateStatistic({
|
|
3953
|
+
name: "tokens",
|
|
3954
|
+
label: statistics.label,
|
|
3955
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
3956
|
+
trigger: statistics.trigger,
|
|
3957
|
+
count: metadata?.totalTokens,
|
|
3958
|
+
user: user.id,
|
|
3959
|
+
role: user?.role?.id
|
|
3960
|
+
})
|
|
3961
|
+
] : []
|
|
3962
|
+
]);
|
|
3420
3963
|
}
|
|
3421
3964
|
}
|
|
3422
3965
|
});
|
|
@@ -3425,7 +3968,12 @@ var ExuluAgent2 = class {
|
|
|
3425
3968
|
};
|
|
3426
3969
|
var getAgentMessages = async ({ session, user, limit, page }) => {
|
|
3427
3970
|
const { db: db3 } = await postgresClient();
|
|
3428
|
-
|
|
3971
|
+
console.log("[EXULU] getting agent messages for session: " + session + " and user: " + user + " and page: " + page);
|
|
3972
|
+
const query = db3.from("agent_messages").where({ session, user }).limit(limit);
|
|
3973
|
+
if (page > 0) {
|
|
3974
|
+
query.offset((page - 1) * limit);
|
|
3975
|
+
}
|
|
3976
|
+
const messages = await query;
|
|
3429
3977
|
return messages;
|
|
3430
3978
|
};
|
|
3431
3979
|
var saveChat = async ({ session, user, messages }) => {
|
|
@@ -3535,22 +4083,22 @@ var ExuluEval = class {
|
|
|
3535
4083
|
throw new Error("Prompt is required for running an agent.");
|
|
3536
4084
|
}
|
|
3537
4085
|
const { db: db4 } = await postgresClient();
|
|
3538
|
-
const variableName = runner.agent.
|
|
4086
|
+
const variableName = runner.agent.providerapikey;
|
|
3539
4087
|
const variable = await db4.from("variables").where({ name: variableName }).first();
|
|
3540
4088
|
if (!variable) {
|
|
3541
|
-
throw new Error(`Provider API key for variable "${runner.agent.
|
|
4089
|
+
throw new Error(`Provider API key for variable "${runner.agent.providerapikey}" not found.`);
|
|
3542
4090
|
}
|
|
3543
|
-
let
|
|
4091
|
+
let providerapikey = variable.value;
|
|
3544
4092
|
if (!variable.encrypted) {
|
|
3545
|
-
throw new Error(`Provider API key for variable "${runner.agent.
|
|
4093
|
+
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.`);
|
|
3546
4094
|
}
|
|
3547
4095
|
if (variable.encrypted) {
|
|
3548
4096
|
const bytes = CryptoJS2.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
3549
|
-
|
|
4097
|
+
providerapikey = bytes.toString(CryptoJS2.enc.Utf8);
|
|
3550
4098
|
}
|
|
3551
4099
|
const result = await runner.agent.generateSync({
|
|
3552
4100
|
prompt: data.prompt,
|
|
3553
|
-
|
|
4101
|
+
providerapikey
|
|
3554
4102
|
});
|
|
3555
4103
|
data.result = result;
|
|
3556
4104
|
}
|
|
@@ -3709,10 +4257,6 @@ var ExuluContext = class {
|
|
|
3709
4257
|
label: statistics?.label || this.name,
|
|
3710
4258
|
trigger: statistics?.trigger || "agent"
|
|
3711
4259
|
}, user, role);
|
|
3712
|
-
const exists = await db3.schema.hasTable(getChunksTableName(this.id));
|
|
3713
|
-
if (!exists) {
|
|
3714
|
-
await this.createChunksTable();
|
|
3715
|
-
}
|
|
3716
4260
|
await db3.from(getChunksTableName(this.id)).where({ source }).delete();
|
|
3717
4261
|
await db3.from(getChunksTableName(this.id)).insert(chunks.map((chunk) => ({
|
|
3718
4262
|
source,
|
|
@@ -3729,21 +4273,115 @@ var ExuluContext = class {
|
|
|
3729
4273
|
job
|
|
3730
4274
|
};
|
|
3731
4275
|
};
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
4276
|
+
createItem = async (item, user, role, upsert) => {
|
|
4277
|
+
const { db: db3 } = await postgresClient();
|
|
4278
|
+
const mutation = db3.from(getTableName(
|
|
4279
|
+
this.id
|
|
4280
|
+
)).insert(
|
|
4281
|
+
{
|
|
4282
|
+
...item,
|
|
4283
|
+
tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
|
|
4284
|
+
}
|
|
4285
|
+
).returning("id");
|
|
4286
|
+
if (upsert) {
|
|
4287
|
+
mutation.onConflict().merge();
|
|
4288
|
+
}
|
|
4289
|
+
const results = await mutation;
|
|
4290
|
+
if (!results[0]) {
|
|
4291
|
+
throw new Error("Failed to create item.");
|
|
4292
|
+
}
|
|
4293
|
+
if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
|
|
4294
|
+
const { job } = await this.embeddings.generate.one({
|
|
4295
|
+
item: results[0],
|
|
3736
4296
|
user,
|
|
3737
4297
|
role,
|
|
3738
|
-
trigger
|
|
3739
|
-
})
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
4298
|
+
trigger: "api"
|
|
4299
|
+
});
|
|
4300
|
+
return {
|
|
4301
|
+
item: results[0],
|
|
4302
|
+
job
|
|
4303
|
+
};
|
|
4304
|
+
}
|
|
4305
|
+
return {
|
|
4306
|
+
item: results[0],
|
|
4307
|
+
job: void 0
|
|
4308
|
+
};
|
|
4309
|
+
};
|
|
4310
|
+
updateItem = async (item, user, role) => {
|
|
4311
|
+
const { db: db3 } = await postgresClient();
|
|
4312
|
+
const record = await db3.from(
|
|
4313
|
+
getTableName(this.id)
|
|
4314
|
+
).where(
|
|
4315
|
+
{ id: item.id }
|
|
4316
|
+
).first();
|
|
4317
|
+
if (!record) {
|
|
4318
|
+
throw new Error("Item not found.");
|
|
4319
|
+
}
|
|
4320
|
+
const mutation = db3.from(
|
|
4321
|
+
getTableName(this.id)
|
|
4322
|
+
).where(
|
|
4323
|
+
{ id: record.id }
|
|
4324
|
+
).update(
|
|
4325
|
+
{
|
|
4326
|
+
...item,
|
|
4327
|
+
tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
|
|
4328
|
+
}
|
|
4329
|
+
).returning("id");
|
|
4330
|
+
await mutation;
|
|
4331
|
+
if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
|
|
4332
|
+
const { job } = await this.embeddings.generate.one({
|
|
4333
|
+
item: record,
|
|
4334
|
+
// important we need to full record here with all fields
|
|
4335
|
+
user,
|
|
4336
|
+
role,
|
|
4337
|
+
trigger: "api"
|
|
4338
|
+
});
|
|
4339
|
+
return {
|
|
4340
|
+
item: record,
|
|
4341
|
+
job
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
return {
|
|
4345
|
+
item: record,
|
|
4346
|
+
job: void 0
|
|
4347
|
+
};
|
|
4348
|
+
};
|
|
4349
|
+
deleteItem = async (item, user, role) => {
|
|
4350
|
+
if (!item.id) {
|
|
4351
|
+
throw new Error("Item id is required for deleting item.");
|
|
4352
|
+
}
|
|
4353
|
+
const { db: db3 } = await postgresClient();
|
|
4354
|
+
await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
|
|
4355
|
+
if (!this.embedder) {
|
|
4356
|
+
return {
|
|
4357
|
+
id: item.id,
|
|
4358
|
+
job: void 0
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
const chunks = await db3.from(getChunksTableName(this.id)).where({ source: item.id }).select("id");
|
|
4362
|
+
if (chunks.length > 0) {
|
|
4363
|
+
await db3.from(getChunksTableName(this.id)).where({ source: item.id }).delete();
|
|
4364
|
+
}
|
|
4365
|
+
return {
|
|
4366
|
+
id: item.id,
|
|
4367
|
+
job: void 0
|
|
4368
|
+
};
|
|
4369
|
+
};
|
|
4370
|
+
embeddings = {
|
|
4371
|
+
generate: {
|
|
4372
|
+
one: async ({
|
|
4373
|
+
item,
|
|
4374
|
+
user,
|
|
4375
|
+
role,
|
|
4376
|
+
trigger
|
|
4377
|
+
}) => {
|
|
4378
|
+
console.log("[EXULU] Generating embeddings for item", item.id);
|
|
4379
|
+
if (!this.embedder) {
|
|
4380
|
+
throw new Error("Embedder is not set for this context.");
|
|
4381
|
+
}
|
|
4382
|
+
if (!item.id) {
|
|
4383
|
+
throw new Error("Item id is required for generating embeddings.");
|
|
4384
|
+
}
|
|
3747
4385
|
if (this.embedder.queue?.name) {
|
|
3748
4386
|
console.log("[EXULU] embedder is in queue mode, scheduling job.");
|
|
3749
4387
|
const job = await bullmqDecorator({
|
|
@@ -3796,301 +4434,6 @@ var ExuluContext = class {
|
|
|
3796
4434
|
}
|
|
3797
4435
|
}
|
|
3798
4436
|
};
|
|
3799
|
-
getItems = async ({
|
|
3800
|
-
statistics,
|
|
3801
|
-
limit,
|
|
3802
|
-
sort,
|
|
3803
|
-
order,
|
|
3804
|
-
page,
|
|
3805
|
-
name,
|
|
3806
|
-
user,
|
|
3807
|
-
role,
|
|
3808
|
-
archived,
|
|
3809
|
-
query,
|
|
3810
|
-
method
|
|
3811
|
-
}) => {
|
|
3812
|
-
if (!query && limit > 500) {
|
|
3813
|
-
throw new Error("Limit cannot be greater than 500.");
|
|
3814
|
-
}
|
|
3815
|
-
if (query && limit > 50) {
|
|
3816
|
-
throw new Error("Limit cannot be greater than 50 when using a vector search query.");
|
|
3817
|
-
}
|
|
3818
|
-
if (page < 1) page = 1;
|
|
3819
|
-
if (limit < 1) limit = 10;
|
|
3820
|
-
let offset = (page - 1) * limit;
|
|
3821
|
-
const mainTable = getTableName(this.id);
|
|
3822
|
-
const { db: db3 } = await postgresClient();
|
|
3823
|
-
const columns = await db3(mainTable).columnInfo();
|
|
3824
|
-
const totalQuery = db3.count("* as count").from(mainTable).first();
|
|
3825
|
-
const itemsQuery = db3.select(Object.keys(columns).map((column) => mainTable + "." + column)).from(mainTable).offset(offset).limit(limit);
|
|
3826
|
-
if (sort) {
|
|
3827
|
-
itemsQuery.orderBy(sort, order === "desc" ? "desc" : "asc");
|
|
3828
|
-
}
|
|
3829
|
-
if (typeof name === "string") {
|
|
3830
|
-
itemsQuery.whereILike("name", `%${name}%`);
|
|
3831
|
-
totalQuery.whereILike("name", `%${name}%`);
|
|
3832
|
-
}
|
|
3833
|
-
if (typeof archived === "boolean") {
|
|
3834
|
-
itemsQuery.where("archived", archived);
|
|
3835
|
-
totalQuery.where("archived", archived);
|
|
3836
|
-
}
|
|
3837
|
-
if (!query) {
|
|
3838
|
-
const total = await totalQuery;
|
|
3839
|
-
let items = await itemsQuery;
|
|
3840
|
-
const last = Math.ceil(total.count / limit);
|
|
3841
|
-
return {
|
|
3842
|
-
pagination: {
|
|
3843
|
-
totalCount: parseInt(total.count),
|
|
3844
|
-
currentPage: page,
|
|
3845
|
-
limit,
|
|
3846
|
-
from: offset,
|
|
3847
|
-
pageCount: last || 1,
|
|
3848
|
-
to: offset + items.length,
|
|
3849
|
-
lastPage: last || 1,
|
|
3850
|
-
nextPage: page + 1 > last ? null : page + 1,
|
|
3851
|
-
previousPage: page - 1 || null
|
|
3852
|
-
},
|
|
3853
|
-
filters: {
|
|
3854
|
-
archived,
|
|
3855
|
-
name,
|
|
3856
|
-
query
|
|
3857
|
-
},
|
|
3858
|
-
context: {
|
|
3859
|
-
name: this.name,
|
|
3860
|
-
id: this.id,
|
|
3861
|
-
embedder: this.embedder?.name || void 0
|
|
3862
|
-
},
|
|
3863
|
-
items
|
|
3864
|
-
};
|
|
3865
|
-
}
|
|
3866
|
-
if (typeof query === "string" && this.embedder) {
|
|
3867
|
-
if (!method) {
|
|
3868
|
-
method = "cosineDistance";
|
|
3869
|
-
}
|
|
3870
|
-
itemsQuery.limit(limit * 5);
|
|
3871
|
-
if (statistics) {
|
|
3872
|
-
await updateStatistic({
|
|
3873
|
-
name: "count",
|
|
3874
|
-
label: statistics.label,
|
|
3875
|
-
type: STATISTICS_TYPE_ENUM.CONTEXT_RETRIEVE,
|
|
3876
|
-
trigger: statistics.trigger,
|
|
3877
|
-
user,
|
|
3878
|
-
role
|
|
3879
|
-
});
|
|
3880
|
-
}
|
|
3881
|
-
if (this.queryRewriter) {
|
|
3882
|
-
query = await this.queryRewriter(query);
|
|
3883
|
-
}
|
|
3884
|
-
const chunksTable = getChunksTableName(this.id);
|
|
3885
|
-
itemsQuery.leftJoin(chunksTable, function() {
|
|
3886
|
-
this.on(chunksTable + ".source", "=", mainTable + ".id");
|
|
3887
|
-
});
|
|
3888
|
-
itemsQuery.select(chunksTable + ".id as chunk_id");
|
|
3889
|
-
itemsQuery.select(chunksTable + ".source");
|
|
3890
|
-
itemsQuery.select(chunksTable + ".content");
|
|
3891
|
-
itemsQuery.select(chunksTable + ".chunk_index");
|
|
3892
|
-
itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
|
|
3893
|
-
itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
|
|
3894
|
-
const { chunks } = await this.embedder.generateFromQuery(query, {
|
|
3895
|
-
label: this.name,
|
|
3896
|
-
trigger: "agent"
|
|
3897
|
-
}, user, role);
|
|
3898
|
-
if (!chunks?.[0]?.vector) {
|
|
3899
|
-
throw new Error("No vector generated for query.");
|
|
3900
|
-
}
|
|
3901
|
-
const vector = chunks[0].vector;
|
|
3902
|
-
const vectorStr = `ARRAY[${vector.join(",")}]`;
|
|
3903
|
-
const vectorExpr = `${vectorStr}::vector`;
|
|
3904
|
-
const language = this.configuration.language || "english";
|
|
3905
|
-
let items = [];
|
|
3906
|
-
switch (method) {
|
|
3907
|
-
case "tsvector":
|
|
3908
|
-
itemsQuery.select(db3.raw(
|
|
3909
|
-
`ts_rank(${chunksTable}.fts, websearch_to_tsquery(?, ?)) as fts_rank`,
|
|
3910
|
-
[language, query]
|
|
3911
|
-
)).whereRaw(
|
|
3912
|
-
`${chunksTable}.fts @@ websearch_to_tsquery(?, ?)`,
|
|
3913
|
-
[language, query]
|
|
3914
|
-
).orderByRaw(`fts_rank DESC`);
|
|
3915
|
-
items = await itemsQuery;
|
|
3916
|
-
break;
|
|
3917
|
-
case "cosineDistance":
|
|
3918
|
-
default:
|
|
3919
|
-
itemsQuery.whereNotNull(`${chunksTable}.embedding`);
|
|
3920
|
-
itemsQuery.select(
|
|
3921
|
-
db3.raw(`1 - (${chunksTable}.embedding <=> ${vectorExpr}) AS cosine_distance`)
|
|
3922
|
-
);
|
|
3923
|
-
itemsQuery.orderByRaw(
|
|
3924
|
-
`${chunksTable}.embedding <=> ${vectorExpr} ASC NULLS LAST`
|
|
3925
|
-
);
|
|
3926
|
-
items = await itemsQuery;
|
|
3927
|
-
break;
|
|
3928
|
-
case "hybridSearch":
|
|
3929
|
-
const matchCount = Math.min(limit * 5, 30);
|
|
3930
|
-
const fullTextWeight = 1;
|
|
3931
|
-
const semanticWeight = 1;
|
|
3932
|
-
const rrfK = 50;
|
|
3933
|
-
const hybridSQL = `
|
|
3934
|
-
WITH full_text AS (
|
|
3935
|
-
SELECT
|
|
3936
|
-
c.id,
|
|
3937
|
-
c.source,
|
|
3938
|
-
row_number() OVER (
|
|
3939
|
-
ORDER BY ts_rank_cd(c.fts, websearch_to_tsquery(?, ?)) DESC
|
|
3940
|
-
) AS rank_ix
|
|
3941
|
-
FROM ${chunksTable} c
|
|
3942
|
-
WHERE c.fts @@ websearch_to_tsquery(?, ?)
|
|
3943
|
-
ORDER BY rank_ix
|
|
3944
|
-
LIMIT LEAST(?, 30) * 2
|
|
3945
|
-
),
|
|
3946
|
-
semantic AS (
|
|
3947
|
-
SELECT
|
|
3948
|
-
c.id,
|
|
3949
|
-
c.source,
|
|
3950
|
-
row_number() OVER (
|
|
3951
|
-
ORDER BY c.embedding <=> ${vectorExpr} ASC
|
|
3952
|
-
) AS rank_ix
|
|
3953
|
-
FROM ${chunksTable} c
|
|
3954
|
-
WHERE c.embedding IS NOT NULL
|
|
3955
|
-
ORDER BY rank_ix
|
|
3956
|
-
LIMIT LEAST(?, 30) * 2
|
|
3957
|
-
)
|
|
3958
|
-
SELECT
|
|
3959
|
-
m.*,
|
|
3960
|
-
c.id AS chunk_id,
|
|
3961
|
-
c.source,
|
|
3962
|
-
c.content,
|
|
3963
|
-
c.chunk_index,
|
|
3964
|
-
c.created_at AS chunk_created_at,
|
|
3965
|
-
c.updated_at AS chunk_updated_at,
|
|
3966
|
-
|
|
3967
|
-
/* Per-signal scores for introspection */
|
|
3968
|
-
ts_rank(c.fts, websearch_to_tsquery(?, ?)) AS fts_rank,
|
|
3969
|
-
(1 - (c.embedding <=> ${vectorExpr})) AS cosine_distance,
|
|
3970
|
-
|
|
3971
|
-
/* Hybrid RRF score */
|
|
3972
|
-
(
|
|
3973
|
-
COALESCE(1.0 / (? + ft.rank_ix), 0.0) * ?
|
|
3974
|
-
+
|
|
3975
|
-
COALESCE(1.0 / (? + se.rank_ix), 0.0) * ?
|
|
3976
|
-
)::float AS hybrid_score
|
|
3977
|
-
|
|
3978
|
-
FROM full_text ft
|
|
3979
|
-
FULL OUTER JOIN semantic se
|
|
3980
|
-
ON ft.id = se.id
|
|
3981
|
-
JOIN ${chunksTable} c
|
|
3982
|
-
ON COALESCE(ft.id, se.id) = c.id
|
|
3983
|
-
JOIN ${mainTable} m
|
|
3984
|
-
ON m.id = c.source
|
|
3985
|
-
ORDER BY hybrid_score DESC
|
|
3986
|
-
LIMIT LEAST(?, 30)
|
|
3987
|
-
OFFSET 0
|
|
3988
|
-
`;
|
|
3989
|
-
const bindings = [
|
|
3990
|
-
// full_text: websearch_to_tsquery(lang, query) in rank and where
|
|
3991
|
-
language,
|
|
3992
|
-
query,
|
|
3993
|
-
language,
|
|
3994
|
-
query,
|
|
3995
|
-
matchCount,
|
|
3996
|
-
// full_text limit
|
|
3997
|
-
matchCount,
|
|
3998
|
-
// semantic limit
|
|
3999
|
-
// fts_rank (ts_rank) call
|
|
4000
|
-
language,
|
|
4001
|
-
query,
|
|
4002
|
-
// RRF fusion parameters
|
|
4003
|
-
rrfK,
|
|
4004
|
-
fullTextWeight,
|
|
4005
|
-
rrfK,
|
|
4006
|
-
semanticWeight,
|
|
4007
|
-
matchCount
|
|
4008
|
-
// final limit
|
|
4009
|
-
];
|
|
4010
|
-
items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
|
|
4011
|
-
}
|
|
4012
|
-
console.log("items", items);
|
|
4013
|
-
const seenSources = /* @__PURE__ */ new Map();
|
|
4014
|
-
items = items.reduce((acc, item) => {
|
|
4015
|
-
if (!seenSources.has(item.source)) {
|
|
4016
|
-
seenSources.set(item.source, {
|
|
4017
|
-
...Object.fromEntries(
|
|
4018
|
-
Object.keys(item).filter(
|
|
4019
|
-
(key) => key !== "cosine_distance" && // kept per chunk below
|
|
4020
|
-
key !== "fts_rank" && // kept per chunk below
|
|
4021
|
-
key !== "hybrid_score" && // we will compute per item below
|
|
4022
|
-
key !== "content" && key !== "source" && key !== "chunk_index" && key !== "chunk_id" && key !== "chunk_created_at" && key !== "chunk_updated_at"
|
|
4023
|
-
).map((key) => [key, item[key]])
|
|
4024
|
-
),
|
|
4025
|
-
chunks: [{
|
|
4026
|
-
content: item.content,
|
|
4027
|
-
chunk_index: item.chunk_index,
|
|
4028
|
-
...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
|
|
4029
|
-
...(method === "tsvector" || method === "hybridSearch") && { fts_rank: item.fts_rank },
|
|
4030
|
-
...method === "hybridSearch" && { hybrid_score: item.hybrid_score }
|
|
4031
|
-
}]
|
|
4032
|
-
});
|
|
4033
|
-
acc.push(seenSources.get(item.source));
|
|
4034
|
-
} else {
|
|
4035
|
-
seenSources.get(item.source).chunks.push({
|
|
4036
|
-
content: item.content,
|
|
4037
|
-
chunk_index: item.chunk_index,
|
|
4038
|
-
...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
|
|
4039
|
-
...(method === "tsvector" || method === "hybridSearch") && { fts_rank: item.fts_rank },
|
|
4040
|
-
...method === "hybridSearch" && { hybrid_score: item.hybrid_score }
|
|
4041
|
-
});
|
|
4042
|
-
}
|
|
4043
|
-
return acc;
|
|
4044
|
-
}, []);
|
|
4045
|
-
console.log("items", items);
|
|
4046
|
-
items.forEach((item) => {
|
|
4047
|
-
if (!item.chunks?.length) {
|
|
4048
|
-
return;
|
|
4049
|
-
}
|
|
4050
|
-
if (method === "tsvector") {
|
|
4051
|
-
const ranks = item.chunks.map((c) => typeof c.fts_rank === "number" ? c.fts_rank : 0);
|
|
4052
|
-
const total = ranks.reduce((a, b) => a + b, 0);
|
|
4053
|
-
const average = ranks.length ? total / ranks.length : 0;
|
|
4054
|
-
item.averageRelevance = average;
|
|
4055
|
-
item.totalRelevance = total;
|
|
4056
|
-
} else if (method === "cosineDistance") {
|
|
4057
|
-
let methodProperty = "cosine_distance";
|
|
4058
|
-
const average = item.chunks.reduce((acc, item2) => {
|
|
4059
|
-
return acc + item2[methodProperty];
|
|
4060
|
-
}, 0) / item.chunks.length;
|
|
4061
|
-
const total = item.chunks.reduce((acc, item2) => {
|
|
4062
|
-
return acc + item2[methodProperty];
|
|
4063
|
-
}, 0);
|
|
4064
|
-
item.averageRelevance = average;
|
|
4065
|
-
item.totalRelevance = total;
|
|
4066
|
-
} else if (method === "hybridSearch") {
|
|
4067
|
-
console.log("item.chunks", item.chunks);
|
|
4068
|
-
const scores = item.chunks.map((c) => typeof c.hybrid_score === "number" ? c.hybrid_score * 10 + 1 : 0);
|
|
4069
|
-
const total = scores.reduce((a, b) => a + b, 0);
|
|
4070
|
-
const average = scores.length ? total / scores.length : 0;
|
|
4071
|
-
item.averageRelevance = average;
|
|
4072
|
-
item.totalRelevance = total;
|
|
4073
|
-
}
|
|
4074
|
-
});
|
|
4075
|
-
if (this.resultReranker && query) {
|
|
4076
|
-
items = await this.resultReranker(items);
|
|
4077
|
-
}
|
|
4078
|
-
items = items.slice(0, limit);
|
|
4079
|
-
return {
|
|
4080
|
-
filters: {
|
|
4081
|
-
archived,
|
|
4082
|
-
name,
|
|
4083
|
-
query
|
|
4084
|
-
},
|
|
4085
|
-
context: {
|
|
4086
|
-
name: this.name,
|
|
4087
|
-
id: this.id,
|
|
4088
|
-
embedder: this.embedder.name
|
|
4089
|
-
},
|
|
4090
|
-
items
|
|
4091
|
-
};
|
|
4092
|
-
}
|
|
4093
|
-
};
|
|
4094
4437
|
createItemsTable = async () => {
|
|
4095
4438
|
const { db: db3 } = await postgresClient();
|
|
4096
4439
|
const tableName = getTableName(this.id);
|
|
@@ -4104,6 +4447,7 @@ var ExuluContext = class {
|
|
|
4104
4447
|
table.boolean("archived").defaultTo(false);
|
|
4105
4448
|
table.text("external_id");
|
|
4106
4449
|
table.text("created_by");
|
|
4450
|
+
table.text("ttl");
|
|
4107
4451
|
table.text("rights_mode").defaultTo(this.configuration?.defaultRightsMode ?? "private");
|
|
4108
4452
|
table.integer("textlength");
|
|
4109
4453
|
table.text("source");
|
|
@@ -4121,7 +4465,7 @@ var ExuluContext = class {
|
|
|
4121
4465
|
});
|
|
4122
4466
|
};
|
|
4123
4467
|
createChunksTable = async () => {
|
|
4124
|
-
const { db: db3 } = await
|
|
4468
|
+
const { db: db3 } = await refreshPostgresClient();
|
|
4125
4469
|
const tableName = getChunksTableName(this.id);
|
|
4126
4470
|
console.log("[EXULU] Creating table: " + tableName);
|
|
4127
4471
|
await db3.schema.createTable(tableName, (table) => {
|
|
@@ -4146,8 +4490,8 @@ var ExuluContext = class {
|
|
|
4146
4490
|
CREATE INDEX IF NOT EXISTS ${tableName}_embedding_hnsw_cosine
|
|
4147
4491
|
ON ${tableName}
|
|
4148
4492
|
USING hnsw (embedding vector_cosine_ops)
|
|
4149
|
-
WHERE embedding IS NOT NULL
|
|
4150
4493
|
WITH (m = 16, ef_construction = 64)
|
|
4494
|
+
WHERE embedding IS NOT NULL
|
|
4151
4495
|
`);
|
|
4152
4496
|
return;
|
|
4153
4497
|
};
|
|
@@ -4164,7 +4508,7 @@ var ExuluContext = class {
|
|
|
4164
4508
|
description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
|
|
4165
4509
|
execute: async ({ query, user, role }) => {
|
|
4166
4510
|
const { db: db3 } = await postgresClient();
|
|
4167
|
-
await vectorSearch({
|
|
4511
|
+
const result = await vectorSearch({
|
|
4168
4512
|
page: 1,
|
|
4169
4513
|
limit: 10,
|
|
4170
4514
|
query,
|
|
@@ -4177,6 +4521,9 @@ var ExuluContext = class {
|
|
|
4177
4521
|
sort: void 0,
|
|
4178
4522
|
trigger: "agent"
|
|
4179
4523
|
});
|
|
4524
|
+
return {
|
|
4525
|
+
items: result.items
|
|
4526
|
+
};
|
|
4180
4527
|
}
|
|
4181
4528
|
});
|
|
4182
4529
|
};
|
|
@@ -4192,7 +4539,6 @@ var updateStatistic = async (statistic) => {
|
|
|
4192
4539
|
type: statistic.type,
|
|
4193
4540
|
createdAt: currentDate
|
|
4194
4541
|
}).first();
|
|
4195
|
-
console.log("!!! existing !!!", existing);
|
|
4196
4542
|
if (!existing) {
|
|
4197
4543
|
await db3.from("tracking").insert({
|
|
4198
4544
|
name: statistic.name,
|
|
@@ -4214,6 +4560,53 @@ var updateStatistic = async (statistic) => {
|
|
|
4214
4560
|
});
|
|
4215
4561
|
}
|
|
4216
4562
|
};
|
|
4563
|
+
var generateS3Key = (filename) => `${randomUUID()}-${filename}`;
|
|
4564
|
+
var getMimeType = (type) => {
|
|
4565
|
+
switch (type) {
|
|
4566
|
+
case ".png":
|
|
4567
|
+
return "image/png";
|
|
4568
|
+
case ".jpg":
|
|
4569
|
+
return "image/jpg";
|
|
4570
|
+
case ".jpeg":
|
|
4571
|
+
return "image/jpeg";
|
|
4572
|
+
case ".gif":
|
|
4573
|
+
return "image/gif";
|
|
4574
|
+
case ".webp":
|
|
4575
|
+
return "image/webp";
|
|
4576
|
+
case ".pdf":
|
|
4577
|
+
return "application/pdf";
|
|
4578
|
+
case ".docx":
|
|
4579
|
+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
4580
|
+
case ".xlsx":
|
|
4581
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
4582
|
+
case ".xls":
|
|
4583
|
+
return "application/vnd.ms-excel";
|
|
4584
|
+
case ".csv":
|
|
4585
|
+
return "text/csv";
|
|
4586
|
+
case ".pptx":
|
|
4587
|
+
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
4588
|
+
case ".ppt":
|
|
4589
|
+
return "application/vnd.ms-powerpoint";
|
|
4590
|
+
case ".m4a":
|
|
4591
|
+
return "audio/mp4";
|
|
4592
|
+
case ".mp4":
|
|
4593
|
+
return "audio/mp4";
|
|
4594
|
+
case ".mpeg":
|
|
4595
|
+
return "audio/mpeg";
|
|
4596
|
+
case ".mp3":
|
|
4597
|
+
return "audio/mp3";
|
|
4598
|
+
case ".wav":
|
|
4599
|
+
return "audio/wav";
|
|
4600
|
+
case ".txt":
|
|
4601
|
+
return "text/plain";
|
|
4602
|
+
case ".md":
|
|
4603
|
+
return "text/markdown";
|
|
4604
|
+
case ".json":
|
|
4605
|
+
return "application/json";
|
|
4606
|
+
default:
|
|
4607
|
+
return "";
|
|
4608
|
+
}
|
|
4609
|
+
};
|
|
4217
4610
|
|
|
4218
4611
|
// src/registry/index.ts
|
|
4219
4612
|
import "express";
|
|
@@ -4221,42 +4614,6 @@ import "express";
|
|
|
4221
4614
|
// src/registry/routes.ts
|
|
4222
4615
|
import "express";
|
|
4223
4616
|
|
|
4224
|
-
// src/registry/rate-limiter.ts
|
|
4225
|
-
var rateLimiter = async (key, windowSeconds, limit, points) => {
|
|
4226
|
-
try {
|
|
4227
|
-
const { client: client2 } = await redisClient();
|
|
4228
|
-
if (!client2) {
|
|
4229
|
-
console.warn("[EXULU] Rate limiting disabled - Redis not available");
|
|
4230
|
-
return {
|
|
4231
|
-
status: true,
|
|
4232
|
-
retryAfter: null
|
|
4233
|
-
};
|
|
4234
|
-
}
|
|
4235
|
-
const redisKey = `exulu/${key}`;
|
|
4236
|
-
const current = await client2.incrBy(redisKey, points);
|
|
4237
|
-
if (current === points) {
|
|
4238
|
-
await client2.expire(redisKey, windowSeconds);
|
|
4239
|
-
}
|
|
4240
|
-
if (current > limit) {
|
|
4241
|
-
const ttl = await client2.ttl(redisKey);
|
|
4242
|
-
return {
|
|
4243
|
-
status: false,
|
|
4244
|
-
retryAfter: ttl
|
|
4245
|
-
};
|
|
4246
|
-
}
|
|
4247
|
-
return {
|
|
4248
|
-
status: true,
|
|
4249
|
-
retryAfter: null
|
|
4250
|
-
};
|
|
4251
|
-
} catch (error) {
|
|
4252
|
-
console.error("[EXULU] Rate limiting error:", error);
|
|
4253
|
-
return {
|
|
4254
|
-
status: true,
|
|
4255
|
-
retryAfter: null
|
|
4256
|
-
};
|
|
4257
|
-
}
|
|
4258
|
-
};
|
|
4259
|
-
|
|
4260
4617
|
// src/bullmq/queues.ts
|
|
4261
4618
|
import { Queue as Queue3 } from "bullmq";
|
|
4262
4619
|
import { BullMQOtel } from "bullmq-otel";
|
|
@@ -4300,22 +4657,22 @@ import { expressMiddleware } from "@as-integrations/express5";
|
|
|
4300
4657
|
// src/registry/uppy.ts
|
|
4301
4658
|
import "express";
|
|
4302
4659
|
import {
|
|
4303
|
-
S3Client,
|
|
4660
|
+
S3Client as S3Client2,
|
|
4304
4661
|
AbortMultipartUploadCommand,
|
|
4305
4662
|
CompleteMultipartUploadCommand,
|
|
4306
4663
|
CreateMultipartUploadCommand,
|
|
4307
4664
|
GetObjectCommand,
|
|
4308
4665
|
ListPartsCommand,
|
|
4309
|
-
PutObjectCommand,
|
|
4666
|
+
PutObjectCommand as PutObjectCommand2,
|
|
4310
4667
|
UploadPartCommand,
|
|
4311
|
-
|
|
4668
|
+
DeleteObjectCommand
|
|
4312
4669
|
} from "@aws-sdk/client-s3";
|
|
4313
4670
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
4314
4671
|
import {
|
|
4315
4672
|
STSClient,
|
|
4316
4673
|
GetFederationTokenCommand
|
|
4317
4674
|
} from "@aws-sdk/client-sts";
|
|
4318
|
-
import { randomUUID } from "crypto";
|
|
4675
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4319
4676
|
var createUppyRoutes = async (app, config) => {
|
|
4320
4677
|
const policy = {
|
|
4321
4678
|
Version: "2012-10-17",
|
|
@@ -4332,11 +4689,11 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4332
4689
|
}
|
|
4333
4690
|
]
|
|
4334
4691
|
};
|
|
4335
|
-
let
|
|
4692
|
+
let s3Client2;
|
|
4336
4693
|
let stsClient;
|
|
4337
4694
|
const expiresIn = 60 * 60 * 24 * 1;
|
|
4338
4695
|
function getS3Client() {
|
|
4339
|
-
|
|
4696
|
+
s3Client2 ??= new S3Client2({
|
|
4340
4697
|
region: config.fileUploads.s3region,
|
|
4341
4698
|
...config.fileUploads.s3endpoint && {
|
|
4342
4699
|
forcePathStyle: true,
|
|
@@ -4347,7 +4704,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4347
4704
|
secretAccessKey: config.fileUploads.s3secret
|
|
4348
4705
|
}
|
|
4349
4706
|
});
|
|
4350
|
-
return
|
|
4707
|
+
return s3Client2;
|
|
4351
4708
|
}
|
|
4352
4709
|
function getSTSClient() {
|
|
4353
4710
|
stsClient ??= new STSClient({
|
|
@@ -4360,54 +4717,51 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4360
4717
|
});
|
|
4361
4718
|
return stsClient;
|
|
4362
4719
|
}
|
|
4363
|
-
app.
|
|
4364
|
-
req.accepts;
|
|
4720
|
+
app.delete("/s3/delete", async (req, res, next) => {
|
|
4365
4721
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
4722
|
+
const internalkey = req.headers["internal-key"] || null;
|
|
4723
|
+
const { db: db3 } = await postgresClient();
|
|
4366
4724
|
let authtoken = null;
|
|
4367
|
-
if (typeof apikey !== "string") {
|
|
4725
|
+
if (typeof apikey !== "string" && typeof internalkey !== "string") {
|
|
4368
4726
|
authtoken = await getToken(req.headers.authorization ?? "");
|
|
4369
4727
|
}
|
|
4370
|
-
const { db: db3 } = await postgresClient();
|
|
4371
4728
|
const authenticationResult = await authentication({
|
|
4372
4729
|
authtoken,
|
|
4373
4730
|
apikey,
|
|
4731
|
+
internalkey,
|
|
4374
4732
|
db: db3
|
|
4375
4733
|
});
|
|
4376
4734
|
if (!authenticationResult.user?.id) {
|
|
4377
4735
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4378
4736
|
return;
|
|
4379
4737
|
}
|
|
4380
|
-
const {
|
|
4381
|
-
if (typeof
|
|
4382
|
-
res.status(400).json({ error: "
|
|
4738
|
+
const { key } = req.query;
|
|
4739
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
4740
|
+
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4741
|
+
return;
|
|
4742
|
+
}
|
|
4743
|
+
const userPrefix = key.split("/")[0];
|
|
4744
|
+
console.log("userPrefix", userPrefix);
|
|
4745
|
+
console.log("authenticationResult.user.id", authenticationResult.user.id);
|
|
4746
|
+
if (!userPrefix) {
|
|
4747
|
+
res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
|
|
4383
4748
|
return;
|
|
4384
4749
|
}
|
|
4385
|
-
if (
|
|
4386
|
-
res.status(405).json({ error: "Not allowed to
|
|
4750
|
+
if (userPrefix !== authenticationResult.user.id.toString()) {
|
|
4751
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4387
4752
|
return;
|
|
4388
4753
|
}
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
Prefix: prefix,
|
|
4393
|
-
MaxKeys: 1e3
|
|
4394
|
-
// Adjust this value based on your needs
|
|
4395
|
-
});
|
|
4396
|
-
const data = await getS3Client().send(command);
|
|
4397
|
-
const files = data.Contents?.map((item) => ({
|
|
4398
|
-
key: item.Key,
|
|
4399
|
-
size: item.Size,
|
|
4400
|
-
lastModified: item.LastModified
|
|
4401
|
-
})) || [];
|
|
4402
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
4403
|
-
res.status(200).json({
|
|
4404
|
-
files,
|
|
4405
|
-
isTruncated: data.IsTruncated,
|
|
4406
|
-
nextContinuationToken: data.NextContinuationToken
|
|
4407
|
-
});
|
|
4408
|
-
} catch (err) {
|
|
4409
|
-
next(err);
|
|
4754
|
+
if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
|
|
4755
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4756
|
+
return;
|
|
4410
4757
|
}
|
|
4758
|
+
const client2 = getS3Client();
|
|
4759
|
+
const command = new DeleteObjectCommand({
|
|
4760
|
+
Bucket: config.fileUploads.s3Bucket,
|
|
4761
|
+
Key: key
|
|
4762
|
+
});
|
|
4763
|
+
await client2.send(command);
|
|
4764
|
+
res.json({ key });
|
|
4411
4765
|
});
|
|
4412
4766
|
app.get("/s3/download", async (req, res, next) => {
|
|
4413
4767
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
@@ -4432,7 +4786,16 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4432
4786
|
res.status(400).json({ error: "Missing or invalid `key` query parameter." });
|
|
4433
4787
|
return;
|
|
4434
4788
|
}
|
|
4435
|
-
|
|
4789
|
+
const userPrefix = key.split("/")[0];
|
|
4790
|
+
if (!userPrefix) {
|
|
4791
|
+
res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
|
|
4792
|
+
return;
|
|
4793
|
+
}
|
|
4794
|
+
if (userPrefix !== authenticationResult.user.id.toString()) {
|
|
4795
|
+
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4796
|
+
return;
|
|
4797
|
+
}
|
|
4798
|
+
if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
|
|
4436
4799
|
res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
|
|
4437
4800
|
return;
|
|
4438
4801
|
}
|
|
@@ -4482,7 +4845,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4482
4845
|
contentType: params.type
|
|
4483
4846
|
};
|
|
4484
4847
|
};
|
|
4485
|
-
const
|
|
4848
|
+
const generateS3Key2 = (filename) => `${randomUUID2()}-${filename}`;
|
|
4486
4849
|
const signOnServer = async (req, res, next) => {
|
|
4487
4850
|
const apikey = req.headers["exulu-api-key"] || null;
|
|
4488
4851
|
const { db: db3 } = await postgresClient();
|
|
@@ -4501,16 +4864,11 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4501
4864
|
}
|
|
4502
4865
|
const { filename, contentType } = extractFileParameters(req);
|
|
4503
4866
|
validateFileParameters(filename, contentType);
|
|
4504
|
-
const key =
|
|
4505
|
-
let folder =
|
|
4506
|
-
if (authenticationResult.user.type === "api") {
|
|
4507
|
-
folder = `api/`;
|
|
4508
|
-
} else {
|
|
4509
|
-
folder = `${authenticationResult.user.id}/`;
|
|
4510
|
-
}
|
|
4867
|
+
const key = generateS3Key2(filename);
|
|
4868
|
+
let folder = `${authenticationResult.user.id}/`;
|
|
4511
4869
|
getSignedUrl(
|
|
4512
4870
|
getS3Client(),
|
|
4513
|
-
new
|
|
4871
|
+
new PutObjectCommand2({
|
|
4514
4872
|
Bucket: config.fileUploads.s3Bucket,
|
|
4515
4873
|
Key: folder + key,
|
|
4516
4874
|
ContentType: contentType
|
|
@@ -4556,7 +4914,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4556
4914
|
if (typeof type !== "string") {
|
|
4557
4915
|
return res.status(400).json({ error: "s3: content type must be a string" });
|
|
4558
4916
|
}
|
|
4559
|
-
const key = `${
|
|
4917
|
+
const key = `${randomUUID2()}-${filename}`;
|
|
4560
4918
|
let folder = "";
|
|
4561
4919
|
if (authenticationResult.user.type === "api") {
|
|
4562
4920
|
folder = `api/`;
|
|
@@ -4697,6 +5055,13 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4697
5055
|
import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
|
|
4698
5056
|
import bodyParser from "body-parser";
|
|
4699
5057
|
import CryptoJS3 from "crypto-js";
|
|
5058
|
+
import OpenAI from "openai";
|
|
5059
|
+
import fs from "fs";
|
|
5060
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
5061
|
+
import "@opentelemetry/api";
|
|
5062
|
+
import { jsonSchema } from "ai";
|
|
5063
|
+
import proxy from "express-http-proxy";
|
|
5064
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
4700
5065
|
|
|
4701
5066
|
// src/registry/utils/claude-messages.ts
|
|
4702
5067
|
var CLAUDE_MESSAGES = {
|
|
@@ -4721,10 +5086,6 @@ var CLAUDE_MESSAGES = {
|
|
|
4721
5086
|
};
|
|
4722
5087
|
|
|
4723
5088
|
// src/registry/routes.ts
|
|
4724
|
-
import OpenAI from "openai";
|
|
4725
|
-
import fs2 from "fs";
|
|
4726
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
4727
|
-
import "@opentelemetry/api";
|
|
4728
5089
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
4729
5090
|
var global_queues = {
|
|
4730
5091
|
logs_cleaner: "logs-cleaner"
|
|
@@ -4774,7 +5135,7 @@ var createRecurringJobs = async () => {
|
|
|
4774
5135
|
);
|
|
4775
5136
|
return queue;
|
|
4776
5137
|
};
|
|
4777
|
-
var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
|
|
5138
|
+
var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer, filesContext2) => {
|
|
4778
5139
|
var corsOptions = {
|
|
4779
5140
|
origin: "*",
|
|
4780
5141
|
exposedHeaders: "*",
|
|
@@ -4799,7 +5160,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4799
5160
|
if (redisServer.host?.length && redisServer.port?.length) {
|
|
4800
5161
|
await createRecurringJobs();
|
|
4801
5162
|
} else {
|
|
4802
|
-
console.log("[
|
|
5163
|
+
console.log("[EXULU] no redis server configured, not setting up recurring jobs.");
|
|
4803
5164
|
}
|
|
4804
5165
|
const schema = createSDL([
|
|
4805
5166
|
usersSchema2(),
|
|
@@ -4814,7 +5175,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4814
5175
|
workflowTemplatesSchema2(),
|
|
4815
5176
|
statisticsSchema2(),
|
|
4816
5177
|
rbacSchema2()
|
|
4817
|
-
], contexts, agents, tools);
|
|
5178
|
+
], contexts ?? [], agents, tools);
|
|
4818
5179
|
const server = new ApolloServer({
|
|
4819
5180
|
cache: new InMemoryLRUCache(),
|
|
4820
5181
|
schema,
|
|
@@ -4872,7 +5233,7 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4872
5233
|
});
|
|
4873
5234
|
return;
|
|
4874
5235
|
}
|
|
4875
|
-
let
|
|
5236
|
+
let providerapikey = variable.value;
|
|
4876
5237
|
if (!variable.encrypted) {
|
|
4877
5238
|
res.status(400).json({
|
|
4878
5239
|
message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
|
|
@@ -4881,10 +5242,10 @@ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, t
|
|
|
4881
5242
|
}
|
|
4882
5243
|
if (variable.encrypted) {
|
|
4883
5244
|
const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
4884
|
-
|
|
5245
|
+
providerapikey = bytes.toString(CryptoJS3.enc.Utf8);
|
|
4885
5246
|
}
|
|
4886
5247
|
const openai = new OpenAI({
|
|
4887
|
-
apiKey:
|
|
5248
|
+
apiKey: providerapikey
|
|
4888
5249
|
});
|
|
4889
5250
|
let style_reference = "";
|
|
4890
5251
|
if (style === "origami") {
|
|
@@ -4931,11 +5292,11 @@ Mood: friendly and intelligent.
|
|
|
4931
5292
|
return;
|
|
4932
5293
|
}
|
|
4933
5294
|
const image_bytes = Buffer.from(image_base64, "base64");
|
|
4934
|
-
const uuid =
|
|
4935
|
-
if (!
|
|
4936
|
-
|
|
5295
|
+
const uuid = randomUUID3();
|
|
5296
|
+
if (!fs.existsSync("public")) {
|
|
5297
|
+
fs.mkdirSync("public");
|
|
4937
5298
|
}
|
|
4938
|
-
|
|
5299
|
+
fs.writeFileSync(`public/${uuid}.png`, image_bytes);
|
|
4939
5300
|
res.status(200).json({
|
|
4940
5301
|
message: "Image generated successfully.",
|
|
4941
5302
|
image: `${process.env.BACKEND}/${uuid}.png`
|
|
@@ -4957,6 +5318,12 @@ Mood: friendly and intelligent.
|
|
|
4957
5318
|
const slug = agent.slug;
|
|
4958
5319
|
if (!slug) return;
|
|
4959
5320
|
app.post(slug + "/:instance", async (req, res) => {
|
|
5321
|
+
const headers = {
|
|
5322
|
+
stream: req.headers["stream"] === "true" || false,
|
|
5323
|
+
user: req.headers["user"] || null,
|
|
5324
|
+
session: req.headers["session"] || null
|
|
5325
|
+
};
|
|
5326
|
+
await checkAgentRateLimit(agent);
|
|
4960
5327
|
const instance = req.params.instance;
|
|
4961
5328
|
if (!instance) {
|
|
4962
5329
|
res.status(400).json({
|
|
@@ -4965,38 +5332,7 @@ Mood: friendly and intelligent.
|
|
|
4965
5332
|
return;
|
|
4966
5333
|
}
|
|
4967
5334
|
const { db: db3 } = await postgresClient();
|
|
4968
|
-
const agentInstance = await
|
|
4969
|
-
id: instance
|
|
4970
|
-
}).first();
|
|
4971
|
-
const agentRbac = await RBACResolver(db3, "agent", agentInstance.id, agentInstance.rights_mode || "private");
|
|
4972
|
-
agentInstance.RBAC = agentRbac;
|
|
4973
|
-
if (!agentInstance) {
|
|
4974
|
-
res.status(400).json({
|
|
4975
|
-
message: "Agent instance not found."
|
|
4976
|
-
});
|
|
4977
|
-
return;
|
|
4978
|
-
}
|
|
4979
|
-
if (agent.rateLimit) {
|
|
4980
|
-
console.log("[EXULU] rate limiting agent.", agent.rateLimit);
|
|
4981
|
-
const limit = await rateLimiter(
|
|
4982
|
-
agent.rateLimit.name || agent.id,
|
|
4983
|
-
agent.rateLimit.rate_limit.time,
|
|
4984
|
-
agent.rateLimit.rate_limit.limit,
|
|
4985
|
-
1
|
|
4986
|
-
);
|
|
4987
|
-
if (!limit.status) {
|
|
4988
|
-
res.status(429).json({
|
|
4989
|
-
message: "Rate limit exceeded.",
|
|
4990
|
-
retryAfter: limit.retryAfter
|
|
4991
|
-
});
|
|
4992
|
-
return;
|
|
4993
|
-
}
|
|
4994
|
-
}
|
|
4995
|
-
const headers = {
|
|
4996
|
-
stream: req.headers["stream"] === "true" || false,
|
|
4997
|
-
user: req.headers["user"] || null,
|
|
4998
|
-
session: req.headers["session"] || null
|
|
4999
|
-
};
|
|
5335
|
+
const agentInstance = await loadAgent(instance);
|
|
5000
5336
|
const requestValidationResult = requestValidators.agents(req);
|
|
5001
5337
|
if (requestValidationResult.error) {
|
|
5002
5338
|
res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
|
|
@@ -5008,85 +5344,24 @@ Mood: friendly and intelligent.
|
|
|
5008
5344
|
return;
|
|
5009
5345
|
}
|
|
5010
5346
|
const user = authenticationResult.user;
|
|
5011
|
-
const
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
const isAgentCreator = agentInstance.created_by === user.id;
|
|
5015
|
-
const isAdmin = user.super_admin;
|
|
5016
|
-
const isApi = user.type === "api";
|
|
5017
|
-
let hasAccessToAgent = "none";
|
|
5018
|
-
if (agentIsPublic || isAgentCreator || isAdmin || isApi) {
|
|
5019
|
-
hasAccessToAgent = "write";
|
|
5020
|
-
}
|
|
5021
|
-
if (agentByUsers) {
|
|
5022
|
-
hasAccessToAgent = agentInstance.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
5023
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none" || hasAccessToAgent === "read") {
|
|
5024
|
-
res.status(410).json({
|
|
5025
|
-
message: `Your current user ${user.id} does not have access to this agent.`
|
|
5026
|
-
});
|
|
5027
|
-
return;
|
|
5028
|
-
}
|
|
5029
|
-
}
|
|
5030
|
-
if (agentByRoles) {
|
|
5031
|
-
hasAccessToAgent = agentInstance.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
5032
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none" || hasAccessToAgent === "read") {
|
|
5033
|
-
res.status(410).json({
|
|
5034
|
-
message: `Your current role ${user.role?.name} does not have access to this agent.`
|
|
5035
|
-
});
|
|
5036
|
-
return;
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
let hasAccessToSession = "none";
|
|
5040
|
-
;
|
|
5041
|
-
if (headers.session) {
|
|
5042
|
-
const session = await db3.from("agents").where({
|
|
5043
|
-
id: instance
|
|
5044
|
-
}).first();
|
|
5045
|
-
const sessionIsPublic = agentInstance.rights_mode === "public";
|
|
5046
|
-
const sessionByUsers = agentInstance.rights_mode === "users";
|
|
5047
|
-
const sessionByRoles = agentInstance.rights_mode === "roles";
|
|
5048
|
-
const isSessionCreator = agentInstance.created_by === user.id;
|
|
5049
|
-
const isAdmin2 = user.super_admin;
|
|
5050
|
-
const isApi2 = user.type === "api";
|
|
5051
|
-
if (sessionIsPublic || isSessionCreator || isAdmin2 || isApi2) {
|
|
5052
|
-
hasAccessToSession = "write";
|
|
5053
|
-
}
|
|
5054
|
-
if (sessionByUsers) {
|
|
5055
|
-
hasAccessToSession = session.RBAC?.users?.find((x) => x.id === user.id)?.rights || "none";
|
|
5056
|
-
if (!hasAccessToSession || hasAccessToSession === "none" || hasAccessToSession === "read") {
|
|
5057
|
-
res.status(410).json({
|
|
5058
|
-
message: `Your current user ${user.id} does not have access to this session.`
|
|
5059
|
-
});
|
|
5060
|
-
return;
|
|
5061
|
-
}
|
|
5062
|
-
}
|
|
5063
|
-
if (sessionByRoles) {
|
|
5064
|
-
hasAccessToSession = session.RBAC?.roles?.find((x) => x.id === user.role?.id)?.rights || "none";
|
|
5065
|
-
if (!hasAccessToSession || hasAccessToSession === "none" || hasAccessToSession === "read") {
|
|
5066
|
-
res.status(410).json({
|
|
5067
|
-
message: `Your current role ${user.role?.name} does not have access to this session.`
|
|
5068
|
-
});
|
|
5069
|
-
return;
|
|
5070
|
-
}
|
|
5071
|
-
}
|
|
5072
|
-
}
|
|
5073
|
-
if (!hasAccessToAgent || hasAccessToAgent === "none") {
|
|
5074
|
-
res.status(410).json({
|
|
5347
|
+
const hasAccessToAgent = await checkRecordAccess(agentInstance, "read", user);
|
|
5348
|
+
if (!hasAccessToAgent) {
|
|
5349
|
+
res.status(401).json({
|
|
5075
5350
|
message: "You don't have access to this agent."
|
|
5076
5351
|
});
|
|
5077
5352
|
return;
|
|
5078
5353
|
}
|
|
5079
|
-
if (
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
});
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5354
|
+
if (headers.session) {
|
|
5355
|
+
const session = await db3.from("agent_sessions").where({
|
|
5356
|
+
id: headers.session
|
|
5357
|
+
}).first();
|
|
5358
|
+
let hasAccessToSession = await checkRecordAccess(session, "write", user);
|
|
5359
|
+
if (!hasAccessToSession) {
|
|
5360
|
+
res.status(401).json({
|
|
5361
|
+
message: "You don't have access to this session."
|
|
5362
|
+
});
|
|
5363
|
+
return;
|
|
5364
|
+
}
|
|
5090
5365
|
}
|
|
5091
5366
|
if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
|
|
5092
5367
|
res.status(400).json({
|
|
@@ -5094,16 +5369,11 @@ Mood: friendly and intelligent.
|
|
|
5094
5369
|
});
|
|
5095
5370
|
return;
|
|
5096
5371
|
}
|
|
5097
|
-
console.log("[EXULU] agent tools", agentInstance.tools);
|
|
5098
|
-
let enabledTools = agentInstance.tools ? agentInstance.tools.map(
|
|
5099
|
-
({ config: config2, toolId }) => tools.find(({ id }) => id === toolId)
|
|
5100
|
-
).filter(Boolean) : [];
|
|
5101
|
-
console.log("[EXULU] available tools", enabledTools?.length);
|
|
5372
|
+
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
5102
5373
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
const variableName = agentInstance.providerApiKey;
|
|
5374
|
+
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
5375
|
+
console.log("[EXULU] enabled tools", enabledTools?.map((x) => x.name + " (" + x.id + ")"));
|
|
5376
|
+
const variableName = agentInstance.providerapikey;
|
|
5107
5377
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5108
5378
|
if (!variable) {
|
|
5109
5379
|
res.status(400).json({
|
|
@@ -5111,7 +5381,7 @@ Mood: friendly and intelligent.
|
|
|
5111
5381
|
});
|
|
5112
5382
|
return;
|
|
5113
5383
|
}
|
|
5114
|
-
let
|
|
5384
|
+
let providerapikey = variable.value;
|
|
5115
5385
|
if (!variable.encrypted) {
|
|
5116
5386
|
res.status(400).json({
|
|
5117
5387
|
message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
|
|
@@ -5120,7 +5390,7 @@ Mood: friendly and intelligent.
|
|
|
5120
5390
|
}
|
|
5121
5391
|
if (variable.encrypted) {
|
|
5122
5392
|
const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
5123
|
-
|
|
5393
|
+
providerapikey = bytes.toString(CryptoJS3.enc.Utf8);
|
|
5124
5394
|
}
|
|
5125
5395
|
if (!!headers.stream) {
|
|
5126
5396
|
await agent.generateStream({
|
|
@@ -5128,13 +5398,17 @@ Mood: friendly and intelligent.
|
|
|
5128
5398
|
res,
|
|
5129
5399
|
req
|
|
5130
5400
|
},
|
|
5131
|
-
|
|
5132
|
-
|
|
5401
|
+
contexts,
|
|
5402
|
+
user,
|
|
5403
|
+
instructions: agentInstance.instructions,
|
|
5133
5404
|
session: headers.session,
|
|
5134
5405
|
message: req.body.message,
|
|
5135
|
-
|
|
5136
|
-
|
|
5406
|
+
currentTools: enabledTools,
|
|
5407
|
+
allExuluTools: tools,
|
|
5408
|
+
providerapikey,
|
|
5137
5409
|
toolConfigs: agentInstance.tools,
|
|
5410
|
+
exuluConfig: config,
|
|
5411
|
+
filesContext: filesContext2,
|
|
5138
5412
|
statistics: {
|
|
5139
5413
|
label: agent.name,
|
|
5140
5414
|
trigger: "agent"
|
|
@@ -5143,13 +5417,17 @@ Mood: friendly and intelligent.
|
|
|
5143
5417
|
return;
|
|
5144
5418
|
} else {
|
|
5145
5419
|
const response = await agent.generateSync({
|
|
5146
|
-
user
|
|
5420
|
+
user,
|
|
5421
|
+
instructions: agentInstance.instructions,
|
|
5147
5422
|
session: headers.session,
|
|
5148
|
-
role: user?.role?.id,
|
|
5149
5423
|
message: req.body.message,
|
|
5150
|
-
|
|
5151
|
-
|
|
5424
|
+
contexts,
|
|
5425
|
+
currentTools: enabledTools,
|
|
5426
|
+
allExuluTools: tools,
|
|
5427
|
+
providerapikey,
|
|
5428
|
+
exuluConfig: config,
|
|
5152
5429
|
toolConfigs: agentInstance.tools,
|
|
5430
|
+
filesContext: filesContext2,
|
|
5153
5431
|
statistics: {
|
|
5154
5432
|
label: agent.name,
|
|
5155
5433
|
trigger: "agent"
|
|
@@ -5165,10 +5443,92 @@ Mood: friendly and intelligent.
|
|
|
5165
5443
|
} else {
|
|
5166
5444
|
console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in ExuluApp instance.");
|
|
5167
5445
|
}
|
|
5168
|
-
|
|
5446
|
+
app.use("/xxx/anthropic/:id", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), proxy(
|
|
5447
|
+
(req, res, next) => {
|
|
5448
|
+
return "https://api.anthropic.com";
|
|
5449
|
+
},
|
|
5450
|
+
{
|
|
5451
|
+
limit: "50mb",
|
|
5452
|
+
memoizeHost: false,
|
|
5453
|
+
preserveHostHdr: true,
|
|
5454
|
+
secure: false,
|
|
5455
|
+
reqAsBuffer: true,
|
|
5456
|
+
proxyReqBodyDecorator: function(bodyContent, srcReq) {
|
|
5457
|
+
return bodyContent;
|
|
5458
|
+
},
|
|
5459
|
+
userResDecorator: function(proxyRes, proxyResData, userReq, userRes) {
|
|
5460
|
+
console.log("[EXULU] Proxy response!", proxyResData);
|
|
5461
|
+
proxyResData = proxyResData.toString();
|
|
5462
|
+
console.log("[EXULU] Proxy response string!", proxyResData);
|
|
5463
|
+
return proxyResData;
|
|
5464
|
+
},
|
|
5465
|
+
proxyReqPathResolver: (req) => {
|
|
5466
|
+
const prefix = `/gateway/anthropic/${req.params.id}`;
|
|
5467
|
+
let path2 = req.url.startsWith(prefix) ? req.url.slice(prefix.length) : req.url;
|
|
5468
|
+
if (!path2.startsWith("/")) path2 = "/" + path2;
|
|
5469
|
+
console.log("[EXULU] Provider path:", path2);
|
|
5470
|
+
return path2;
|
|
5471
|
+
},
|
|
5472
|
+
proxyReqOptDecorator: function(proxyReqOpts, srcReq) {
|
|
5473
|
+
return new Promise(async (resolve, reject) => {
|
|
5474
|
+
try {
|
|
5475
|
+
const authenticationResult = await requestValidators.authenticate(srcReq);
|
|
5476
|
+
if (!authenticationResult.user?.id) {
|
|
5477
|
+
console.log("[EXULU] failed authentication result", authenticationResult);
|
|
5478
|
+
reject(authenticationResult.message);
|
|
5479
|
+
return;
|
|
5480
|
+
}
|
|
5481
|
+
console.log("[EXULU] Authenticated call", authenticationResult.user?.email);
|
|
5482
|
+
const { db: db3 } = await postgresClient();
|
|
5483
|
+
let query = db3("agents");
|
|
5484
|
+
query.select("*");
|
|
5485
|
+
query = applyAccessControl(agentsSchema2(), authenticationResult.user, query);
|
|
5486
|
+
query.where({ id: srcReq.params.id });
|
|
5487
|
+
const agent = await query.first();
|
|
5488
|
+
if (!agent) {
|
|
5489
|
+
reject(new Error("Agent with id " + srcReq.params.id + " not found."));
|
|
5490
|
+
return;
|
|
5491
|
+
}
|
|
5492
|
+
console.log("[EXULU] Agent loaded", agent.name);
|
|
5493
|
+
const backend = agents.find((x) => x.id === agent.backend);
|
|
5494
|
+
if (!process.env.NEXTAUTH_SECRET) {
|
|
5495
|
+
reject(new Error("Missing NEXTAUTH_SECRET"));
|
|
5496
|
+
return;
|
|
5497
|
+
}
|
|
5498
|
+
if (!agent.providerapikey) {
|
|
5499
|
+
reject(new Error("API Key not set for agent"));
|
|
5500
|
+
return;
|
|
5501
|
+
}
|
|
5502
|
+
const variableName = agent.providerapikey;
|
|
5503
|
+
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5504
|
+
console.log("[EXULU] Variable loaded", variable);
|
|
5505
|
+
let providerapikey = variable.value;
|
|
5506
|
+
if (!variable.encrypted) {
|
|
5507
|
+
reject(new Error("API Key not encrypted for agent"));
|
|
5508
|
+
return;
|
|
5509
|
+
}
|
|
5510
|
+
if (variable.encrypted) {
|
|
5511
|
+
const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
5512
|
+
providerapikey = bytes.toString(CryptoJS3.enc.Utf8);
|
|
5513
|
+
}
|
|
5514
|
+
console.log("[EXULU] Provider API key", providerapikey);
|
|
5515
|
+
proxyReqOpts.headers["x-api-key"] = providerapikey;
|
|
5516
|
+
proxyReqOpts.rejectUnauthorized = false;
|
|
5517
|
+
delete proxyReqOpts.headers["provider"];
|
|
5518
|
+
const url = new URL("https://api.anthropic.com");
|
|
5519
|
+
proxyReqOpts.headers["host"] = url.host;
|
|
5520
|
+
proxyReqOpts.headers["anthropic-version"] = "2023-06-01";
|
|
5521
|
+
console.log("[EXULU] Proxy request headers", proxyReqOpts.headers);
|
|
5522
|
+
resolve(proxyReqOpts);
|
|
5523
|
+
} catch (error) {
|
|
5524
|
+
console.error("[EXULU] Proxy error", error);
|
|
5525
|
+
reject(error);
|
|
5526
|
+
}
|
|
5527
|
+
});
|
|
5528
|
+
}
|
|
5529
|
+
}
|
|
5530
|
+
));
|
|
5169
5531
|
app.use("/gateway/anthropic/:id", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
5170
|
-
const path3 = req.url;
|
|
5171
|
-
const url = `${TARGET_API}${path3}`;
|
|
5172
5532
|
try {
|
|
5173
5533
|
if (!req.body.tools) {
|
|
5174
5534
|
req.body.tools = [];
|
|
@@ -5200,13 +5560,13 @@ Mood: friendly and intelligent.
|
|
|
5200
5560
|
res.end(Buffer.from(arrayBuffer));
|
|
5201
5561
|
return;
|
|
5202
5562
|
}
|
|
5203
|
-
if (!agent.
|
|
5563
|
+
if (!agent.providerapikey) {
|
|
5204
5564
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.not_enabled);
|
|
5205
5565
|
res.setHeader("Content-Type", "application/json");
|
|
5206
5566
|
res.end(Buffer.from(arrayBuffer));
|
|
5207
5567
|
return;
|
|
5208
5568
|
}
|
|
5209
|
-
const variableName = agent.
|
|
5569
|
+
const variableName = agent.providerapikey;
|
|
5210
5570
|
const variable = await db3.from("variables").where({ name: variableName }).first();
|
|
5211
5571
|
if (!variable) {
|
|
5212
5572
|
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.anthropic_token_variable_not_found);
|
|
@@ -5232,45 +5592,21 @@ Mood: friendly and intelligent.
|
|
|
5232
5592
|
};
|
|
5233
5593
|
if (req.headers["accept"]) headers["accept"] = req.headers["accept"];
|
|
5234
5594
|
if (req.headers["user-agent"]) headers["user-agent"] = req.headers["user-agent"];
|
|
5235
|
-
const
|
|
5236
|
-
|
|
5237
|
-
headers,
|
|
5238
|
-
body: req.method !== "GET" ? JSON.stringify(req.body) : void 0
|
|
5239
|
-
});
|
|
5240
|
-
await updateStatistic({
|
|
5241
|
-
name: "count",
|
|
5242
|
-
label: "Claude Code",
|
|
5243
|
-
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
5244
|
-
trigger: "claude-code",
|
|
5245
|
-
count: 1,
|
|
5246
|
-
user: authenticationResult.user?.id,
|
|
5247
|
-
role: authenticationResult.user.role?.id
|
|
5595
|
+
const client2 = new Anthropic({
|
|
5596
|
+
apiKey: anthropicApiKey
|
|
5248
5597
|
});
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
res.status(response.status);
|
|
5253
|
-
const isStreaming = response.headers.get("content-type")?.includes("text/event-stream");
|
|
5254
|
-
if (isStreaming && !response?.body) {
|
|
5255
|
-
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_body);
|
|
5256
|
-
res.setHeader("Content-Type", "application/json");
|
|
5257
|
-
res.end(Buffer.from(arrayBuffer));
|
|
5258
|
-
return;
|
|
5259
|
-
}
|
|
5260
|
-
if (isStreaming) {
|
|
5261
|
-
const reader = response.body.getReader();
|
|
5262
|
-
const decoder = new TextDecoder();
|
|
5263
|
-
while (true) {
|
|
5264
|
-
const { done, value } = await reader.read();
|
|
5265
|
-
if (done) break;
|
|
5266
|
-
const chunk = decoder.decode(value, { stream: true });
|
|
5267
|
-
res.write(chunk);
|
|
5598
|
+
for await (const event of client2.messages.stream(req.body)) {
|
|
5599
|
+
console.log("[EXULU] Event", event);
|
|
5600
|
+
if (event.message?.usage) {
|
|
5268
5601
|
}
|
|
5269
|
-
|
|
5270
|
-
|
|
5602
|
+
const msg = `event: ${event.type}
|
|
5603
|
+
data: ${JSON.stringify(event)}
|
|
5604
|
+
|
|
5605
|
+
`;
|
|
5606
|
+
res.write(msg);
|
|
5271
5607
|
}
|
|
5272
|
-
|
|
5273
|
-
res.end(
|
|
5608
|
+
res.write("event: done\ndata: [DONE]\n\n");
|
|
5609
|
+
res.end();
|
|
5274
5610
|
} catch (error) {
|
|
5275
5611
|
console.error("[PROXY] Manual proxy error:", error);
|
|
5276
5612
|
if (!res.headersSent) {
|
|
@@ -5303,33 +5639,10 @@ var createCustomAnthropicStreamingMessage = (message) => {
|
|
|
5303
5639
|
// src/registry/workers.ts
|
|
5304
5640
|
import IORedis from "ioredis";
|
|
5305
5641
|
import { Worker } from "bullmq";
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
var bullmq = {
|
|
5309
|
-
validate: (id, data) => {
|
|
5310
|
-
if (!data) {
|
|
5311
|
-
throw new Error(`Missing job data for job ${id}.`);
|
|
5312
|
-
}
|
|
5313
|
-
if (!data.type) {
|
|
5314
|
-
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
5315
|
-
}
|
|
5316
|
-
if (!data.inputs) {
|
|
5317
|
-
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
5318
|
-
}
|
|
5319
|
-
if (data.type !== "embedder" && data.type !== "workflow") {
|
|
5320
|
-
throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
|
|
5321
|
-
}
|
|
5322
|
-
if (!data.workflow && !data.embedder) {
|
|
5323
|
-
throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
|
|
5324
|
-
}
|
|
5325
|
-
}
|
|
5326
|
-
};
|
|
5327
|
-
|
|
5328
|
-
// src/registry/workers.ts
|
|
5329
|
-
import * as fs3 from "fs";
|
|
5330
|
-
import path2 from "path";
|
|
5642
|
+
import * as fs2 from "fs";
|
|
5643
|
+
import path from "path";
|
|
5331
5644
|
import "@opentelemetry/api";
|
|
5332
|
-
var defaultLogsDir =
|
|
5645
|
+
var defaultLogsDir = path.join(process.cwd(), "logs");
|
|
5333
5646
|
var redisConnection;
|
|
5334
5647
|
var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
|
|
5335
5648
|
if (!redisServer.host || !redisServer.port) {
|
|
@@ -5370,8 +5683,6 @@ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
|
|
|
5370
5683
|
}, data.role, bullmqJob.id);
|
|
5371
5684
|
return result;
|
|
5372
5685
|
}
|
|
5373
|
-
if (bullmqJob.data.type === "workflow") {
|
|
5374
|
-
}
|
|
5375
5686
|
} catch (error) {
|
|
5376
5687
|
await db3.from("jobs").where({ redis: bullmqJob.id }).update({
|
|
5377
5688
|
status: "failed",
|
|
@@ -5406,16 +5717,16 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
5406
5717
|
global_queues.logs_cleaner,
|
|
5407
5718
|
async (job) => {
|
|
5408
5719
|
console.log(`[EXULU] recurring job ${job.id}.`);
|
|
5409
|
-
const folder =
|
|
5720
|
+
const folder = fs2.readdirSync(logsDir);
|
|
5410
5721
|
const files = folder.filter((file) => file.endsWith(".log"));
|
|
5411
5722
|
const now = /* @__PURE__ */ new Date();
|
|
5412
5723
|
const daysToKeep = job.data.ttld;
|
|
5413
5724
|
const dateToKeep = new Date(now.getTime() - daysToKeep * 24 * 60 * 60 * 1e3);
|
|
5414
5725
|
files.forEach((file) => {
|
|
5415
|
-
const filePath =
|
|
5416
|
-
const fileStats =
|
|
5726
|
+
const filePath = path.join(logsDir, file);
|
|
5727
|
+
const fileStats = fs2.statSync(filePath);
|
|
5417
5728
|
if (fileStats.mtime < dateToKeep) {
|
|
5418
|
-
|
|
5729
|
+
fs2.unlinkSync(filePath);
|
|
5419
5730
|
}
|
|
5420
5731
|
});
|
|
5421
5732
|
},
|
|
@@ -5435,7 +5746,7 @@ var createLogsCleanerWorker = (logsDir) => {
|
|
|
5435
5746
|
|
|
5436
5747
|
// src/mcp/index.ts
|
|
5437
5748
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5438
|
-
import { randomUUID as
|
|
5749
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
5439
5750
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5440
5751
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5441
5752
|
import { z as z2 } from "zod";
|
|
@@ -5517,7 +5828,6 @@ ${code}`
|
|
|
5517
5828
|
if (!this.server) {
|
|
5518
5829
|
throw new Error("MCP server not initialized.");
|
|
5519
5830
|
}
|
|
5520
|
-
console.log("[EXULU] Wiring up MCP server routes to express app.");
|
|
5521
5831
|
this.express.post("/mcp", async (req, res) => {
|
|
5522
5832
|
if (!this.server) {
|
|
5523
5833
|
throw new Error("MCP server not initialized.");
|
|
@@ -5528,7 +5838,7 @@ ${code}`
|
|
|
5528
5838
|
transport = this.transports[sessionId];
|
|
5529
5839
|
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
5530
5840
|
transport = new StreamableHTTPServerTransport({
|
|
5531
|
-
sessionIdGenerator: () =>
|
|
5841
|
+
sessionIdGenerator: () => randomUUID4(),
|
|
5532
5842
|
onsessioninitialized: (sessionId2) => {
|
|
5533
5843
|
this.transports[sessionId2] = transport;
|
|
5534
5844
|
}
|
|
@@ -5570,35 +5880,49 @@ ${code}`
|
|
|
5570
5880
|
// src/registry/index.ts
|
|
5571
5881
|
import express2 from "express";
|
|
5572
5882
|
|
|
5573
|
-
// src/templates/agents/claude-
|
|
5574
|
-
|
|
5883
|
+
// src/templates/agents/claude-sonnet-4.ts
|
|
5884
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
5885
|
+
var claudeSonnet4Agent = new ExuluAgent2({
|
|
5575
5886
|
id: `claude_code_agent`,
|
|
5576
5887
|
name: `Claude Code Agent`,
|
|
5577
5888
|
description: `Claude Code agent, enabling the creation of multiple Claude Code Agent instances with different configurations (rate limits, functions, etc).`,
|
|
5578
|
-
type: "
|
|
5889
|
+
type: "agent",
|
|
5890
|
+
config: {
|
|
5891
|
+
name: `Default Claude Code agent`,
|
|
5892
|
+
instructions: "You are a coding assistant.",
|
|
5893
|
+
model: {
|
|
5894
|
+
create: ({ apiKey }) => {
|
|
5895
|
+
const anthropic = createAnthropic({
|
|
5896
|
+
apiKey
|
|
5897
|
+
});
|
|
5898
|
+
return anthropic.languageModel("claude-sonnet-4-20250514");
|
|
5899
|
+
}
|
|
5900
|
+
}
|
|
5901
|
+
}
|
|
5579
5902
|
});
|
|
5580
5903
|
|
|
5581
5904
|
// src/templates/agents/claude-opus-4.ts
|
|
5582
|
-
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
5583
|
-
var
|
|
5905
|
+
import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
|
|
5906
|
+
var claudeOpus4Agent = new ExuluAgent2({
|
|
5584
5907
|
id: `default_claude_4_opus_agent`,
|
|
5585
|
-
name: `Default Claude 4 Opus
|
|
5586
|
-
description: `Basic agent
|
|
5908
|
+
name: `Default Claude 4 Opus anthropic provider`,
|
|
5909
|
+
description: `Basic agent claude 4 opus agent you can use to chat with.`,
|
|
5587
5910
|
type: "agent",
|
|
5588
5911
|
capabilities: {
|
|
5589
5912
|
text: true,
|
|
5590
5913
|
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
5591
|
-
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt"],
|
|
5914
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
5592
5915
|
audio: [],
|
|
5593
5916
|
video: []
|
|
5594
5917
|
},
|
|
5595
5918
|
evals: [],
|
|
5919
|
+
maxContextLength: 2e5,
|
|
5596
5920
|
config: {
|
|
5597
5921
|
name: `Default agent`,
|
|
5598
5922
|
instructions: "You are a helpful assistant.",
|
|
5599
5923
|
model: {
|
|
5600
5924
|
create: ({ apiKey }) => {
|
|
5601
|
-
const anthropic =
|
|
5925
|
+
const anthropic = createAnthropic2({
|
|
5602
5926
|
apiKey
|
|
5603
5927
|
});
|
|
5604
5928
|
return anthropic.languageModel("claude-4-opus-20250514");
|
|
@@ -5614,8 +5938,79 @@ var defaultAgent = new ExuluAgent2({
|
|
|
5614
5938
|
}
|
|
5615
5939
|
});
|
|
5616
5940
|
|
|
5941
|
+
// src/templates/agents/gpt-5.ts
|
|
5942
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
5943
|
+
var gpt5MiniAgent = new ExuluAgent2({
|
|
5944
|
+
id: `default_gpt_5_mini_agent`,
|
|
5945
|
+
name: `Default GPT 5 Mini OpenAI provider`,
|
|
5946
|
+
description: `Basic agent gpt 5 mini agent you can use to chat with.`,
|
|
5947
|
+
type: "agent",
|
|
5948
|
+
capabilities: {
|
|
5949
|
+
text: true,
|
|
5950
|
+
images: [".png", ".jpg", ".jpeg", ".webp"],
|
|
5951
|
+
files: [".pdf", ".docx", ".xlsx", ".xls", ".csv", ".pptx", ".ppt", ".json"],
|
|
5952
|
+
audio: [],
|
|
5953
|
+
video: []
|
|
5954
|
+
},
|
|
5955
|
+
evals: [],
|
|
5956
|
+
maxContextLength: 128e3,
|
|
5957
|
+
config: {
|
|
5958
|
+
name: `Default agent`,
|
|
5959
|
+
instructions: "You are a helpful assistant.",
|
|
5960
|
+
model: {
|
|
5961
|
+
create: ({ apiKey }) => {
|
|
5962
|
+
const openai = createOpenAI({
|
|
5963
|
+
apiKey
|
|
5964
|
+
});
|
|
5965
|
+
return openai.languageModel("gpt-5-mini");
|
|
5966
|
+
}
|
|
5967
|
+
// todo add a field of type string that adds a dropdown list from which the user can select the model
|
|
5968
|
+
// todo for each model, check which provider is used, and require the admin to add one or multiple
|
|
5969
|
+
// API keys for the provider (which we can then auto-rotate).
|
|
5970
|
+
// todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
|
|
5971
|
+
// and allow him/her to decide if the rate limit is per user or per agent.
|
|
5972
|
+
// todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
|
|
5973
|
+
// and stores the logs into the pre-defined storage.
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
});
|
|
5977
|
+
var gpt5agent = new ExuluAgent2({
|
|
5978
|
+
id: `default_gpt_5_agent`,
|
|
5979
|
+
name: `Default GPT 5 OpenAI provider`,
|
|
5980
|
+
description: `Basic agent gpt 5 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 = createOpenAI({
|
|
5997
|
+
apiKey
|
|
5998
|
+
});
|
|
5999
|
+
return openai.languageModel("gpt-5");
|
|
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
|
+
|
|
5617
6012
|
// src/registry/index.ts
|
|
5618
|
-
import { trace
|
|
6013
|
+
import { trace } from "@opentelemetry/api";
|
|
5619
6014
|
|
|
5620
6015
|
// src/registry/logger.ts
|
|
5621
6016
|
import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
|
|
@@ -5667,21 +6062,21 @@ var codeStandardsContext = new ExuluContext({
|
|
|
5667
6062
|
active: true
|
|
5668
6063
|
});
|
|
5669
6064
|
|
|
5670
|
-
// src/templates/contexts/
|
|
5671
|
-
var
|
|
5672
|
-
id: "
|
|
5673
|
-
name: "
|
|
5674
|
-
description: "
|
|
6065
|
+
// src/templates/contexts/outputs.ts
|
|
6066
|
+
var outputsContext = new ExuluContext({
|
|
6067
|
+
id: "outputs_default_context",
|
|
6068
|
+
name: "Outputs",
|
|
6069
|
+
description: "Outputs from agent sessions that you have saved for re-used later.",
|
|
5675
6070
|
configuration: {
|
|
5676
|
-
defaultRightsMode: "
|
|
6071
|
+
defaultRightsMode: "private",
|
|
6072
|
+
calculateVectors: "manual"
|
|
5677
6073
|
},
|
|
5678
|
-
fields: [
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
}],
|
|
6074
|
+
fields: [
|
|
6075
|
+
{
|
|
6076
|
+
name: "content",
|
|
6077
|
+
type: "longText"
|
|
6078
|
+
}
|
|
6079
|
+
],
|
|
5685
6080
|
active: true
|
|
5686
6081
|
});
|
|
5687
6082
|
|
|
@@ -5699,14 +6094,6 @@ var filesContext = new ExuluContext({
|
|
|
5699
6094
|
name: "type",
|
|
5700
6095
|
type: "text"
|
|
5701
6096
|
},
|
|
5702
|
-
{
|
|
5703
|
-
name: "s3bucket",
|
|
5704
|
-
type: "text"
|
|
5705
|
-
},
|
|
5706
|
-
{
|
|
5707
|
-
name: "s3region",
|
|
5708
|
-
type: "text"
|
|
5709
|
-
},
|
|
5710
6097
|
{
|
|
5711
6098
|
name: "url",
|
|
5712
6099
|
type: "text"
|
|
@@ -5716,10 +6103,6 @@ var filesContext = new ExuluContext({
|
|
|
5716
6103
|
// ID of the file in S3 storage
|
|
5717
6104
|
type: "text"
|
|
5718
6105
|
},
|
|
5719
|
-
{
|
|
5720
|
-
name: "s3endpoint",
|
|
5721
|
-
type: "text"
|
|
5722
|
-
},
|
|
5723
6106
|
{
|
|
5724
6107
|
name: "content",
|
|
5725
6108
|
type: "longText"
|
|
@@ -5749,22 +6132,25 @@ var ExuluApp = class {
|
|
|
5749
6132
|
create = async ({ contexts, agents, config, tools }) => {
|
|
5750
6133
|
this._contexts = {
|
|
5751
6134
|
...contexts,
|
|
5752
|
-
projectsContext,
|
|
5753
6135
|
codeStandardsContext,
|
|
5754
|
-
filesContext
|
|
6136
|
+
filesContext,
|
|
6137
|
+
outputsContext
|
|
5755
6138
|
};
|
|
5756
6139
|
this._agents = [
|
|
5757
|
-
|
|
5758
|
-
|
|
6140
|
+
claudeSonnet4Agent,
|
|
6141
|
+
claudeOpus4Agent,
|
|
6142
|
+
gpt5MiniAgent,
|
|
6143
|
+
gpt5agent,
|
|
5759
6144
|
...agents ?? []
|
|
5760
6145
|
];
|
|
5761
6146
|
this._config = config;
|
|
5762
6147
|
this._tools = [
|
|
5763
6148
|
...tools ?? [],
|
|
5764
6149
|
// Add contexts as tools
|
|
5765
|
-
...Object.values(contexts || {}).map((context) => context.tool())
|
|
5766
|
-
//
|
|
5767
|
-
|
|
6150
|
+
...Object.values(contexts || {}).map((context) => context.tool())
|
|
6151
|
+
// Because agents are stored in the database, we add those as tools
|
|
6152
|
+
// at request time, not during ExuluApp initialization. We add them
|
|
6153
|
+
// in the grahql tools resolver.
|
|
5768
6154
|
];
|
|
5769
6155
|
const checks = [
|
|
5770
6156
|
...Object.keys(this._contexts || {}).map((x) => ({
|
|
@@ -5857,7 +6243,7 @@ var ExuluApp = class {
|
|
|
5857
6243
|
create: async () => {
|
|
5858
6244
|
let tracer;
|
|
5859
6245
|
if (this._config?.telemetry?.enabled) {
|
|
5860
|
-
tracer =
|
|
6246
|
+
tracer = trace.getTracer("exulu", "1.0.0");
|
|
5861
6247
|
}
|
|
5862
6248
|
const logger = logger_default({
|
|
5863
6249
|
enableOtel: this._config?.workers?.telemetry?.enabled ?? false
|
|
@@ -5881,7 +6267,7 @@ var ExuluApp = class {
|
|
|
5881
6267
|
const app = this._expressApp;
|
|
5882
6268
|
let tracer;
|
|
5883
6269
|
if (this._config?.telemetry?.enabled) {
|
|
5884
|
-
tracer =
|
|
6270
|
+
tracer = trace.getTracer("exulu", "1.0.0");
|
|
5885
6271
|
}
|
|
5886
6272
|
const logger = logger_default({
|
|
5887
6273
|
enableOtel: this._config?.telemetry?.enabled ?? false
|
|
@@ -5893,7 +6279,8 @@ var ExuluApp = class {
|
|
|
5893
6279
|
this._tools,
|
|
5894
6280
|
Object.values(this._contexts ?? {}),
|
|
5895
6281
|
this._config,
|
|
5896
|
-
tracer
|
|
6282
|
+
tracer,
|
|
6283
|
+
filesContext
|
|
5897
6284
|
);
|
|
5898
6285
|
if (this._config?.MCP.enabled) {
|
|
5899
6286
|
const mcp = new ExuluMCP();
|
|
@@ -6173,7 +6560,7 @@ var RecursiveRules = class _RecursiveRules {
|
|
|
6173
6560
|
* @param {string} path - The path to the recipe.
|
|
6174
6561
|
* @returns {Promise<RecursiveRules>} The RecursiveRules object.
|
|
6175
6562
|
*/
|
|
6176
|
-
static async fromRecipe(name = "default", lang = "en",
|
|
6563
|
+
static async fromRecipe(name = "default", lang = "en", path2) {
|
|
6177
6564
|
throw new Error("Not implemented");
|
|
6178
6565
|
}
|
|
6179
6566
|
};
|
|
@@ -7162,7 +7549,20 @@ var generateApiKey = async (name, email) => {
|
|
|
7162
7549
|
};
|
|
7163
7550
|
|
|
7164
7551
|
// src/postgres/init-db.ts
|
|
7165
|
-
var {
|
|
7552
|
+
var {
|
|
7553
|
+
agentsSchema: agentsSchema3,
|
|
7554
|
+
evalResultsSchema: evalResultsSchema3,
|
|
7555
|
+
jobsSchema: jobsSchema3,
|
|
7556
|
+
agentSessionsSchema: agentSessionsSchema3,
|
|
7557
|
+
agentMessagesSchema: agentMessagesSchema3,
|
|
7558
|
+
rolesSchema: rolesSchema3,
|
|
7559
|
+
usersSchema: usersSchema3,
|
|
7560
|
+
statisticsSchema: statisticsSchema3,
|
|
7561
|
+
variablesSchema: variablesSchema3,
|
|
7562
|
+
workflowTemplatesSchema: workflowTemplatesSchema3,
|
|
7563
|
+
rbacSchema: rbacSchema3,
|
|
7564
|
+
projectsSchema: projectsSchema3
|
|
7565
|
+
} = coreSchemas.get();
|
|
7166
7566
|
var addMissingFields = async (knex, tableName, fields, skipFields = []) => {
|
|
7167
7567
|
for (const field of fields) {
|
|
7168
7568
|
const { type, name, default: defaultValue, unique } = field;
|
|
@@ -7219,6 +7619,7 @@ var up = async function(knex) {
|
|
|
7219
7619
|
}
|
|
7220
7620
|
};
|
|
7221
7621
|
for (const schema of schemas) {
|
|
7622
|
+
console.log(`[EXULU] Creating ${schema.name.plural} table.`, schema.fields);
|
|
7222
7623
|
await createTable(schema);
|
|
7223
7624
|
}
|
|
7224
7625
|
if (!await knex.schema.hasTable("verification_token")) {
|
|
@@ -7375,6 +7776,9 @@ var create = ({
|
|
|
7375
7776
|
return sdk;
|
|
7376
7777
|
};
|
|
7377
7778
|
|
|
7779
|
+
// src/index.ts
|
|
7780
|
+
import CryptoJS4 from "crypto-js";
|
|
7781
|
+
|
|
7378
7782
|
// types/enums/jobs.ts
|
|
7379
7783
|
var JOB_STATUS_ENUM = {
|
|
7380
7784
|
completed: "completed",
|
|
@@ -7393,6 +7797,113 @@ var ExuluJobs = {
|
|
|
7393
7797
|
validate: validateJob
|
|
7394
7798
|
}
|
|
7395
7799
|
};
|
|
7800
|
+
var ExuluDefaultContexts = {
|
|
7801
|
+
files: filesContext,
|
|
7802
|
+
codeStandards: codeStandardsContext,
|
|
7803
|
+
outputs: outputsContext
|
|
7804
|
+
};
|
|
7805
|
+
var ExuluDefaultAgents = {
|
|
7806
|
+
anthropic: {
|
|
7807
|
+
opus4: claudeOpus4Agent,
|
|
7808
|
+
sonnet4: claudeSonnet4Agent
|
|
7809
|
+
},
|
|
7810
|
+
openai: {
|
|
7811
|
+
gpt5Mini: gpt5MiniAgent,
|
|
7812
|
+
gpt5: gpt5agent
|
|
7813
|
+
}
|
|
7814
|
+
};
|
|
7815
|
+
var ExuluVariables = {
|
|
7816
|
+
get: async (name) => {
|
|
7817
|
+
const { db: db3 } = await postgresClient();
|
|
7818
|
+
let variable = await db3.from("variables").where({ name }).first();
|
|
7819
|
+
if (!variable) {
|
|
7820
|
+
throw new Error(`Variable ${name} not found.`);
|
|
7821
|
+
}
|
|
7822
|
+
if (variable.encrypted) {
|
|
7823
|
+
const bytes = CryptoJS4.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
|
|
7824
|
+
variable.value = bytes.toString(CryptoJS4.enc.Utf8);
|
|
7825
|
+
}
|
|
7826
|
+
return variable.value;
|
|
7827
|
+
}
|
|
7828
|
+
};
|
|
7829
|
+
var ExuluUtils = {
|
|
7830
|
+
batch: async ({
|
|
7831
|
+
fn,
|
|
7832
|
+
size,
|
|
7833
|
+
inputs,
|
|
7834
|
+
delay,
|
|
7835
|
+
retries
|
|
7836
|
+
}) => {
|
|
7837
|
+
if (!size) {
|
|
7838
|
+
size = 10;
|
|
7839
|
+
}
|
|
7840
|
+
if (!inputs) {
|
|
7841
|
+
throw new Error("Inputs are required.");
|
|
7842
|
+
}
|
|
7843
|
+
if (!delay) {
|
|
7844
|
+
delay = 0;
|
|
7845
|
+
}
|
|
7846
|
+
let results = [];
|
|
7847
|
+
let lastBatchTime = 0;
|
|
7848
|
+
for (let start = 0; start < inputs.length; start += size) {
|
|
7849
|
+
const currentTime = Date.now();
|
|
7850
|
+
const timeSinceLastBatch = currentTime - lastBatchTime;
|
|
7851
|
+
if (timeSinceLastBatch < delay * 1e3) {
|
|
7852
|
+
console.log("[EXULU] Utils function, waiting for", delay - timeSinceLastBatch, "seconds");
|
|
7853
|
+
await new Promise((resolve) => setTimeout(resolve, delay * 1e3 - timeSinceLastBatch));
|
|
7854
|
+
}
|
|
7855
|
+
lastBatchTime = Date.now();
|
|
7856
|
+
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})`);
|
|
7857
|
+
const end = start + size > inputs.length ? inputs.length : start + size;
|
|
7858
|
+
const slicedResults = await Promise.all(inputs.slice(start, end).map((data, i) => {
|
|
7859
|
+
if (retries?.max) {
|
|
7860
|
+
return ExuluUtils.retry({
|
|
7861
|
+
fn: async () => {
|
|
7862
|
+
return await fn(data);
|
|
7863
|
+
},
|
|
7864
|
+
retries: retries.max,
|
|
7865
|
+
delays: retries.delays
|
|
7866
|
+
});
|
|
7867
|
+
} else {
|
|
7868
|
+
return fn(data);
|
|
7869
|
+
}
|
|
7870
|
+
}));
|
|
7871
|
+
results = [
|
|
7872
|
+
...results,
|
|
7873
|
+
...slicedResults
|
|
7874
|
+
];
|
|
7875
|
+
}
|
|
7876
|
+
return results;
|
|
7877
|
+
},
|
|
7878
|
+
retry: async ({
|
|
7879
|
+
fn,
|
|
7880
|
+
retries,
|
|
7881
|
+
delays
|
|
7882
|
+
}) => {
|
|
7883
|
+
if (!retries) {
|
|
7884
|
+
retries = 3;
|
|
7885
|
+
}
|
|
7886
|
+
if (!delays) {
|
|
7887
|
+
delays = [1e3, 5e3, 1e4];
|
|
7888
|
+
}
|
|
7889
|
+
for (let i = 0; i < retries; i++) {
|
|
7890
|
+
try {
|
|
7891
|
+
return await fn();
|
|
7892
|
+
} catch (error) {
|
|
7893
|
+
console.error(`[EXULU] Util function, retry attempt ${i + 1} failed:`, error);
|
|
7894
|
+
if (i >= retries - 1) {
|
|
7895
|
+
throw error;
|
|
7896
|
+
}
|
|
7897
|
+
if (!delays[i]) {
|
|
7898
|
+
delays[i] = delays[delays.length - 1] || 1e4;
|
|
7899
|
+
}
|
|
7900
|
+
const delay = delays && delays[i] ? delays[i] : 1e4;
|
|
7901
|
+
console.log(`[EXULU] Util function, retrying in ${delay / 1e3} seconds...`);
|
|
7902
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
7903
|
+
}
|
|
7904
|
+
}
|
|
7905
|
+
}
|
|
7906
|
+
};
|
|
7396
7907
|
var ExuluOtel = {
|
|
7397
7908
|
create: ({
|
|
7398
7909
|
SIGNOZ_ACCESS_TOKEN,
|
|
@@ -7435,11 +7946,15 @@ export {
|
|
|
7435
7946
|
authentication as ExuluAuthentication,
|
|
7436
7947
|
ExuluChunkers,
|
|
7437
7948
|
ExuluContext,
|
|
7949
|
+
ExuluDefaultAgents,
|
|
7950
|
+
ExuluDefaultContexts,
|
|
7438
7951
|
ExuluEmbedder,
|
|
7439
7952
|
ExuluEval,
|
|
7440
7953
|
ExuluJobs,
|
|
7441
7954
|
ExuluOtel,
|
|
7442
7955
|
queues as ExuluQueues,
|
|
7443
7956
|
ExuluTool2 as ExuluTool,
|
|
7957
|
+
ExuluUtils,
|
|
7958
|
+
ExuluVariables,
|
|
7444
7959
|
db2 as db
|
|
7445
7960
|
};
|