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