@exulu/backend 1.13.0 → 1.14.1

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
@@ -75,19 +75,19 @@ import "bullmq";
75
75
  import { z } from "zod";
76
76
  import * as fs from "fs";
77
77
  import * as path from "path";
78
- import { generateObject, generateText, streamText, tool } from "ai";
78
+ import { convertToModelMessages, createIdGenerator, generateObject, generateText, streamText, tool, validateUIMessages, stepCountIs } from "ai";
79
79
 
80
80
  // types/enums/statistics.ts
81
81
  var STATISTICS_TYPE_ENUM = {
82
- CONTEXT_RETRIEVE: "context.retrieve",
83
- SOURCE_UPDATE: "source.update",
84
- EMBEDDER_UPSERT: "embedder.upsert",
85
- EMBEDDER_GENERATE: "embedder.generate",
86
- EMBEDDER_DELETE: "embedder.delete",
87
- WORKFLOW_RUN: "workflow.run",
88
- CONTEXT_UPSERT: "context.upsert",
89
- TOOL_CALL: "tool.call",
90
- AGENT_RUN: "agent.run"
82
+ CONTEXT_RETRIEVE: "CONTEXT_RETRIEVE",
83
+ SOURCE_UPDATE: "SOURCE_UPDATE",
84
+ EMBEDDER_UPSERT: "EMBEDDER_UPSERT",
85
+ EMBEDDER_GENERATE: "EMBEDDER_GENERATE",
86
+ EMBEDDER_DELETE: "EMBEDDER_DELETE",
87
+ WORKFLOW_RUN: "WORKFLOW_RUN",
88
+ CONTEXT_UPSERT: "CONTEXT_UPSERT",
89
+ TOOL_CALL: "TOOL_CALL",
90
+ AGENT_RUN: "AGENT_RUN"
91
91
  };
92
92
 
93
93
  // types/enums/eval-types.ts
