@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.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
  }`;
@@ -2016,6 +2119,17 @@ function createMutations(table) {
2016
2119
  const validateWriteAccess = async (id, context) => {
2017
2120
  try {
2018
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;
2132
+ }
2019
2133
  const record = await db3.from(tableNamePlural).select(["rights_mode", "created_by"]).where({ id }).first();
2020
2134
  if (!record) {
2021
2135
  throw new Error("Record not found");
@@ -2025,17 +2139,6 @@ function createMutations(table) {
2025
2139
  throw new Error("You are not authorized to edit this record");
2026
2140
  }
2027
2141
  }
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
2142
  if (record.rights_mode === "public") {
2040
2143
  return true;
2041
2144
  }
@@ -2216,15 +2319,15 @@ function createMutations(table) {
2216
2319
  var applyAccessControl = (table, user, query) => {
2217
2320
  console.log("table", table);
2218
2321
  const tableNamePlural = table.name.plural.toLowerCase();
2219
- const hasRBAC = table.RBAC === true;
2220
- if (!hasRBAC) {
2322
+ if (!user.super_admin && table.name.plural === "jobs") {
2323
+ query = query.where("created_by", user.id);
2221
2324
  return query;
2222
2325
  }
2223
- if (user.super_admin === true) {
2326
+ const hasRBAC = table.RBAC === true;
2327
+ if (!hasRBAC) {
2224
2328
  return query;
2225
2329
  }
2226
- if (table.name.plural === "jobs") {
2227
- query = query.where("created_by", user.id);
2330
+ if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2228
2331
  return query;
2229
2332
  }
2230
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"))) {
@@ -2255,6 +2358,27 @@ var applyAccessControl = (table, user, query) => {
2255
2358
  }
2256
2359
  return query;
2257
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
+ };
2258
2382
  function createQueries(table) {
2259
2383
  const tableNamePlural = table.name.plural.toLowerCase();
2260
2384
  const tableNameSingular = table.name.singular.toLowerCase();
@@ -2262,18 +2386,19 @@ function createQueries(table) {
2262
2386
  filters.forEach((filter) => {
2263
2387
  Object.entries(filter).forEach(([fieldName, operators]) => {
2264
2388
  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);
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
+ });
2273
2394
  }
2274
- if (operators.contains !== void 0) {
2275
- 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
+ });
2276
2399
  }
2400
+ query = converOperatorToQuery(query, fieldName, operators);
2401
+ console.log("query", query);
2277
2402
  }
2278
2403
  });
2279
2404
  });
@@ -2346,57 +2471,36 @@ function createQueries(table) {
2346
2471
  query = applyFilters(query, filters);
2347
2472
  query = applyAccessControl(table, context.user, query);
2348
2473
  if (groupBy) {
2349
- 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);
2350
2482
  return results.map((r) => ({
2351
2483
  group: r[groupBy],
2352
- count: Number(r.count)
2484
+ count: r.count ? Number(r.count) : 0
2353
2485
  }));
2354
2486
  } 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);
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
+ }];
2379
2501
  }
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
2502
  }
2399
- } : {}
2503
+ }
2400
2504
  };
2401
2505
  }
2402
2506
  var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
@@ -2416,6 +2520,16 @@ var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
2416
2520
  };
2417
2521
  };
2418
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
+ });
2419
2533
  console.log("[EXULU] Creating SDL");
2420
2534
  let typeDefs = `
2421
2535
  scalar JSON
