@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.js CHANGED
@@ -5,6 +5,9 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
7
 
8
+ // src/index.ts
9
+ import "dotenv/config";
10
+
8
11
  // src/redis/client.ts
9
12
  import { createClient } from "redis";
10
13
 
@@ -114,6 +117,10 @@ var usersSchema = {
114
117
  name: "firstname",
115
118
  type: "text"
116
119
  },
120
+ {
121
+ name: "name",
122
+ type: "text"
123
+ },
117
124
  {
118
125
  name: "lastname",
119
126
  type: "text"
@@ -217,6 +224,70 @@ var statisticsSchema = {
217
224
  }
218
225
  ]
219
226
  };
227
+ var workflowSchema = {
228
+ name: {
229
+ plural: "workflows",
230
+ singular: "workflow"
231
+ },
232
+ fields: [
233
+ {
234
+ name: "workflow_name",
235
+ type: "text"
236
+ },
237
+ {
238
+ name: "run_id",
239
+ type: "text"
240
+ },
241
+ {
242
+ name: "snapshot",
243
+ type: "text"
244
+ }
245
+ ]
246
+ };
247
+ var threadsSchema = {
248
+ name: {
249
+ plural: "threads",
250
+ singular: "thread"
251
+ },
252
+ fields: [
253
+ {
254
+ name: "resourceId",
255
+ type: "text"
256
+ },
257
+ {
258
+ name: "title",
259
+ type: "text"
260
+ },
261
+ {
262
+ name: "metadata",
263
+ type: "text"
264
+ }
265
+ ]
266
+ };
267
+ var messagesSchema = {
268
+ name: {
269
+ plural: "messages",
270
+ singular: "message"
271
+ },
272
+ fields: [
273
+ {
274
+ name: "thread_id",
275
+ type: "text"
276
+ },
277
+ {
278
+ name: "content",
279
+ type: "text"
280
+ },
281
+ {
282
+ name: "role",
283
+ type: "text"
284
+ },
285
+ {
286
+ name: "type",
287
+ type: "text"
288
+ }
289
+ ]
290
+ };
220
291
  var jobsSchema = {
221
292
  name: {
222
293
  plural: "jobs",
@@ -241,7 +312,7 @@ var jobsSchema = {
241
312
  },
242
313
  {
243
314
  name: "result",
244
- type: "text"
315
+ type: "longText"
245
316
  },
246
317
  {
247
318
  name: "name",
@@ -251,6 +322,10 @@ var jobsSchema = {
251
322
  name: "agent",
252
323
  type: "text"
253
324
  },
325
+ {
326
+ name: "workflow",
327
+ type: "text"
328
+ },
254
329
  {
255
330
  name: "user",
256
331
  type: "text"
@@ -259,6 +334,10 @@ var jobsSchema = {
259
334
  name: "item",
260
335
  type: "text"
261
336
  },
337
+ {
338
+ name: "steps",
339
+ type: "number"
340
+ },
262
341
  {
263
342
  name: "inputs",
264
343
  type: "json"
@@ -362,14 +441,11 @@ var sanitizeName = (name) => {
362
441
  return name.toLowerCase().replace(/ /g, "_");
363
442
  };
364
443
 
365
- // src/postgres/init-db.ts
366
- import bcrypt2 from "bcryptjs";
367
-
368
444
  // src/auth/generate-key.ts
369
445
  import bcrypt from "bcryptjs";
370
446
  var SALT_ROUNDS = 12;
371
- async function encryptApiKey(apikey) {
372
- const hash = await bcrypt.hash(apikey, SALT_ROUNDS);
447
+ async function encryptString(string) {
448
+ const hash = await bcrypt.hash(string, SALT_ROUNDS);
373
449
  return hash;
374
450
  }
375
451
  var generateApiKey = async (name, email) => {
@@ -391,7 +467,7 @@ var generateApiKey = async (name, email) => {
391
467
  const newKeyName = name;
392
468
  const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
393
469
  const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
394
- const encryptedKey = await encryptApiKey(plainKey);
470
+ const encryptedKey = await encryptString(plainKey);
395
471
  const existingApiUser = await db2.from("users").where({ email }).first();
396
472
  if (!existingApiUser) {
397
473
  console.log("[EXULU] Creating default api user.");
@@ -402,6 +478,7 @@ var generateApiKey = async (name, email) => {
402
478
  createdAt: /* @__PURE__ */ new Date(),
403
479
  updatedAt: /* @__PURE__ */ new Date(),
404
480
  type: "api",
481
+ emailVerified: /* @__PURE__ */ new Date(),
405
482
  apikey: `${encryptedKey}${postFix}`,
406
483
  // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
407
484
  role: roleId
@@ -566,11 +643,6 @@ var up = async function(knex) {
566
643
  });
567
644
  }
568
645
  };
569
- var SALT_ROUNDS2 = 12;
570
- async function encryptApiKey2(apikey) {
571
- const hash = await bcrypt2.hash(apikey, SALT_ROUNDS2);
572
- return hash;
573
- }
574
646
  var execute = async () => {
575
647
  console.log("[EXULU] Initializing database.");
576
648
  const { db: db2 } = await postgresClient();
@@ -583,33 +655,33 @@ var execute = async () => {
583
655
  const role = await db2.from("roles").insert({
584
656
  name: "admin",
585
657
  is_admin: true,
586
- agents: []
658
+ agents: JSON.stringify([])
587
659
  }).returning("id");
588
660
  roleId = role[0].id;
589
661
  } else {
590
662
  roleId = existingRole.id;
591
663
  }
592
- const newKeyName = "exulu_default_key";
593
- const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
594
- const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
595
- const encryptedKey = await encryptApiKey2(plainKey);
596
664
  const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
597
665
  if (!existingUser) {
666
+ const password = await encryptString("admin");
598
667
  console.log("[EXULU] Creating default admin user.");
599
668
  await db2.from("users").insert({
600
669
  name: "exulu",
601
670
  email: "admin@exulu.com",
602
671
  super_admin: true,
603
672
  createdAt: /* @__PURE__ */ new Date(),
673
+ emailVerified: /* @__PURE__ */ new Date(),
604
674
  updatedAt: /* @__PURE__ */ new Date(),
675
+ password,
605
676
  type: "user",
606
- // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
607
677
  role: roleId
608
678
  });
609
679
  }
610
680
  const { key } = await generateApiKey("exulu", "api@exulu.com");
611
681
  console.log("[EXULU] Database initialized.");
612
682
  console.log("[EXULU] Default api key: ", `${key}`);
683
+ console.log("[EXULU] Default password if using password auth: ", `admin`);
684
+ console.log("[EXULU] Default email if using password auth: ", `admin@exulu.com`);
613
685
  return;
614
686
  };
615
687
 
@@ -620,7 +692,6 @@ import { Agent as MastraAgent } from "@mastra/core";
620
692
  import { z } from "zod";
621
693
  import * as fs from "fs";
622
694
  import * as path from "path";
623
- import "bullmq";
624
695
  import { Memory } from "@mastra/memory";
625
696
  import { PostgresStore, PgVector } from "@mastra/pg";
626
697
 
@@ -656,6 +727,7 @@ var bullmqDecorator = async ({
656
727
  configuration,
657
728
  updater,
658
729
  context,
730
+ steps,
659
731
  source,
660
732
  documents,
661
733
  trigger,
@@ -673,11 +745,13 @@ var bullmqDecorator = async ({
673
745
  ...context && { context },
674
746
  ...source && { source },
675
747
  ...documents && { documents },
748
+ ...steps && { steps },
676
749
  ...trigger && { trigger },
677
750
  ...item && { item },
678
751
  agent,
679
752
  user,
680
753
  inputs,
754
+ label,
681
755
  session
682
756
  },
683
757
  {
@@ -702,6 +776,7 @@ var bullmqDecorator = async ({
702
776
  ...embedder && { embedder },
703
777
  ...workflow && { workflow },
704
778
  ...configuration && { configuration },
779
+ ...steps && { steps },
705
780
  ...updater && { updater },
706
781
  ...context && { context },
707
782
  ...source && { source },
@@ -725,6 +800,17 @@ var bullmqDecorator = async ({
725
800
  };
726
801
  };
727
802
 
803
+ // types/enums/jobs.ts
804
+ var JOB_STATUS_ENUM = {
805
+ completed: "completed",
806
+ failed: "failed",
807
+ delayed: "delayed",
808
+ active: "active",
809
+ waiting: "waiting",
810
+ paused: "paused",
811
+ stuck: "stuck"
812
+ };
813
+
728
814
  // src/registry/classes.ts
729
815
  function generateSlug(name) {
730
816
  const normalized = name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
@@ -759,6 +845,7 @@ var ExuluAgent = class {
759
845
  config;
760
846
  memory;
761
847
  tools;
848
+ agent;
762
849
  capabilities;
763
850
  constructor({ id, name, description, outputSchema, config, rateLimit, type, capabilities, tools }) {
764
851
  this.id = id;
@@ -771,6 +858,14 @@ var ExuluAgent = class {
771
858
  this.config = config;
772
859
  this.capabilities = capabilities;
773
860
  this.slug = `/agents/${generateSlug(this.name)}/run`;
861
+ if (this.type === "agent") {
862
+ this.agent = new MastraAgent({
863
+ name: this.config.name,
864
+ instructions: this.config.instructions,
865
+ model: this.config.model,
866
+ memory: this.memory ? this.memory : void 0
867
+ });
868
+ }
774
869
  if (config?.memory) {
775
870
  console.log("[EXULU] Initializing memory for agent " + this.name);
776
871
  const connectionString = `postgresql://${process.env.POSTGRES_DB_USER}:${process.env.POSTGRES_DB_PASSWORD}@${process.env.POSTGRES_DB_HOST}:${process.env.POSTGRES_DB_PORT}/exulu`;
@@ -784,7 +879,11 @@ var ExuluAgent = class {
784
879
  password: process.env.POSTGRES_DB_PASSWORD || "",
785
880
  ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
786
881
  }),
787
- ...config?.memory.vector ? { vector: new PgVector(connectionString) } : {},
882
+ ...config?.memory.vector ? {
883
+ vector: new PgVector({
884
+ connectionString
885
+ })
886
+ } : {},
788
887
  options: {
789
888
  lastMessages: config?.memory.lastMessages || 10,
790
889
  semanticRecall: {
@@ -795,33 +894,17 @@ var ExuluAgent = class {
795
894
  });
796
895
  }
797
896
  }
798
- chat = async (id) => {
799
- const { db: db2 } = await postgresClient();
800
- const agent = await db2.from("agents").select("*").where("id", "=", id).first();
801
- if (!agent) {
897
+ chat = () => {
898
+ if (!this.agent) {
802
899
  throw new Error("Agent not found");
803
900
  }
804
- let tools = {};
805
- agent.tools?.forEach(({ name }) => {
806
- const tool = this.tools?.find((t) => t.name === name);
807
- if (!tool) {
808
- return;
809
- }
810
- return tool;
811
- });
812
901
  updateStatistic({
813
902
  name: "count",
814
903
  label: this.name,
815
904
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
816
905
  trigger: "agent"
817
906
  });
818
- return new MastraAgent({
819
- name: this.config.name,
820
- instructions: this.config.instructions,
821
- model: this.config.model,
822
- tools,
823
- memory: this.memory ? this.memory : void 0
824
- });
907
+ return this.agent;
825
908
  };
826
909
  };
827
910
  var ExuluEmbedder = class {
@@ -878,35 +961,141 @@ var ExuluWorkflow = class {
878
961
  enable_batch = false;
879
962
  slug = "";
880
963
  queue;
881
- workflow;
882
- inputSchema;
883
- constructor({ id, name, description, workflow, queue, enable_batch, inputSchema }) {
964
+ steps;
965
+ constructor({ id, name, description, steps, queue, enable_batch }) {
884
966
  this.id = id;
885
967
  this.name = name;
886
968
  this.description = description;
887
969
  this.enable_batch = enable_batch;
888
970
  this.slug = `/workflows/${generateSlug(this.name)}/run`;
889
971
  this.queue = queue;
890
- this.inputSchema = inputSchema;
891
- this.workflow = workflow;
972
+ this.steps = steps;
892
973
  }
974
+ start = async ({
975
+ inputs: initialInputs,
976
+ user,
977
+ logger,
978
+ job,
979
+ session,
980
+ agent,
981
+ label
982
+ }) => {
983
+ let inputs;
984
+ const { db: db2 } = await postgresClient();
985
+ if (!job?.id) {
986
+ logger.write(`Creating new job for workflow ${this.name} with inputs: ${JSON.stringify(initialInputs)}`, "INFO");
987
+ const result = await db2("jobs").insert({
988
+ status: JOB_STATUS_ENUM.active,
989
+ name: `Job running '${this.name}' for '${label}'`,
990
+ agent,
991
+ workflow: this.id,
992
+ type: "workflow",
993
+ steps: this.steps?.length || 0,
994
+ inputs: initialInputs,
995
+ session,
996
+ user
997
+ }).returning(["id", "status"]);
998
+ job = result[0];
999
+ logger.write(`Created new job for workflow ${this.name}, job id: ${job?.id}`, "INFO");
1000
+ }
1001
+ if (!job) {
1002
+ throw new Error("Job not found, or failed to be created.");
1003
+ }
1004
+ if (job.status !== JOB_STATUS_ENUM.active) {
1005
+ await db2("jobs").update({
1006
+ status: JOB_STATUS_ENUM.active,
1007
+ inputs: initialInputs
1008
+ }).where({ id: job?.id }).returning("id");
1009
+ }
1010
+ const runStep = async (step, inputs2) => {
1011
+ let result;
1012
+ try {
1013
+ result = await step.fn({
1014
+ inputs: inputs2,
1015
+ logger,
1016
+ job,
1017
+ user
1018
+ });
1019
+ return result;
1020
+ } catch (error) {
1021
+ logger.write(`Step ${step.name} failed with error: ${error.message}`, "ERROR");
1022
+ if (step.retries && step.retries > 0) {
1023
+ logger.write(`Retrying step ${step.name} with ${step.retries} retries left`, "INFO");
1024
+ step.retries--;
1025
+ let result2 = await runStep(step, inputs2);
1026
+ return result2;
1027
+ }
1028
+ logger.write(`Step ${step.name} failed with error: ${error.message}`, "ERROR");
1029
+ throw error;
1030
+ }
1031
+ };
1032
+ let final;
1033
+ try {
1034
+ for (let i = 0; i < this.steps.length; i++) {
1035
+ const step = this.steps[i];
1036
+ if (!step) {
1037
+ throw new Error("Step not found.");
1038
+ }
1039
+ if (i === 0) {
1040
+ inputs = initialInputs;
1041
+ }
1042
+ logger.write(`Running step ${step.name} with inputs: ${JSON.stringify(inputs)}`, "INFO");
1043
+ let result = await runStep(step, inputs);
1044
+ inputs = result;
1045
+ logger.write(`Step ${step.name} output: ${JSON.stringify(result)}`, "INFO");
1046
+ final = result;
1047
+ }
1048
+ await db2("jobs").update({
1049
+ status: JOB_STATUS_ENUM.completed,
1050
+ result: JSON.stringify(final),
1051
+ finished_at: db2.fn.now()
1052
+ }).where({ id: job?.id }).returning("id");
1053
+ return final;
1054
+ } catch (error) {
1055
+ logger.write(`Workflow ${this.name} failed with error: ${error.message} for job ${job?.id}`, "ERROR");
1056
+ await db2("jobs").update({
1057
+ status: JOB_STATUS_ENUM.failed,
1058
+ result: JSON.stringify({
1059
+ error: error.message || error,
1060
+ stack: error.stack || "No stack trace available"
1061
+ })
1062
+ }).where({ id: job?.id }).returning("id");
1063
+ throw error;
1064
+ }
1065
+ };
893
1066
  };
894
1067
  var ExuluLogger = class {
895
1068
  logPath;
896
1069
  job;
897
1070
  constructor(job, logsDir) {
898
1071
  this.job = job;
899
- if (!fs.existsSync(logsDir)) {
900
- fs.mkdirSync(logsDir, { recursive: true });
1072
+ if (logsDir && job) {
1073
+ if (!fs.existsSync(logsDir)) {
1074
+ fs.mkdirSync(logsDir, { recursive: true });
1075
+ }
1076
+ this.logPath = path.join(logsDir, `${job.id}_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.txt`);
901
1077
  }
902
- this.logPath = path.join(logsDir, `${job.id}_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.txt`);
903
1078
  }
904
1079
  async write(message, level) {
905
1080
  const logMessage = message.endsWith("\n") ? message : message + "\n";
1081
+ if (!this.logPath) {
1082
+ switch (level) {
1083
+ case "INFO":
1084
+ console.log(message);
1085
+ break;
1086
+ case "WARNING":
1087
+ console.warn(message);
1088
+ break;
1089
+ case "ERROR":
1090
+ console.error(message);
1091
+ break;
1092
+ }
1093
+ return;
1094
+ }
906
1095
  try {
907
1096
  await fs.promises.appendFile(this.logPath, `[EXULU][${level}] - ${(/* @__PURE__ */ new Date()).toISOString()}: ${logMessage}`);
908
1097
  } catch (error) {
909
- console.error(`Error writing to log file ${this.job.id}:`, error);
1098
+ console.error(`Error writing to log file ${this.job ? this.job.id : "unknown job"}:`, error);
910
1099
  throw error;
911
1100
  }
912
1101
  }
@@ -1469,7 +1658,7 @@ import "express";
1469
1658
  import { getToken } from "next-auth/jwt";
1470
1659
 
1471
1660
  // src/auth/auth.ts
1472
- import bcrypt3 from "bcryptjs";
1661
+ import bcrypt2 from "bcryptjs";
1473
1662
  var authentication = async ({
1474
1663
  apikey,
1475
1664
  authtoken,
@@ -1562,16 +1751,14 @@ var authentication = async ({
1562
1751
  code: 401
1563
1752
  };
1564
1753
  }
1565
- console.log("[EXULU] users", users);
1566
1754
  console.log("[EXULU] request_key_name", request_key_name);
1567
1755
  console.log("[EXULU] request_key_compare_value", request_key_compare_value);
1568
1756
  const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(request_key_name));
1569
- console.log("[EXULU] filtered", filtered);
1570
1757
  for (const user of filtered) {
1571
1758
  const user_key_last_slash_index = user.apikey.lastIndexOf("/");
1572
1759
  const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
1573
1760
  console.log("[EXULU] user_key_compare_value", user_key_compare_value);
1574
- const isMatch = await bcrypt3.compare(request_key_compare_value, user_key_compare_value);
1761
+ const isMatch = await bcrypt2.compare(request_key_compare_value, user_key_compare_value);
1575
1762
  console.log("[EXULU] isMatch", isMatch);
1576
1763
  if (isMatch) {
1577
1764
  await db2.from("users").where({ id: user.id }).update({
@@ -1793,6 +1980,7 @@ var VectorMethodEnum = {
1793
1980
  // src/registry/routes.ts
1794
1981
  import express from "express";
1795
1982
  import { ApolloServer } from "@apollo/server";
1983
+ import * as Papa from "papaparse";
1796
1984
  import cors from "cors";
1797
1985
  import "reflect-metadata";
1798
1986
 
@@ -2129,8 +2317,8 @@ import { expressMiddleware } from "@as-integrations/express5";
2129
2317
 
2130
2318
  // src/registry/uppy.ts
2131
2319
  import "express";
2320
+ import bodyParser from "body-parser";
2132
2321
  import { getToken as getToken2 } from "next-auth/jwt";
2133
- var bodyParser = __require("body-parser");
2134
2322
  var createUppyRoutes = async (app) => {
2135
2323
  const {
2136
2324
  S3Client,
@@ -2526,7 +2714,6 @@ var createUppyRoutes = async (app) => {
2526
2714
 
2527
2715
  // src/registry/routes.ts
2528
2716
  import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
2529
- var Papa = __require("papaparse");
2530
2717
  var global_queues = {
2531
2718
  logs_cleaner: "logs-cleaner"
2532
2719
  };
@@ -2624,7 +2811,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2624
2811
  } else {
2625
2812
  console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
2626
2813
  }
2627
- const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
2814
+ const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema, workflowSchema, threadsSchema, messagesSchema]);
2628
2815
  console.log("[EXULU] graphql server");
2629
2816
  const server = new ApolloServer({
2630
2817
  cache: new InMemoryLRUCache(),
@@ -2680,6 +2867,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2680
2867
  description: agent.description,
2681
2868
  active: agent.active,
2682
2869
  public: agent.public,
2870
+ type: agent.type,
2683
2871
  slug: backend?.slug,
2684
2872
  rateLimit: backend?.rateLimit,
2685
2873
  streaming: backend?.streaming,
@@ -2889,21 +3077,27 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2889
3077
  }
2890
3078
  const context = contexts.find((context2) => context2.id === req.params.context);
2891
3079
  if (!context) {
3080
+ console.error("[EXULU] context not found in registry.", req.params.context);
2892
3081
  res.status(400).json({
2893
3082
  message: "Context not found in registry."
2894
3083
  });
2895
3084
  return;
2896
3085
  }
3086
+ console.log("[EXULU] context", context);
2897
3087
  const exists = await context.tableExists();
2898
3088
  if (!exists) {
3089
+ console.log("[EXULU] context table does not exist, creating it.");
2899
3090
  await context.createItemsTable();
2900
3091
  }
3092
+ console.log("[EXULU] inserting item", req.body);
2901
3093
  const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert);
3094
+ console.log("[EXULU] result", result);
2902
3095
  res.status(200).json({
2903
3096
  message: "Item created successfully.",
2904
3097
  id: result
2905
3098
  });
2906
3099
  } catch (error) {
3100
+ console.error("[EXULU] error upserting item", error);
2907
3101
  res.status(500).json({
2908
3102
  message: error?.message || "An error occurred while creating the item."
2909
3103
  });
@@ -3248,7 +3442,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3248
3442
  slug: workflow.slug,
3249
3443
  enable_batch: workflow.enable_batch,
3250
3444
  queue: workflow.queue?.name,
3251
- inputSchema: workflow.inputSchema ? zerialize(workflow.inputSchema) : null
3445
+ inputSchema: workflow.steps[0]?.inputSchema ? zerialize(workflow.steps[0].inputSchema) : null
3252
3446
  })));
3253
3447
  });
3254
3448
  console.log("[EXULU] workflow by id");
@@ -3275,7 +3469,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3275
3469
  res.status(200).json({
3276
3470
  ...workflow,
3277
3471
  queue: workflow.queue?.name,
3278
- inputSchema: workflow.inputSchema ? zerialize(workflow.inputSchema) : null,
3472
+ inputSchema: workflow.steps[0]?.inputSchema ? zerialize(workflow.steps[0].inputSchema) : null,
3279
3473
  workflow: void 0
3280
3474
  });
3281
3475
  });
@@ -3411,10 +3605,6 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3411
3605
  note: `Execute workflow ${workflow.name}`
3412
3606
  });
3413
3607
  app.post(`${workflow.slug}`, async (req, res) => {
3414
- if (!workflow.queue) {
3415
- res.status(500).json({ detail: "No queue set for workflow." });
3416
- return;
3417
- }
3418
3608
  const authenticationResult = await requestValidators.authenticate(req);
3419
3609
  if (!authenticationResult.user?.id) {
3420
3610
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
@@ -3431,6 +3621,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3431
3621
  label: `Job running '${workflow.name}' for '${req.body.label}'`,
3432
3622
  agent: req.body.agent,
3433
3623
  workflow: workflow.id,
3624
+ steps: workflow.steps?.length || 0,
3434
3625
  type: "workflow",
3435
3626
  inputs,
3436
3627
  session: req.body.session,
@@ -3449,22 +3640,19 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3449
3640
  });
3450
3641
  return;
3451
3642
  }
3452
- const { runId, start, watch } = workflow.workflow.createRun();
3453
3643
  console.log("[EXULU] running workflow with inputs.", inputs);
3454
- const output = await start({
3455
- triggerData: {
3456
- ...inputs,
3457
- user: authenticationResult.user.id
3458
- }
3644
+ const logger = new ExuluLogger();
3645
+ const result = await workflow.start({
3646
+ inputs,
3647
+ user: authenticationResult.user.id,
3648
+ logger,
3649
+ session: req.body.session,
3650
+ agent: req.body.agent,
3651
+ label: req.body.label
3459
3652
  });
3460
- const failedSteps = Object.entries(output.results).filter(([_, step]) => step.status === "failed").map(([id, step]) => `${id}: ${step.error}`);
3461
- if (failedSteps.length > 0) {
3462
- const message = `Workflow has failed steps: ${failedSteps.join("\n - ")}`;
3463
- throw new Error(message);
3464
- }
3465
3653
  res.status(200).json({
3466
3654
  "job": {},
3467
- "output": output
3655
+ "output": result
3468
3656
  });
3469
3657
  return;
3470
3658
  });
@@ -3527,43 +3715,40 @@ import { Worker } from "bullmq";
3527
3715
  // src/registry/utils.ts
3528
3716
  import "bullmq";
3529
3717
  var bullmq = {
3530
- validate: (job) => {
3531
- if (!job.data) {
3532
- throw new Error(`Missing job data for job ${job.id}.`);
3718
+ validate: (bullmqJob) => {
3719
+ if (!bullmqJob.data) {
3720
+ throw new Error(`Missing job data for job ${bullmqJob.id}.`);
3533
3721
  }
3534
- if (!job.data.type) {
3535
- throw new Error(`Missing property "type" in data for job ${job.id}.`);
3722
+ if (!bullmqJob.data.type) {
3723
+ throw new Error(`Missing property "type" in data for job ${bullmqJob.id}.`);
3536
3724
  }
3537
- if (!job.data.inputs) {
3538
- throw new Error(`Missing property "inputs" in data for job ${job.id}.`);
3725
+ if (!bullmqJob.data.inputs) {
3726
+ throw new Error(`Missing property "inputs" in data for job ${bullmqJob.id}.`);
3539
3727
  }
3540
- if (job.data.type !== "embedder" && job.data.type !== "workflow") {
3541
- throw new Error(`Property "type" in data for job ${job.id} must be of value "embedder" or "workflow".`);
3728
+ if (bullmqJob.data.type !== "embedder" && bullmqJob.data.type !== "workflow") {
3729
+ throw new Error(`Property "type" in data for job ${bullmqJob.id} must be of value "embedder" or "workflow".`);
3542
3730
  }
3543
- if (!job.data.workflow && !job.data.embedder) {
3544
- throw new Error(`Property "backend" in data for job ${job.id} missing. Job data: ${JSON.stringify(job)}`);
3731
+ if (!bullmqJob.data.workflow && !bullmqJob.data.embedder) {
3732
+ throw new Error(`Property "backend" in data for job ${bullmqJob.id} missing. Job data: ${JSON.stringify(bullmqJob)}`);
3545
3733
  }
3546
3734
  },
3547
3735
  process: {
3548
- workflow: async (job, workflow, logsDir) => {
3736
+ workflow: async (bullmqJob, exuluJob, workflow, logsDir) => {
3549
3737
  if (!workflow) {
3550
- throw new Error(`Workflow function with id: ${job.data.backend} not found in registry.`);
3551
- }
3552
- const { runId, start, watch } = workflow.workflow.createRun();
3553
- console.log("[EXULU] starting workflow with job inputs.", job.data.inputs);
3554
- const logger = new ExuluLogger(job, logsDir);
3555
- const output = await start({ triggerData: {
3556
- ...job.data.inputs,
3557
- redis: job.id,
3558
- logger
3559
- } });
3560
- const failedSteps = Object.entries(output.results).filter(([_, step]) => step.status === "failed").map(([id, step]) => `${id}: ${step.error}`);
3561
- if (failedSteps.length > 0) {
3562
- const message = `Workflow has failed steps: ${failedSteps.join("\n - ")}`;
3563
- logger.write(message, "ERROR");
3564
- throw new Error(message);
3565
- }
3566
- await logger.write(`Workflow completed. ${JSON.stringify(output.results)}`, "INFO");
3738
+ throw new Error(`Workflow function with id: ${bullmqJob.data.backend} not found in registry.`);
3739
+ }
3740
+ console.log("[EXULU] starting workflow with job inputs.", bullmqJob.data.inputs);
3741
+ const logger = new ExuluLogger(exuluJob, logsDir);
3742
+ const output = await workflow.start({
3743
+ job: exuluJob,
3744
+ inputs: bullmqJob.data.inputs,
3745
+ user: bullmqJob.data.user,
3746
+ logger,
3747
+ session: bullmqJob.data.session,
3748
+ agent: bullmqJob.data.agent,
3749
+ label: bullmqJob.data.label
3750
+ });
3751
+ await logger.write(`Workflow completed. ${JSON.stringify(output)}`, "INFO");
3567
3752
  return output;
3568
3753
  }
3569
3754
  }
@@ -3590,56 +3775,56 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3590
3775
  console.log(`[EXULU] creating worker for queue ${queue}.`);
3591
3776
  const worker = new Worker(
3592
3777
  `${queue}`,
3593
- async (job) => {
3778
+ async (bullmqJob) => {
3594
3779
  const { db: db2 } = await postgresClient();
3595
3780
  try {
3596
- bullmq.validate(job);
3597
- if (job.data.type === "embedder") {
3598
- if (!job.data.updater) {
3781
+ bullmq.validate(bullmqJob);
3782
+ if (bullmqJob.data.type === "embedder") {
3783
+ if (!bullmqJob.data.updater) {
3599
3784
  throw new Error("No updater set for embedder job.");
3600
3785
  }
3601
- const context = contexts.find((context2) => context2.id === job.data.context);
3786
+ const context = contexts.find((context2) => context2.id === bullmqJob.data.context);
3602
3787
  if (!context) {
3603
- throw new Error(`Context ${job.data.context} not found in the registry.`);
3788
+ throw new Error(`Context ${bullmqJob.data.context} not found in the registry.`);
3604
3789
  }
3605
- if (!job.data.embedder) {
3790
+ if (!bullmqJob.data.embedder) {
3606
3791
  throw new Error(`No embedder set for embedder job.`);
3607
3792
  }
3608
- const embedder = embedders.find((embedder2) => embedder2.id === job.data.embedder);
3793
+ const embedder = embedders.find((embedder2) => embedder2.id === bullmqJob.data.embedder);
3609
3794
  if (!embedder) {
3610
- throw new Error(`Embedder ${job.data.embedder} not found in the registry.`);
3795
+ throw new Error(`Embedder ${bullmqJob.data.embedder} not found in the registry.`);
3611
3796
  }
3612
- if (!job.data.source) {
3797
+ if (!bullmqJob.data.source) {
3613
3798
  throw new Error("No source set for embedder job.");
3614
3799
  }
3615
- const source = context.sources.get(job.data.source);
3800
+ const source = context.sources.get(bullmqJob.data.source);
3616
3801
  if (!source) {
3617
- throw new Error(`Source ${job.data.source} not found in the registry.`);
3802
+ throw new Error(`Source ${bullmqJob.data.source} not found in the registry.`);
3618
3803
  }
3619
- if (!job.data.updater) {
3804
+ if (!bullmqJob.data.updater) {
3620
3805
  throw new Error("No updater set for embedder job.");
3621
3806
  }
3622
- const updater = source.updaters.find((updater2) => updater2.id === job.data.updater);
3807
+ const updater = source.updaters.find((updater2) => updater2.id === bullmqJob.data.updater);
3623
3808
  if (!updater) {
3624
- throw new Error(`Updater ${job.data.updater} not found in the registry.`);
3809
+ throw new Error(`Updater ${bullmqJob.data.updater} not found in the registry.`);
3625
3810
  }
3626
- if (!job.data.documents) {
3811
+ if (!bullmqJob.data.documents) {
3627
3812
  throw new Error("No input documents set for embedder job.");
3628
3813
  }
3629
- if (!Array.isArray(job.data.documents)) {
3814
+ if (!Array.isArray(bullmqJob.data.documents)) {
3630
3815
  throw new Error("Input documents must be an array.");
3631
3816
  }
3632
- const result = await embedder.upsert(job.data.context, job.data.documents, {
3817
+ const result = await embedder.upsert(bullmqJob.data.context, bullmqJob.data.documents, {
3633
3818
  label: context.name,
3634
- trigger: job.data.trigger || "unknown"
3819
+ trigger: bullmqJob.data.trigger || "unknown"
3635
3820
  });
3636
- const mongoRecord = await db2.from("jobs").where({ redis: job.id }).first();
3821
+ const mongoRecord = await db2.from("jobs").where({ redis: bullmqJob.id }).first();
3637
3822
  if (!mongoRecord) {
3638
3823
  throw new Error("Job not found in the database.");
3639
3824
  }
3640
3825
  const finishedAt = /* @__PURE__ */ new Date();
3641
3826
  const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
3642
- await db2.from("jobs").where({ redis: job.id }).update({
3827
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3643
3828
  status: "completed",
3644
3829
  finishedAt,
3645
3830
  duration,
@@ -3647,19 +3832,19 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3647
3832
  });
3648
3833
  return result;
3649
3834
  }
3650
- if (job.data.type === "workflow") {
3651
- const workflow = workflows.find((workflow2) => workflow2.id === job.data.workflow);
3835
+ if (bullmqJob.data.type === "workflow") {
3836
+ const workflow = workflows.find((workflow2) => workflow2.id === bullmqJob.data.workflow);
3652
3837
  if (!workflow) {
3653
- throw new Error(`Workflow ${job.data.workflow} not found in the registry.`);
3838
+ throw new Error(`Workflow ${bullmqJob.data.workflow} not found in the registry.`);
3654
3839
  }
3655
- const result = await bullmq.process.workflow(job, workflow, logsDir);
3656
- const mongoRecord = await db2.from("jobs").where({ redis: job.id }).first();
3657
- if (!mongoRecord) {
3840
+ const exuluJob = await db2.from("jobs").where({ redis: bullmqJob.id }).first();
3841
+ if (!exuluJob) {
3658
3842
  throw new Error("Job not found in the database.");
3659
3843
  }
3844
+ const result = await bullmq.process.workflow(bullmqJob, exuluJob, workflow, logsDir);
3660
3845
  const finishedAt = /* @__PURE__ */ new Date();
3661
- const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
3662
- await db2.from("jobs").where({ redis: job.id }).update({
3846
+ const duration = (finishedAt.getTime() - new Date(exuluJob.createdAt).getTime()) / 1e3;
3847
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3663
3848
  status: "completed",
3664
3849
  finishedAt,
3665
3850
  duration,
@@ -3668,7 +3853,7 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3668
3853
  return result;
3669
3854
  }
3670
3855
  } catch (error) {
3671
- await db2.from("jobs").where({ redis: job.id }).update({
3856
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3672
3857
  status: "failed",
3673
3858
  finishedAt: /* @__PURE__ */ new Date(),
3674
3859
  error: error instanceof Error ? error.message : String(error)
@@ -3810,12 +3995,18 @@ var ExuluApp = class {
3810
3995
  };
3811
3996
 
3812
3997
  // src/index.ts
3998
+ import { SentenceChunker } from "chonkie";
3813
3999
  var ExuluJobs = {
3814
4000
  redis: redisClient,
3815
4001
  jobs: {
3816
4002
  validate: validateJob
3817
4003
  }
3818
4004
  };
4005
+ var ExuluChunkers = {
4006
+ chonkie: {
4007
+ sentence: SentenceChunker
4008
+ }
4009
+ };
3819
4010
  var ExuluDatabase = {
3820
4011
  init: async () => {
3821
4012
  await execute();
@@ -3825,14 +4016,17 @@ var ExuluDatabase = {
3825
4016
  }
3826
4017
  };
3827
4018
  export {
4019
+ JOB_STATUS_ENUM as EXULU_JOB_STATUS_ENUM,
3828
4020
  STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM,
3829
4021
  ExuluAgent,
3830
4022
  ExuluApp,
3831
4023
  authentication as ExuluAuthentication,
4024
+ ExuluChunkers,
3832
4025
  ExuluContext,
3833
4026
  ExuluDatabase,
3834
4027
  ExuluEmbedder,
3835
4028
  ExuluJobs,
4029
+ ExuluLogger,
3836
4030
  queues as ExuluQueues,
3837
4031
  ExuluSource,
3838
4032
  ExuluTool,