@exulu/backend 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -121,15 +121,15 @@ var import_ai = require("ai");
121
121
 
122
122
  // types/enums/statistics.ts
123
123
  var STATISTICS_TYPE_ENUM = {
124
- CONTEXT_RETRIEVE: "context.retrieve",
125
- SOURCE_UPDATE: "source.update",
126
- EMBEDDER_UPSERT: "embedder.upsert",
127
- EMBEDDER_GENERATE: "embedder.generate",
128
- EMBEDDER_DELETE: "embedder.delete",
129
- WORKFLOW_RUN: "workflow.run",
130
- CONTEXT_UPSERT: "context.upsert",
131
- TOOL_CALL: "tool.call",
132
- AGENT_RUN: "agent.run"
124
+ CONTEXT_RETRIEVE: "CONTEXT_RETRIEVE",
125
+ SOURCE_UPDATE: "SOURCE_UPDATE",
126
+ EMBEDDER_UPSERT: "EMBEDDER_UPSERT",
127
+ EMBEDDER_GENERATE: "EMBEDDER_GENERATE",
128
+ EMBEDDER_DELETE: "EMBEDDER_DELETE",
129
+ WORKFLOW_RUN: "WORKFLOW_RUN",
130
+ CONTEXT_UPSERT: "CONTEXT_UPSERT",
131
+ TOOL_CALL: "TOOL_CALL",
132
+ AGENT_RUN: "AGENT_RUN"
133
133
  };
134
134
 
135
135
  // types/enums/eval-types.ts
