@exulu/backend 1.23.3 → 1.25.0

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