@exulu/backend 0.2.2 → 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,
@@ -2737,13 +2925,22 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2737
2925
  if (!exists) {
2738
2926
  throw new Error("Table with name " + context.getTableName() + " does not exist.");
2739
2927
  }
2740
- const mutation = db2.from(context.getTableName()).delete().returning("id");
2928
+ const query = db2.from(context.getTableName()).select("id");
2741
2929
  if (id) {
2742
- mutation.where({ id });
2930
+ query.where({ id });
2743
2931
  }
2744
2932
  if (external_id) {
2745
- mutation.where({ external_id });
2933
+ query.where({ external_id });
2934
+ }
2935
+ const item = await query.first();
2936
+ if (!item) {
2937
+ throw new Error("Item not found.");
2746
2938
  }
2939
+ const chunks = await db2.from(context.getChunksTableName()).where({ source: item.id }).select("id");
2940
+ if (chunks.length > 0) {
2941
+ await db2.from(context.getChunksTableName()).where({ source: item.id }).delete();
2942
+ }
2943
+ const mutation = db2.from(context.getTableName()).where({ id: item.id }).delete().returning("id");
2747
2944
  const result = await mutation;
2748
2945
  return result;
2749
2946
  };
@@ -2880,21 +3077,27 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2880
3077
  }
2881
3078
  const context = contexts.find((context2) => context2.id === req.params.context);
2882
3079
  if (!context) {
3080
+ console.error("[EXULU] context not found in registry.", req.params.context);
2883
3081
  res.status(400).json({
2884
3082
  message: "Context not found in registry."
2885
3083
  });
2886
3084
  return;
2887
3085
  }
3086
+ console.log("[EXULU] context", context);
2888
3087
  const exists = await context.tableExists();
2889
3088
  if (!exists) {
3089
+ console.log("[EXULU] context table does not exist, creating it.");
2890
3090
  await context.createItemsTable();
2891
3091
  }
3092
+ console.log("[EXULU] inserting item", req.body);
2892
3093
  const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert);
3094
+ console.log("[EXULU] result", result);
2893
3095
  res.status(200).json({
2894
3096
  message: "Item created successfully.",
2895
3097
  id: result
2896
3098
  });
2897
3099
  } catch (error) {
3100
+ console.error("[EXULU] error upserting item", error);
2898
3101
  res.status(500).json({
2899
3102
  message: error?.message || "An error occurred while creating the item."
2900
3103
  });
@@ -3239,7 +3442,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3239
3442
  slug: workflow.slug,
3240
3443
  enable_batch: workflow.enable_batch,
3241
3444
  queue: workflow.queue?.name,
3242
- inputSchema: workflow.inputSchema ? zerialize(workflow.inputSchema) : null
3445
+ inputSchema: workflow.steps[0]?.inputSchema ? zerialize(workflow.steps[0].inputSchema) : null
3243
3446
  })));
3244
3447
  });
3245
3448
  console.log("[EXULU] workflow by id");
@@ -3266,7 +3469,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3266
3469
  res.status(200).json({
3267
3470
  ...workflow,
3268
3471
  queue: workflow.queue?.name,
3269
- inputSchema: workflow.inputSchema ? zerialize(workflow.inputSchema) : null,
3472
+ inputSchema: workflow.steps[0]?.inputSchema ? zerialize(workflow.steps[0].inputSchema) : null,
3270
3473
  workflow: void 0
3271
3474
  });
3272
3475
  });
@@ -3402,10 +3605,6 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3402
3605
  note: `Execute workflow ${workflow.name}`
3403
3606
  });
3404
3607
  app.post(`${workflow.slug}`, async (req, res) => {
3405
- if (!workflow.queue) {
3406
- res.status(500).json({ detail: "No queue set for workflow." });
3407
- return;
3408
- }
3409
3608
  const authenticationResult = await requestValidators.authenticate(req);
3410
3609
  if (!authenticationResult.user?.id) {
3411
3610
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
@@ -3422,6 +3621,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3422
3621
  label: `Job running '${workflow.name}' for '${req.body.label}'`,
3423
3622
  agent: req.body.agent,
3424
3623
  workflow: workflow.id,
3624
+ steps: workflow.steps?.length || 0,
3425
3625
  type: "workflow",
3426
3626
  inputs,
3427
3627
  session: req.body.session,
@@ -3440,22 +3640,19 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3440
3640
  });
3441
3641
  return;
3442
3642
  }
3443
- const { runId, start, watch } = workflow.workflow.createRun();
3444
3643
  console.log("[EXULU] running workflow with inputs.", inputs);