@@ -304,7 +304,7 @@ var bullmqDecorator = async ({
304
304
 
305
305
  // src/registry/utils/map-types.ts
306
306
  var mapType = (t, type, name, defaultValue, unique) => {
307
- if (type === "text") {
307
+ if (type === "text" || type === "enum") {
308
308
  t.text(name);
309
309
  if (unique) t.unique(name);
310
310
  if (defaultValue) t.defaultTo(defaultValue);
@@ -445,6 +445,7 @@ var ExuluEvalUtils = {
445
445
 
446
446
  // src/registry/classes.ts
447
447
  var import_crypto_js = __toESM(require("crypto-js"), 1);
448
+ var import_express = require("express");
448
449
  function sanitizeToolName(name) {
449
450
  if (typeof name !== "string") return "";
450
451
  let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
@@ -463,7 +464,7 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
463
464
  console.log("[EXULU] Sanitized tools", sanitizedTools);
464
465
  const askForConfirmation = {
465
466
  description: "Ask the user for confirmation.",
466
- parameters: import_zod2.z.object({
467
+ inputSchema: import_zod2.z.object({
467
468
  message: import_zod2.z.string().describe("The message to ask for confirmation.")
468
469
  })
469
470
  };
@@ -574,10 +575,12 @@ var ExuluAgent = class {
574
575
  this.model = this.config?.model;
575
576
  }
576
577
  get providerName() {
577
- return this.config?.model?.create({ apiKey: "" })?.provider || "";
578
+ const model = this.config?.model?.create({ apiKey: "" });
579
+ return typeof model === "string" ? model : model?.provider || "";
578
580
  }
579
581
  get modelName() {
580
- return this.config?.model?.create({ apiKey: "" })?.modelId || "";
582
+ const model = this.config?.model?.create({ apiKey: "" });
583
+ return typeof model === "string" ? model : model?.modelId || "";
581
584
  }
582
585
  // Exports the agent as a tool that can be used by another agent
583
586
  // todo test this
@@ -603,28 +606,44 @@ var ExuluAgent = class {
603
606
  }
604
607
  });
605
608
  };
606
- generateSync = async ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
609
+ generateSync = async ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
607
610
  if (!this.model) {
608
611
  throw new Error("Model is required for streaming.");
609
612
  }
610
613
  if (!this.config) {
611
614
  throw new Error("Config is required for generating.");
612
615
  }
613
- if (prompt && messages) {
614
- throw new Error("Prompt and messages cannot be provided at the same time.");
616
+ if (prompt && message) {
617
+ throw new Error("Message and prompt cannot be provided at the same time.");
615
618
  }
616
619
  const model = this.model.create({
617
620
  apiKey: providerApiKey
618
621
  });
622
+ let messages = [];
623
+ if (message && session && user) {
624
+ const previousMessages = await getAgentMessages({
625
+ session,
626
+ user,
627
+ limit: 50,
628
+ page: 1
629
+ });
630
+ const previousMessagesContent = previousMessages.map((message2) => JSON.parse(message2.content));
631
+ messages = await (0, import_ai.validateUIMessages)({
632
+ // append the new message to the previous messages:
633
+ messages: [...previousMessagesContent, message]
634
+ });
635
+ }
636
+ console.log("[EXULU] Model provider key", providerApiKey);
637
+ console.log("[EXULU] Tool configs", toolConfigs);
619
638
  const { text } = await (0, import_ai.generateText)({
620
639
  model,
621
640
  // Should be a LanguageModelV1
622
641
  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.",
623
- messages,
642
+ messages: messages ? (0, import_ai.convertToModelMessages)(messages) : void 0,
624
643
  prompt,
625
644
  maxRetries: 2,
626
645
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
627
- maxSteps: 5
646
+ stopWhen: [(0, import_ai.stepCountIs)(5)]
628
647
  });
629
648
  if (statistics) {
630
649
  await updateStatistic({
@@ -637,36 +656,61 @@ var ExuluAgent = class {
637
656
  }
638
657
  return text;
639
658
  };
640
- generateStream = ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
659
+ generateStream = async ({ express: express3, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
641
660
  if (!this.model) {
642
661
  throw new Error("Model is required for streaming.");
643
662
  }
644
663
  if (!this.config) {
645
664
  throw new Error("Config is required for generating.");
646
665
  }
647
- if (prompt && messages) {
648
- throw new Error("Prompt and messages cannot be provided at the same time.");
666
+ if (!message) {
667
+ throw new Error("Message is required for streaming.");
649
668
  }
650
669
  const model = this.model.create({
651
670
  apiKey: providerApiKey
652
671
  });
672
+ let messages = [];
673
+ const previousMessages = await getAgentMessages({
674
+ session,
675
+ user,
676
+ limit: 50,
677
+ page: 1
678
+ });
679
+ const previousMessagesContent = previousMessages.map((message2) => JSON.parse(message2.content));
680
+ messages = await (0, import_ai.validateUIMessages)({
681
+ // append the new message to the previous messages:
682
+ messages: [...previousMessagesContent, message]
683
+ });
653
684
  console.log("[EXULU] Model provider key", providerApiKey);
654
685
  console.log("[EXULU] Tool configs", toolConfigs);
655
- return (0, import_ai.streamText)({
686
+ const result = (0, import_ai.streamText)({
656
687
  model,
657
688
  // Should be a LanguageModelV1
658
- messages,
659
- prompt,
689
+ messages: messages ? (0, import_ai.convertToModelMessages)(messages) : void 0,
660
690
  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.",
661
691
  maxRetries: 2,
662
692
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
663
- maxSteps: 5,
664
693
  onError: (error) => console.error("[EXULU] chat stream error.", error),
665
- onFinish: async ({ response, usage }) => {
694
+ stopWhen: [(0, import_ai.stepCountIs)(5)]
695
+ });
696
+ result.consumeStream();
697
+ result.pipeUIMessageStreamToResponse(express3.res, {
698
+ originalMessages: messages,
699
+ sendReasoning: true,
700
+ generateMessageId: (0, import_ai.createIdGenerator)({
701
+ prefix: "msg_",
702
+ size: 16
703
+ }),
704
+ onFinish: async ({ messages: messages2 }) => {
666
705
  console.info(
667
706
  "[EXULU] chat stream finished.",
668
- usage
707
+ messages2
669
708
  );
709
+ await saveChat({
710
+ session,
711
+ user,
712
+ messages: messages2
713
+ });
670
714
  if (statistics) {
671
715
  await updateStatistic({
672
716
  name: "count",
@@ -678,8 +722,26 @@ var ExuluAgent = class {
678
722
  }
679
723
  }
680
724
  });
725
+ return;
681
726
  };
682
727
  };
728
+ var getAgentMessages = async ({ session, user, limit, page }) => {
729
+ const { db: db3 } = await postgresClient();
730
+ const messages = await db3.from("agent_messages").where({ session, user }).limit(limit).offset(page * limit);
731
+ return messages;
732
+ };
733
+ var saveChat = async ({ session, user, messages }) => {
734
+ const { db: db3 } = await postgresClient();
735
+ const promises2 = messages.map((message) => {
736
+ return db3.from("agent_messages").insert({
737
+ session,
738
+ user,
739
+ content: JSON.stringify(message),
740
+ title: message.role === "user" ? "User" : "Assistant"
741
+ });
742
+ });
743
+ await Promise.all(promises2);
744
+ };
683
745
  var ExuluEmbedder = class {
684
746
  id;
685
747
  name;
@@ -930,7 +992,7 @@ var ExuluTool = class {
930
992
  this.type = type;
931
993
  this.tool = (0, import_ai.tool)({
932
994
  description,
933
- parameters: inputSchema || import_zod2.z.object({}),
995
+ inputSchema: inputSchema || import_zod2.z.object({}),
934
996
  execute: execute2
935
997
  });
936
998
  }
@@ -1419,22 +1481,27 @@ var ExuluSource = class {
1419
1481
  var updateStatistic = async (statistic) => {
1420
1482
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1421
1483
  const { db: db3 } = await postgresClient();
1422
- const existing = await db3.from("statistics").where({
1484
+ const existing = await db3.from("tracking").where({
1485
+ ...statistic.user ? { user: statistic.user } : {},
1486
+ ...statistic.role ? { role: statistic.role } : {},
1423
1487
  name: statistic.name,
1424
1488
  label: statistic.label,
1425
1489
  type: statistic.type,
1426
1490
  createdAt: currentDate
1427
1491
  }).first();
1492
+ console.log("!!! existing !!!", existing);
1428
1493
  if (!existing) {
1429
- await db3.from("statistics").insert({
1494
+ await db3.from("tracking").insert({
1430
1495
  name: statistic.name,
1431
1496
  label: statistic.label,
1432
1497
  type: statistic.type,
1433
1498
  total: statistic.count ?? 1,
1434
- createdAt: currentDate
1499
+ createdAt: currentDate,
1500
+ ...statistic.user ? { user: statistic.user } : {},
1501
+ ...statistic.role ? { role: statistic.role } : {}
1435
1502
  });
1436
1503
  } else {
1437
- await db3.from("statistics").update({
1504
+ await db3.from("tracking").update({
1438
1505
  total: db3.raw("total + ?", [statistic.count ?? 1])
1439
1506
  }).where({
1440
1507
  name: statistic.name,
@@ -1446,10 +1513,10 @@ var updateStatistic = async (statistic) => {
1446
1513
  };
1447
1514
 
1448
1515
  // src/registry/index.ts
1449
- var import_express6 = require("express");
1516
+ var import_express7 = require("express");
1450
1517
 
1451
1518
  // src/registry/routes.ts
1452
- var import_express2 = require("express");
1519
+ var import_express3 = require("express");
1453
1520
 
1454
1521
  // src/registry/rate-limiter.ts
1455
1522
  var rateLimiter = async (key, windowSeconds, limit, points) => {
@@ -1773,25 +1840,25 @@ var requestValidators = {
1773
1840
  message: "Missing body."
1774
1841
  };
1775
1842
  }
1776
- if (!req.body.threadId) {
1843
+ if (!req.headers["user"]) {
1777
1844
  return {
1778
1845
  error: true,
1779
1846
  code: 400,
1780
- message: "Missing threadId in body."
1847
+ message: 'Missing "user" property in headers.'
1781
1848
  };
1782
1849
  }
1783
- if (!req.body.resourceId) {
1850
+ if (!req.headers["session"]) {
1784
1851
  return {
1785
1852
  error: true,
1786
1853
  code: 400,
1787
- message: "Missing resourceId in body."
1854
+ message: 'Missing "session" property in headers.'
1788
1855
  };
1789
1856
  }
1790
- if (!req.body.messages) {
1857
+ if (!req.body.message) {
1791
1858
  return {
1792
1859
  error: true,
1793
1860
  code: 400,
1794
- message: 'Missing "messages" property in body.'
1861
+ message: 'Missing "message" property in body.'
1795
1862
  };
1796
1863
  }
1797
1864
  return {
@@ -1840,7 +1907,7 @@ var VectorMethodEnum = {
1840
1907
  };
1841
1908
 
1842
1909
  // src/registry/routes.ts
1843
- var import_express3 = __toESM(require("express"), 1);
1910
+ var import_express4 = __toESM(require("express"), 1);
1844
1911
  var import_server3 = require("@apollo/server");
1845
1912
  var Papa = __toESM(require("papaparse"), 1);
1846
1913
  var import_cors = __toESM(require("cors"), 1);
@@ -1895,6 +1962,9 @@ var map = (field) => {
1895
1962
  case "code":
1896
1963
  type = "String";
1897
1964
  break;
1965
+ case "enum":
1966
+ type = field.enumValues ? `${field.name}Enum` : "String";
1967
+ break;
1898
1968
  case "number":
1899
1969
  type = "Float";
1900
1970
  break;
@@ -1913,6 +1983,16 @@ var map = (field) => {
1913
1983
  return type;
1914
1984
  };
1915
1985
  function createTypeDefs(table) {
1986
+ const enumDefs = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
1987
+ const enumValues = field.enumValues.map((value) => {
1988
+ const sanitized = String(value).replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[0-9]/, "_$&").toUpperCase();
1989
+ return ` ${sanitized}`;
1990
+ }).join("\n");
1991
+ return `
1992
+ enum ${field.name}Enum {
1993
+ ${enumValues}
1994
+ }`;
1995
+ }).join("\n");
1916
1996
  const fields = table.fields.map((field) => {
1917
1997
  let type;
1918
1998
  type = map(field);
@@ -1924,8 +2004,6 @@ function createTypeDefs(table) {
1924
2004
  type ${table.name.singular} {
1925
2005
  ${fields.join("\n")}
1926
2006
  ${table.fields.find((field) => field.name === "id") ? "" : "id: ID!"}
1927
- createdAt: Date!
1928
- updatedAt: Date!
1929
2007
  ${rbacField}
1930
2008
  }
1931
2009
  `;
@@ -1936,39 +2014,62 @@ ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
1936
2014
  ${rbacInputField}
1937
2015
  }
1938
2016
  `;
1939
- return typeDef + inputDef;
2017
+ return enumDefs + typeDef + inputDef;
1940
2018
  }
1941
2019
  function createFilterTypeDefs(table) {
1942
2020
  const fieldFilters = table.fields.map((field) => {
1943
2021
  let type;
1944
- type = map(field);
2022
+ if (field.type === "enum" && field.enumValues) {
2023
+ type = `${field.name}Enum`;
2024
+ } else {
2025
+ type = map(field);
2026
+ }
1945
2027
  return `
1946
2028
  ${field.name}: FilterOperator${type}`;
1947
2029
  });
1948
2030
  const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2031
+ const enumFilterOperators = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
2032
+ const enumTypeName = `${field.name}Enum`;
2033
+ return `
2034
+ input FilterOperator${enumTypeName} {
2035
+ eq: ${enumTypeName}
2036
+ ne: ${enumTypeName}
2037
+ in: [${enumTypeName}]
2038
+ and: [FilterOperator${enumTypeName}]
2039
+ or: [FilterOperator${enumTypeName}]
2040
+ }`;
2041
+ }).join("\n");
1949
2042
  const operatorTypes = `
1950
2043
  input FilterOperatorString {
1951
2044
  eq: String
1952
2045
  ne: String
1953
2046
  in: [String]
1954
2047
  contains: String
2048
+ and: [FilterOperatorString]
2049
+ or: [FilterOperatorString]
1955
2050
  }
1956
2051
 
1957
2052
  input FilterOperatorDate {
1958
2053
  lte: Date
1959
2054
  gte: Date
2055
+ and: [FilterOperatorDate]
2056
+ or: [FilterOperatorDate]
1960
2057
  }
1961
2058
 
1962
2059
  input FilterOperatorFloat {
1963
2060
  eq: Float
1964
2061
  ne: Float
1965
2062
  in: [Float]
2063
+ and: [FilterOperatorFloat]
2064
+ or: [FilterOperatorFloat]
1966
2065
  }
1967
2066
 
1968
2067
  input FilterOperatorBoolean {
1969
2068
  eq: Boolean
1970
2069
  ne: Boolean
1971
2070
  in: [Boolean]
2071
+ and: [FilterOperatorBoolean]
2072
+ or: [FilterOperatorBoolean]
1972
2073
  }
1973
2074
 
1974
2075
  input FilterOperatorJSON {
@@ -1987,6 +2088,8 @@ enum SortDirection {
1987
2088
  DESC
1988
2089
  }
1989
2090
 
2091
+ ${enumFilterOperators}
2092
+
1990
2093
  input Filter${tableNameSingularUpperCaseFirst} {
1991
2094
  ${fieldFilters.join("\n")}
1992
2095
  }`;
@@ -2058,6 +2161,17 @@ function createMutations(table) {
2058
2161
  const validateWriteAccess = async (id, context) => {
2059
2162
  try {
2060
2163
  const { db: db3, req, user } = context;
2164
+ if (user.super_admin === true) {
2165
+ return true;
2166
+ }
2167
+ 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")) {
2168
+ console.error("Access control error: no role found for current user or no access to entity type.");
2169
+ throw new Error("Access control error: no role found for current user or no access to entity type.");
2170
+ }
2171
+ const hasRBAC = table.RBAC === true;
2172
+ if (!hasRBAC) {
2173
+ return true;
2174
+ }
2061
2175
  const record = await db3.from(tableNamePlural).select(["rights_mode", "created_by"]).where({ id }).first();
2062
2176
  if (!record) {
2063
2177
  throw new Error("Record not found");
@@ -2067,17 +2181,6 @@ function createMutations(table) {
2067
2181
  throw new Error("You are not authorized to edit this record");
2068
2182
  }
2069
2183
  }
2070
- const hasRBAC = table.RBAC === true;
2071
- if (!hasRBAC) {
2072
- return true;
2073
- }
2074
- if (user.super_admin === true) {
2075
- return true;
2076
- }
2077
- 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")) {
2078
- console.error("Access control error: no role found for current user or no access to entity type.");
2079
- throw new Error("Access control error: no role found for current user or no access to entity type.");
2080
- }
2081
2184
  if (record.rights_mode === "public") {
2082
2185
  return true;
2083
2186
  }
@@ -2258,15 +2361,15 @@ function createMutations(table) {
2258
2361
  var applyAccessControl = (table, user, query) => {
2259
2362
  console.log("table", table);
2260
2363
  const tableNamePlural = table.name.plural.toLowerCase();
2261
- const hasRBAC = table.RBAC === true;
2262
- if (!hasRBAC) {
2364
+ if (!user.super_admin && table.name.plural === "jobs") {
2365
+ query = query.where("created_by", user.id);
2263
2366
  return query;
2264
2367
  }
2265
- if (user.super_admin === true) {
2368
+ const hasRBAC = table.RBAC === true;
2369
+ if (!hasRBAC) {
2266
2370
  return query;
2267
2371
  }
2268
- if (table.name.plural === "jobs") {
2269
- query = query.where("created_by", user.id);
2372
+ if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2270
2373
  return query;
2271
2374
  }
2272
2375
  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"))) {
@@ -2297,6 +2400,27 @@ var applyAccessControl = (table, user, query) => {
2297
2400
  }
2298
2401
  return query;
2299
2402
  };
2403
+ var converOperatorToQuery = (query, fieldName, operators) => {
2404
+ if (operators.eq !== void 0) {
2405
+ query = query.where(fieldName, operators.eq);
2406
+ }
2407
+ if (operators.ne !== void 0) {
2408
+ query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2409
+ }
2410
+ if (operators.in !== void 0) {
2411
+ query = query.whereIn(fieldName, operators.in);
2412
+ }
2413
+ if (operators.contains !== void 0) {
2414
+ query = query.where(fieldName, "like", `%${operators.contains}%`);
2415
+ }
2416
+ if (operators.lte !== void 0) {
2417
+ query = query.where(fieldName, "<=", operators.lte);
2418
+ }
2419
+ if (operators.gte !== void 0) {
2420
+ query = query.where(fieldName, ">=", operators.gte);
2421
+ }
2422
+ return query;
2423
+ };
2300
2424
  function createQueries(table) {
2301
2425
  const tableNamePlural = table.name.plural.toLowerCase();
2302
2426
  const tableNameSingular = table.name.singular.toLowerCase();
@@ -2304,18 +2428,19 @@ function createQueries(table) {
2304
2428
  filters.forEach((filter) => {
2305
2429
  Object.entries(filter).forEach(([fieldName, operators]) => {
2306
2430
  if (operators) {
2307
- if (operators.eq !== void 0) {
2308
- query = query.where(fieldName, operators.eq);
2309
- }
2310
- if (operators.ne !== void 0) {
2311
- query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2312
- }
2313
- if (operators.in !== void 0) {
2314
- query = query.whereIn(fieldName, operators.in);
2431
+ if (operators.and !== void 0) {
2432
+ console.log("operators.and", operators.and);
2433
+ operators.and.forEach((operator) => {
2434
+ query = converOperatorToQuery(query, fieldName, operator);
2435
+ });
2315
2436
  }
2316
- if (operators.contains !== void 0) {
2317
- query = query.where(fieldName, "like", `%${operators.contains}%`);
2437
+ if (operators.or !== void 0) {
2438
+ operators.or.forEach((operator) => {
2439
+ query = converOperatorToQuery(query, fieldName, operator);
2440
+ });
2318
2441
  }
2442
+ query = converOperatorToQuery(query, fieldName, operators);
2443
+ console.log("query", query);
2319
2444
  }
2320
2445
  });
2321
2446
  });
@@ -2388,57 +2513,36 @@ function createQueries(table) {
2388
2513
  query = applyFilters(query, filters);
2389
2514
  query = applyAccessControl(table, context.user, query);
2390
2515
  if (groupBy) {
2391
- const results = await query.select(groupBy).count("* as count").groupBy(groupBy);
2516
+ query = query.select(groupBy).groupBy(groupBy);
2517
+ if (tableNamePlural === "tracking") {
2518
+ query = query.sum("total as count");
2519
+ } else {
2520
+ query = query.count("* as count");
2521
+ }
2522
+ const results = await query;
2523
+ console.log("!!! results !!!", results);
2392
2524
  return results.map((r) => ({
2393
2525
  group: r[groupBy],
2394
- count: Number(r.count)
2526
+ count: r.count ? Number(r.count) : 0
2395
2527
  }));
2396
2528
  } else {
2397
- const [{ count }] = await query.count("* as count");
2398
- return [{
2399
- group: "total",
2400
- count: Number(count)
2401
- }];
2402
- }
2403
- },
2404
- // Add jobStatistics query for jobs table (backward compatibility)
2405
- ...tableNamePlural === "jobs" ? {
2406
- jobStatistics: async (_, args, context, info) => {
2407
- const { user, agent, from, to } = args;
2408
- const { db: db3 } = context;
2409
- let query = db3("jobs");
2410
- if (user) {
2411
- query = query.where("user", user);
2412
- }
2413
- if (agent) {
2414
- query = query.where("agent", agent);
2415
- }
2416
- if (from) {
2417
- query = query.where("createdAt", ">=", from);
2418
- }
2419
- if (to) {
2420
- query = query.where("createdAt", "<=", to);
2529
+ if (tableNamePlural === "tracking") {
2530
+ query = query.sum("total as count");
2531
+ const [{ count }] = await query.sum("total as count");
2532
+ console.log("!!! count !!!", count);
2533
+ return [{
2534
+ group: "total",
2535
+ count: count ? Number(count) : 0
2536
+ }];
2537
+ } else {
2538
+ const [{ count }] = await query.count("* as count");
2539
+ return [{
2540
+ group: "total",
2541
+ count: count ? Number(count) : 0
2542
+ }];
2421
2543
  }
2422
- query = applyAccessControl(table, context.user, query);
2423
- const runningQuery = query.clone().whereIn("status", ["active", "waiting", "delayed", "paused"]);
2424
- const [{ runningCount }] = await runningQuery.count("* as runningCount");
2425
- const erroredQuery = query.clone().whereIn("status", ["failed", "stuck"]);
2426
- const [{ erroredCount }] = await erroredQuery.count("* as erroredCount");
2427
- const completedQuery = query.clone().where("status", "completed");
2428
- const [{ completedCount }] = await completedQuery.count("* as completedCount");
2429
- const failedQuery = query.clone().where("status", "failed");
2430
- const [{ failedCount }] = await failedQuery.count("* as failedCount");
2431
- const durationQuery = query.clone().where("status", "completed").whereNotNull("duration").select(db3.raw('AVG("duration") as averageDuration'));
2432
- const [{ averageDuration }] = await durationQuery;
2433
- return {
2434
- runningCount: Number(runningCount),
2435
- erroredCount: Number(erroredCount),
2436
- completedCount: Number(completedCount),
2437
- failedCount: Number(failedCount),
2438
- averageDuration: averageDuration ? Number(averageDuration) : 0
2439
- };
2440
2544
  }
2441
- } : {}
2545
+ }
2442
2546
  };
2443
2547
  }
2444
2548
  var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
@@ -2458,6 +2562,16 @@ var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
2458
2562
  };
2459
2563
  };
2460
2564
  function createSDL(tables) {
2565
+ tables.forEach((table) => {
2566
+ table.fields.push({
2567
+ name: "createdAt",
2568
+ type: "date"
2569
+ });
2570
+ table.fields.push({
2571
+ name: "updatedAt",
2572
+ type: "date"
2573
+ });
2574
+ });
2461
2575
  console.log("[EXULU] Creating SDL");
2462
2576
  let typeDefs = `
2463
2577
  scalar JSON
@@ -2514,7 +2628,6 @@ function createSDL(tables) {
2514
2628
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2515
2629
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2516
2630
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
2517
- ${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
2518
2631
  `;
2519
2632
  mutationDefs += `
2520
2633
  ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
@@ -2539,17 +2652,6 @@ type PageInfo {
2539
2652
  hasNextPage: Boolean!
2540
2653
  }
2541
2654
  `;
2542
- if (tableNamePlural === "jobs") {
2543
- modelDefs += `
2544
- type JobStatistics {
2545
- runningCount: Int!
2546
- erroredCount: Int!
2547
- completedCount: Int!
2548
- failedCount: Int!
2549
- averageDuration: Float!
2550
- }
2551
- `;
2552
- }
2553
2655
  Object.assign(resolvers.Query, createQueries(table));
2554
2656
  Object.assign(resolvers.Mutation, createMutations(table));
2555
2657
  if (table.RBAC) {
@@ -2641,6 +2743,10 @@ var agentMessagesSchema = {
2641
2743
  name: "title",
2642
2744
  type: "text"
2643
2745
  },
2746
+ {
2747
+ name: "user",
2748
+ type: "number"
2749
+ },
2644
2750
  {
2645
2751
  name: "session",
2646
2752
  type: "text"
@@ -2877,6 +2983,10 @@ var rolesSchema = {
2877
2983
  type: "text"
2878
2984
  // write | read access to agents
2879
2985
  },
2986
+ {
2987
+ name: "api",
2988
+ type: "text"
2989
+ },
2880
2990
  {
2881
2991
  name: "workflows",
2882
2992
  type: "text"
@@ -2896,8 +3006,8 @@ var rolesSchema = {
2896
3006
  };
2897
3007
  var statisticsSchema = {
2898
3008
  name: {
2899
- plural: "statistics",
2900
- singular: "statistic"
3009
+ plural: "tracking",
3010
+ singular: "tracking"
2901
3011
  },
2902
3012
  fields: [
2903
3013
  {
@@ -2910,11 +3020,20 @@ var statisticsSchema = {
2910
3020
  },
2911
3021
  {
2912
3022
  name: "type",
2913
- type: "text"
3023
+ type: "enum",
3024
+ enumValues: Object.values(STATISTICS_TYPE_ENUM)
2914
3025
  },
2915
3026
  {
2916
3027
  name: "total",
2917
3028
  type: "number"
3029
+ },
3030
+ {
3031
+ name: "user",
3032
+ type: "number"
3033
+ },
3034
+ {
3035
+ name: "role",
3036
+ type: "uuid"
2918
3037
  }
2919
3038
  ]
2920
3039
  };
@@ -2975,6 +3094,7 @@ var jobsSchema = {
2975
3094
  plural: "jobs",
2976
3095
  singular: "job"
2977
3096
  },
3097
+ RBAC: true,
2978
3098
  fields: [
2979
3099
  {
2980
3100
  name: "redis",
@@ -2984,10 +3104,6 @@ var jobsSchema = {
2984
3104
  name: "session",
2985
3105
  type: "text"
2986
3106
  },
2987
- {
2988
- name: "created_by",
2989
- type: "number"
2990
- },
2991
3107
  {
2992
3108
  name: "status",
2993
3109
  type: "text"
@@ -3113,7 +3229,7 @@ var coreSchemas = {
3113
3229
  };
3114
3230
 
3115
3231
  // src/registry/uppy.ts
3116
- var import_express = require("express");
3232
+ var import_express2 = require("express");
3117
3233
  var createUppyRoutes = async (app) => {
3118
3234
  const {
3119
3235
  S3Client,
@@ -3587,7 +3703,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3587
3703
  optionsSuccessStatus: 200
3588
3704
  // some legacy browsers (IE11, various SmartTVs) choke on 204
3589
3705
  };
3590
- app.use(import_express3.default.json({ limit: REQUEST_SIZE_LIMIT }));
3706
+ app.use(import_express4.default.json({ limit: REQUEST_SIZE_LIMIT }));
3591
3707
  app.use((0, import_cors.default)(corsOptions));
3592
3708
  app.use(import_body_parser.default.urlencoded({ extended: true, limit: REQUEST_SIZE_LIMIT }));
3593
3709
  app.use(import_body_parser.default.json({ limit: REQUEST_SIZE_LIMIT }));
@@ -3630,11 +3746,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3630
3746
  { route: "/agents/:id", method: "GET", note: "Get specific agent" },
3631
3747
  { route: "/contexts", method: "GET", note: "List all contexts" },
3632
3748
  { route: "/contexts/:id", method: "GET", note: "Get specific context" },
3633
- { route: "/contexts/statistics", method: "GET", note: "Get context statistics" },
3634
3749
  { route: "/tools", method: "GET", note: "List all tools" },
3635
3750
  { route: "/tools/:id", method: "GET", note: "Get specific tool" },
3636
- { route: "/statistics/timeseries", method: "POST", note: "Get time series statistics" },
3637
- { route: "/statistics/totals", method: "POST", note: "Get totals statistics" },
3638
3751
  { route: "/items/:context", method: "POST", note: "Create new item in context" },
3639
3752
  { route: "/items/:context", method: "GET", note: "Get items from context" },
3640
3753
  { route: "/items/export/:context", method: "GET", note: "Export items from context" },
@@ -3672,7 +3785,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3672
3785
  app.use(
3673
3786
  "/graphql",
3674
3787
  (0, import_cors.default)(corsOptions),
3675
- import_express3.default.json({ limit: REQUEST_SIZE_LIMIT }),
3788
+ import_express4.default.json({ limit: REQUEST_SIZE_LIMIT }),
3676
3789
  (0, import_express5.expressMiddleware)(server, {
3677
3790
  context: async ({ req }) => {
3678
3791
  const authenticationResult = await requestValidators.authenticate(req);
@@ -4167,115 +4280,6 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4167
4280
  authenticated: true
4168
4281
  });
4169
4282
  });
4170
- console.log("[EXULU] statistics timeseries");
4171
- app.post("/statistics/timeseries", async (req, res) => {
4172
- const authenticationResult = await requestValidators.authenticate(req);
4173
- if (!authenticationResult.user?.id) {
4174
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4175
- return;
4176
- }
4177
- const { db: db3 } = await postgresClient();
4178
- const type = req.body.type;
4179
- if (!Object.values(STATISTICS_TYPE_ENUM).includes(type)) {
4180
- res.status(400).json({
4181
- message: "Invalid type, must be one of: " + Object.values(STATISTICS_TYPE_ENUM).join(", ")
4182
- });
4183
- return;
4184
- }
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
- const query = db3.from("statistics").select("*");
4192
- query.where("name", "count");
4193
- query.andWhere("type", type);
4194
- query.andWhere("createdAt", ">=", from);
4195
- query.andWhere("createdAt", "<=", to);
4196
- const results = await query;
4197
- const dates = [];
4198
- for (let i = 0; i < (to.getTime() - from.getTime()) / (1e3 * 60 * 60 * 24); i++) {
4199
- dates.push(new Date(from.getTime() + i * (1e3 * 60 * 60 * 24)));
4200
- }
4201
- const data = dates.map((date) => {
4202
- const result = results.find((result2) => result2.date === date);
4203
- if (result) {
4204
- return result;
4205
- }
4206
- return {
4207
- date,
4208
- count: 0
4209
- };
4210
- });
4211
- res.status(200).json({
4212
- data,
4213
- filter: {
4214
- from,
4215
- to
4216
- }
4217
- });
4218
- });
4219
- console.log("[EXULU] statistics totals");
4220
- app.post("/statistics/totals", async (req, res) => {
4221
- const authenticationResult = await requestValidators.authenticate(req);
4222
- if (!authenticationResult.user?.id) {
4223
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4224
- return;
4225
- }
4226
- const { db: db3 } = await postgresClient();
4227
- let from = new Date(req.body.from);
4228
- let to = new Date(req.body.to);
4229
- if (!from || !to) {
4230
- from = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4231
- to = /* @__PURE__ */ new Date();
4232
- }
4233
- let promises2 = Object.values(STATISTICS_TYPE_ENUM).map(async (type) => {
4234
- const result = await db3.from("statistics").where("name", "count").andWhere("type", type).andWhere("createdAt", ">=", from).andWhere("createdAt", "<=", to).sum("total as total");
4235
- return {
4236
- [type]: result[0]?.total || 0
4237
- };
4238
- });
4239
- const results = await Promise.all(promises2);
4240
- res.status(200).json({
4241
- data: { ...Object.assign({}, ...results) },
4242
- filter: {
4243
- from,
4244
- to
4245
- }
4246
- });
4247
- });
4248
- console.log("[EXULU] contexts statistics");
4249
- app.get("/contexts/statistics", async (req, res) => {
4250
- const authenticationResult = await requestValidators.authenticate(req);
4251
- if (!authenticationResult.user?.id) {
4252
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4253
- return;
4254
- }
4255
- const { db: db3 } = await postgresClient();
4256
- const statistics = await db3("statistics").where("name", "count").andWhere("type", "context.retrieve").sum("total as total").first();
4257
- 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) => ({
4258
- jobs: rows
4259
- }));
4260
- let jobs = [];
4261
- if (response[0]) {
4262
- jobs = response[0].jobs.map((job) => ({
4263
- date: job.id,
4264
- count: job.count
4265
- }));
4266
- }
4267
- const embeddingsCountResult = await db3("jobs").where("type", "embedder").count("* as count").first();
4268
- res.status(200).json({
4269
- active: contexts.filter((context) => context.active).length,
4270
- inactive: contexts.filter((context) => !context.active).length,
4271
- sources: contexts.reduce((acc, context) => acc + context.sources.get().length, 0),
4272
- queries: statistics?.total || 0,
4273
- jobs,
4274
- totals: {
4275
- embeddings: embeddingsCountResult?.count || 0
4276
- }
4277
- });
4278
- });
4279
4283
  console.log("[EXULU] context by id");
4280
4284
  app.get(`/contexts/:id`, async (req, res) => {
4281
4285
  const authenticationResult = await requestValidators.authenticate(req);
@@ -4446,7 +4450,11 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4446
4450
  return;
4447
4451
  }
4448
4452
  }
4449
- const stream = req.headers["stream"] || false;
4453
+ const headers = {
4454
+ stream: req.headers["stream"] === "true" || false,
4455
+ user: req.headers["user"] || null,
4456
+ session: req.headers["session"] || null
4457
+ };
4450
4458
  const requestValidationResult = requestValidators.agents(req);
4451
4459
  if (requestValidationResult.error) {
4452
4460
  res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
@@ -4457,6 +4465,13 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4457
4465
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4458
4466
  return;
4459
4467
  }
4468
+ const user = authenticationResult.user;
4469
+ if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
4470
+ res.status(400).json({
4471
+ 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."
4472
+ });
4473
+ return;
4474
+ }
4460
4475
  console.log("[EXULU] agent tools", agentInstance.tools);
4461
4476
  const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4462
4477
  console.log("[EXULU] enabled tools", enabledTools);
@@ -4479,9 +4494,15 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4479
4494
  const bytes = import_crypto_js3.default.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
4480
4495
  providerApiKey = bytes.toString(import_crypto_js3.default.enc.Utf8);
4481
4496
  }
4482
- if (!!stream) {
4483
- const result = agent.generateStream({
4484
- messages: req.body.messages,
4497
+ if (!!headers.stream) {
4498
+ await agent.generateStream({
4499
+ express: {
4500
+ res,
4501
+ req
4502
+ },
4503
+ user: headers.user,
4504
+ session: headers.session,
4505
+ message: req.body.message,
4485
4506
  tools: enabledTools,
4486
4507
  providerApiKey,
4487
4508
  toolConfigs: agentInstance.tools,
@@ -4490,11 +4511,12 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4490
4511
  trigger: "agent"
4491
4512
  }
4492
4513
  });
4493
- result.pipeDataStreamToResponse(res);
4494
4514
  return;
4495
4515
  } else {
4496
4516
  const response = await agent.generateSync({
4497
- messages: req.body.messages,
4517
+ user: headers.user,
4518
+ session: headers.session,
4519
+ message: req.body.message,
4498
4520
  tools: enabledTools.map((tool2) => tool2.tool),
4499
4521
  providerApiKey,
4500
4522
  toolConfigs: agentInstance.tools,
@@ -4516,7 +4538,7 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4516
4538
  console.log("Routes:");
4517
4539
  console.table(routeLogs);
4518
4540
  const TARGET_API = "https://api.anthropic.com";
4519
- app.use("/gateway/anthropic/:id", import_express3.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
4541
+ app.use("/gateway/anthropic/:id", import_express4.default.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
4520
4542
  const path3 = req.url;
4521
4543
  const url = `${TARGET_API}${path3}`;
4522
4544
  try {
@@ -4861,7 +4883,7 @@ var import_node_crypto = require("crypto");
4861
4883
  var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
4862
4884
  var import_types = require("@modelcontextprotocol/sdk/types.js");
4863
4885
  var import_zod3 = require("zod");
4864
- var import_express4 = require("express");
4886
+ var import_express6 = require("express");
4865
4887
  var SESSION_ID_HEADER = "mcp-session-id";
4866
4888
  var ExuluMCP = class {
4867
4889
  server;
@@ -4999,7 +5021,7 @@ ${code}`
4999
5021
  };
5000
5022
 
5001
5023
  // src/registry/index.ts
5002
- var import_express7 = __toESM(require("express"), 1);
5024
+ var import_express8 = __toESM(require("express"), 1);
5003
5025
 
5004
5026
  // src/templates/agents/claude-code.ts
5005
5027
  var agentId = "0832-5178-1145-2194";
@@ -5019,7 +5041,6 @@ var defaultAgent = new ExuluAgent({
5019
5041
  description: `Basic agent without any defined tools, that can support MCP's.`,
5020
5042
  type: "agent",
5021
5043
  capabilities: {
5022
- tools: false,
5023
5044
  images: [],
5024
5045
  files: [],
5025
5046
  audio: [],
@@ -5031,10 +5052,10 @@ var defaultAgent = new ExuluAgent({
5031
5052
  instructions: "You are a helpful assistant.",
5032
5053
  model: {
5033
5054
  create: ({ apiKey }) => {
5034
- const anthropic2 = (0, import_anthropic.createAnthropic)({
5055
+ const anthropic = (0, import_anthropic.createAnthropic)({
5035
5056
  apiKey
5036
5057
  });
5037
- return anthropic2("claude-4-opus-20250514");
5058
+ return anthropic.languageModel("claude-4-opus-20250514");
5038
5059
  }
5039
5060
  // todo add a field of type string that adds a dropdown list from which the user can select the model
5040
5061
  // todo for each model, check which provider is used, and require the admin to add one or multiple
@@ -5080,7 +5101,7 @@ var ExuluApp = class {
5080
5101
  ];
5081
5102
  this._queues = [...new Set(queues2.filter((o) => !!o))];
5082
5103
  if (!this._expressApp) {
5083
- this._expressApp = (0, import_express7.default)();
5104
+ this._expressApp = (0, import_express8.default)();
5084
5105
  await this.server.express.init();
5085
5106
  console.log("[EXULU] Express app initialized.");
5086
5107
  }
@@ -6575,6 +6596,13 @@ var ExuluJobs = {
6575
6596
  var db2 = {
6576
6597
  init: async () => {
6577
6598
  await execute();
6599
+ },
6600
+ api: {
6601
+ key: {
6602
+ generate: async (name, email) => {
6603
+ return await generateApiKey(name, email);
6604
+ }
6605
+ }
6578
6606
  }
6579
6607
  };
6580
6608
  var ExuluChunkers = {