@exulu/backend 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -175,87 +175,48 @@ import pgvector2 from "pgvector/knex";
175
175
  import "bullmq";
176
176
  import { v4 as uuidv4 } from "uuid";
177
177
  var bullmqDecorator = async ({
178
+ queue,
178
179
  label,
179
- type,
180
- workflow,
181
180
  embedder,
182
181
  inputs,
183
- queue,
184
182
  user,
185
- agent,
186
- session,
187
- configuration,
188
- updater,
189
- context,
190
- steps,
191
- source,
192
- documents,
183
+ role,
193
184
  trigger,
194
- item
185
+ workflow,
186
+ item,
187
+ context
195
188
  }) => {
189
+ if (embedder && workflow) {
190
+ throw new Error("Cannot have both embedder and workflow in the same job.");
191
+ }
192
+ if (workflow && item) {
193
+ throw new Error("Cannot have both workflow and item in the same job.");
194
+ }
195
+ let type = "embedder";
196
+ if (workflow) {
197
+ type = "workflow";
198
+ }
196
199
  const redisId = uuidv4();
197
200
  const job = await queue.add(
198
201
  `${embedder || workflow}`,
199
202
  {
200
- type: `${type}`,
203
+ label,
201
204
  ...embedder && { embedder },
202
- ...workflow && { workflow },
203
- ...configuration && { configuration },
204
- ...updater && { updater },
205
- ...context && { context },
206
- ...source && { source },
207
- ...documents && { documents },
208
- ...steps && { steps },
205
+ type: `${type}`,
206
+ inputs,
207
+ ...user && { user },
208
+ ...role && { role },
209
209
  ...trigger && { trigger },
210
+ ...workflow && { workflow },
210
211
  ...item && { item },
211
- agent,
212
- user,
213
- inputs,
214
- label,
215
- session
212
+ ...context && { context }
216
213
  },
217
214
  {
218
215
  jobId: redisId
219
216
  }
220
217
  );
221
- const { db: db3 } = await postgresClient();
222
- const now = /* @__PURE__ */ new Date();
223
- console.log("[EXULU] scheduling new job", inputs);
224
- const insertData = {
225
- name: `${label}`,
226
- redis: job.id,
227
- status: "waiting",
228
- type,
229
- inputs,
230
- agent,
231
- item,
232
- createdAt: now,
233
- updatedAt: now,
234
- user,
235
- session,
236
- ...embedder && { embedder },
237
- ...workflow && { workflow },
238
- ...configuration && { configuration },
239
- ...steps && { steps },
240
- ...updater && { updater },
241
- ...context && { context },
242
- ...source && { source },
243
- ...documents && { documents: documents.map((doc2) => doc2.id) },
244
- ...trigger && { trigger }
245
- };
246
- await db3("jobs").insert(insertData).onConflict("redis").merge({
247
- ...insertData,
248
- updatedAt: now
249
- // Only updatedAt changes on updates
250
- });
251
- const doc = await db3.from("jobs").where({ redis: job.id }).first();
252
- if (!doc?.id) {
253
- throw new Error("Failed to get job ID after insert/update");
254
- }
255
- console.log("[EXULU] created job", doc?.id);
256
218
  return {
257
219
  ...job,
258
- id: doc?.id,
259
220
  redis: job.id
260
221
  };
261
222
  };
