@exulu/backend 0.2.3 → 0.2.4

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
@@ -30,14 +30,17 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ EXULU_JOB_STATUS_ENUM: () => JOB_STATUS_ENUM,
33
34
  EXULU_STATISTICS_TYPE_ENUM: () => STATISTICS_TYPE_ENUM,
34
35
  ExuluAgent: () => ExuluAgent,
35
36
  ExuluApp: () => ExuluApp,
36
37
  ExuluAuthentication: () => authentication,
38
+ ExuluChunkers: () => ExuluChunkers,
37
39
  ExuluContext: () => ExuluContext,
38
40
  ExuluDatabase: () => ExuluDatabase,
39
41
  ExuluEmbedder: () => ExuluEmbedder,
40
42
  ExuluJobs: () => ExuluJobs,
43
+ ExuluLogger: () => ExuluLogger,
41
44
  ExuluQueues: () => queues,
42
45
  ExuluSource: () => ExuluSource,
43
46
  ExuluTool: () => ExuluTool,
@@ -45,6 +48,7 @@ __export(index_exports, {
45
48
  ExuluZodFileType: () => ExuluZodFileType
46
49
  });
47
50
  module.exports = __toCommonJS(index_exports);
51
+ var import_config = require("dotenv/config");
48
52
 
49
53
  // src/redis/client.ts
50
54
  var import_redis = require("redis");
@@ -155,6 +159,10 @@ var usersSchema = {
155
159
  name: "firstname",
156
160
  type: "text"
157
161
  },
162
+ {
163
+ name: "name",
164
+ type: "text"
165
+ },
158
166
  {
159
167
  name: "lastname",
160
168
  type: "text"
@@ -258,6 +266,70 @@ var statisticsSchema = {
258
266
  }
259
267
  ]
260
268
  };