3445
- const output = await start({
3446
- triggerData: {
3447
- ...inputs,
3448
- user: authenticationResult.user.id
3449
- }
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
3450
3652
  });
3451
- const failedSteps = Object.entries(output.results).filter(([_, step]) => step.status === "failed").map(([id, step]) => `${id}: ${step.error}`);
3452
- if (failedSteps.length > 0) {
3453
- const message = `Workflow has failed steps: ${failedSteps.join("\n - ")}`;
3454
- throw new Error(message);
3455
- }
3456
3653
  res.status(200).json({
3457
3654
  "job": {},
3458
- "output": output
3655
+ "output": result
3459
3656
  });
3460
3657
  return;
3461
3658
  });
@@ -3518,43 +3715,40 @@ import { Worker } from "bullmq";
3518
3715
  // src/registry/utils.ts
3519
3716
  import "bullmq";
3520
3717
  var bullmq = {
3521
- validate: (job) => {
3522
- if (!job.data) {
3523
- 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}.`);
3524
3721
  }
3525
- if (!job.data.type) {
3526
- 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}.`);
3527
3724
  }
3528
- if (!job.data.inputs) {
3529
- 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}.`);
3530
3727
  }
3531
- if (job.data.type !== "embedder" && job.data.type !== "workflow") {
3532
- 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".`);
3533
3730
  }
3534
- if (!job.data.workflow && !job.data.embedder) {
3535
- 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)}`);
3536
3733
  }
3537
3734
  },
3538
3735
  process: {
3539
- workflow: async (job, workflow, logsDir) => {
3736
+ workflow: async (bullmqJob, exuluJob, workflow, logsDir) => {
3540
3737
  if (!workflow) {
3541
- throw new Error(`Workflow function with id: ${job.data.backend} not found in registry.`);
3542
- }
3543
- const { runId, start, watch } = workflow.workflow.createRun();
3544
- console.log("[EXULU] starting workflow with job inputs.", job.data.inputs);
3545
- const logger = new ExuluLogger(job, logsDir);
3546
- const output = await start({ triggerData: {
3547
- ...job.data.inputs,
3548
- redis: job.id,
3549
- logger
3550
- } });
3551
- const failedSteps = Object.entries(output.results).filter(([_, step]) => step.status === "failed").map(([id, step]) => `${id}: ${step.error}`);
3552
- if (failedSteps.length > 0) {
3553
- const message = `Workflow has failed steps: ${failedSteps.join("\n - ")}`;
3554
- logger.write(message, "ERROR");
3555
- throw new Error(message);
3556
- }
3557
- 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");
3558
3752
  return output;
3559
3753
  }
3560
3754
  }
@@ -3581,56 +3775,56 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3581
3775
  console.log(`[EXULU] creating worker for queue ${queue}.`);
3582
3776
  const worker = new Worker(
3583
3777
  `${queue}`,
3584
- async (job) => {
3778
+ async (bullmqJob) => {
3585
3779
  const { db: db2 } = await postgresClient();
3586
3780
  try {
3587
- bullmq.validate(job);
3588
- if (job.data.type === "embedder") {
3589
- if (!job.data.updater) {
3781
+ bullmq.validate(bullmqJob);
3782
+ if (bullmqJob.data.type === "embedder") {
3783
+ if (!bullmqJob.data.updater) {
3590
3784
  throw new Error("No updater set for embedder job.");
3591
3785
  }
3592
- const context = contexts.find((context2) => context2.id === job.data.context);
3786
+ const context = contexts.find((context2) => context2.id === bullmqJob.data.context);
3593
3787
  if (!context) {
3594
- 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.`);
3595
3789
  }
3596
- if (!job.data.embedder) {
3790
+ if (!bullmqJob.data.embedder) {
3597
3791
  throw new Error(`No embedder set for embedder job.`);
3598
3792
  }
3599
- const embedder = embedders.find((embedder2) => embedder2.id === job.data.embedder);
3793
+ const embedder = embedders.find((embedder2) => embedder2.id === bullmqJob.data.embedder);
3600
3794
  if (!embedder) {
3601
- 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.`);
3602
3796
  }
3603
- if (!job.data.source) {
3797
+ if (!bullmqJob.data.source) {
3604
3798
  throw new Error("No source set for embedder job.");
3605
3799
  }
3606
- const source = context.sources.get(job.data.source);
3800
+ const source = context.sources.get(bullmqJob.data.source);
3607
3801
  if (!source) {
3608
- 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.`);
3609
3803
  }
