@exulu/backend 1.23.2 → 1.24.0

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