@@ -262,58 +262,81 @@ var bullmqDecorator = async ({
262
262
 
263
263
  // src/registry/utils/map-types.ts
264
264
  var mapType = (t, type, name, defaultValue, unique) => {
265
- if (type === "text") {
266
- t.text(name);
265
+ if (type === "text" || type === "enum") {
266
+ if (defaultValue) {
267
+ t.text(name).defaultTo(defaultValue);
268
+ } else {
269
+ t.text(name);
270
+ }
267
271
  if (unique) t.unique(name);
268
- if (defaultValue) t.defaultTo(defaultValue);
269
272
  return;
270
273
  }
271
274
  if (type === "longText") {
272
- t.text(name);
275
+ if (defaultValue) {
276
+ t.text(name).defaultTo(defaultValue);
277
+ } else {
278
+ t.text(name);
279
+ }
273
280
  if (unique) t.unique(name);
274
- if (defaultValue) t.defaultTo(defaultValue);
275
281
  return;
276
282
  }
277
283
  if (type === "shortText") {
278
- t.string(name, 100);
284
+ if (defaultValue) {
285
+ t.string(name, 100).defaultTo(defaultValue);
286
+ } else {
287
+ t.string(name, 100);
288
+ }
279
289
  if (unique) t.unique(name);
280
- if (defaultValue) t.defaultTo(defaultValue);
281
290
  return;
282
291
  }
283
292
  if (type === "number") {
284
- t.float(name);
293
+ if (defaultValue) {
294
+ t.float(name).defaultTo(defaultValue);
295
+ } else {
296
+ t.float(name);
297
+ }
285
298
  if (unique) t.unique(name);
286
- if (defaultValue) t.defaultTo(defaultValue);
287
299
  return;
288
300
  }
289
301
  if (type === "boolean") {
290
302
  t.boolean(name).defaultTo(defaultValue || false);
291
303
  if (unique) t.unique(name);
292
- if (defaultValue) t.defaultTo(defaultValue);
293
304
  return;
294
305
  }
295
306
  if (type === "code") {
296
- t.text(name);
307
+ if (defaultValue) {
308
+ t.text(name).defaultTo(defaultValue);
309
+ } else {
310
+ t.text(name);
311
+ }
297
312
  if (unique) t.unique(name);
298
- if (defaultValue) t.defaultTo(defaultValue);
299
313
  return;
300
314
  }
301
315
  if (type === "json") {
302
- t.jsonb(name);
316
+ if (defaultValue) {
317
+ t.jsonb(name).defaultTo(defaultValue);
318
+ } else {
319
+ t.jsonb(name);
320
+ }
303
321
  if (unique) t.unique(name);
304
- if (defaultValue) t.defaultTo(defaultValue);
305
322
  return;
306
323
  }
307
324
  if (type === "date") {
308
- t.timestamp(name);
325
+ if (defaultValue) {
326
+ t.timestamp(name).defaultTo(defaultValue);
327
+ } else {
328
+ t.timestamp(name);
329
+ }
309
330
  if (unique) t.unique(name);
310
- if (defaultValue) t.defaultTo(defaultValue);
311
331
  return;
312
332
  }
313
333
  if (type === "uuid") {
314
- t.uuid(name);
334
+ if (defaultValue) {
335
+ t.uuid(name).defaultTo(defaultValue);
336
+ } else {
337
+ t.uuid(name);
338
+ }
315
339
  if (unique) t.unique(name);
316
- if (defaultValue) t.defaultTo(defaultValue);
317
340
  return;
318
341
  }
319
342
  throw new Error("Invalid type: " + type);
@@ -403,6 +426,7 @@ var ExuluEvalUtils = {
403
426
 
404
427
  // src/registry/classes.ts
405
428
  import CryptoJS from "crypto-js";
429
+ import "express";
406
430
  function sanitizeToolName(name) {
407
431
  if (typeof name !== "string") return "";
408
432
  let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
@@ -421,7 +445,7 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
421
445
  console.log("[EXULU] Sanitized tools", sanitizedTools);
422
446
  const askForConfirmation = {
423
447
  description: "Ask the user for confirmation.",
424
- parameters: z.object({
448
+ inputSchema: z.object({
425
449
  message: z.string().describe("The message to ask for confirmation.")
426
450
  })
427
451
  };
@@ -532,10 +556,12 @@ var ExuluAgent = class {
532
556
  this.model = this.config?.model;
533
557
  }
534
558
  get providerName() {
535
- return this.config?.model?.create({ apiKey: "" })?.provider || "";
559
+ const model = this.config?.model?.create({ apiKey: "" });
560
+ return typeof model === "string" ? model : model?.provider || "";
536
561
  }
537
562
  get modelName() {
538
- return this.config?.model?.create({ apiKey: "" })?.modelId || "";
563
+ const model = this.config?.model?.create({ apiKey: "" });
564
+ return typeof model === "string" ? model : model?.modelId || "";
539
565
  }
540
566
  // Exports the agent as a tool that can be used by another agent
541
567
  // todo test this
@@ -561,28 +587,44 @@ var ExuluAgent = class {
561
587
  }
562
588
  });
563
589
  };
564
- generateSync = async ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
590
+ generateSync = async ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
565
591
  if (!this.model) {
566
592
  throw new Error("Model is required for streaming.");
567
593
  }
568
594
  if (!this.config) {
569
595
  throw new Error("Config is required for generating.");
570
596
  }
571
- if (prompt && messages) {
572
- throw new Error("Prompt and messages cannot be provided at the same time.");
597
+ if (prompt && message) {
598
+ throw new Error("Message and prompt cannot be provided at the same time.");
573
599
  }
574
600
  const model = this.model.create({
575
601
  apiKey: providerApiKey
576
602
  });
603
+ let messages = [];
604
+ if (message && session && user) {
605
+ const previousMessages = await getAgentMessages({
606
+ session,
607
+ user,
608
+ limit: 50,
609
+ page: 1
610
+ });
611
+ const previousMessagesContent = previousMessages.map((message2) => JSON.parse(message2.content));
612
+ messages = await validateUIMessages({
613
+ // append the new message to the previous messages:
614
+ messages: [...previousMessagesContent, message]
615
+ });
616
+ }
617
+ console.log("[EXULU] Model provider key", providerApiKey);
618
+ console.log("[EXULU] Tool configs", toolConfigs);
577
619
  const { text } = await generateText({
578
620
  model,
579
621
  // Should be a LanguageModelV1
580
622
  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.",
581
- messages,
623
+ messages: messages ? convertToModelMessages(messages) : void 0,
582
624
  prompt,
583
625
  maxRetries: 2,
584
626
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
585
- maxSteps: 5
627
+ stopWhen: [stepCountIs(5)]
586
628
  });
587
629
  if (statistics) {
588
630
  await updateStatistic({
@@ -595,36 +637,61 @@ var ExuluAgent = class {
595
637
  }
596
638
  return text;
597
639
  };
598
- generateStream = ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
640
+ generateStream = async ({ express: express3, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
599
641
  if (!this.model) {
600
642
  throw new Error("Model is required for streaming.");
601
643
  }
602
644
  if (!this.config) {
603
645
  throw new Error("Config is required for generating.");
604
646
  }
605
- if (prompt && messages) {
606
- throw new Error("Prompt and messages cannot be provided at the same time.");
647
+ if (!message) {
648
+ throw new Error("Message is required for streaming.");
607
649
  }
608
650
  const model = this.model.create({
609
651
  apiKey: providerApiKey
610
652
  });
653
+ let messages = [];
654
+ const previousMessages = await getAgentMessages({
655
+ session,
656
+ user,
657
+ limit: 50,
658
+ page: 1
659
+ });
660
+ const previousMessagesContent = previousMessages.map((message2) => JSON.parse(message2.content));
661
+ messages = await validateUIMessages({
662
+ // append the new message to the previous messages:
663
+ messages: [...previousMessagesContent, message]
664
+ });
611
665
  console.log("[EXULU] Model provider key", providerApiKey);
612
666
  console.log("[EXULU] Tool configs", toolConfigs);
613
- return streamText({
667
+ const result = streamText({
614
668
  model,
615
669
  // Should be a LanguageModelV1
616
- messages,
617
- prompt,
670
+ messages: messages ? convertToModelMessages(messages) : void 0,
618
671
  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.",
619
672
  maxRetries: 2,
620
673
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
621
- maxSteps: 5,
622
674
  onError: (error) => console.error("[EXULU] chat stream error.", error),
623
- onFinish: async ({ response, usage }) => {
675
+ stopWhen: [stepCountIs(5)]
676
+ });
677
+ result.consumeStream();
678
+ result.pipeUIMessageStreamToResponse(express3.res, {
679
+ originalMessages: messages,
680
+ sendReasoning: true,
681
+ generateMessageId: createIdGenerator({
682
+ prefix: "msg_",
683
+ size: 16
684
+ }),
685
+ onFinish: async ({ messages: messages2 }) => {
624
686
  console.info(
625
687
  "[EXULU] chat stream finished.",
626
- usage
688
+ messages2
627
689
  );
690
+ await saveChat({
691
+ session,
692
+ user,
693
+ messages: messages2
694
+ });
628
695
  if (statistics) {
629
696
  await updateStatistic({
630
697
  name: "count",
@@ -636,8 +703,26 @@ var ExuluAgent = class {
636
703
  }
637
704
  }
638
705
  });
706
+ return;
639
707
  };
640
708
  };
709
+ var getAgentMessages = async ({ session, user, limit, page }) => {
710
+ const { db: db3 } = await postgresClient();
711
+ const messages = await db3.from("agent_messages").where({ session, user }).limit(limit).offset(page * limit);
712
+ return messages;
713
+ };
714
+ var saveChat = async ({ session, user, messages }) => {
715
+ const { db: db3 } = await postgresClient();
716
+ const promises2 = messages.map((message) => {
717
+ return db3.from("agent_messages").insert({
718
+ session,
719
+ user,
720
+ content: JSON.stringify(message),
721
+ title: message.role === "user" ? "User" : "Assistant"
722
+ });
723
+ });
724
+ await Promise.all(promises2);
725
+ };
641
726
  var ExuluEmbedder = class {
642
727
  id;
643
728
  name;
@@ -888,7 +973,7 @@ var ExuluTool = class {
888
973
  this.type = type;
889
974
  this.tool = tool({
890
975
  description,
891
- parameters: inputSchema || z.object({}),
976
+ inputSchema: inputSchema || z.object({}),
892
977
  execute: execute2
893
978
  });
894
979
  }
@@ -1377,22 +1462,27 @@ var ExuluSource = class {
1377
1462
  var updateStatistic = async (statistic) => {
1378
1463
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1379
1464
  const { db: db3 } = await postgresClient();
1380
- const existing = await db3.from("statistics").where({
1465
+ const existing = await db3.from("tracking").where({
1466
+ ...statistic.user ? { user: statistic.user } : {},
1467
+ ...statistic.role ? { role: statistic.role } : {},
1381
1468
  name: statistic.name,
1382
1469
  label: statistic.label,
1383
1470
  type: statistic.type,
1384
1471
  createdAt: currentDate
1385
1472
  }).first();
1473
+ console.log("!!! existing !!!", existing);
1386
1474
  if (!existing) {
1387
- await db3.from("statistics").insert({
1475
+ await db3.from("tracking").insert({
1388
1476
  name: statistic.name,
1389
1477
  label: statistic.label,
1390
1478
  type: statistic.type,
1391
1479
  total: statistic.count ?? 1,
1392
- createdAt: currentDate
1480
+ createdAt: currentDate,
1481
+ ...statistic.user ? { user: statistic.user } : {},
1482
+ ...statistic.role ? { role: statistic.role } : {}
1393
1483
  });
1394
1484
  } else {
1395
- await db3.from("statistics").update({
1485
+ await db3.from("tracking").update({
1396
1486
  total: db3.raw("total + ?", [statistic.count ?? 1])
1397
1487
  }).where({
1398
1488
  name: statistic.name,
@@ -1731,25 +1821,25 @@ var requestValidators = {
1731
1821
  message: "Missing body."
1732
1822
  };
1733
1823
  }
1734
- if (!req.body.threadId) {
1824
+ if (!req.headers["user"]) {
1735
1825
  return {
1736
1826
  error: true,
1737
1827
  code: 400,
1738
- message: "Missing threadId in body."
1828
+ message: 'Missing "user" property in headers.'
1739
1829
  };
1740
1830
  }
1741
- if (!req.body.resourceId) {
1831
+ if (!req.headers["session"]) {
1742
1832
  return {
1743
1833
  error: true,
1744
1834
  code: 400,
1745
- message: "Missing resourceId in body."
1835
+ message: 'Missing "session" property in headers.'
1746
1836
  };
1747
1837
  }
1748
- if (!req.body.messages) {
1838
+ if (!req.body.message) {
1749
1839
  return {
1750
1840
  error: true,
1751
1841
  code: 400,
1752
- message: 'Missing "messages" property in body.'
1842
+ message: 'Missing "message" property in body.'
1753
1843
  };
1754
1844
  }
1755
1845
  return {
@@ -1853,6 +1943,9 @@ var map = (field) => {
1853
1943
  case "code":
1854
1944
  type = "String";
1855
1945
  break;
1946
+ case "enum":
1947
+ type = field.enumValues ? `${field.name}Enum` : "String";
1948
+ break;
1856
1949
  case "number":
1857
1950
  type = "Float";
1858
1951
  break;
@@ -1871,6 +1964,16 @@ var map = (field) => {
1871
1964
  return type;
1872
1965
  };
1873
1966
  function createTypeDefs(table) {
1967
+ const enumDefs = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
1968
+ const enumValues = field.enumValues.map((value) => {
1969
+ const sanitized = String(value).replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[0-9]/, "_$&").toUpperCase();
1970
+ return ` ${sanitized}`;
1971
+ }).join("\n");
1972
+ return `
1973
+ enum ${field.name}Enum {
1974
+ ${enumValues}
1975
+ }`;
1976
+ }).join("\n");
1874
1977
  const fields = table.fields.map((field) => {
1875
1978
  let type;
1876
1979
  type = map(field);
@@ -1882,8 +1985,6 @@ function createTypeDefs(table) {
1882
1985
  type ${table.name.singular} {
1883
1986
  ${fields.join("\n")}
1884
1987
  ${table.fields.find((field) => field.name === "id") ? "" : "id: ID!"}
1885
- createdAt: Date!
1886
- updatedAt: Date!
1887
1988
  ${rbacField}
1888
1989
  }
1889
1990
  `;
@@ -1894,39 +1995,62 @@ ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
1894
1995
  ${rbacInputField}
1895
1996
  }
1896
1997
  `;
1897
- return typeDef + inputDef;
1998
+ return enumDefs + typeDef + inputDef;
1898
1999
  }
1899
2000
  function createFilterTypeDefs(table) {
1900
2001
  const fieldFilters = table.fields.map((field) => {
1901
2002
  let type;
1902
- type = map(field);
2003
+ if (field.type === "enum" && field.enumValues) {
2004
+ type = `${field.name}Enum`;
2005
+ } else {
2006
+ type = map(field);
2007
+ }
1903
2008
  return `
1904
2009
  ${field.name}: FilterOperator${type}`;
1905
2010
  });
1906
2011
  const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2012
+ const enumFilterOperators = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
2013
+ const enumTypeName = `${field.name}Enum`;
2014
+ return `
2015
+ input FilterOperator${enumTypeName} {
2016
+ eq: ${enumTypeName}
2017
+ ne: ${enumTypeName}
2018
+ in: [${enumTypeName}]
2019
+ and: [FilterOperator${enumTypeName}]
2020
+ or: [FilterOperator${enumTypeName}]
2021
+ }`;
2022
+ }).join("\n");
1907
2023
  const operatorTypes = `
1908
2024
  input FilterOperatorString {
1909
2025
  eq: String
1910
2026
  ne: String
1911
2027
  in: [String]
1912
2028
  contains: String
2029
+ and: [FilterOperatorString]
2030
+ or: [FilterOperatorString]
1913
2031
  }
1914
2032
 
1915
2033
  input FilterOperatorDate {
1916
2034
  lte: Date
1917
2035
  gte: Date
2036
+ and: [FilterOperatorDate]
2037
+ or: [FilterOperatorDate]
1918
2038
  }
1919
2039
 
1920
2040
  input FilterOperatorFloat {
1921
2041
  eq: Float
1922
2042
  ne: Float
1923
2043
  in: [Float]
2044
+ and: [FilterOperatorFloat]
2045
+ or: [FilterOperatorFloat]
1924
2046
  }
1925
2047
 
1926
2048
  input FilterOperatorBoolean {
1927
2049
  eq: Boolean
1928
2050
  ne: Boolean
1929
2051
  in: [Boolean]
2052
+ and: [FilterOperatorBoolean]
2053
+ or: [FilterOperatorBoolean]
1930
2054
  }
1931
2055
 
1932
2056
  input FilterOperatorJSON {
@@ -1945,6 +2069,8 @@ enum SortDirection {
1945
2069
  DESC
1946
2070
  }
1947
2071
 
2072
+ ${enumFilterOperators}
2073
+
1948
2074
  input Filter${tableNameSingularUpperCaseFirst} {
1949
2075
  ${fieldFilters.join("\n")}
1950
2076
  }`;
@@ -2016,6 +2142,17 @@ function createMutations(table) {
2016
2142
  const validateWriteAccess = async (id, context) => {
2017
2143
  try {
2018
2144
  const { db: db3, req, user } = context;
2145
+ if (user.super_admin === true) {
2146
+ return true;
2147
+ }
2148
+ if (!user.role || !(table.name.plural === "agents" && user.role.agents === "write") && !(table.name.plural === "workflow_templates" && user.role.workflows === "write") && !(table.name.plural === "variables" && user.role.variables === "write") && !(table.name.plural === "users" && user.role.users === "write")) {
2149
+ console.error("Access control error: no role found for current user or no access to entity type.");
2150
+ throw new Error("Access control error: no role found for current user or no access to entity type.");
2151
+ }
2152
+ const hasRBAC = table.RBAC === true;
2153
+ if (!hasRBAC) {
2154
+ return true;
2155
+ }
2019
2156
  const record = await db3.from(tableNamePlural).select(["rights_mode", "created_by"]).where({ id }).first();
2020
2157
  if (!record) {
2021
2158
  throw new Error("Record not found");
@@ -2025,17 +2162,6 @@ function createMutations(table) {
2025
2162
  throw new Error("You are not authorized to edit this record");
2026
2163
  }
2027
2164
  }
2028
- const hasRBAC = table.RBAC === true;
2029
- if (!hasRBAC) {
2030
- return true;
2031
- }
2032
- if (user.super_admin === true) {
2033
- return true;
2034
- }
2035
- if (!user.role || !(table.name.plural === "agents" && user.role.agents === "write") && !(table.name.plural === "workflow_templates" && user.role.workflows === "write") && !(table.name.plural === "variables" && user.role.variables === "write") && !(table.name.plural === "users" && user.role.users === "write")) {
2036
- console.error("Access control error: no role found for current user or no access to entity type.");
2037
- throw new Error("Access control error: no role found for current user or no access to entity type.");
2038
- }
2039
2165
  if (record.rights_mode === "public") {
2040
2166
  return true;
2041
2167
  }
@@ -2216,15 +2342,15 @@ function createMutations(table) {
2216
2342
  var applyAccessControl = (table, user, query) => {
2217
2343
  console.log("table", table);
2218
2344
  const tableNamePlural = table.name.plural.toLowerCase();
2345
+ if (!user.super_admin && table.name.plural === "jobs") {
2346
+ query = query.where("created_by", user.id);
2347
+ return query;
2348
+ }
2219
2349
  const hasRBAC = table.RBAC === true;
2220
2350
  if (!hasRBAC) {
2221
2351
  return query;
2222
2352
  }
2223
- if (user.super_admin === true) {
2224
- return query;
2225
- }
2226
- if (table.name.plural === "jobs") {
2227
- query = query.where("created_by", user.id);
2353
+ if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2228
2354
  return query;
2229
2355
  }
2230
2356
  if (!user.role || !(table.name.plural === "agents" && (user.role.agents === "read" || user.role.agents === "write")) && !(table.name.plural === "workflow_templates" && (user.role.workflows === "read" || user.role.workflows === "write")) && !(table.name.plural === "variables" && (user.role.variables === "read" || user.role.variables === "write")) && !(table.name.plural === "users" && (user.role.users === "read" || user.role.users === "write"))) {
@@ -2255,6 +2381,27 @@ var applyAccessControl = (table, user, query) => {
2255
2381
  }
2256
2382
  return query;
2257
2383
  };
2384
+ var converOperatorToQuery = (query, fieldName, operators) => {
2385
+ if (operators.eq !== void 0) {
2386
+ query = query.where(fieldName, operators.eq);
2387
+ }
2388
+ if (operators.ne !== void 0) {
2389
+ query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2390
+ }
2391
+ if (operators.in !== void 0) {
2392
+ query = query.whereIn(fieldName, operators.in);
2393
+ }
2394
+ if (operators.contains !== void 0) {
2395
+ query = query.where(fieldName, "like", `%${operators.contains}%`);
2396
+ }
2397
+ if (operators.lte !== void 0) {
2398
+ query = query.where(fieldName, "<=", operators.lte);
2399
+ }
2400
+ if (operators.gte !== void 0) {
2401
+ query = query.where(fieldName, ">=", operators.gte);
2402
+ }
2403
+ return query;
2404
+ };
2258
2405
  function createQueries(table) {
2259
2406
  const tableNamePlural = table.name.plural.toLowerCase();
2260
2407
  const tableNameSingular = table.name.singular.toLowerCase();
@@ -2262,18 +2409,19 @@ function createQueries(table) {
2262
2409
  filters.forEach((filter) => {
2263
2410
  Object.entries(filter).forEach(([fieldName, operators]) => {
2264
2411
  if (operators) {
2265
- if (operators.eq !== void 0) {
2266
- query = query.where(fieldName, operators.eq);
2267
- }
2268
- if (operators.ne !== void 0) {
2269
- query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2270
- }
2271
- if (operators.in !== void 0) {
2272
- query = query.whereIn(fieldName, operators.in);
2412
+ if (operators.and !== void 0) {
2413
+ console.log("operators.and", operators.and);
2414
+ operators.and.forEach((operator) => {
2415
+ query = converOperatorToQuery(query, fieldName, operator);
2416
+ });
2273
2417
  }
2274
- if (operators.contains !== void 0) {
2275
- query = query.where(fieldName, "like", `%${operators.contains}%`);
2418
+ if (operators.or !== void 0) {
2419
+ operators.or.forEach((operator) => {
2420
+ query = converOperatorToQuery(query, fieldName, operator);
2421
+ });
2276
2422
  }
2423
+ query = converOperatorToQuery(query, fieldName, operators);
2424
+ console.log("query", query);
2277
2425
  }
2278
2426
  });
2279
2427
  });
@@ -2346,57 +2494,36 @@ function createQueries(table) {
2346
2494
  query = applyFilters(query, filters);
2347
2495
  query = applyAccessControl(table, context.user, query);
2348
2496
  if (groupBy) {
2349
- const results = await query.select(groupBy).count("* as count").groupBy(groupBy);
2497
+ query = query.select(groupBy).groupBy(groupBy);
2498
+ if (tableNamePlural === "tracking") {
2499
+ query = query.sum("total as count");
2500
+ } else {
2501
+ query = query.count("* as count");
2502
+ }
2503
+ const results = await query;
2504
+ console.log("!!! results !!!", results);
2350
2505
  return results.map((r) => ({
2351
2506
  group: r[groupBy],
2352
- count: Number(r.count)
2507
+ count: r.count ? Number(r.count) : 0
2353
2508
  }));
2354
2509
  } else {
2355
- const [{ count }] = await query.count("* as count");
2356
- return [{
2357
- group: "total",
2358
- count: Number(count)
2359
- }];
2360
- }
2361
- },
2362
- // Add jobStatistics query for jobs table (backward compatibility)
2363
- ...tableNamePlural === "jobs" ? {
2364
- jobStatistics: async (_, args, context, info) => {
2365
- const { user, agent, from, to } = args;
2366
- const { db: db3 } = context;
2367
- let query = db3("jobs");
2368
- if (user) {
2369
- query = query.where("user", user);
2370
- }
2371
- if (agent) {
2372
- query = query.where("agent", agent);
2373
- }
2374
- if (from) {
2375
- query = query.where("createdAt", ">=", from);
2376
- }
2377
- if (to) {
2378
- query = query.where("createdAt", "<=", to);
2510
+ if (tableNamePlural === "tracking") {
2511
+ query = query.sum("total as count");
2512
+ const [{ count }] = await query.sum("total as count");
2513
+ console.log("!!! count !!!", count);
2514
+ return [{
2515
+ group: "total",
2516
+ count: count ? Number(count) : 0
2517
+ }];
2518
+ } else {
2519
+ const [{ count }] = await query.count("* as count");
2520
+ return [{
2521
+ group: "total",
2522
+ count: count ? Number(count) : 0
2523
+ }];
2379
2524
  }
2380
- query = applyAccessControl(table, context.user, query);
2381
- const runningQuery = query.clone().whereIn("status", ["active", "waiting", "delayed", "paused"]);
2382
- const [{ runningCount }] = await runningQuery.count("* as runningCount");
2383
- const erroredQuery = query.clone().whereIn("status", ["failed", "stuck"]);
2384
- const [{ erroredCount }] = await erroredQuery.count("* as erroredCount");
2385
- const completedQuery = query.clone().where("status", "completed");
2386
- const [{ completedCount }] = await completedQuery.count("* as completedCount");
2387
- const failedQuery = query.clone().where("status", "failed");
2388
- const [{ failedCount }] = await failedQuery.count("* as failedCount");
2389
- const durationQuery = query.clone().where("status", "completed").whereNotNull("duration").select(db3.raw('AVG("duration") as averageDuration'));
2390
- const [{ averageDuration }] = await durationQuery;
2391
- return {
2392
- runningCount: Number(runningCount),
2393
- erroredCount: Number(erroredCount),
2394
- completedCount: Number(completedCount),
2395
- failedCount: Number(failedCount),
2396
- averageDuration: averageDuration ? Number(averageDuration) : 0
2397
- };
2398
2525
  }
2399
- } : {}
2526
+ }
2400
2527
  };
2401
2528
  }
2402
2529
  var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
@@ -2416,6 +2543,16 @@ var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
2416
2543
  };
2417
2544
  };
2418
2545
  function createSDL(tables) {
2546
+ tables.forEach((table) => {
2547
+ table.fields.push({
2548
+ name: "createdAt",
2549
+ type: "date"
2550
+ });
2551
+ table.fields.push({
2552
+ name: "updatedAt",
2553
+ type: "date"
2554
+ });
2555
+ });
2419
2556
  console.log("[EXULU] Creating SDL");
2420
2557
  let typeDefs = `
2421
2558
  scalar JSON
@@ -2472,7 +2609,6 @@ function createSDL(tables) {
2472
2609
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2473
2610
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2474
2611
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
2475
- ${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
2476
2612
  `;
2477
2613
  mutationDefs += `
2478
2614
  ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
@@ -2497,17 +2633,6 @@ type PageInfo {
2497
2633
  hasNextPage: Boolean!
2498
2634
  }
2499
2635
  `;
2500
- if (tableNamePlural === "jobs") {
2501
- modelDefs += `
2502
- type JobStatistics {
2503
- runningCount: Int!
2504
- erroredCount: Int!
2505
- completedCount: Int!
2506
- failedCount: Int!
2507
- averageDuration: Float!
2508
- }
2509
- `;
2510
- }
2511
2636
  Object.assign(resolvers.Query, createQueries(table));
2512
2637
  Object.assign(resolvers.Mutation, createMutations(table));
2513
2638
  if (table.RBAC) {
@@ -2599,6 +2724,10 @@ var agentMessagesSchema = {
2599
2724
  name: "title",
2600
2725
  type: "text"
2601
2726
  },
2727
+ {
2728
+ name: "user",
2729
+ type: "number"
2730
+ },
2602
2731
  {
2603
2732
  name: "session",
2604
2733
  type: "text"
@@ -2835,6 +2964,10 @@ var rolesSchema = {
2835
2964
  type: "text"
2836
2965
  // write | read access to agents
2837
2966
  },
2967
+ {
2968
+ name: "api",
2969
+ type: "text"
2970
+ },
2838
2971
  {
2839
2972
  name: "workflows",
2840
2973
  type: "text"
@@ -2854,8 +2987,8 @@ var rolesSchema = {
2854
2987
  };
2855
2988
  var statisticsSchema = {
2856
2989
  name: {
2857
- plural: "statistics",
2858
- singular: "statistic"
2990
+ plural: "tracking",
2991
+ singular: "tracking"
2859
2992
  },
2860
2993
  fields: [
2861
2994
  {
@@ -2868,11 +3001,20 @@ var statisticsSchema = {
2868
3001
  },
2869
3002
  {
2870
3003
  name: "type",
2871
- type: "text"
3004
+ type: "enum",
3005
+ enumValues: Object.values(STATISTICS_TYPE_ENUM)
2872
3006
  },
2873
3007
  {
2874
3008
  name: "total",
2875
3009
  type: "number"
3010
+ },
3011
+ {
3012
+ name: "user",
3013
+ type: "number"
3014
+ },
3015
+ {
3016
+ name: "role",
3017
+ type: "uuid"
2876
3018
  }
2877
3019
  ]
2878
3020
  };
@@ -2933,6 +3075,7 @@ var jobsSchema = {
2933
3075
  plural: "jobs",
2934
3076
  singular: "job"
2935
3077
  },
3078
+ RBAC: true,
2936
3079
  fields: [
2937
3080
  {
2938
3081
  name: "redis",
@@ -2942,10 +3085,6 @@ var jobsSchema = {
2942
3085
  name: "session",
2943
3086
  type: "text"
2944
3087
  },
2945
- {
2946
- name: "created_by",
2947
- type: "number"
2948
- },
2949
3088
  {
2950
3089
  name: "status",
2951
3090
  type: "text"
@@ -3588,11 +3727,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3588
3727
  { route: "/agents/:id", method: "GET", note: "Get specific agent" },
3589
3728
  { route: "/contexts", method: "GET", note: "List all contexts" },
3590
3729
  { route: "/contexts/:id", method: "GET", note: "Get specific context" },
3591
- { route: "/contexts/statistics", method: "GET", note: "Get context statistics" },
3592
3730
  { route: "/tools", method: "GET", note: "List all tools" },
3593
3731
  { route: "/tools/:id", method: "GET", note: "Get specific tool" },
3594
- { route: "/statistics/timeseries", method: "POST", note: "Get time series statistics" },
3595
- { route: "/statistics/totals", method: "POST", note: "Get totals statistics" },
3596
3732
  { route: "/items/:context", method: "POST", note: "Create new item in context" },
3597
3733
  { route: "/items/:context", method: "GET", note: "Get items from context" },
3598
3734
  { route: "/items/export/:context", method: "GET", note: "Export items from context" },
@@ -4125,115 +4261,6 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4125
4261
  authenticated: true
4126
4262
  });
4127
4263
  });
4128
- console.log("[EXULU] statistics timeseries");
4129
- app.post("/statistics/timeseries", async (req, res) => {
4130
- const authenticationResult = await requestValidators.authenticate(req);
4131
- if (!authenticationResult.user?.id) {
4132
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4133
- return;
4134
- }
4135
- const { db: db3 } = await postgresClient();
4136
- const type = req.body.type;
4137
- if (!Object.values(STATISTICS_TYPE_ENUM).includes(type)) {
4138
- res.status(400).json({
4139
- message: "Invalid type, must be one of: " + Object.values(STATISTICS_TYPE_ENUM).join(", ")
4140
- });
4141
- return;
4142
- }
4143
- let from = new Date(req.body.from);
4144
- let to = new Date(req.body.to);
4145
- if (!from || !to) {
4146
- from = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4147
- to = /* @__PURE__ */ new Date();
4148
- }
4149
- const query = db3.from("statistics").select("*");
4150
- query.where("name", "count");
4151
- query.andWhere("type", type);
4152
- query.andWhere("createdAt", ">=", from);
4153
- query.andWhere("createdAt", "<=", to);
4154
- const results = await query;
4155
- const dates = [];
4156
- for (let i = 0; i < (to.getTime() - from.getTime()) / (1e3 * 60 * 60 * 24); i++) {
4157
- dates.push(new Date(from.getTime() + i * (1e3 * 60 * 60 * 24)));
4158
- }
4159
- const data = dates.map((date) => {
4160
- const result = results.find((result2) => result2.date === date);
4161
- if (result) {
4162
- return result;
4163
- }
4164
- return {
4165
- date,
4166
- count: 0
4167
- };
4168
- });
4169
- res.status(200).json({
4170
- data,
4171
- filter: {
4172
- from,
4173
- to
4174
- }
4175
- });
4176
- });
4177
- console.log("[EXULU] statistics totals");
4178
- app.post("/statistics/totals", async (req, res) => {
4179
- const authenticationResult = await requestValidators.authenticate(req);
4180
- if (!authenticationResult.user?.id) {
4181
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4182
- return;
4183
- }
4184
- const { db: db3 } = await postgresClient();
4185
- let from = new Date(req.body.from);
4186
- let to = new Date(req.body.to);
4187
- if (!from || !to) {
4188
- from = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4189
- to = /* @__PURE__ */ new Date();
4190
- }
4191
- let promises2 = Object.values(STATISTICS_TYPE_ENUM).map(async (type) => {
4192
- const result = await db3.from("statistics").where("name", "count").andWhere("type", type).andWhere("createdAt", ">=", from).andWhere("createdAt", "<=", to).sum("total as total");
4193
- return {
4194
- [type]: result[0]?.total || 0
4195
- };
4196
- });
4197
- const results = await Promise.all(promises2);
4198
- res.status(200).json({
4199
- data: { ...Object.assign({}, ...results) },
4200
- filter: {
4201
- from,
4202
- to
4203
- }
4204
- });
4205
- });
4206
- console.log("[EXULU] contexts statistics");
4207
- app.get("/contexts/statistics", async (req, res) => {
4208
- const authenticationResult = await requestValidators.authenticate(req);
4209
- if (!authenticationResult.user?.id) {
4210
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4211
- return;
4212
- }
4213
- const { db: db3 } = await postgresClient();
4214
- const statistics = await db3("statistics").where("name", "count").andWhere("type", "context.retrieve").sum("total as total").first();
4215
- const response = await db3("jobs").select(db3.raw(`to_char("createdAt", 'YYYY-MM-DD') as date`)).count("* as count").where("type", "embedder").groupByRaw(`to_char("createdAt", 'YYYY-MM-DD')`).then((rows) => ({
4216
- jobs: rows
4217
- }));
4218
- let jobs = [];
4219
- if (response[0]) {
4220
- jobs = response[0].jobs.map((job) => ({
4221
- date: job.id,
4222
- count: job.count
4223
- }));
4224
- }
4225
- const embeddingsCountResult = await db3("jobs").where("type", "embedder").count("* as count").first();
4226
- res.status(200).json({
4227
- active: contexts.filter((context) => context.active).length,
4228
- inactive: contexts.filter((context) => !context.active).length,
4229
- sources: contexts.reduce((acc, context) => acc + context.sources.get().length, 0),
4230
- queries: statistics?.total || 0,
4231
- jobs,
4232
- totals: {
4233
- embeddings: embeddingsCountResult?.count || 0
4234
- }
4235
- });
4236
- });
4237
4264
  console.log("[EXULU] context by id");
4238
4265
  app.get(`/contexts/:id`, async (req, res) => {
4239
4266
  const authenticationResult = await requestValidators.authenticate(req);
@@ -4404,7 +4431,11 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4404
4431
  return;
4405
4432
  }
4406
4433
  }
4407
- const stream = req.headers["stream"] || false;
4434
+ const headers = {
4435
+ stream: req.headers["stream"] === "true" || false,
4436
+ user: req.headers["user"] || null,
4437
+ session: req.headers["session"] || null
4438
+ };
4408
4439
  const requestValidationResult = requestValidators.agents(req);
4409
4440
  if (requestValidationResult.error) {
4410
4441
  res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
@@ -4415,6 +4446,13 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4415
4446
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4416
4447
  return;
4417
4448
  }
4449
+ const user = authenticationResult.user;
4450
+ if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
4451
+ res.status(400).json({
4452
+ message: "The provided user id in the resourceId field is not the same as the authenticated user. Only super admins and API users can impersonate other users."
4453
+ });
4454
+ return;
4455
+ }
4418
4456
  console.log("[EXULU] agent tools", agentInstance.tools);
4419
4457
  const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4420
4458
  console.log("[EXULU] enabled tools", enabledTools);
@@ -4437,9 +4475,15 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4437
4475
  const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
4438
4476
  providerApiKey = bytes.toString(CryptoJS3.enc.Utf8);
4439
4477
  }
4440
- if (!!stream) {
4441
- const result = agent.generateStream({
4442
- messages: req.body.messages,
4478
+ if (!!headers.stream) {
4479
+ await agent.generateStream({
4480
+ express: {
4481
+ res,
4482
+ req
4483
+ },
4484
+ user: headers.user,
4485
+ session: headers.session,
4486
+ message: req.body.message,
4443
4487
  tools: enabledTools,
4444
4488
  providerApiKey,
4445
4489
  toolConfigs: agentInstance.tools,
@@ -4448,11 +4492,12 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4448
4492
  trigger: "agent"
4449
4493
  }
4450
4494
  });
4451
- result.pipeDataStreamToResponse(res);
4452
4495
  return;
4453
4496
  } else {
4454
4497
  const response = await agent.generateSync({
4455
- messages: req.body.messages,
4498
+ user: headers.user,
4499
+ session: headers.session,
4500
+ message: req.body.message,
4456
4501
  tools: enabledTools.map((tool2) => tool2.tool),
4457
4502
  providerApiKey,
4458
4503
  toolConfigs: agentInstance.tools,
@@ -4977,7 +5022,6 @@ var defaultAgent = new ExuluAgent({
4977
5022
  description: `Basic agent without any defined tools, that can support MCP's.`,
4978
5023
  type: "agent",
4979
5024
  capabilities: {
4980
- tools: false,
4981
5025
  images: [],
4982
5026
  files: [],
4983
5027
  audio: [],
@@ -4989,10 +5033,10 @@ var defaultAgent = new ExuluAgent({
4989
5033
  instructions: "You are a helpful assistant.",
4990
5034
  model: {
4991
5035
  create: ({ apiKey }) => {
4992
- const anthropic2 = createAnthropic({
5036
+ const anthropic = createAnthropic({
4993
5037
  apiKey
4994
5038
  });
4995
- return anthropic2("claude-4-opus-20250514");
5039
+ return anthropic.languageModel("claude-4-opus-20250514");
4996
5040
  }
4997
5041
  // todo add a field of type string that adds a dropdown list from which the user can select the model
4998
5042
  // todo for each model, check which provider is used, and require the admin to add one or multiple
@@ -6533,6 +6577,13 @@ var ExuluJobs = {
6533
6577
  var db2 = {
6534
6578
  init: async () => {
6535
6579
  await execute();
6580
+ },
6581
+ api: {
6582
+ key: {
6583
+ generate: async (name, email) => {
6584
+ return await generateApiKey(name, email);
6585
+ }
6586
+ }
6536
6587
  }
6537
6588
  };
6538
6589
  var ExuluChunkers = {