3610
- if (!job.data.updater) {
3804
+ if (!bullmqJob.data.updater) {
3611
3805
  throw new Error("No updater set for embedder job.");
3612
3806
  }
3613
- const updater = source.updaters.find((updater2) => updater2.id === job.data.updater);
3807
+ const updater = source.updaters.find((updater2) => updater2.id === bullmqJob.data.updater);
3614
3808
  if (!updater) {
3615
- 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.`);
3616
3810
  }
3617
- if (!job.data.documents) {
3811
+ if (!bullmqJob.data.documents) {
3618
3812
  throw new Error("No input documents set for embedder job.");
3619
3813
  }
3620
- if (!Array.isArray(job.data.documents)) {
3814
+ if (!Array.isArray(bullmqJob.data.documents)) {
3621
3815
  throw new Error("Input documents must be an array.");
3622
3816
  }
3623
- const result = await embedder.upsert(job.data.context, job.data.documents, {
3817
+ const result = await embedder.upsert(bullmqJob.data.context, bullmqJob.data.documents, {
3624
3818
  label: context.name,
3625
- trigger: job.data.trigger || "unknown"
3819
+ trigger: bullmqJob.data.trigger || "unknown"
3626
3820
  });
3627
- const mongoRecord = await db2.from("jobs").where({ redis: job.id }).first();
3821
+ const mongoRecord = await db2.from("jobs").where({ redis: bullmqJob.id }).first();
3628
3822
  if (!mongoRecord) {
3629
3823
  throw new Error("Job not found in the database.");
3630
3824
  }
3631
3825
  const finishedAt = /* @__PURE__ */ new Date();
3632
3826
  const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
3633
- await db2.from("jobs").where({ redis: job.id }).update({
3827
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3634
3828
  status: "completed",
3635
3829
  finishedAt,
3636
3830
  duration,
@@ -3638,19 +3832,19 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3638
3832
  });
3639
3833
  return result;
3640
3834
  }
3641
- if (job.data.type === "workflow") {
3642
- 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);
3643
3837
  if (!workflow) {
3644
- 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.`);
3645
3839
  }
3646
- const result = await bullmq.process.workflow(job, workflow, logsDir);
3647
- const mongoRecord = await db2.from("jobs").where({ redis: job.id }).first();
3648
- if (!mongoRecord) {
3840
+ const exuluJob = await db2.from("jobs").where({ redis: bullmqJob.id }).first();
3841
+ if (!exuluJob) {
3649
3842
  throw new Error("Job not found in the database.");
3650
3843
  }
3844
+ const result = await bullmq.process.workflow(bullmqJob, exuluJob, workflow, logsDir);
3651
3845
  const finishedAt = /* @__PURE__ */ new Date();
3652
- const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
3653
- 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({
3654
3848
  status: "completed",
3655
3849
  finishedAt,
3656
3850
  duration,
@@ -3659,7 +3853,7 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
3659
3853
  return result;
3660
3854
  }
3661
3855
  } catch (error) {
3662
- await db2.from("jobs").where({ redis: job.id }).update({
3856
+ await db2.from("jobs").where({ redis: bullmqJob.id }).update({
3663
3857
  status: "failed",
3664
3858
  finishedAt: /* @__PURE__ */ new Date(),
3665
3859
  error: error instanceof Error ? error.message : String(error)
@@ -3801,12 +3995,18 @@ var ExuluApp = class {
3801
3995
  };
3802
3996
 
3803
3997
  // src/index.ts
3998
+ import { SentenceChunker } from "chonkie";
3804
3999
  var ExuluJobs = {
3805
4000
  redis: redisClient,
3806
4001
  jobs: {
3807
4002
  validate: validateJob
3808
4003
  }
3809
4004
  };
4005
+ var ExuluChunkers = {
4006
+ chonkie: {
4007
+ sentence: SentenceChunker
4008
+ }
4009
+ };
3810
4010
  var ExuluDatabase = {
3811
4011
  init: async () => {
3812
4012
  await execute();
@@ -3816,14 +4016,17 @@ var ExuluDatabase = {
3816
4016
  }
3817
4017
  };
3818
4018
  export {
4019
+ JOB_STATUS_ENUM as EXULU_JOB_STATUS_ENUM,
3819
4020
  STATISTICS_TYPE_ENUM as EXULU_STATISTICS_TYPE_ENUM,
3820
4021
  ExuluAgent,
3821
4022
  ExuluApp,
3822
4023
  authentication as ExuluAuthentication,
4024
+ ExuluChunkers,
3823
4025
  ExuluContext,
3824
4026
  ExuluDatabase,
3825
4027
  ExuluEmbedder,
3826
4028
  ExuluJobs,
4029
+ ExuluLogger,
3827
4030
  queues as ExuluQueues,
3828
4031
  ExuluSource,
3829
4032
  ExuluTool,