@@ -427,6 +388,7 @@ var ExuluEvalUtils = {
427
388
  // src/registry/classes.ts
428
389
  import CryptoJS from "crypto-js";
429
390
  import "express";
391
+ import "@opentelemetry/api";
430
392
  function sanitizeToolName(name) {
431
393
  if (typeof name !== "string") return "";
432
394
  let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
@@ -436,7 +398,7 @@ function sanitizeToolName(name) {
436
398
  }
437
399
  return sanitized;
438
400
  }
439
- var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
401
+ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) => {
440
402
  if (!tools) return {};
441
403
  const sanitizedTools = tools ? tools.map((tool2) => ({
442
404
  ...tool2,
@@ -471,6 +433,8 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
471
433
  // is available, after we added the .value property
472
434
  // by hydrating it from the variables table.
473
435
  providerApiKey,
436
+ user,
437
+ role,
474
438
  config: config ? config.config.reduce((acc, curr) => {
475
439
  acc[curr.name] = curr.value;
476
440
  return acc;
@@ -576,10 +540,12 @@ var ExuluAgent = class {
576
540
  }),
577
541
  description: `A function that calls an AI agent named: ${this.name}. The agent does the following: ${this.description}.`,
578
542
  config: [],
579
- execute: async ({ prompt, config, providerApiKey }) => {
543
+ execute: async ({ prompt, config, providerApiKey, user, role }) => {
580
544
  return await this.generateSync({
581
545
  prompt,
582
546
  providerApiKey,
547
+ user,
548
+ role,
583
549
  statistics: {
584
550
  label: "",
585
551
  trigger: "tool"
@@ -588,7 +554,7 @@ var ExuluAgent = class {
588
554
  }
589
555
  });
590
556
  };
591
- generateSync = async ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
557
+ generateSync = async ({ prompt, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
592
558
  if (!this.model) {
593
559
  throw new Error("Model is required for streaming.");
594
560
  }
@@ -598,6 +564,9 @@ var ExuluAgent = class {
598
564
  if (prompt && message) {
599
565
  throw new Error("Message and prompt cannot be provided at the same time.");
600
566
  }
567
+ if (!prompt && !message) {
568
+ throw new Error("Prompt or message is required for generating.");
569
+ }
601
570
  const model = this.model.create({
602
571
  apiKey: providerApiKey
603
572
  });
@@ -617,28 +586,54 @@ var ExuluAgent = class {
617
586
  }
618
587
  console.log("[EXULU] Model provider key", providerApiKey);
619
588
  console.log("[EXULU] Tool configs", toolConfigs);
620
- const { text } = await generateText({
621
- model,
622
- // Should be a LanguageModelV1
623
- system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
624
- messages: messages ? convertToModelMessages(messages) : void 0,
625
- prompt,
626
- maxRetries: 2,
627
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
628
- stopWhen: [stepCountIs(5)]
629
- });
630
- if (statistics) {
631
- await updateStatistic({
632
- name: "count",
633
- label: statistics.label,
634
- type: STATISTICS_TYPE_ENUM.AGENT_RUN,
635
- trigger: statistics.trigger,
636
- count: 1
589
+ if (prompt) {
590
+ const { text } = await generateText({
591
+ model,
592
+ // Should be a LanguageModelV1
593
+ system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
594
+ prompt,
595
+ maxRetries: 2,
596
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
597
+ stopWhen: [stepCountIs(5)]
598
+ });
599
+ if (statistics) {
600
+ await updateStatistic({
601
+ name: "count",
602
+ label: statistics.label,
603
+ type: STATISTICS_TYPE_ENUM.AGENT_RUN,
604
+ trigger: statistics.trigger,
605
+ count: 1,
606
+ user,
607
+ role
608
+ });
609
+ }
610
+ return text;
611
+ }
612
+ if (messages) {
613
+ const { text } = await generateText({
614
+ model,
615
+ // Should be a LanguageModelV1
616
+ system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
617
+ messages: convertToModelMessages(messages),
618
+ maxRetries: 2,
619
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
620
+ stopWhen: [stepCountIs(5)]
637
621
  });
622
+ if (statistics) {
623
+ await updateStatistic({
624
+ name: "count",
625
+ label: statistics.label,
626
+ type: STATISTICS_TYPE_ENUM.AGENT_RUN,
627
+ trigger: statistics.trigger,
628
+ count: 1,
629
+ user,
630
+ role
631
+ });
632
+ }
633
+ return text;
638
634
  }
639
- return text;
640
635
  };
641
- generateStream = async ({ express: express3, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
636
+ generateStream = async ({ express: express3, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
642
637
  if (!this.model) {
643
638
  throw new Error("Model is required for streaming.");
644
639
  }
@@ -668,10 +663,10 @@ var ExuluAgent = class {
668
663
  const result = streamText({
669
664
  model,
670
665
  // Should be a LanguageModelV1
671
- messages: messages ? convertToModelMessages(messages) : void 0,
666
+ messages: convertToModelMessages(messages),
672
667
  system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
673
668
  maxRetries: 2,
674
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
669
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
675
670
  onError: (error) => console.error("[EXULU] chat stream error.", error),
676
671
  stopWhen: [stepCountIs(5)]
677
672
  });
@@ -699,7 +694,9 @@ var ExuluAgent = class {
699
694
  label: statistics.label,
700
695
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
701
696
  trigger: statistics.trigger,
702
- count: 1
697
+ count: 1,
698
+ user,
699
+ role
703
700
  });
704
701
  }
705
702
  }
@@ -743,14 +740,16 @@ var ExuluEmbedder = class {
743
740
  this.queue = queue;
744
741
  this.generateEmbeddings = generateEmbeddings;
745
742
  }
746
- async generateFromQuery(query, statistics) {
743
+ async generateFromQuery(query, statistics, user, role) {
747
744
  if (statistics) {
748
745
  await updateStatistic({
749
746
  name: "count",
750
747
  label: statistics.label,
751
748
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
752
749
  trigger: statistics.trigger,
753
- count: 1
750
+ count: 1,
751
+ user,
752
+ role
754
753
  });
755
754
  }
756
755
  return await this.generateEmbeddings({
@@ -763,14 +762,16 @@ var ExuluEmbedder = class {
763
762
  }]
764
763
  });
765
764
  }
766
- async generateFromDocument(input, statistics) {
765
+ async generateFromDocument(input, statistics, user, role) {
767
766
  if (statistics) {
768
767
  await updateStatistic({
769
768
  name: "count",
770
769
  label: statistics.label,
771
770
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
772
771
  trigger: statistics.trigger,
773
- count: 1
772
+ count: 1,
773
+ user,
774
+ role
774
775
  });
775
776
  }
776
777
  if (!this.chunker) {
@@ -1024,8 +1025,36 @@ var ExuluContext = class {
1024
1025
  const tableExists = await db3.schema.hasTable(this.getTableName());
1025
1026
  return tableExists;
1026
1027
  };
1027
- async updateItem(user, id, item) {
1028
- if (!id) {
1028
+ createAndUpsertEmbeddings = async (item, user, statistics, role) => {
1029
+ const { db: db3 } = await postgresClient();
1030
+ const { id: source, chunks } = await this.embedder.generateFromDocument({
1031
+ ...item,
1032
+ id: item.id
1033
+ }, {
1034
+ label: statistics.label || this.name,
1035
+ trigger: statistics.trigger || "agent"
1036
+ }, user, role);
1037
+ const exists = await db3.schema.hasTable(this.getChunksTableName());
1038
+ if (!exists) {
1039
+ await this.createChunksTable();
1040
+ }
1041
+ await db3.from(this.getChunksTableName()).where({ source }).delete();
1042
+ await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1043
+ source,
1044
+ content: chunk.content,
1045
+ chunk_index: chunk.index,
1046
+ embedding: pgvector2.toSql(chunk.vector)
1047
+ })));
1048
+ await db3.from(this.getTableName()).where({ id: item.id }).update({
1049
+ embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1050
+ }).returning("id");
1051
+ return {
1052
+ id: item.id,
1053
+ chunks: chunks?.length || 0
1054
+ };
1055
+ };
1056
+ async updateItem(user, item, role, trigger) {
1057
+ if (!item.id) {
1029
1058
  throw new Error("Id is required for updating an item.");
1030
1059
  }
1031
1060
  const { db: db3 } = await postgresClient();
@@ -1042,51 +1071,37 @@ var ExuluContext = class {
1042
1071
  delete item.created_at;
1043
1072
  delete item.upsert;
1044
1073
  item.updated_at = db3.fn.now();
1045
- const result = await db3.from(this.getTableName()).where({ id }).update(item).returning("id");
1074
+ const result = await db3.from(this.getTableName()).where({ id: item.id }).update(item).returning("id");
1046
1075
  if (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always") {
1047
1076
  if (this.embedder.queue?.name) {
1048
1077
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1049
1078
  const job = await bullmqDecorator({
1050
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1079
+ label: `${this.embedder.name}`,
1051
1080
  embedder: this.embedder.id,
1052
- type: "embedder",
1081
+ context: this.id,
1053
1082
  inputs: item,
1083
+ item: item.id,
1054
1084
  queue: this.embedder.queue,
1055
- user
1085
+ user,
1086
+ role,
1087
+ trigger: trigger || "agent"
1056
1088
  });
1057
1089
  return {
1058
1090
  id: result[0].id,
1059
1091
  job: job.id
1060
1092
  };
1061
1093
  }
1062
- const { id: source, chunks } = await this.embedder.generateFromDocument({
1063
- ...item,
1064
- id
1065
- }, {
1066
- label: this.name,
1067
- trigger: "agent"
1068
- });
1069
- const exists = await db3.schema.hasTable(this.getChunksTableName());
1070
- if (!exists) {
1071
- await this.createChunksTable();
1072
- }
1073
- await db3.from(this.getChunksTableName()).where({ source }).delete();
1074
- await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1075
- source,
1076
- content: chunk.content,
1077
- chunk_index: chunk.index,
1078
- embedding: pgvector2.toSql(chunk.vector)
1079
- })));
1080
- await db3.from(this.getTableName()).where({ id }).update({
1081
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1082
- }).returning("id");
1094
+ await this.createAndUpsertEmbeddings(item, user, {
1095
+ label: this.embedder.name,
1096
+ trigger: trigger || "agent"
1097
+ }, role);
1083
1098
  }
1084
1099
  return {
1085
1100
  id: result[0].id,
1086
1101
  job: void 0
1087
1102
  };
1088
1103
  }
1089
- async insertItem(user, item, upsert = false) {
1104
+ async insertItem(user, item, upsert = false, role, trigger) {
1090
1105
  if (!item.name) {
1091
1106
  throw new Error("Name field is required.");
1092
1107
  }
@@ -1097,14 +1112,20 @@ var ExuluContext = class {
1097
1112
  throw new Error("Item with external id " + item.external_id + " already exists.");
1098
1113
  }
1099
1114
  if (existingItem && upsert) {
1100
- await this.updateItem(user, existingItem.id, item);
1115
+ await this.updateItem(user, {
1116
+ ...item,
1117
+ id: existingItem.id
1118
+ }, role, trigger);
1101
1119
  return existingItem.id;
1102
1120
  }
1103
1121
  }
1104
1122
  if (upsert && item.id) {
1105
1123
  const existingItem = await db3.from(this.getTableName()).where({ id: item.id }).first();
1106
1124
  if (existingItem && upsert) {
1107
- await this.updateItem(user, existingItem.id, item);
1125
+ await this.updateItem(user, {
1126
+ ...item,
1127
+ id: existingItem.id
1128
+ }, role, trigger);
1108
1129
  return existingItem.id;
1109
1130
  }
1110
1131
  }
@@ -1129,12 +1150,15 @@ var ExuluContext = class {
1129
1150
  if (this.embedder.queue?.name) {
1130
1151
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1131
1152
  const job = await bullmqDecorator({
1132
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1153
+ label: `${this.embedder.name}`,
1133
1154
  embedder: this.embedder.id,
1134
- type: "embedder",
1155
+ context: this.id,
1135
1156
  inputs: item,
1157
+ item: item.id,
1136
1158
  queue: this.embedder.queue,
1137
- user
1159
+ user,
1160
+ role,
1161
+ trigger: trigger || "agent"
1138
1162
  });
1139
1163
  return {
1140
1164
  id: result[0].id,
@@ -1142,27 +1166,13 @@ var ExuluContext = class {
1142
1166
  };
1143
1167
  }
1144
1168
  console.log("[EXULU] embedder is not in queue mode, calculating vectors directly.");
1145
- const { id: source, chunks } = await this.embedder.generateFromDocument({
1169
+ await this.createAndUpsertEmbeddings({
1146
1170
  ...item,
1147
1171
  id: result[0].id
1148
- }, {
1149
- label: this.name,
1150
- trigger: "agent"
1151
- });
1152
- const exists = await db3.schema.hasTable(this.getChunksTableName());
1153
- if (!exists) {
1154
- await this.createChunksTable();
1155
- }
1156
- console.log("[EXULU] Inserting chunks.");
1157
- await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1158
- source,
1159
- content: chunk.content,
1160
- chunk_index: chunk.index,
1161
- embedding: pgvector2.toSql(chunk.vector)
1162
- })));
1163
- await db3.from(this.getTableName()).where({ id: result[0].id }).update({
1164
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1165
- }).returning("id");
1172
+ }, user, {
1173
+ label: this.embedder.name,
1174
+ trigger: trigger || "agent"
1175
+ }, role);
1166
1176
  }
1167
1177
  return {
1168
1178
  id: result[0].id,
@@ -1176,6 +1186,8 @@ var ExuluContext = class {
1176
1186
  order,
1177
1187
  page,
1178
1188
  name,
1189
+ user,
1190
+ role,
1179
1191
  archived,
1180
1192
  query,
1181
1193
  method
@@ -1244,7 +1256,9 @@ var ExuluContext = class {
1244
1256
  name: "count",
1245
1257
  label: statistics.label,
1246
1258
  type: STATISTICS_TYPE_ENUM.CONTEXT_RETRIEVE,
1247
- trigger: statistics.trigger
1259
+ trigger: statistics.trigger,
1260
+ user,
1261
+ role
1248
1262
  });
1249
1263
  }
1250
1264
  if (this.queryRewriter) {
@@ -1260,7 +1274,10 @@ var ExuluContext = class {
1260
1274
  itemsQuery.select(chunksTable + ".chunk_index");
1261
1275
  itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
1262
1276
  itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
1263
- const { chunks } = await this.embedder.generateFromQuery(query);
1277
+ const { chunks } = await this.embedder.generateFromQuery(query, {
1278
+ label: this.name,
1279
+ trigger: "agent"
1280
+ }, user, role);
1264
1281
  if (!chunks?.[0]?.vector) {
1265
1282
  throw new Error("No vector generated for query.");
1266
1283
  }
@@ -1408,11 +1425,13 @@ var ExuluContext = class {
1408
1425
  }),
1409
1426
  config: [],
1410
1427
  description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
1411
- execute: async ({ query }) => {
1428
+ execute: async ({ query, user, role }) => {
1412
1429
  return await this.getItems({
1413
1430
  page: 1,
1414
1431
  limit: 10,
1415
1432
  query,
1433
+ user,
1434
+ role,
1416
1435
  statistics: {
1417
1436
  label: this.name,
1418
1437
  trigger: "agent"
@@ -1854,6 +1873,7 @@ import { zerialize } from "zodex";
1854
1873
 
1855
1874
  // src/bullmq/queues.ts
1856
1875
  import { Queue as Queue3 } from "bullmq";
1876
+ import { BullMQOtel } from "bullmq-otel";
1857
1877
  var ExuluQueues = class {
1858
1878
  queues;
1859
1879
  constructor() {
@@ -1871,7 +1891,13 @@ var ExuluQueues = class {
1871
1891
  console.error(`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or embedder (look for ExuluQueues.use() ).`);
1872
1892
  throw new Error(`[EXULU] no redis server configured.`);
1873
1893
  }
1874
- const newQueue = new Queue3(`${name}`, { connection: redisServer });
1894
+ const newQueue = new Queue3(
1895
+ `${name}`,
1896
+ {
1897
+ connection: redisServer,
1898
+ telemetry: new BullMQOtel("simple-guide")
1899
+ }
1900
+ );
1875
1901
  this.queues.push(newQueue);
1876
1902
  return newQueue;
1877
1903
  }
@@ -3658,6 +3684,7 @@ Intelligence Management Platform
3658
3684
  import OpenAI from "openai";
3659
3685
  import fs2 from "fs";
3660
3686
  import { randomUUID } from "crypto";
3687
+ import "@opentelemetry/api";
3661
3688
  var REQUEST_SIZE_LIMIT = "50mb";
3662
3689
  var global_queues = {
3663
3690
  logs_cleaner: "logs-cleaner"
@@ -3696,7 +3723,7 @@ var createRecurringJobs = async () => {
3696
3723
  console.table(recurringJobSchedulersLogs);
3697
3724
  return queue;
3698
3725
  };
3699
- var createExpressRoutes = async (app, agents, tools, contexts) => {
3726
+ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
3700
3727
  const routeLogs = [];
3701
3728
  var corsOptions = {
3702
3729
  origin: "*",
@@ -3790,6 +3817,16 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3790
3817
  express.json({ limit: REQUEST_SIZE_LIMIT }),
3791
3818
  expressMiddleware(server, {
3792
3819
  context: async ({ req }) => {
3820
+ logger.info("================");
3821
+ logger.info({
3822
+ message: "Incoming Request",
3823
+ method: req.method,
3824
+ path: req.path,
3825
+ requestId: "req-" + Date.now(),
3826
+ ipAddress: req.ip,
3827
+ userAgent: req.get("User-Agent")
3828
+ });
3829
+ logger.info("================");
3793
3830
  const authenticationResult = await requestValidators.authenticate(req);
3794
3831
  if (!authenticationResult.user?.id) {
3795
3832
  throw new Error(authenticationResult.message);
@@ -4176,7 +4213,15 @@ Mood: friendly and intelligent.
4176
4213
  if (!exists) {
4177
4214
  await context.createItemsTable();
4178
4215
  }
4179
- const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body);
4216
+ const result = await context.updateItem(
4217
+ authenticationResult.user.id,
4218
+ {
4219
+ ...req.body,
4220
+ id: req.params.id
4221
+ },
4222
+ authenticationResult.user.role?.id,
4223
+ authenticationResult.user.type === "api" ? "api" : "user"
4224
+ );
4180
4225
  res.status(200).json({
4181
4226
  message: "Item updated successfully.",
4182
4227
  id: result
@@ -4223,7 +4268,13 @@ Mood: friendly and intelligent.
4223
4268
  await context.createItemsTable();
4224
4269
  }
4225
4270
  console.log("[EXULU] inserting item", req.body);
4226
- const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert);
4271
+ const result = await context.insertItem(
4272
+ authenticationResult.user.id,
4273
+ req.body,
4274
+ !!req.body.upsert,
4275
+ authenticationResult.user.role?.id,
4276
+ authenticationResult.user.type === "api" ? "api" : "user"
4277
+ );
4227
4278
  console.log("[EXULU] result", result);
4228
4279
  res.status(200).json({
4229
4280
  message: "Item created successfully.",
@@ -4284,6 +4335,8 @@ Mood: friendly and intelligent.
4284
4335
  const result = await context.getItems({
4285
4336
  sort,
4286
4337
  order,
4338
+ user: authenticationResult.user?.id,
4339
+ role: authenticationResult.user?.role?.id,
4287
4340
  page,
4288
4341
  limit,
4289
4342
  archived: req.query.archived === "true",
@@ -4445,6 +4498,8 @@ Mood: friendly and intelligent.
4445
4498
  const items = await context.getItems({
4446
4499
  page: 1,
4447
4500
  // todo add pagination
4501
+ user: authenticationResult.user?.id,
4502
+ role: authenticationResult.user?.role?.id,
4448
4503
  limit: 500
4449
4504
  });
4450
4505
  const csv = Papa.unparse(items);
@@ -4568,7 +4623,7 @@ Mood: friendly and intelligent.
4568
4623
  return;
4569
4624
  }
4570
4625
  console.log("[EXULU] agent tools", agentInstance.tools);
4571
- const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4626
+ const enabledTools = agentInstance.tools.map(({ config: config2, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4572
4627
  console.log("[EXULU] enabled tools", enabledTools);
4573
4628
  const variableName = agentInstance.providerApiKey;
4574
4629
  const variable = await db3.from("variables").where({ name: variableName }).first();
@@ -4595,7 +4650,8 @@ Mood: friendly and intelligent.
4595
4650
  res,
4596
4651
  req
4597
4652
  },
4598
- user: headers.user,
4653
+ user: user?.id,
4654
+ role: user?.role?.id,
4599
4655
  session: headers.session,
4600
4656
  message: req.body.message,
4601
4657
  tools: enabledTools,
@@ -4609,8 +4665,9 @@ Mood: friendly and intelligent.
4609
4665
  return;
4610
4666
  } else {
4611
4667
  const response = await agent.generateSync({
4612
- user: headers.user,
4668
+ user: user?.id,
4613
4669
  session: headers.session,
4670
+ role: user?.role?.id,
4614
4671
  message: req.body.message,
4615
4672
  tools: enabledTools.map((tool2) => tool2.tool),
4616
4673
  providerApiKey,
@@ -4709,7 +4766,9 @@ Mood: friendly and intelligent.
4709
4766
  label: "Claude Code",
4710
4767
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
4711
4768
  trigger: "claude-code",
4712
- count: 1
4769
+ count: 1,
4770
+ user: authenticationResult.user?.id,
4771
+ role: authenticationResult.user.role?.id
4713
4772
  });
4714
4773
  response.headers.forEach((value, key) => {
4715
4774
  res.setHeader(key, value);
@@ -4770,43 +4829,22 @@ import IORedis from "ioredis";
4770
4829
  import { Worker } from "bullmq";
4771
4830
 
4772
4831
  // src/registry/utils.ts
4773
- import "bullmq";
4774
4832
  var bullmq = {
4775
- validate: (bullmqJob) => {
4776
- if (!bullmqJob.data) {
4777
- throw new Error(`Missing job data for job ${bullmqJob.id}.`);
4778
- }
4779
- if (!bullmqJob.data.type) {
4780
- throw new Error(`Missing property "type" in data for job ${bullmqJob.id}.`);
4833
+ validate: (id, data) => {
4834
+ if (!data) {
4835
+ throw new Error(`Missing job data for job ${id}.`);
4781
4836
  }
4782
- if (!bullmqJob.data.inputs) {
4783
- throw new Error(`Missing property "inputs" in data for job ${bullmqJob.id}.`);
4837
+ if (!data.type) {
4838
+ throw new Error(`Missing property "type" in data for job ${id}.`);
4784
4839
  }
4785
- if (bullmqJob.data.type !== "embedder" && bullmqJob.data.type !== "workflow") {
4786
- throw new Error(`Property "type" in data for job ${bullmqJob.id} must be of value "embedder" or "workflow".`);
4840
+ if (!data.inputs) {
4841
+ throw new Error(`Missing property "inputs" in data for job ${id}.`);
4787
4842
  }
4788
- if (!bullmqJob.data.workflow && !bullmqJob.data.embedder) {
4789
- throw new Error(`Property "backend" in data for job ${bullmqJob.id} missing. Job data: ${JSON.stringify(bullmqJob)}`);
4843
+ if (data.type !== "embedder" && data.type !== "workflow") {
4844
+ throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
4790
4845
  }
4791
- },
4792
- process: {
4793
- workflow: async (bullmqJob, exuluJob, workflow, logsDir) => {
4794
- if (!workflow) {
4795
- throw new Error(`Workflow function with id: ${bullmqJob.data.backend} not found in registry.`);
4796
- }
4797
- console.log("[EXULU] starting workflow with job inputs.", bullmqJob.data.inputs);
4798
- const logger = new ExuluLogger(exuluJob, logsDir);
4799
- const output = await workflow.start({
4800
- job: exuluJob,
4801
- inputs: bullmqJob.data.inputs,
4802
- user: bullmqJob.data.user,
4803
- logger,
4804
- session: bullmqJob.data.session,
4805
- agent: bullmqJob.data.agent,
4806
- label: bullmqJob.data.label
4807
- });
4808
- await logger.write(`Workflow completed. ${JSON.stringify(output)}`, "INFO");
4809
- return output;
4846
+ if (!data.workflow && !data.embedder) {
4847
+ throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
4810
4848
  }
4811
4849
  }
4812
4850
  };
@@ -4814,9 +4852,10 @@ var bullmq = {
4814
4852
  // src/registry/workers.ts
4815
4853
  import * as fs3 from "fs";
4816
4854
  import path2 from "path";
4855
+ import "@opentelemetry/api";
4817
4856
  var defaultLogsDir = path2.join(process.cwd(), "logs");
4818
4857
  var redisConnection;
4819
- var createWorkers = async (queues2, contexts, _logsDir) => {
4858
+ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
4820
4859
  if (!redisServer.host || !redisServer.port) {
4821
4860
  console.error("[EXULU] you are trying to start worker, but no redis server is configured in the environment.");
4822
4861
  throw new Error("No redis server configured in the environment, so cannot start worker.");
@@ -4833,84 +4872,30 @@ var createWorkers = async (queues2, contexts, _logsDir) => {
4833
4872
  const worker = new Worker(
4834
4873
  `${queue}`,
4835
4874
  async (bullmqJob) => {
4875
+ const logger2 = new ExuluLogger(bullmqJob, logsDir);
4836
4876
  const { db: db3 } = await postgresClient();
4837
4877
  try {
4838
- bullmq.validate(bullmqJob);
4839
- if (bullmqJob.data.type === "embedder") {
4840
- if (!bullmqJob.data.updater) {
4841
- throw new Error("No updater set for embedder job.");
4842
- }
4843
- const context = contexts.find((context2) => context2.id === bullmqJob.data.context);
4878
+ const data = bullmqJob.data;
4879
+ bullmq.validate(bullmqJob.id, data);
4880
+ if (data.type === "embedder") {
4881
+ const context = contexts.find((context2) => context2.id === data.context);
4844
4882
  if (!context) {
4845
- throw new Error(`Context ${bullmqJob.data.context} not found in the registry.`);
4883
+ throw new Error(`Context ${data.context} not found in the registry.`);
4846
4884
  }
4847
- if (!bullmqJob.data.embedder) {
4885
+ if (!data.embedder) {
4848
4886
  throw new Error(`No embedder set for embedder job.`);
4849
4887
  }
4850
- const embedder = contexts.find((context2) => context2.embedder?.id === bullmqJob.data.embedder);
4888
+ const embedder = contexts.find((context2) => context2.embedder?.id === data.embedder);
4851
4889
  if (!embedder) {
4852
- throw new Error(`Embedder ${bullmqJob.data.embedder} not found in the registry.`);
4853
- }
4854
- if (!bullmqJob.data.source) {
4855
- throw new Error("No source set for embedder job.");
4856
- }
4857
- const source = context.sources.get(bullmqJob.data.source);
4858
- if (!source) {
4859
- throw new Error(`Source ${bullmqJob.data.source} not found in the registry.`);
4860
- }
4861
- if (!bullmqJob.data.updater) {
4862
- throw new Error("No updater set for embedder job.");
4890
+ throw new Error(`Embedder ${data.embedder} not found in the registry.`);
4863
4891
  }
4864
- const updater = source.updaters.find((updater2) => updater2.id === bullmqJob.data.updater);
4865
- if (!updater) {
4866
- throw new Error(`Updater ${bullmqJob.data.updater} not found in the registry.`);
4867
- }
4868
- if (!bullmqJob.data.documents) {
4869
- throw new Error("No input documents set for embedder job.");
4870
- }
4871
- if (!Array.isArray(bullmqJob.data.documents)) {
4872
- throw new Error("Input documents must be an array.");
4873
- }
4874
- const result = await embedder.upsert(bullmqJob.data.context, bullmqJob.data.documents, {
4875
- label: context.name,
4876
- trigger: bullmqJob.data.trigger || "unknown"
4892
+ const result = await context.createAndUpsertEmbeddings(data.inputs, data.user, {
4893
+ label: embedder.name,
4894
+ trigger: data.trigger
4877
4895
  });
4878
- const mongoRecord = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
4879
- if (!mongoRecord) {
4880
- throw new Error("Job not found in the database.");
4881
- }
4882
- const finishedAt = /* @__PURE__ */ new Date();
4883
- const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
4884
- await db3.from("jobs").where({ redis: bullmqJob.id }).update({
4885
- status: "completed",
4886
- finishedAt,
4887
- duration,
4888
- result: JSON.stringify(result)
4889
- });
4890
- await db3.from((void 0).getTableName()).where({ id: result[0].id }).update({
4891
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
4892
- }).returning("id");
4893
4896
  return result;
4894
4897
  }
4895
4898
  if (bullmqJob.data.type === "workflow") {
4896
- const workflow = workflows.find((workflow2) => workflow2.id === bullmqJob.data.workflow);
4897
- if (!workflow) {
4898
- throw new Error(`Workflow ${bullmqJob.data.workflow} not found in the registry.`);
4899
- }
4900
- const exuluJob = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
4901
- if (!exuluJob) {
4902
- throw new Error("Job not found in the database.");
4903
- }
4904
- const result = await bullmq.process.workflow(bullmqJob, exuluJob, workflow, logsDir);
4905
- const finishedAt = /* @__PURE__ */ new Date();
4906
- const duration = (finishedAt.getTime() - new Date(exuluJob.createdAt).getTime()) / 1e3;
4907
- await db3.from("jobs").where({ redis: bullmqJob.id }).update({
4908
- status: "completed",
4909
- finishedAt,
4910
- duration,
4911
- result: JSON.stringify(result)
4912
- });
4913
- return result;
4914
4899
  }
4915
4900
  } catch (error) {
4916
4901
  await db3.from("jobs").where({ redis: bullmqJob.id }).update({
@@ -4980,6 +4965,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
4980
4965
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
4981
4966
  import { z as z2 } from "zod";
4982
4967
  import "express";
4968
+ import "@opentelemetry/api";
4983
4969
  var SESSION_ID_HEADER = "mcp-session-id";
4984
4970
  var ExuluMCP = class {
4985
4971
  server;
@@ -4987,7 +4973,7 @@ var ExuluMCP = class {
4987
4973
  express;
4988
4974
  constructor() {
4989
4975
  }
4990
- create = async ({ express: express3, contexts, agents, config, tools }) => {
4976
+ create = async ({ express: express3, contexts, agents, config, tools, tracer, logger }) => {
4991
4977
  this.express = express3;
4992
4978
  if (!this.server) {
4993
4979
  console.log("[EXULU] Creating MCP server.");
@@ -5165,6 +5151,38 @@ var defaultAgent = new ExuluAgent({
5165
5151
  }
5166
5152
  });
5167
5153
 
5154
+ // src/registry/index.ts
5155
+ import { trace as trace2 } from "@opentelemetry/api";
5156
+
5157
+ // src/registry/logger.ts
5158
+ import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
5159
+ import winston from "winston";
5160
+ var createLogger = ({
5161
+ enableOtel
5162
+ }) => {
5163
+ const logger = winston.createLogger({
5164
+ level: "debug",
5165
+ format: winston.format.combine(
5166
+ winston.format.timestamp(),
5167
+ winston.format.errors({
5168
+ stack: true
5169
+ }),
5170
+ winston.format.metadata(),
5171
+ winston.format.json()
5172
+ ),
5173
+ defaultMeta: {
5174
+ service: "Test-Exulu",
5175
+ environment: process.env.NODE_ENV || "development"
5176
+ },
5177
+ transports: [
5178
+ new winston.transports.Console(),
5179
+ ...enableOtel ? [new OpenTelemetryTransportV3()] : []
5180
+ ]
5181
+ });
5182
+ return logger;
5183
+ };
5184
+ var logger_default = createLogger;
5185
+
5168
5186
  // src/registry/index.ts
5169
5187
  var ExuluApp = class {
5170
5188
  _agents = [];
@@ -5231,10 +5249,20 @@ var ExuluApp = class {
5231
5249
  bullmq = {
5232
5250
  workers: {
5233
5251
  create: async () => {
5252
+ let tracer;
5253
+ if (this._config?.telemetry?.enabled) {
5254
+ console.log("[EXULU] telemetry enabled");
5255
+ tracer = trace2.getTracer("exulu", "1.0.0");
5256
+ }
5257
+ const logger = logger_default({
5258
+ enableOtel: this._config?.workers?.telemetry?.enabled ?? false
5259
+ });
5234
5260
  return await createWorkers(
5235
5261
  this._queues,
5262
+ logger,
5236
5263
  Object.values(this._contexts ?? {}),
5237
- this._config?.workers?.logsDir
5264
+ this._config?.workers?.logsDir,
5265
+ tracer
5238
5266
  );
5239
5267
  }
5240
5268
  }
@@ -5246,11 +5274,22 @@ var ExuluApp = class {
5246
5274
  throw new Error("Express app not initialized");
5247
5275
  }
5248
5276
  const app = this._expressApp;
5277
+ let tracer;
5278
+ if (this._config?.telemetry?.enabled) {
5279
+ console.log("[EXULU] telemetry enabled");
5280
+ tracer = trace2.getTracer("exulu", "1.0.0");
5281
+ }
5282
+ const logger = logger_default({
5283
+ enableOtel: this._config?.telemetry?.enabled ?? false
5284
+ });
5249
5285
  await createExpressRoutes(
5250
5286
  app,
5287
+ logger,
5251
5288
  this._agents,
5252
5289
  this._tools,
5253
- Object.values(this._contexts ?? {})
5290
+ Object.values(this._contexts ?? {}),
5291
+ this._config,
5292
+ tracer
5254
5293
  );
5255
5294
  if (this._config?.MCP.enabled) {
5256
5295
  const mcp = new ExuluMCP();
@@ -5259,7 +5298,9 @@ var ExuluApp = class {
5259
5298
  contexts: this._contexts,
5260
5299
  agents: this._agents,
5261
5300
  config: this._config,
5262
- tools: this._tools
5301
+ tools: this._tools,
5302
+ tracer,
5303
+ logger
5263
5304
  });
5264
5305
  await mcp.connect();
5265
5306
  }
@@ -6672,6 +6713,50 @@ var execute = async () => {
6672
6713
  return;
6673
6714
  };
6674
6715
 
6716
+ // src/registry/otel.ts
6717
+ import process2 from "process";
6718
+ import { NodeSDK } from "@opentelemetry/sdk-node";
6719
+ import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
6720
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
6721
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
6722
+ import { resourceFromAttributes } from "@opentelemetry/resources";
6723
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
6724
+ import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
6725
+ var create = ({
6726
+ SIGNOZ_ACCESS_TOKEN,
6727
+ SIGNOZ_TRACES_URL,
6728
+ SIGNOZ_LOGS_URL
6729
+ }) => {
6730
+ console.log("SIGNOZ_ACCESS_TOKEN", SIGNOZ_ACCESS_TOKEN);
6731
+ console.log("SIGNOZ_TRACES_URL", SIGNOZ_TRACES_URL);
6732
+ console.log("SIGNOZ_LOGS_URL", SIGNOZ_LOGS_URL);
6733
+ const exporterOptions = {
6734
+ url: SIGNOZ_TRACES_URL,
6735
+ headers: {
6736
+ "signoz-access-token": SIGNOZ_ACCESS_TOKEN
6737
+ }
6738
+ };
6739
+ const traceExporter = new OTLPTraceExporter(exporterOptions);
6740
+ const logExporter = new OTLPLogExporter({
6741
+ url: SIGNOZ_LOGS_URL,
6742
+ headers: {
6743
+ "signoz-access-token": SIGNOZ_ACCESS_TOKEN
6744
+ }
6745
+ });
6746
+ const sdk = new NodeSDK({
6747
+ traceExporter,
6748
+ logRecordProcessors: [new BatchLogRecordProcessor(logExporter)],
6749
+ instrumentations: [getNodeAutoInstrumentations()],
6750
+ resource: resourceFromAttributes({
6751
+ [ATTR_SERVICE_NAME]: "Test-Exulu"
6752
+ })
6753
+ });
6754
+ process2.on("SIGTERM", () => {
6755
+ sdk.shutdown().then(() => console.log("Tracing terminated")).catch((error) => console.log("Error terminating tracing", error)).finally(() => process2.exit(0));
6756
+ });
6757
+ return sdk;
6758
+ };
6759
+
6675
6760
  // types/enums/jobs.ts
6676
6761
  var JOB_STATUS_ENUM = {
6677
6762
  completed: "completed",
@@ -6690,6 +6775,19 @@ var ExuluJobs = {
6690
6775
  validate: validateJob
6691
6776
  }
6692
6777
  };
6778
+ var ExuluOtel = {
6779
+ create: ({
6780
+ SIGNOZ_ACCESS_TOKEN,
6781
+ SIGNOZ_TRACES_URL,
6782
+ SIGNOZ_LOGS_URL
6783
+ }) => {
6784
+ return create({
6785
+ SIGNOZ_ACCESS_TOKEN,
6786
+ SIGNOZ_TRACES_URL,
6787
+ SIGNOZ_LOGS_URL
6788
+ });
6789
+ }
6790
+ };
6693
6791
  var db2 = {
6694
6792
  init: async () => {
6695
6793
  await execute();
@@ -6721,6 +6819,7 @@ export {
6721
6819
  ExuluEval,
6722
6820
  ExuluJobs,
6723
6821
  ExuluLogger,
6822
+ ExuluOtel,
6724
6823
  queues as ExuluQueues,
6725
6824
  ExuluSource,
6726
6825
  ExuluTool,