269
+ var workflowSchema = {
270
+ name: {
271
+ plural: "workflows",
272
+ singular: "workflow"
273
+ },
274
+ fields: [
275
+ {
276
+ name: "workflow_name",
277
+ type: "text"
278
+ },
279
+ {
280
+ name: "run_id",
281
+ type: "text"
282
+ },
283
+ {
284
+ name: "snapshot",
285
+ type: "text"
286
+ }
287
+ ]
288
+ };
289
+ var threadsSchema = {
290
+ name: {
291
+ plural: "threads",
292
+ singular: "thread"
293
+ },
294
+ fields: [
295
+ {
296
+ name: "resourceId",
297
+ type: "text"
298
+ },
299
+ {
300
+ name: "title",
301
+ type: "text"
302
+ },
303
+ {
304
+ name: "metadata",
305
+ type: "text"
306
+ }
307
+ ]
308
+ };
309
+ var messagesSchema = {
310
+ name: {
311
+ plural: "messages",
312
+ singular: "message"
313
+ },
314
+ fields: [
315
+ {
316
+ name: "thread_id",
317
+ type: "text"
318
+ },
319
+ {
320
+ name: "content",
321
+ type: "text"
322
+ },
323
+ {
324
+ name: "role",
325
+ type: "text"
326
+ },
327
+ {
328
+ name: "type",
329
+ type: "text"
330
+ }
331
+ ]
332
+ };
261
333
  var jobsSchema = {
262
334
  name: {
263
335
  plural: "jobs",
@@ -282,7 +354,7 @@ var jobsSchema = {
282
354
  },
283
355
  {
284
356
  name: "result",
285
- type: "text"
357
+ type: "longText"
286
358
  },
287
359
  {
288
360
  name: "name",
@@ -292,6 +364,10 @@ var jobsSchema = {
292
364
  name: "agent",
293
365
  type: "text"
294
366
  },
367
+ {
368
+ name: "workflow",
369
+ type: "text"
370
+ },
295
371
  {
296
372
  name: "user",
297
373
  type: "text"
@@ -300,6 +376,10 @@ var jobsSchema = {
300
376
  name: "item",
301
377
  type: "text"
302
378
  },
379
+ {
380
+ name: "steps",
381
+ type: "number"
382
+ },
303
383
  {
304
384
  name: "inputs",
305
385
  type: "json"
@@ -403,14 +483,11 @@ var sanitizeName = (name) => {
403
483
  return name.toLowerCase().replace(/ /g, "_");
404
484
  };
405
485
 
406
- // src/postgres/init-db.ts
407
- var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
408
-
409
486
  // src/auth/generate-key.ts
410
487
  var import_bcryptjs = __toESM(require("bcryptjs"), 1);
411
488
  var SALT_ROUNDS = 12;
412
- async function encryptApiKey(apikey) {
413
- const hash = await import_bcryptjs.default.hash(apikey, SALT_ROUNDS);
489
+ async function encryptString(string) {
490
+ const hash = await import_bcryptjs.default.hash(string, SALT_ROUNDS);
414
491
  return hash;
415
492
  }
416
493
  var generateApiKey = async (name, email) => {
@@ -432,7 +509,7 @@ var generateApiKey = async (name, email) => {
432
509
  const newKeyName = name;
433
510
  const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
434
511
  const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
435
- const encryptedKey = await encryptApiKey(plainKey);
512
+ const encryptedKey = await encryptString(plainKey);
436
513
  const existingApiUser = await db2.from("users").where({ email }).first();
437
514
  if (!existingApiUser) {
438
515
  console.log("[EXULU] Creating default api user.");
@@ -443,6 +520,7 @@ var generateApiKey = async (name, email) => {
443
520
  createdAt: /* @__PURE__ */ new Date(),
444
521
  updatedAt: /* @__PURE__ */ new Date(),
445
522
  type: "api",
523
+ emailVerified: /* @__PURE__ */ new Date(),
446
524
  apikey: `${encryptedKey}${postFix}`,
447
525
  // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
448
526
  role: roleId
@@ -607,11 +685,6 @@ var up = async function(knex) {
607
685
  });
608
686
  }
609
687
  };
610
- var SALT_ROUNDS2 = 12;
611
- async function encryptApiKey2(apikey) {
612
- const hash = await import_bcryptjs2.default.hash(apikey, SALT_ROUNDS2);
613
- return hash;
614
- }
615
688
  var execute = async () => {
616
689
  console.log("[EXULU] Initializing database.");
617
690
  const { db: db2 } = await postgresClient();
@@ -624,33 +697,33 @@ var execute = async () => {
624
697
  const role = await db2.from("roles").insert({
625
698
  name: "admin",
626
699
  is_admin: true,
627
- agents: []
700
+ agents: JSON.stringify([])
628
701
  }).returning("id");
629
702
  roleId = role[0].id;
630
703
  } else {
631
704
  roleId = existingRole.id;
632
705
  }
633
- const newKeyName = "exulu_default_key";
634
- const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
635
- const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
636
- const encryptedKey = await encryptApiKey2(plainKey);
637
706
  const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
638
707
  if (!existingUser) {
708
+ const password = await encryptString("admin");
639
709
  console.log("[EXULU] Creating default admin user.");
640
710
  await db2.from("users").insert({
641
711
  name: "exulu",
642
712
  email: "admin@exulu.com",
643
713
  super_admin: true,
644
714
  createdAt: /* @__PURE__ */ new Date(),
715
+ emailVerified: /* @__PURE__ */ new Date(),
645
716
  updatedAt: /* @__PURE__ */ new Date(),
717
+ password,
646
718
  type: "user",
647
- // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
648
719
  role: roleId
649
720
  });
650
721
  }
651
722
  const { key } = await generateApiKey("exulu", "api@exulu.com");
652
723
  console.log("[EXULU] Database initialized.");
653
724
  console.log("[EXULU] Default api key: ", `${key}`);
725
+ console.log("[EXULU] Default password if using password auth: ", `admin`);
726
+ console.log("[EXULU] Default email if using password auth: ", `admin@exulu.com`);
654
727
  return;
655
728
  };
656
729
 
@@ -661,7 +734,6 @@ var import_core = require("@mastra/core");
661
734
  var import_zod2 = require("zod");
662
735
  var fs = __toESM(require("fs"), 1);
663
736
  var path = __toESM(require("path"), 1);
664
- var import_bullmq3 = require("bullmq");
665
737
  var import_memory = require("@mastra/memory");
666
738
  var import_pg = require("@mastra/pg");
667
739
 
@@ -697,6 +769,7 @@ var bullmqDecorator = async ({
697
769
  configuration,
698
770
  updater,
699
771
  context,
772
+ steps,
700
773
  source,
701
774
  documents,
702
775
  trigger,
@@ -714,11 +787,13 @@ var bullmqDecorator = async ({
714
787
  ...context && { context },
715
788
  ...source && { source },
716
789
  ...documents && { documents },
790
+ ...steps && { steps },
717
791
  ...trigger && { trigger },
718
792
  ...item && { item },
719
793
  agent,
720
794
  user,
721
795
  inputs,
796
+ label,
722
797
  session
723
798
  },
724
799
  {
@@ -743,6 +818,7 @@ var bullmqDecorator = async ({
743
818
  ...embedder && { embedder },
744
819
  ...workflow && { workflow },
745
820
  ...configuration && { configuration },
821
+ ...steps && { steps },
746
822
  ...updater && { updater },
747
823
  ...context && { context },
748
824
  ...source && { source },
@@ -766,6 +842,17 @@ var bullmqDecorator = async ({
766
842
  };
767
843
  };
768
844
 
845
+ // types/enums/jobs.ts
846
+ var JOB_STATUS_ENUM = {
847
+ completed: "completed",
848
+ failed: "failed",
849
+ delayed: "delayed",
850
+ active: "active",
851
+ waiting: "waiting",
852
+ paused: "paused",
853
+ stuck: "stuck"
854
+ };
855
+
769
856
  // src/registry/classes.ts
770
857
  function generateSlug(name) {
771
858
  const normalized = name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
@@ -800,6 +887,7 @@ var ExuluAgent = class {
800
887
  config;
801
888
  memory;
802
889
  tools;
890
+ agent;
803
891
  capabilities;
804
892
  constructor({ id, name, description, outputSchema, config, rateLimit, type, capabilities, tools }) {
805
893
  this.id = id;
@@ -812,6 +900,14 @@ var ExuluAgent = class {
812
900
  this.config = config;
813
901
  this.capabilities = capabilities;
814
902
  this.slug = `/agents/${generateSlug(this.name)}/run`;
903
+ if (this.type === "agent") {
904
+ this.agent = new import_core.Agent({
905
+ name: this.config.name,
906
+ instructions: this.config.instructions,
907
+ model: this.config.model,
908
+ memory: this.memory ? this.memory : void 0
909
+ });
910
+ }
815
911
  if (config?.memory) {
816
912
  console.log("[EXULU] Initializing memory for agent " + this.name);
817
913
  const connectionString = `postgresql://${process.env.POSTGRES_DB_USER}:${process.env.POSTGRES_DB_PASSWORD}@${process.env.POSTGRES_DB_HOST}:${process.env.POSTGRES_DB_PORT}/exulu`;
@@ -825,7 +921,11 @@ var ExuluAgent = class {
825
921
  password: process.env.POSTGRES_DB_PASSWORD || "",
826
922
  ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
827
923
  }),
828
- ...config?.memory.vector ? { vector: new import_pg.PgVector(connectionString) } : {},
924
+ ...config?.memory.vector ? {
925
+ vector: new import_pg.PgVector({
926
+ connectionString
927
+ })
928
+ } : {},
829
929
  options: {
830
930
  lastMessages: config?.memory.lastMessages || 10,
831
931
  semanticRecall: {
@@ -836,33 +936,17 @@ var ExuluAgent = class {
836
936
  });
837
937
  }
838
938
  }
839
- chat = async (id) => {
840
- const { db: db2 } = await postgresClient();
841
- const agent = await db2.from("agents").select("*").where("id", "=", id).first();
842
- if (!agent) {
939
+ chat = () => {
940
+ if (!this.agent) {
843
941
  throw new Error("Agent not found");
844
942
  }
845
- let tools = {};
846
- agent.tools?.forEach(({ name }) => {
847
- const tool = this.tools?.find((t) => t.name === name);
848
- if (!tool) {
849
- return;
850
- }
851
- return tool;
852
- });
853
943
  updateStatistic({
854
944
  name: "count",
855
945
  label: this.name,
856
946
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
857
947
  trigger: "agent"
858
948
  });
859
- return new import_core.Agent({
860
- name: this.config.name,
861
- instructions: this.config.instructions,
862
- model: this.config.model,
863
- tools,
864
- memory: this.memory ? this.memory : void 0
865
- });
949
+ return this.agent;
866
950
  };
867
951
  };
868
952
  var ExuluEmbedder = class {
@@ -919,35 +1003,141 @@ var ExuluWorkflow = class {
919
1003
  enable_batch = false;
920
1004
  slug = "";
921
1005
  queue;
922
- workflow;
923
- inputSchema;
924
- constructor({ id, name, description, workflow, queue, enable_batch, inputSchema }) {
1006
+ steps;
1007
+ constructor({ id, name, description, steps, queue, enable_batch }) {
925
1008
  this.id = id;
926
1009
  this.name = name;
927
1010
  this.description = description;
928
1011
  this.enable_batch = enable_batch;
929
1012
  this.slug = `/workflows/${generateSlug(this.name)}/run`;
930
1013
  this.queue = queue;
931
- this.inputSchema = inputSchema;
932
- this.workflow = workflow;
1014
+ this.steps = steps;
933
1015
  }
1016
+ start = async ({
1017
+ inputs: initialInputs,
1018
+ user,
1019
+ logger,
1020
+ job,
1021
+ session,
1022
+ agent,
1023
+ label
1024
+ }) => {
1025
+ let inputs;
1026
+ const { db: db2 } = await postgresClient();
1027
+ if (!job?.id) {
1028
+ logger.write(`Creating new job for workflow ${this.name} with inputs: ${JSON.stringify(initialInputs)}`, "INFO");
1029
+ const result = await db2("jobs").insert({
1030
+ status: JOB_STATUS_ENUM.active,
1031
+ name: `Job running '${this.name}' for '${label}'`,
1032
+ agent,
1033
+ workflow: this.id,
1034
+ type: "workflow",
1035
+ steps: this.steps?.length || 0,
1036
+ inputs: initialInputs,
1037
+ session,
1038
+ user
1039
+ }).returning(["id", "status"]);
1040
+ job = result[0];
1041
+ logger.write(`Created new job for workflow ${this.name}, job id: ${job?.id}`, "INFO");
1042
+ }
1043
+ if (!job) {
1044
+ throw new Error("Job not found, or failed to be created.");
1045
+ }
1046
+ if (job.status !== JOB_STATUS_ENUM.active) {
1047
+ await db2("jobs").update({
1048
+ status: JOB_STATUS_ENUM.active,
1049
+ inputs: initialInputs
1050
+ }).where({ id: job?.id }).returning("id");
1051
+ }
1052
+ const runStep = async (step, inputs2) => {
1053
+ let result;
1054
+ try {
1055
+ result = await step.fn({
1056
+ inputs: inputs2,
1057
+ logger,
1058
+ job,
1059
+ user
1060
+ });
1061
+ return result;
1062
+ } catch (error) {
1063
+ logger.write(`Step ${step.name} failed with error: ${error.message}`, "ERROR");
1064
+ if (step.retries && step.retries > 0) {
1065
+ logger.write(`Retrying step ${step.name} with ${step.retries} retries left`, "INFO");
1066
+ step.retries--;
1067
+ let result2 = await runStep(step, inputs2);
1068
+ return result2;
1069
+ }
1070
+ logger.write(`Step ${step.name} failed with error: ${error.message}`, "ERROR");
1071
+ throw error;
1072
+ }
1073
+ };
1074
+ let final;
1075
+ try {
1076
+ for (let i = 0; i < this.steps.length; i++) {
1077
+ const step = this.steps[i];
1078
+ if (!step) {
1079
+ throw new Error("Step not found.");
1080
+ }
1081
+ if (i === 0) {
1082
+ inputs = initialInputs;
1083
+ }
1084
+ logger.write(`Running step ${step.name} with inputs: ${JSON.stringify(inputs)}`, "INFO");
1085
+ let result = await runStep(step, inputs);
1086
+ inputs = result;
1087
+ logger.write(`Step ${step.name} output: ${JSON.stringify(result)}`, "INFO");
1088
+ final = result;
1089
+ }
1090
+ await db2("jobs").update({
1091
+ status: JOB_STATUS_ENUM.completed,
1092
+ result: JSON.stringify(final),
1093
+ finished_at: db2.fn.now()
1094
+ }).where({ id: job?.id }).returning("id");
1095
+ return final;
1096
+ } catch (error) {
1097
+ logger.write(`Workflow ${this.name} failed with error: ${error.message} for job ${job?.id}`, "ERROR");
1098
+ await db2("jobs").update({
1099
+ status: JOB_STATUS_ENUM.failed,
1100
+ result: JSON.stringify({
1101
+ error: error.message || error,
1102
+ stack: error.stack || "No stack trace available"
1103
+ })
1104
+ }).where({ id: job?.id }).returning("id");
1105
+ throw error;
1106
+ }
1107
+ };
934
1108
  };
935
1109
  var ExuluLogger = class {
936
1110
  logPath;
937
1111
  job;
938
1112
  constructor(job, logsDir) {
939
1113
  this.job = job;
940
- if (!fs.existsSync(logsDir)) {
941
- fs.mkdirSync(logsDir, { recursive: true });
1114
+ if (logsDir && job) {
1115
+ if (!fs.existsSync(logsDir)) {
1116
+ fs.mkdirSync(logsDir, { recursive: true });
1117
+ }
1118
+ this.logPath = path.join(logsDir, `${job.id}_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.txt`);
942
1119
  }
943
- this.logPath = path.join(logsDir, `${job.id}_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.txt`);
944
1120
  }
945
1121
  async write(message, level) {
946
1122
  const logMessage = message.endsWith("\n") ? message : message + "\n";
1123
+ if (!this.logPath) {
1124
+ switch (level) {
1125
+ case "INFO":
1126
+ console.log(message);
1127
+ break;
1128
+ case "WARNING":
1129
+ console.warn(message);
1130
+ break;
1131
+ case "ERROR":
1132
+ console.error(message);
1133
+ break;
1134
+ }
1135
+ return;
1136
+ }
947
1137
  try {
948
1138
  await fs.promises.appendFile(this.logPath, `[EXULU][${level}] - ${(/* @__PURE__ */ new Date()).toISOString()}: ${logMessage}`);
949
1139
  } catch (error) {
950
- console.error(`Error writing to log file ${this.job.id}:`, error);
1140
+ console.error(`Error writing to log file ${this.job ? this.job.id : "unknown job"}:`, error);
951
1141
  throw error;
952
1142
  }
953
1143
  }
@@ -1510,7 +1700,7 @@ var import_express = require("express");
1510
1700
  var import_jwt = require("next-auth/jwt");
1511
1701
 
1512
1702
  // src/auth/auth.ts
1513
- var import_bcryptjs3 = __toESM(require("bcryptjs"), 1);
1703
+ var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
1514
1704
  var authentication = async ({
1515
1705
  apikey,
1516
1706
  authtoken,
@@ -1603,16 +1793,14 @@ var authentication = async ({
1603
1793
  code: 401
1604
1794
  };
1605
1795
  }
1606
- console.log("[EXULU] users", users);
1607
1796
  console.log("[EXULU] request_key_name", request_key_name);
1608
1797
  console.log("[EXULU] request_key_compare_value", request_key_compare_value);
1609
1798
  const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(request_key_name));
1610
- console.log("[EXULU] filtered", filtered);
1611
1799
  for (const user of filtered) {
1612
1800
  const user_key_last_slash_index = user.apikey.lastIndexOf("/");
1613
1801
  const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
1614
1802
  console.log("[EXULU] user_key_compare_value", user_key_compare_value);
1615
- const isMatch = await import_bcryptjs3.default.compare(request_key_compare_value, user_key_compare_value);
1803
+ const isMatch = await import_bcryptjs2.default.compare(request_key_compare_value, user_key_compare_value);
1616
1804
  console.log("[EXULU] isMatch", isMatch);
1617
1805
  if (isMatch) {
1618
1806
  await db2.from("users").where({ id: user.id }).update({
@@ -1796,7 +1984,7 @@ var requestValidators = {
1796
1984
  var import_zodex = require("zodex");
1797
1985
 
1798
1986
  // src/bullmq/queues.ts
1799
- var import_bullmq5 = require("bullmq");
1987
+ var import_bullmq4 = require("bullmq");
1800
1988
  var ExuluQueues = class {
1801
1989
  queues;
1802
1990
  constructor() {
@@ -1814,7 +2002,7 @@ var ExuluQueues = class {
1814
2002
  console.error(`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or workflow (look for ExuluQueues.use() ).`);
1815
2003
  throw new Error(`[EXULU] no redis server configured.`);
1816
2004
  }
1817
- const newQueue = new import_bullmq5.Queue(`${name}`, { connection: redisServer });
2005
+ const newQueue = new import_bullmq4.Queue(`${name}`, { connection: redisServer });
1818
2006
  this.queues.push(newQueue);
1819
2007
  return newQueue;
1820
2008
  }
@@ -1834,6 +2022,7 @@ var VectorMethodEnum = {
1834
2022
  // src/registry/routes.ts
1835
2023
  var import_express4 = __toESM(require("express"), 1);
1836
2024
  var import_server3 = require("@apollo/server");
2025
+ var Papa = __toESM(require("papaparse"), 1);
1837
2026
  var import_cors = __toESM(require("cors"), 1);
1838
2027
  var import_reflect_metadata = require("reflect-metadata");
1839
2028
 
@@ -2170,8 +2359,8 @@ var import_express5 = require("@as-integrations/express5");
2170
2359
 
2171
2360
  // src/registry/uppy.ts
2172
2361
  var import_express2 = require("express");
2362
+ var import_body_parser = __toESM(require("body-parser"), 1);
2173
2363
  var import_jwt2 = require("next-auth/jwt");
2174
- var bodyParser = require("body-parser");
2175
2364
  var createUppyRoutes = async (app) => {
2176
2365
  const {
2177
2366
  S3Client,
@@ -2232,7 +2421,7 @@ var createUppyRoutes = async (app) => {
2232
2421
  });
2233
2422
  return stsClient;
2234
2423
  }
2235
- app.use(bodyParser.urlencoded({ extended: true }), bodyParser.json());
2424
+ app.use(import_body_parser.default.urlencoded({ extended: true }), import_body_parser.default.json());
2236
2425
  app.get("/s3/list", async (req, res, next) => {
2237
2426
  const apikey = req.headers["exulu-api-key"] || null;
2238
2427
  let authtoken = null;
@@ -2567,7 +2756,6 @@ var createUppyRoutes = async (app) => {
2567
2756
 
2568
2757
  // src/registry/routes.ts
2569
2758
  var import_utils = require("@apollo/utils.keyvaluecache");
2570
- var Papa = require("papaparse");
2571
2759
  var global_queues = {
2572
2760
  logs_cleaner: "logs-cleaner"
2573
2761
  };
@@ -2665,7 +2853,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2665
2853
  } else {
2666
2854
  console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
2667
2855
  }
2668
- const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
2856
+ const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema, workflowSchema, threadsSchema, messagesSchema]);
2669
2857
  console.log("[EXULU] graphql server");
2670
2858
  const server = new import_server3.ApolloServer({
2671
2859
  cache: new import_utils.InMemoryLRUCache(),
@@ -2721,6 +2909,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2721
2909
  description: agent.description,
2722
2910
  active: agent.active,
2723
2911
  public: agent.public,
2912
+ type: agent.type,
2724
2913
  slug: backend?.slug,
2725
2914
  rateLimit: backend?.rateLimit,
2726
2915
  streaming: backend?.streaming,
@@ -2930,21 +3119,27 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2930
3119
  }
2931
3120
  const context = contexts.find((context2) => context2.id === req.params.context);
2932
3121
  if (!context) {
3122
+ console.error("[EXULU] context not found in registry.", req.params.context);
2933
3123
  res.status(400).json({
2934
3124
  message: "Context not found in registry."
2935
3125
  });
2936
3126
  return;
2937
3127
  }
3128
+ console.log("[EXULU] context", context);
2938
3129
  const exists = await context.tableExists();
2939
3130
  if (!exists) {
3131
+ console.log("[EXULU] context table does not exist, creating it.");
2940
3132
  await context.createItemsTable();
2941
3133
  }
3134
+ console.log("[EXULU] inserting item", req.body);
2942
3135
  const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert);
3136
+ console.log("[EXULU] result", result);
2943
3137
  res.status(200).json({
2944
3138
  message: "Item created successfully.",
2945
3139
  id: result
2946
3140
  });
2947
3141
  } catch (error) {
3142
+ console.error("[EXULU] error upserting item", error);
2948
3143
  res.status(500).json({
2949
3144
  message: error?.message || "An error occurred while creating the item."
2950
3145
  });
@@ -3289,7 +3484,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3289
3484
  slug: workflow.slug,
3290
3485
  enable_batch: workflow.enable_batch,
3291
3486
  queue: workflow.queue?.name,
3292
- inputSchema: workflow.inputSchema ? (0, import_zodex.zerialize)(workflow.inputSchema) : null
3487
+ inputSchema: workflow.steps[0]?.inputSchema ? (0, import_zodex.zerialize)(workflow.steps[0].inputSchema) : null
3293
3488
  })));
3294
3489
  });
3295
3490
  console.log("[EXULU] workflow by id");
@@ -3316,7 +3511,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3316
3511
  res.status(200).json({
3317
3512
  ...workflow,
3318
3513
  queue: workflow.queue?.name,
3319
- inputSchema: workflow.inputSchema ? (0, import_zodex.zerialize)(workflow.inputSchema) : null,
3514
+ inputSchema: workflow.steps[0]?.inputSchema ? (0, import_zodex.zerialize)(workflow.steps[0].inputSchema) : null,
3320
3515
  workflow: void 0
3321
3516
  });
3322
3517
  });
@@ -3452,10 +3647,6 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3452
3647
  note: `Execute workflow ${workflow.name}`
3453
3648
  });
3454
3649
  app.post(`${workflow.slug}`, async (req, res) => {
3455
- if (!workflow.queue) {
3456
- res.status(500).json({ detail: "No queue set for workflow." });
3457
- return;
3458
- }
3459
3650
  const authenticationResult = await requestValidators.authenticate(req);
3460
3651
  if (!authenticationResult.user?.id) {
3461
3652
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
@@ -3472,6 +3663,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3472
3663
  label: `Job running '${workflow.name}' for '${req.body.label}'`,
3473
3664
  agent: req.body.agent,
3474
3665
  workflow: workflow.id,
3666
+ steps: workflow.steps?.length || 0,
3475
3667
  type: "workflow",
3476
3668
  inputs,
3477
3669
  session: req.body.session,
@@ -3490,22 +3682,19 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3490
3682
  });
3491
3683
  return;
3492
3684
  }
3493
- const { runId, start, watch } = workflow.workflow.createRun();
3494
3685
  console.log("[EXULU] running workflow with inputs.", inputs);
3495
- const output = await start({
3496
- triggerData: {
3497
- ...inputs,
3498
- user: authenticationResult.user.id
3499
- }
3686
+ const logger = new ExuluLogger();
3687
+ const result = await workflow.start({
3688
+ inputs,
3689
+ user: authenticationResult.user.id,
3690
+ logger,
3691
+ session: req.body.session,
3692
+ agent: req.body.agent,
3693
+ label: req.body.label
3500
3694
  });
3501
- const failedSteps = Object.entries(output.results).filter(([_, step]) => step.status === "failed").map(([id, step]) => `${id}: ${step.error}`);
3502
- if (failedSteps.length > 0) {
3503
- const message = `Workflow has failed steps: ${failedSteps.join("\n - ")}`;
3504
- throw new Error(message);
3505
- }
3506
3695
  res.status(200).json({
3507
3696
  "job": {},
3508
- "output": output
3697
+ "output": result
3509
3698
  });
3510
3699
  return;
3511
3700
  });
@@ -3563,48 +3752,45 @@ var getPresignedFileUrl = async (key) => {
3563
3752
 
3564
3753
  // src/registry/workers.ts
3565
3754
  var import_ioredis = __toESM(require("ioredis"), 1);
3566
- var import_bullmq8 = require("bullmq");
3755
+ var import_bullmq7 = require("bullmq");
3567
3756
 
3568
3757
  // src/registry/utils.ts
3569
- var import_bullmq7 = require("bullmq");
3758
+ var import_bullmq6 = require("bullmq");
3570
3759
  var bullmq = {
3571
- validate: (job) => {
3572
- if (!job.data) {
3573
- throw new Error(`Missing job data for job ${job.id}.`);
3760
+ validate: (bullmqJob) => {
3761
+ if (!bullmqJob.data) {
3762
+ throw new Error(`Missing job data for job ${bullmqJob.id}.`);
3574
3763
  }
3575
- if (!job.data.type) {
3576
- throw new Error(`Missing property "type" in data for job ${job.id}.`);
3764
+ if (!bullmqJob.data.type) {
3765
+ throw new Error(`Missing property "type" in data for job ${bullmqJob.id}.`);
3577
3766
  }
3578
- if (!job.data.inputs) {
3579
- throw new Error(`Missing property "inputs" in data for job ${job.id}.`);
3767
+ if (!bullmqJob.data.inputs) {
3768
+ throw new Error(`Missing property "inputs" in data for job ${bullmqJob.id}.`);
3580
3769
  }
3581
- if (job.data.type !== "embedder" && job.data.type !== "workflow") {
3582
- throw new Error(`Property "type" in data for job ${job.id} must be of value "embedder" or "workflow".`);
3770
+ if (bullmqJob.data.type !== "embedder" && bullmqJob.data.type !== "workflow") {
3771
+ throw new Error(`Property "type" in data for job ${bullmqJob.id} must be of value "embedder" or "workflow".`);
3583
3772
  }
3584
- if (!job.data.workflow && !job.data.embedder) {
3585
- throw new Error(`Property "backend" in data for job ${job.id} missing. Job data: ${JSON.stringify(job)}`);
3773
+ if (!bullmqJob.data.workflow && !bullmqJob.data.embedder) {
3774
+ throw new Error(`Property "backend" in data for job ${bullmqJob.id} missing. Job data: ${JSON.stringify(bullmqJob)}`);
3586
3775
  }
3587
3776
  },
3588
3777
  process: {
3589
- workflow: async (job, workflow, logsDir) => {
3778
+ workflow: async (bullmqJob, exuluJob, workflow, logsDir) => {
3590
3779
  if (!workflow) {
3591
- throw new Error(`Workflow function with id: ${job.data.backend} not found in registry.`);
3592
- }
3593
- const { runId, start, watch } = workflow.workflow.createRun();
3594
- console.log("[EXULU] starting workflow with job inputs.", job.data.inputs);
3595
- const logger = new ExuluLogger(job, logsDir);
3596
- const output = await start({ triggerData: {
3597
- ...job.data.inputs,
3598
- redis: job.id,
3599
- logger
3600
- } });
3601
- const failedSteps = Object.entries(output.results).filter(([_, step]) => step.status === "failed").map(([id, step]) => `${id}: ${step.error}`);
3602
- if (failedSteps.length > 0) {
3603
- const message = `Workflow has failed steps: ${failedSteps.join("\n - ")}`;
3604
- logger.write(message, "ERROR");
3605
- throw new Error(message);
3606
- }
3607
- await logger.write(`Workflow completed. ${JSON.stringify(output.results)}`, "INFO");
3780
+ throw new Error(`Workflow function with id: ${bullmqJob.data.backend} not found in registry.`);
3781
+ }
3782
+ console.log("[EXULU] starting workflow with job inputs.", bullmqJob.data.inputs);
3783
+ const logger = new ExuluLogger(exuluJob, logsDir);
3784
+ const output = await workflow.start({
3785
+ job: exuluJob,
3786
+ inputs: bullmqJob.data.inputs,
3787
+ user: bullmqJob.data.user,
3788
+ logger,
3789
+ session: bullmqJob.data.session,
3790
+ agent: bullmqJob.data.agent,
3791
+ label: bullmqJob.data.label
3792
+ });
3793
+ await logger.write(`Workflow completed. ${JSON.stringify(output)}`, "INFO");
3608
3794
  return output;
3609
3795
  }
3610
3796
  }
@@ -3629,58 +3815,58 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3629
3815
  const logsDir = _logsDir || defaultLogsDir;
3630
3816
  const workers = queues2.map((queue) => {
3631
3817
  console.log(`[EXULU] creating worker for queue ${queue}.`);
3632
- const worker = new import_bullmq8.Worker(
3818
+ const worker = new import_bullmq7.Worker(
3633
3819
  `${queue}`,
3634
- async (job) => {
3820
+ async (bullmqJob) => {
3635
3821
  const { db: db2 } = await postgresClient();
3636
3822
  try {
3637
- bullmq.validate(job);
3638
- if (job.data.type === "embedder") {
3639
- if (!job.data.updater) {
3823
+ bullmq.validate(bullmqJob);
3824
+ if (bullmqJob.data.type === "embedder") {
3825
+ if (!bullmqJob.data.updater) {
3640
3826
  throw new Error("No updater set for embedder job.");
3641
3827
  }
3642
- const context = contexts.find((context2) => context2.id === job.data.context);
3828
+ const context = contexts.find((context2) => context2.id === bullmqJob.data.context);
3643
3829
  if (!context) {
3644
- throw new Error(`Context ${job.data.context} not found in the registry.`);
3830
+ throw new Error(`Context ${bullmqJob.data.context} not found in the registry.`);
3645
3831
  }
3646
- if (!job.data.embedder) {
3832
+ if (!bullmqJob.data.embedder) {
3647
3833
  throw new Error(`No embedder set for embedder job.`);
3648
3834
  }
3649
- const embedder = embedders.find((embedder2) => embedder2.id === job.data.embedder);
3835
+ const embedder = embedders.find((embedder2) => embedder2.id === bullmqJob.data.embedder);
3650
3836
  if (!embedder) {
3651
- throw new Error(`Embedder ${job.data.embedder} not found in the registry.`);
3837
+ throw new Error(`Embedder ${bullmqJob.data.embedder} not found in the registry.`);
3652
3838
  }
3653
- if (!job.data.source) {
3839
+ if (!bullmqJob.data.source) {
3654
3840
  throw new Error("No source set for embedder job.");
3655
3841
  }
3656
- const source = context.sources.get(job.data.source);
3842
+ const source = context.sources.get(bullmqJob.data.source);
3657
3843
  if (!source) {
3658
- throw new Error(`Source ${job.data.source} not found in the registry.`);
3844
+ throw new Error(`Source ${bullmqJob.data.source} not found in the registry.`);
3659
3845
  }
3660
- if (!job.data.updater) {
3846
+ if (!bullmqJob.data.updater) {
3661
3847
  throw new Error("No updater set for embedder job.");
3662
3848
  }
3663
- const updater = source.updaters.find((updater2) => updater2.id === job.data.updater);
3849
+ const updater = source.updaters.find((updater2) => updater2.id === bullmqJob.data.updater);
3664
3850
  if (!updater) {
3665
- throw new Error(`Updater ${job.data.updater} not found in the registry.`);
3851
+ throw new Error(`Updater ${bullmqJob.data.updater} not found in the registry.`);
3666
3852
  }
3667
- if (!job.data.documents) {
3853
+ if (!bullmqJob.data.documents) {
3668
3854
  throw new Error("No input documents set for embedder job.");
3669
3855
  }
3670
- if (!Array.isArray(job.data.documents)) {
3856
+ if (!Array.isArray(bullmqJob.data.documents)) {
3671
3857
  throw new Error("Input documents must be an array.");
3672
3858
  }
3673
- const result = await embedder.upsert(job.data.context, job.data.documents, {
3859
+ const result = await embedder.upsert(bullmqJob.data.context, bullmqJob.data.documents, {
3674
3860
  label: context.name,
3675
- trigger: job.data.trigger || "unknown"
3861
+ trigger: bullmqJob.data.trigger || "unknown"
3676
3862
  });
3677
- const mongoRecord = await db2.from("jobs").where({ redis: job.id }).first();
3863
+ const mongoRecord = await db2.from("jobs").where({ redis: bullmqJob.id }).first();
3678
3864
  if (!mongoRecord) {
3679
3865
  throw new Error("Job not found in the database.");
3680
3866
  }
3681
3867
  const finishedAt = /* @__PURE__ */ new Date();
3682
3868
  const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
3683
- await db2.from("jobs").where({ redis: job.id }).update({
3869
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3684
3870
  status: "completed",
3685
3871
  finishedAt,
3686
3872
  duration,
@@ -3688,19 +3874,19 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3688
3874
  });
3689
3875
  return result;
3690
3876
  }
3691
- if (job.data.type === "workflow") {
3692
- const workflow = workflows.find((workflow2) => workflow2.id === job.data.workflow);
3877
+ if (bullmqJob.data.type === "workflow") {
3878
+ const workflow = workflows.find((workflow2) => workflow2.id === bullmqJob.data.workflow);
3693
3879
  if (!workflow) {
3694
- throw new Error(`Workflow ${job.data.workflow} not found in the registry.`);
3880
+ throw new Error(`Workflow ${bullmqJob.data.workflow} not found in the registry.`);
3695
3881
  }
3696
- const result = await bullmq.process.workflow(job, workflow, logsDir);
3697
- const mongoRecord = await db2.from("jobs").where({ redis: job.id }).first();
3698
- if (!mongoRecord) {
3882
+ const exuluJob = await db2.from("jobs").where({ redis: bullmqJob.id }).first();
3883
+ if (!exuluJob) {
3699
3884
  throw new Error("Job not found in the database.");
3700
3885
  }
3886
+ const result = await bullmq.process.workflow(bullmqJob, exuluJob, workflow, logsDir);
3701
3887
  const finishedAt = /* @__PURE__ */ new Date();
3702
- const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
3703
- await db2.from("jobs").where({ redis: job.id }).update({
3888
+ const duration = (finishedAt.getTime() - new Date(exuluJob.createdAt).getTime()) / 1e3;
3889
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3704
3890
  status: "completed",
3705
3891
  finishedAt,
3706
3892
  duration,
@@ -3709,7 +3895,7 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3709
3895
  return result;
3710
3896
  }
3711
3897
  } catch (error) {
3712
- await db2.from("jobs").where({ redis: job.id }).update({
3898
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3713
3899
  status: "failed",
3714
3900
  finishedAt: /* @__PURE__ */ new Date(),
3715
3901
  error: error instanceof Error ? error.message : String(error)
@@ -3738,7 +3924,7 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3738
3924
  return workers;
3739
3925
  };
3740
3926
  var createLogsCleanerWorker = (logsDir) => {
3741
- const logsCleaner = new import_bullmq8.Worker(
3927
+ const logsCleaner = new import_bullmq7.Worker(
3742
3928
  global_queues.logs_cleaner,
3743
3929
  async (job) => {
3744
3930
  console.log(`[EXULU] recurring job ${job.id}.`);
@@ -3851,12 +4037,18 @@ var ExuluApp = class {
3851
4037
  };
3852
4038
 
3853
4039
  // src/index.ts
4040
+ var import_chonkie = require("chonkie");
3854
4041
  var ExuluJobs = {
3855
4042
  redis: redisClient,
3856
4043
  jobs: {
3857
4044
  validate: validateJob
3858
4045
  }
3859
4046
  };
4047
+ var ExuluChunkers = {
4048
+ chonkie: {
4049
+ sentence: import_chonkie.SentenceChunker
4050
+ }
4051
+ };
3860
4052
  var ExuluDatabase = {
3861
4053
  init: async () => {
3862
4054
  await execute();
@@ -3867,14 +4059,17 @@ var ExuluDatabase = {
3867
4059
  };
3868
4060
  // Annotate the CommonJS export names for ESM import in node:
3869
4061
  0 && (module.exports = {
4062
+ EXULU_JOB_STATUS_ENUM,
3870
4063
  EXULU_STATISTICS_TYPE_ENUM,
3871
4064
  ExuluAgent,
3872
4065
  ExuluApp,
3873
4066
  ExuluAuthentication,
4067
+ ExuluChunkers,
3874
4068
  ExuluContext,
3875
4069
  ExuluDatabase,
3876
4070
  ExuluEmbedder,
3877
4071
  ExuluJobs,
4072
+ ExuluLogger,
3878
4073
  ExuluQueues,
3879
4074
  ExuluSource,
3880
4075
  ExuluTool,