@exulu/backend 1.12.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.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,7 +262,7 @@ 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") {
265
+ if (type === "text" || type === "enum") {
266
266
  t.text(name);
267
267
  if (unique) t.unique(name);
268
268
  if (defaultValue) t.defaultTo(defaultValue);
@@ -403,6 +403,7 @@ var ExuluEvalUtils = {
403
403
 
404
404
  // src/registry/classes.ts
405
405
  import CryptoJS from "crypto-js";
406
+ import "express";
406
407
  function sanitizeToolName(name) {
407
408
  if (typeof name !== "string") return "";
408
409
  let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
@@ -421,7 +422,7 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
421
422
  console.log("[EXULU] Sanitized tools", sanitizedTools);
422
423
  const askForConfirmation = {
423
424
  description: "Ask the user for confirmation.",
424
- parameters: z.object({
425
+ inputSchema: z.object({
425
426
  message: z.string().describe("The message to ask for confirmation.")
426
427
  })
427
428
  };
@@ -532,10 +533,12 @@ var ExuluAgent = class {
532
533
  this.model = this.config?.model;
533
534
  }
534
535
  get providerName() {
535
- return this.config?.model?.create({ apiKey: "" })?.provider || "";
536
+ const model = this.config?.model?.create({ apiKey: "" });
537
+ return typeof model === "string" ? model : model?.provider || "";
536
538
  }
537
539
  get modelName() {
538
- return this.config?.model?.create({ apiKey: "" })?.modelId || "";
540
+ const model = this.config?.model?.create({ apiKey: "" });
541
+ return typeof model === "string" ? model : model?.modelId || "";
539
542
  }
540
543
  // Exports the agent as a tool that can be used by another agent
541
544
  // todo test this
@@ -561,28 +564,44 @@ var ExuluAgent = class {
561
564
  }
562
565
  });
563
566
  };
564
- generateSync = async ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
567
+ generateSync = async ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
565
568
  if (!this.model) {
566
569
  throw new Error("Model is required for streaming.");
567
570
  }
568
571
  if (!this.config) {
569
572
  throw new Error("Config is required for generating.");
570
573
  }
571
- if (prompt && messages) {
572
- throw new Error("Prompt and messages cannot be provided at the same time.");
574
+ if (prompt && message) {
575
+ throw new Error("Message and prompt cannot be provided at the same time.");
573
576
  }
574
577
  const model = this.model.create({
575
578
  apiKey: providerApiKey
576
579
  });
580
+ let messages = [];
581
+ if (message && session && user) {
582
+ const previousMessages = await getAgentMessages({
583
+ session,
584
+ user,
585
+ limit: 50,
586
+ page: 1
587
+ });
588
+ const previousMessagesContent = previousMessages.map((message2) => JSON.parse(message2.content));
589
+ messages = await validateUIMessages({
590
+ // append the new message to the previous messages:
591
+ messages: [...previousMessagesContent, message]
592
+ });
593
+ }
594
+ console.log("[EXULU] Model provider key", providerApiKey);
595
+ console.log("[EXULU] Tool configs", toolConfigs);
577
596
  const { text } = await generateText({
578
597
  model,
579
598
  // Should be a LanguageModelV1
580
599
  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,
600
+ messages: messages ? convertToModelMessages(messages) : void 0,
582
601
  prompt,
583
602
  maxRetries: 2,
584
603
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
585
- maxSteps: 5
604
+ stopWhen: [stepCountIs(5)]
586
605
  });
587
606
  if (statistics) {
588
607
  await updateStatistic({
@@ -595,36 +614,61 @@ var ExuluAgent = class {
595
614
  }
596
615
  return text;
597
616
  };
598
- generateStream = ({ messages, prompt, tools, statistics, toolConfigs, providerApiKey }) => {
617
+ generateStream = async ({ express: express3, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
599
618
  if (!this.model) {
600
619
  throw new Error("Model is required for streaming.");
601
620
  }
602
621
  if (!this.config) {
603
622
  throw new Error("Config is required for generating.");
604
623
  }
605
- if (prompt && messages) {
606
- throw new Error("Prompt and messages cannot be provided at the same time.");
624
+ if (!message) {
625
+ throw new Error("Message is required for streaming.");
607
626
  }
608
627
  const model = this.model.create({
609
628
  apiKey: providerApiKey
610
629
  });
630
+ let messages = [];
631
+ const previousMessages = await getAgentMessages({
632
+ session,
633
+ user,
634
+ limit: 50,
635
+ page: 1
636
+ });
637
+ const previousMessagesContent = previousMessages.map((message2) => JSON.parse(message2.content));
638
+ messages = await validateUIMessages({
639
+ // append the new message to the previous messages:
640
+ messages: [...previousMessagesContent, message]
641
+ });
611
642
  console.log("[EXULU] Model provider key", providerApiKey);
612
643
  console.log("[EXULU] Tool configs", toolConfigs);
613
- return streamText({
644
+ const result = streamText({
614
645
  model,
615
646
  // Should be a LanguageModelV1
616
- messages,
617
- prompt,
647
+ messages: messages ? convertToModelMessages(messages) : void 0,
618
648
  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
649
  maxRetries: 2,
620
650
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
621
- maxSteps: 5,
622
651
  onError: (error) => console.error("[EXULU] chat stream error.", error),
623
- onFinish: async ({ response, usage }) => {
652
+ stopWhen: [stepCountIs(5)]
653
+ });
654
+ result.consumeStream();
655
+ result.pipeUIMessageStreamToResponse(express3.res, {
656
+ originalMessages: messages,
657
+ sendReasoning: true,
658
+ generateMessageId: createIdGenerator({
659
+ prefix: "msg_",
660
+ size: 16
661
+ }),
662
+ onFinish: async ({ messages: messages2 }) => {
624
663
  console.info(
625
664
  "[EXULU] chat stream finished.",
626
- usage
665
+ messages2
627
666
  );
667
+ await saveChat({
668
+ session,
669
+ user,
670
+ messages: messages2
671
+ });
628
672
  if (statistics) {
629
673
  await updateStatistic({
630
674
  name: "count",
@@ -636,8 +680,26 @@ var ExuluAgent = class {
636
680
  }
637
681
  }
638
682
  });
683
+ return;
639
684
  };
640
685
  };
686
+ var getAgentMessages = async ({ session, user, limit, page }) => {
687
+ const { db: db3 } = await postgresClient();
688
+ const messages = await db3.from("agent_messages").where({ session, user }).limit(limit).offset(page * limit);
689
+ return messages;
690
+ };
691
+ var saveChat = async ({ session, user, messages }) => {
692
+ const { db: db3 } = await postgresClient();
693
+ const promises2 = messages.map((message) => {
694
+ return db3.from("agent_messages").insert({
695
+ session,
696
+ user,
697
+ content: JSON.stringify(message),
698
+ title: message.role === "user" ? "User" : "Assistant"
699
+ });
700
+ });
701
+ await Promise.all(promises2);
702
+ };
641
703
  var ExuluEmbedder = class {
642
704
  id;
643
705
  name;
@@ -888,7 +950,7 @@ var ExuluTool = class {
888
950
  this.type = type;
889
951
  this.tool = tool({
890
952
  description,
891
- parameters: inputSchema || z.object({}),
953
+ inputSchema: inputSchema || z.object({}),
892
954
  execute: execute2
893
955
  });
894
956
  }
@@ -1377,22 +1439,27 @@ var ExuluSource = class {
1377
1439
  var updateStatistic = async (statistic) => {
1378
1440
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1379
1441
  const { db: db3 } = await postgresClient();
1380
- const existing = await db3.from("statistics").where({
1442
+ const existing = await db3.from("tracking").where({
1443
+ ...statistic.user ? { user: statistic.user } : {},
1444
+ ...statistic.role ? { role: statistic.role } : {},
1381
1445
  name: statistic.name,
1382
1446
  label: statistic.label,
1383
1447
  type: statistic.type,
1384
1448
  createdAt: currentDate
1385
1449
  }).first();
1450
+ console.log("!!! existing !!!", existing);
1386
1451
  if (!existing) {
1387
- await db3.from("statistics").insert({
1452
+ await db3.from("tracking").insert({
1388
1453
  name: statistic.name,
1389
1454
  label: statistic.label,
1390
1455
  type: statistic.type,
1391
1456
  total: statistic.count ?? 1,
1392
- createdAt: currentDate
1457
+ createdAt: currentDate,
1458
+ ...statistic.user ? { user: statistic.user } : {},
1459
+ ...statistic.role ? { role: statistic.role } : {}
1393
1460
  });
1394
1461
  } else {
1395
- await db3.from("statistics").update({
1462
+ await db3.from("tracking").update({
1396
1463
  total: db3.raw("total + ?", [statistic.count ?? 1])
1397
1464
  }).where({
1398
1465
  name: statistic.name,
@@ -1731,25 +1798,25 @@ var requestValidators = {
1731
1798
  message: "Missing body."
1732
1799
  };
1733
1800
  }
1734
- if (!req.body.threadId) {
1801
+ if (!req.headers["user"]) {
1735
1802
  return {
1736
1803
  error: true,
1737
1804
  code: 400,
1738
- message: "Missing threadId in body."
1805
+ message: 'Missing "user" property in headers.'
1739
1806
  };
1740
1807
  }
1741
- if (!req.body.resourceId) {
1808
+ if (!req.headers["session"]) {
1742
1809
  return {
1743
1810
  error: true,
1744
1811
  code: 400,
1745
- message: "Missing resourceId in body."
1812
+ message: 'Missing "session" property in headers.'
1746
1813
  };
1747
1814
  }
1748
- if (!req.body.messages) {
1815
+ if (!req.body.message) {
1749
1816
  return {
1750
1817
  error: true,
1751
1818
  code: 400,
1752
- message: 'Missing "messages" property in body.'
1819
+ message: 'Missing "message" property in body.'
1753
1820
  };
1754
1821
  }
1755
1822
  return {
@@ -1853,6 +1920,9 @@ var map = (field) => {
1853
1920
  case "code":
1854
1921
  type = "String";
1855
1922
  break;
1923
+ case "enum":
1924
+ type = field.enumValues ? `${field.name}Enum` : "String";
1925
+ break;
1856
1926
  case "number":
1857
1927
  type = "Float";
1858
1928
  break;
@@ -1871,6 +1941,16 @@ var map = (field) => {
1871
1941
  return type;
1872
1942
  };
1873
1943
  function createTypeDefs(table) {
1944
+ const enumDefs = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
1945
+ const enumValues = field.enumValues.map((value) => {
1946
+ const sanitized = String(value).replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[0-9]/, "_$&").toUpperCase();
1947
+ return ` ${sanitized}`;
1948
+ }).join("\n");
1949
+ return `
1950
+ enum ${field.name}Enum {
1951
+ ${enumValues}
1952
+ }`;
1953
+ }).join("\n");
1874
1954
  const fields = table.fields.map((field) => {
1875
1955
  let type;
1876
1956
  type = map(field);
@@ -1882,8 +1962,6 @@ function createTypeDefs(table) {
1882
1962
  type ${table.name.singular} {
1883
1963
  ${fields.join("\n")}
1884
1964
  ${table.fields.find((field) => field.name === "id") ? "" : "id: ID!"}
1885
- createdAt: Date!
1886
- updatedAt: Date!
1887
1965
  ${rbacField}
1888
1966
  }
1889
1967
  `;
@@ -1894,39 +1972,62 @@ ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
1894
1972
  ${rbacInputField}
1895
1973
  }
1896
1974
  `;
1897
- return typeDef + inputDef;
1975
+ return enumDefs + typeDef + inputDef;
1898
1976
  }
1899
1977
  function createFilterTypeDefs(table) {
1900
1978
  const fieldFilters = table.fields.map((field) => {
1901
1979
  let type;
1902
- type = map(field);
1980
+ if (field.type === "enum" && field.enumValues) {
1981
+ type = `${field.name}Enum`;
1982
+ } else {
1983
+ type = map(field);
1984
+ }
1903
1985
  return `
1904
1986
  ${field.name}: FilterOperator${type}`;
1905
1987
  });
1906
1988
  const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
1989
+ const enumFilterOperators = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
1990
+ const enumTypeName = `${field.name}Enum`;
1991
+ return `
1992
+ input FilterOperator${enumTypeName} {
1993
+ eq: ${enumTypeName}
1994
+ ne: ${enumTypeName}
1995
+ in: [${enumTypeName}]
1996
+ and: [FilterOperator${enumTypeName}]
1997
+ or: [FilterOperator${enumTypeName}]
1998
+ }`;
1999
+ }).join("\n");
1907
2000
  const operatorTypes = `
1908
2001
  input FilterOperatorString {
1909
2002
  eq: String
1910
2003
  ne: String
1911
2004
  in: [String]
1912
2005
  contains: String
2006
+ and: [FilterOperatorString]
2007
+ or: [FilterOperatorString]
1913
2008
  }
1914
2009
 
1915
2010
  input FilterOperatorDate {
1916
2011
  lte: Date
1917
2012
  gte: Date
2013
+ and: [FilterOperatorDate]
2014
+ or: [FilterOperatorDate]
1918
2015
  }
1919
2016
 
1920
2017
  input FilterOperatorFloat {
1921
2018
  eq: Float
1922
2019
  ne: Float
1923
2020
  in: [Float]
2021
+ and: [FilterOperatorFloat]
2022
+ or: [FilterOperatorFloat]
1924
2023
  }
1925
2024
 
1926
2025
  input FilterOperatorBoolean {
1927
2026
  eq: Boolean
1928
2027
  ne: Boolean
1929
2028
  in: [Boolean]
2029
+ and: [FilterOperatorBoolean]
2030
+ or: [FilterOperatorBoolean]
1930
2031
  }
1931
2032
 
1932
2033
  input FilterOperatorJSON {
@@ -1945,6 +2046,8 @@ enum SortDirection {
1945
2046
  DESC
1946
2047
  }
1947
2048
 
2049
+ ${enumFilterOperators}
2050
+
1948
2051
  input Filter${tableNameSingularUpperCaseFirst} {
1949
2052
  ${fieldFilters.join("\n")}
1950
2053
  }`;
@@ -2014,33 +2117,33 @@ var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRba
2014
2117
  function createMutations(table) {
2015
2118
  const tableNamePlural = table.name.plural.toLowerCase();
2016
2119
  const validateWriteAccess = async (id, context) => {
2017
- const { db: db3, req, user } = context;
2018
- const hasRBAC = table.RBAC === true;
2019
- if (!hasRBAC) {
2020
- return true;
2021
- }
2022
- if (user.super_admin === true) {
2023
- return true;
2024
- }
2025
- if (!user.role || !(table.name.plural.includes("agent") && user.role.agents === "write") && !(table.name.plural.includes("workflow") && user.role.workflows === "write") && !(table.name.plural.includes("variable") && user.role.variables === "write") && !(table.name.plural.includes("user") && user.role.users === "write")) {
2026
- console.error("Access control error: no role found for current user or no access to entity type.");
2027
- throw new Error("Access control error: no role found for current user or no access to entity type.");
2028
- }
2029
2120
  try {
2030
- const authResult = await requestValidators.authenticate(req);
2031
- if (authResult.error || !authResult.user) {
2032
- throw new Error("Authentication required");
2121
+ const { db: db3, req, user } = context;
2122
+ if (user.super_admin === true) {
2123
+ return true;
2124
+ }
2125
+ 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")) {
2126
+ console.error("Access control error: no role found for current user or no access to entity type.");
2127
+ throw new Error("Access control error: no role found for current user or no access to entity type.");
2128
+ }
2129
+ const hasRBAC = table.RBAC === true;
2130
+ if (!hasRBAC) {
2131
+ return true;
2033
2132
  }
2034
- const user2 = authResult.user;
2035
2133
  const record = await db3.from(tableNamePlural).select(["rights_mode", "created_by"]).where({ id }).first();
2036
2134
  if (!record) {
2037
2135
  throw new Error("Record not found");
2038
2136
  }
2137
+ if (tableNamePlural === "jobs") {
2138
+ if (!user.super_admin && record.created_by !== user.id) {
2139
+ throw new Error("You are not authorized to edit this record");
2140
+ }
2141
+ }
2039
2142
  if (record.rights_mode === "public") {
2040
2143
  return true;
2041
2144
  }
2042
2145
  if (record.rights_mode === "private") {
2043
- if (record.created_by === user2.id) {
2146
+ if (record.created_by === user.id) {
2044
2147
  return true;
2045
2148
  }
2046
2149
  throw new Error("Only the creator can edit this private record");
@@ -2050,7 +2153,7 @@ function createMutations(table) {
2050
2153
  entity: table.name.singular,
2051
2154
  target_resource_id: id,
2052
2155
  access_type: "User",
2053
- user_id: user2.id,
2156
+ user_id: user.id,
2054
2157
  rights: "write"
2055
2158
  }).first();
2056
2159
  if (rbacRecord) {
@@ -2058,12 +2161,12 @@ function createMutations(table) {
2058
2161
  }
2059
2162
  throw new Error("Insufficient user permissions to edit this record");
2060
2163
  }
2061
- if (record.rights_mode === "roles" && user2.role) {
2164
+ if (record.rights_mode === "roles" && user.role) {
2062
2165
  const rbacRecord = await db3.from("rbac").where({
2063
2166
  entity: table.name.singular,
2064
2167
  target_resource_id: id,
2065
2168
  access_type: "Role",
2066
- role_id: user2.role,
2169
+ role_id: user.role,
2067
2170
  rights: "write"
2068
2171
  }).first();
2069
2172
  if (rbacRecord) {
@@ -2216,14 +2319,18 @@ function createMutations(table) {
2216
2319
  var applyAccessControl = (table, user, query) => {
2217
2320
  console.log("table", table);
2218
2321
  const tableNamePlural = table.name.plural.toLowerCase();
2322
+ if (!user.super_admin && table.name.plural === "jobs") {
2323
+ query = query.where("created_by", user.id);
2324
+ return query;
2325
+ }
2219
2326
  const hasRBAC = table.RBAC === true;
2220
2327
  if (!hasRBAC) {
2221
2328
  return query;
2222
2329
  }
2223
- if (user.super_admin === true) {
2330
+ if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2224
2331
  return query;
2225
2332
  }
2226
- if (!user.role || !(table.name.plural.includes("agent") && (user.role.agents === "read" || user.role.agents === "write")) && !(table.name.plural.includes("workflow") && (user.role.workflows === "read" || user.role.workflows === "write")) && !(table.name.plural.includes("variable") && (user.role.variables === "read" || user.role.variables === "write")) && !(table.name.plural.includes("user") && (user.role.users === "read" || user.role.users === "write"))) {
2333
+ 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"))) {
2227
2334
  console.error("Access control error: no role found or no access to entity type.");
2228
2335
  return query.where("1", "=", "0");
2229
2336
  }
@@ -2251,6 +2358,27 @@ var applyAccessControl = (table, user, query) => {
2251
2358
  }
2252
2359
  return query;
2253
2360
  };
2361
+ var converOperatorToQuery = (query, fieldName, operators) => {
2362
+ if (operators.eq !== void 0) {
2363
+ query = query.where(fieldName, operators.eq);
2364
+ }
2365
+ if (operators.ne !== void 0) {
2366
+ query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2367
+ }
2368
+ if (operators.in !== void 0) {
2369
+ query = query.whereIn(fieldName, operators.in);
2370
+ }
2371
+ if (operators.contains !== void 0) {
2372
+ query = query.where(fieldName, "like", `%${operators.contains}%`);
2373
+ }
2374
+ if (operators.lte !== void 0) {
2375
+ query = query.where(fieldName, "<=", operators.lte);
2376
+ }
2377
+ if (operators.gte !== void 0) {
2378
+ query = query.where(fieldName, ">=", operators.gte);
2379
+ }
2380
+ return query;
2381
+ };
2254
2382
  function createQueries(table) {
2255
2383
  const tableNamePlural = table.name.plural.toLowerCase();
2256
2384
  const tableNameSingular = table.name.singular.toLowerCase();
@@ -2258,18 +2386,19 @@ function createQueries(table) {
2258
2386
  filters.forEach((filter) => {
2259
2387
  Object.entries(filter).forEach(([fieldName, operators]) => {
2260
2388
  if (operators) {
2261
- if (operators.eq !== void 0) {
2262
- query = query.where(fieldName, operators.eq);
2263
- }
2264
- if (operators.ne !== void 0) {
2265
- query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2266
- }
2267
- if (operators.in !== void 0) {
2268
- query = query.whereIn(fieldName, operators.in);
2389
+ if (operators.and !== void 0) {
2390
+ console.log("operators.and", operators.and);
2391
+ operators.and.forEach((operator) => {
2392
+ query = converOperatorToQuery(query, fieldName, operator);
2393
+ });
2269
2394
  }
2270
- if (operators.contains !== void 0) {
2271
- query = query.where(fieldName, "like", `%${operators.contains}%`);
2395
+ if (operators.or !== void 0) {
2396
+ operators.or.forEach((operator) => {
2397
+ query = converOperatorToQuery(query, fieldName, operator);
2398
+ });
2272
2399
  }
2400
+ query = converOperatorToQuery(query, fieldName, operators);
2401
+ console.log("query", query);
2273
2402
  }
2274
2403
  });
2275
2404
  });
@@ -2342,57 +2471,36 @@ function createQueries(table) {
2342
2471
  query = applyFilters(query, filters);
2343
2472
  query = applyAccessControl(table, context.user, query);
2344
2473
  if (groupBy) {
2345
- const results = await query.select(groupBy).count("* as count").groupBy(groupBy);
2474
+ query = query.select(groupBy).groupBy(groupBy);
2475
+ if (tableNamePlural === "tracking") {
2476
+ query = query.sum("total as count");
2477
+ } else {
2478
+ query = query.count("* as count");
2479
+ }
2480
+ const results = await query;
2481
+ console.log("!!! results !!!", results);
2346
2482
  return results.map((r) => ({
2347
2483
  group: r[groupBy],
2348
- count: Number(r.count)
2484
+ count: r.count ? Number(r.count) : 0
2349
2485
  }));
2350
2486
  } else {
2351
- const [{ count }] = await query.count("* as count");
2352
- return [{
2353
- group: "total",
2354
- count: Number(count)
2355
- }];
2356
- }
2357
- },
2358
- // Add jobStatistics query for jobs table (backward compatibility)
2359
- ...tableNamePlural === "jobs" ? {
2360
- jobStatistics: async (_, args, context, info) => {
2361
- const { user, agent, from, to } = args;
2362
- const { db: db3 } = context;
2363
- let query = db3("jobs");
2364
- if (user) {
2365
- query = query.where("user", user);
2366
- }
2367
- if (agent) {
2368
- query = query.where("agent", agent);
2369
- }
2370
- if (from) {
2371
- query = query.where("createdAt", ">=", from);
2372
- }
2373
- if (to) {
2374
- query = query.where("createdAt", "<=", to);
2487
+ if (tableNamePlural === "tracking") {
2488
+ query = query.sum("total as count");
2489
+ const [{ count }] = await query.sum("total as count");
2490
+ console.log("!!! count !!!", count);
2491
+ return [{
2492
+ group: "total",
2493
+ count: count ? Number(count) : 0
2494
+ }];
2495
+ } else {
2496
+ const [{ count }] = await query.count("* as count");
2497
+ return [{
2498
+ group: "total",
2499
+ count: count ? Number(count) : 0
2500
+ }];
2375
2501
  }
2376
- query = applyAccessControl(table, context.user, query);
2377
- const runningQuery = query.clone().whereIn("status", ["active", "waiting", "delayed", "paused"]);
2378
- const [{ runningCount }] = await runningQuery.count("* as runningCount");
2379
- const erroredQuery = query.clone().whereIn("status", ["failed", "stuck"]);
2380
- const [{ erroredCount }] = await erroredQuery.count("* as erroredCount");
2381
- const completedQuery = query.clone().where("status", "completed");
2382
- const [{ completedCount }] = await completedQuery.count("* as completedCount");
2383
- const failedQuery = query.clone().where("status", "failed");
2384
- const [{ failedCount }] = await failedQuery.count("* as failedCount");
2385
- const durationQuery = query.clone().where("status", "completed").whereNotNull("duration").select(db3.raw('AVG("duration") as averageDuration'));
2386
- const [{ averageDuration }] = await durationQuery;
2387
- return {
2388
- runningCount: Number(runningCount),
2389
- erroredCount: Number(erroredCount),
2390
- completedCount: Number(completedCount),
2391
- failedCount: Number(failedCount),
2392
- averageDuration: averageDuration ? Number(averageDuration) : 0
2393
- };
2394
2502
  }
2395
- } : {}
2503
+ }
2396
2504
  };
2397
2505
  }
2398
2506
  var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
@@ -2412,6 +2520,16 @@ var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
2412
2520
  };
2413
2521
  };
2414
2522
  function createSDL(tables) {
2523
+ tables.forEach((table) => {
2524
+ table.fields.push({
2525
+ name: "createdAt",
2526
+ type: "date"
2527
+ });
2528
+ table.fields.push({
2529
+ name: "updatedAt",
2530
+ type: "date"
2531
+ });
2532
+ });
2415
2533
  console.log("[EXULU] Creating SDL");
2416
2534
  let typeDefs = `
2417
2535
  scalar JSON
@@ -2468,7 +2586,6 @@ function createSDL(tables) {
2468
2586
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2469
2587
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2470
2588
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
2471
- ${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
2472
2589
  `;
2473
2590
  mutationDefs += `
2474
2591
  ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
@@ -2493,17 +2610,6 @@ type PageInfo {
2493
2610
  hasNextPage: Boolean!
2494
2611
  }
2495
2612
  `;
2496
- if (tableNamePlural === "jobs") {
2497
- modelDefs += `
2498
- type JobStatistics {
2499
- runningCount: Int!
2500
- erroredCount: Int!
2501
- completedCount: Int!
2502
- failedCount: Int!
2503
- averageDuration: Float!
2504
- }
2505
- `;
2506
- }
2507
2613
  Object.assign(resolvers.Query, createQueries(table));
2508
2614
  Object.assign(resolvers.Mutation, createMutations(table));
2509
2615
  if (table.RBAC) {
@@ -2595,6 +2701,10 @@ var agentMessagesSchema = {
2595
2701
  name: "title",
2596
2702
  type: "text"
2597
2703
  },
2704
+ {
2705
+ name: "user",
2706
+ type: "number"
2707
+ },
2598
2708
  {
2599
2709
  name: "session",
2600
2710
  type: "text"
@@ -2831,6 +2941,10 @@ var rolesSchema = {
2831
2941
  type: "text"
2832
2942
  // write | read access to agents
2833
2943
  },
2944
+ {
2945
+ name: "api",
2946
+ type: "text"
2947
+ },
2834
2948
  {
2835
2949
  name: "workflows",
2836
2950
  type: "text"
@@ -2850,8 +2964,8 @@ var rolesSchema = {
2850
2964
  };
2851
2965
  var statisticsSchema = {
2852
2966
  name: {
2853
- plural: "statistics",
2854
- singular: "statistic"
2967
+ plural: "tracking",
2968
+ singular: "tracking"
2855
2969
  },
2856
2970
  fields: [
2857
2971
  {
@@ -2864,11 +2978,20 @@ var statisticsSchema = {
2864
2978
  },
2865
2979
  {
2866
2980
  name: "type",
2867
- type: "text"
2981
+ type: "enum",
2982
+ enumValues: Object.values(STATISTICS_TYPE_ENUM)
2868
2983
  },
2869
2984
  {
2870
2985
  name: "total",
2871
2986
  type: "number"
2987
+ },
2988
+ {
2989
+ name: "user",
2990
+ type: "number"
2991
+ },
2992
+ {
2993
+ name: "role",
2994
+ type: "uuid"
2872
2995
  }
2873
2996
  ]
2874
2997
  };
@@ -2929,6 +3052,7 @@ var jobsSchema = {
2929
3052
  plural: "jobs",
2930
3053
  singular: "job"
2931
3054
  },
3055
+ RBAC: true,
2932
3056
  fields: [
2933
3057
  {
2934
3058
  name: "redis",
@@ -3580,11 +3704,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3580
3704
  { route: "/agents/:id", method: "GET", note: "Get specific agent" },
3581
3705
  { route: "/contexts", method: "GET", note: "List all contexts" },
3582
3706
  { route: "/contexts/:id", method: "GET", note: "Get specific context" },
3583
- { route: "/contexts/statistics", method: "GET", note: "Get context statistics" },
3584
3707
  { route: "/tools", method: "GET", note: "List all tools" },
3585
3708
  { route: "/tools/:id", method: "GET", note: "Get specific tool" },
3586
- { route: "/statistics/timeseries", method: "POST", note: "Get time series statistics" },
3587
- { route: "/statistics/totals", method: "POST", note: "Get totals statistics" },
3588
3709
  { route: "/items/:context", method: "POST", note: "Create new item in context" },
3589
3710
  { route: "/items/:context", method: "GET", note: "Get items from context" },
3590
3711
  { route: "/items/export/:context", method: "GET", note: "Export items from context" },
@@ -4117,115 +4238,6 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4117
4238
  authenticated: true
4118
4239
  });
4119
4240
  });
4120
- console.log("[EXULU] statistics timeseries");
4121
- app.post("/statistics/timeseries", async (req, res) => {
4122
- const authenticationResult = await requestValidators.authenticate(req);
4123
- if (!authenticationResult.user?.id) {
4124
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4125
- return;
4126
- }
4127
- const { db: db3 } = await postgresClient();
4128
- const type = req.body.type;
4129
- if (!Object.values(STATISTICS_TYPE_ENUM).includes(type)) {
4130
- res.status(400).json({
4131
- message: "Invalid type, must be one of: " + Object.values(STATISTICS_TYPE_ENUM).join(", ")
4132
- });
4133
- return;
4134
- }
4135
- let from = new Date(req.body.from);
4136
- let to = new Date(req.body.to);
4137
- if (!from || !to) {
4138
- from = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4139
- to = /* @__PURE__ */ new Date();
4140
- }
4141
- const query = db3.from("statistics").select("*");
4142
- query.where("name", "count");
4143
- query.andWhere("type", type);
4144
- query.andWhere("createdAt", ">=", from);
4145
- query.andWhere("createdAt", "<=", to);
4146
- const results = await query;
4147
- const dates = [];
4148
- for (let i = 0; i < (to.getTime() - from.getTime()) / (1e3 * 60 * 60 * 24); i++) {
4149
- dates.push(new Date(from.getTime() + i * (1e3 * 60 * 60 * 24)));
4150
- }
4151
- const data = dates.map((date) => {
4152
- const result = results.find((result2) => result2.date === date);
4153
- if (result) {
4154
- return result;
4155
- }
4156
- return {
4157
- date,
4158
- count: 0
4159
- };
4160
- });
4161
- res.status(200).json({
4162
- data,
4163
- filter: {
4164
- from,
4165
- to
4166
- }
4167
- });
4168
- });
4169
- console.log("[EXULU] statistics totals");
4170
- app.post("/statistics/totals", async (req, res) => {
4171
- const authenticationResult = await requestValidators.authenticate(req);
4172
- if (!authenticationResult.user?.id) {
4173
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4174
- return;
4175
- }
4176
- const { db: db3 } = await postgresClient();
4177
- let from = new Date(req.body.from);
4178
- let to = new Date(req.body.to);
4179
- if (!from || !to) {
4180
- from = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3);
4181
- to = /* @__PURE__ */ new Date();
4182
- }
4183
- let promises2 = Object.values(STATISTICS_TYPE_ENUM).map(async (type) => {
4184
- const result = await db3.from("statistics").where("name", "count").andWhere("type", type).andWhere("createdAt", ">=", from).andWhere("createdAt", "<=", to).sum("total as total");
4185
- return {
4186
- [type]: result[0]?.total || 0
4187
- };
4188
- });
4189
- const results = await Promise.all(promises2);
4190
- res.status(200).json({
4191
- data: { ...Object.assign({}, ...results) },
4192
- filter: {
4193
- from,
4194
- to
4195
- }
4196
- });
4197
- });
4198
- console.log("[EXULU] contexts statistics");
4199
- app.get("/contexts/statistics", async (req, res) => {
4200
- const authenticationResult = await requestValidators.authenticate(req);
4201
- if (!authenticationResult.user?.id) {
4202
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4203
- return;
4204
- }
4205
- const { db: db3 } = await postgresClient();
4206
- const statistics = await db3("statistics").where("name", "count").andWhere("type", "context.retrieve").sum("total as total").first();
4207
- 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) => ({
4208
- jobs: rows
4209
- }));
4210
- let jobs = [];
4211
- if (response[0]) {
4212
- jobs = response[0].jobs.map((job) => ({
4213
- date: job.id,
4214
- count: job.count
4215
- }));
4216
- }
4217
- const embeddingsCountResult = await db3("jobs").where("type", "embedder").count("* as count").first();
4218
- res.status(200).json({
4219
- active: contexts.filter((context) => context.active).length,
4220
- inactive: contexts.filter((context) => !context.active).length,
4221
- sources: contexts.reduce((acc, context) => acc + context.sources.get().length, 0),
4222
- queries: statistics?.total || 0,
4223
- jobs,
4224
- totals: {
4225
- embeddings: embeddingsCountResult?.count || 0
4226
- }
4227
- });
4228
- });
4229
4241
  console.log("[EXULU] context by id");
4230
4242
  app.get(`/contexts/:id`, async (req, res) => {
4231
4243
  const authenticationResult = await requestValidators.authenticate(req);
@@ -4396,7 +4408,11 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4396
4408
  return;
4397
4409
  }
4398
4410
  }
4399
- const stream = req.headers["stream"] || false;
4411
+ const headers = {
4412
+ stream: req.headers["stream"] === "true" || false,
4413
+ user: req.headers["user"] || null,
4414
+ session: req.headers["session"] || null
4415
+ };
4400
4416
  const requestValidationResult = requestValidators.agents(req);
4401
4417
  if (requestValidationResult.error) {
4402
4418
  res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
@@ -4407,6 +4423,13 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4407
4423
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4408
4424
  return;
4409
4425
  }
4426
+ const user = authenticationResult.user;
4427
+ if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
4428
+ res.status(400).json({
4429
+ 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."
4430
+ });
4431
+ return;
4432
+ }
4410
4433
  console.log("[EXULU] agent tools", agentInstance.tools);
4411
4434
  const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4412
4435
  console.log("[EXULU] enabled tools", enabledTools);
@@ -4429,9 +4452,15 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4429
4452
  const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
4430
4453
  providerApiKey = bytes.toString(CryptoJS3.enc.Utf8);
4431
4454
  }
4432
- if (!!stream) {
4433
- const result = agent.generateStream({
4434
- messages: req.body.messages,
4455
+ if (!!headers.stream) {
4456
+ await agent.generateStream({
4457
+ express: {
4458
+ res,
4459
+ req
4460
+ },
4461
+ user: headers.user,
4462
+ session: headers.session,
4463
+ message: req.body.message,
4435
4464
  tools: enabledTools,
4436
4465
  providerApiKey,
4437
4466
  toolConfigs: agentInstance.tools,
@@ -4440,11 +4469,12 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4440
4469
  trigger: "agent"
4441
4470
  }
4442
4471
  });
4443
- result.pipeDataStreamToResponse(res);
4444
4472
  return;
4445
4473
  } else {
4446
4474
  const response = await agent.generateSync({
4447
- messages: req.body.messages,
4475
+ user: headers.user,
4476
+ session: headers.session,
4477
+ message: req.body.message,
4448
4478
  tools: enabledTools.map((tool2) => tool2.tool),
4449
4479
  providerApiKey,
4450
4480
  toolConfigs: agentInstance.tools,
@@ -4969,7 +4999,6 @@ var defaultAgent = new ExuluAgent({
4969
4999
  description: `Basic agent without any defined tools, that can support MCP's.`,
4970
5000
  type: "agent",
4971
5001
  capabilities: {
4972
- tools: false,
4973
5002
  images: [],
4974
5003
  files: [],
4975
5004
  audio: [],
@@ -4981,10 +5010,10 @@ var defaultAgent = new ExuluAgent({
4981
5010
  instructions: "You are a helpful assistant.",
4982
5011
  model: {
4983
5012
  create: ({ apiKey }) => {
4984
- const anthropic2 = createAnthropic({
5013
+ const anthropic = createAnthropic({
4985
5014
  apiKey
4986
5015
  });
4987
- return anthropic2("claude-4-opus-20250514");
5016
+ return anthropic.languageModel("claude-4-opus-20250514");
4988
5017
  }
4989
5018
  // todo add a field of type string that adds a dropdown list from which the user can select the model
4990
5019
  // todo for each model, check which provider is used, and require the admin to add one or multiple
@@ -6525,6 +6554,13 @@ var ExuluJobs = {
6525
6554
  var db2 = {
6526
6555
  init: async () => {
6527
6556
  await execute();
6557
+ },
6558
+ api: {
6559
+ key: {
6560
+ generate: async (name, email) => {
6561
+ return await generateApiKey(name, email);
6562
+ }
6563
+ }
6528
6564
  }
6529
6565
  };
6530
6566
  var ExuluChunkers = {