@@ -2472,7 +2586,6 @@ function createSDL(tables) {
2472
2586
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2473
2587
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2474
2588
  ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
2475
- ${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
2476
2589
  `;
2477
2590
  mutationDefs += `
2478
2591
  ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
@@ -2497,17 +2610,6 @@ type PageInfo {
2497
2610
  hasNextPage: Boolean!
2498
2611
  }
2499
2612
  `;
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
2613
  Object.assign(resolvers.Query, createQueries(table));
2512
2614
  Object.assign(resolvers.Mutation, createMutations(table));
2513
2615
  if (table.RBAC) {
@@ -2599,6 +2701,10 @@ var agentMessagesSchema = {
2599
2701
  name: "title",
2600
2702
  type: "text"
2601
2703
  },
2704
+ {
2705
+ name: "user",
2706
+ type: "number"
2707
+ },
2602
2708
  {
2603
2709
  name: "session",
2604
2710
  type: "text"
@@ -2835,6 +2941,10 @@ var rolesSchema = {
2835
2941
  type: "text"
2836
2942
  // write | read access to agents
2837
2943
  },
2944
+ {
2945
+ name: "api",
2946
+ type: "text"
2947
+ },
2838
2948
  {
2839
2949
  name: "workflows",
2840
2950
  type: "text"
@@ -2854,8 +2964,8 @@ var rolesSchema = {
2854
2964
  };
2855
2965
  var statisticsSchema = {
2856
2966
  name: {
2857
- plural: "statistics",
2858
- singular: "statistic"
2967
+ plural: "tracking",
2968
+ singular: "tracking"
2859
2969
  },
2860
2970
  fields: [
2861
2971
  {
@@ -2868,11 +2978,20 @@ var statisticsSchema = {
2868
2978
  },
2869
2979
  {
2870
2980
  name: "type",
2871
- type: "text"
2981
+ type: "enum",
2982
+ enumValues: Object.values(STATISTICS_TYPE_ENUM)
2872
2983
  },
2873
2984
  {
2874
2985
  name: "total",
2875
2986
  type: "number"
2987
+ },
2988
+ {
2989
+ name: "user",
2990
+ type: "number"
2991
+ },
2992
+ {
2993
+ name: "role",
2994
+ type: "uuid"
2876
2995
  }
2877
2996
  ]
2878
2997
  };
@@ -2933,6 +3052,7 @@ var jobsSchema = {
2933
3052
  plural: "jobs",
2934
3053
  singular: "job"
2935
3054
  },
3055
+ RBAC: true,
2936
3056
  fields: [
2937
3057
  {
2938
3058
  name: "redis",
@@ -2942,10 +3062,6 @@ var jobsSchema = {
2942
3062
  name: "session",
2943
3063
  type: "text"
2944
3064
  },
2945
- {
2946
- name: "created_by",
2947
- type: "number"
2948
- },
2949
3065
  {
2950
3066
  name: "status",
2951
3067
  type: "text"
@@ -3588,11 +3704,8 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3588
3704
  { route: "/agents/:id", method: "GET", note: "Get specific agent" },
3589
3705
  { route: "/contexts", method: "GET", note: "List all contexts" },
3590
3706
  { route: "/contexts/:id", method: "GET", note: "Get specific context" },
3591
- { route: "/contexts/statistics", method: "GET", note: "Get context statistics" },
3592
3707
  { route: "/tools", method: "GET", note: "List all tools" },
3593
3708
  { 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
3709
  { route: "/items/:context", method: "POST", note: "Create new item in context" },
3597
3710
  { route: "/items/:context", method: "GET", note: "Get items from context" },
3598
3711
  { route: "/items/export/:context", method: "GET", note: "Export items from context" },
@@ -4125,115 +4238,6 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4125
4238
  authenticated: true
4126
4239
  });
4127
4240
  });
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
4241
  console.log("[EXULU] context by id");
4238
4242
  app.get(`/contexts/:id`, async (req, res) => {
4239
4243
  const authenticationResult = await requestValidators.authenticate(req);
@@ -4404,7 +4408,11 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4404
4408
  return;
4405
4409
  }
4406
4410
  }
4407
- 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
+ };
4408
4416
  const requestValidationResult = requestValidators.agents(req);
4409
4417
  if (requestValidationResult.error) {
4410
4418
  res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
@@ -4415,6 +4423,13 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4415
4423
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4416
4424
  return;
4417
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
+ }
4418
4433
  console.log("[EXULU] agent tools", agentInstance.tools);
4419
4434
  const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4420
4435
  console.log("[EXULU] enabled tools", enabledTools);
@@ -4437,9 +4452,15 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4437
4452
  const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
4438
4453
  providerApiKey = bytes.toString(CryptoJS3.enc.Utf8);
4439
4454
  }
4440
- if (!!stream) {
4441
- const result = agent.generateStream({
4442
- 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,
4443
4464
  tools: enabledTools,
4444
4465
  providerApiKey,
4445
4466
  toolConfigs: agentInstance.tools,
@@ -4448,11 +4469,12 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
4448
4469
  trigger: "agent"
4449
4470
  }
4450
4471
  });
4451
- result.pipeDataStreamToResponse(res);
4452
4472
  return;
4453
4473
  } else {
4454
4474
  const response = await agent.generateSync({
4455
- messages: req.body.messages,
4475
+ user: headers.user,
4476
+ session: headers.session,
4477
+ message: req.body.message,
4456
4478
  tools: enabledTools.map((tool2) => tool2.tool),
4457
4479
  providerApiKey,
4458
4480
  toolConfigs: agentInstance.tools,
@@ -4977,7 +4999,6 @@ var defaultAgent = new ExuluAgent({
4977
4999
  description: `Basic agent without any defined tools, that can support MCP's.`,
4978
5000
  type: "agent",
4979
5001
  capabilities: {
4980
- tools: false,
4981
5002
  images: [],
4982
5003
  files: [],
4983
5004
  audio: [],
@@ -4989,10 +5010,10 @@ var defaultAgent = new ExuluAgent({
4989
5010
  instructions: "You are a helpful assistant.",
4990
5011
  model: {
4991
5012
  create: ({ apiKey }) => {
4992
- const anthropic2 = createAnthropic({
5013
+ const anthropic = createAnthropic({
4993
5014
  apiKey
4994
5015
  });
4995
- return anthropic2("claude-4-opus-20250514");
5016
+ return anthropic.languageModel("claude-4-opus-20250514");
4996
5017
  }
4997
5018
  // todo add a field of type string that adds a dropdown list from which the user can select the model
4998
5019
  // todo for each model, check which provider is used, and require the admin to add one or multiple
@@ -6533,6 +6554,13 @@ var ExuluJobs = {
6533
6554
  var db2 = {
6534
6555
  init: async () => {
6535
6556
  await execute();
6557
+ },
6558
+ api: {
6559
+ key: {
6560
+ generate: async (name, email) => {
6561
+ return await generateApiKey(name, email);
6562
+ }
6563
+ }
6536
6564
  }
6537
6565
  };
6538
6566
  var ExuluChunkers = {