@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.cjs CHANGED
@@ -41,6 +41,7 @@ __export(index_exports, {
41
41
  ExuluEval: () => ExuluEval,
42
42
  ExuluJobs: () => ExuluJobs,
43
43
  ExuluLogger: () => ExuluLogger,
44
+ ExuluOtel: () => ExuluOtel,
44
45
  ExuluQueues: () => queues,
45
46
  ExuluSource: () => ExuluSource,
46
47
  ExuluTool: () => ExuluTool,
@@ -217,87 +218,48 @@ var import_knex4 = __toESM(require("pgvector/knex"), 1);
217
218
  var import_bullmq = require("bullmq");
218
219
  var import_uuid = require("uuid");
219
220
  var bullmqDecorator = async ({
221
+ queue,
220
222
  label,
221
- type,
222
- workflow,
223
223
  embedder,
224
224
  inputs,
225
- queue,
226
225
  user,
227
- agent,
228
- session,
229
- configuration,
230
- updater,
231
- context,
232
- steps,
233
- source,
234
- documents,
226
+ role,
235
227
  trigger,
236
- item
228
+ workflow,
229
+ item,
230
+ context
237
231
  }) => {
232
+ if (embedder && workflow) {
233
+ throw new Error("Cannot have both embedder and workflow in the same job.");
234
+ }
235
+ if (workflow && item) {
236
+ throw new Error("Cannot have both workflow and item in the same job.");
237
+ }
238
+ let type = "embedder";
239
+ if (workflow) {
240
+ type = "workflow";
241
+ }
238
242
  const redisId = (0, import_uuid.v4)();
239
243
  const job = await queue.add(
240
244
  `${embedder || workflow}`,
241
245
  {
242
- type: `${type}`,
246
+ label,
243
247
  ...embedder && { embedder },
244
- ...workflow && { workflow },
245
- ...configuration && { configuration },
246
- ...updater && { updater },
247
- ...context && { context },
248
- ...source && { source },
249
- ...documents && { documents },
250
- ...steps && { steps },
248
+ type: `${type}`,
249
+ inputs,
250
+ ...user && { user },
251
+ ...role && { role },
251
252
  ...trigger && { trigger },
253
+ ...workflow && { workflow },
252
254
  ...item && { item },
253
- agent,
254
- user,
255
- inputs,
256
- label,
257
- session
255
+ ...context && { context }
258
256
  },
259
257
  {
260
258
  jobId: redisId
261
259
  }
262
260
  );
263
- const { db: db3 } = await postgresClient();
264
- const now = /* @__PURE__ */ new Date();
265
- console.log("[EXULU] scheduling new job", inputs);
266
- const insertData = {
267
- name: `${label}`,
268
- redis: job.id,
269
- status: "waiting",
270
- type,
271
- inputs,
272
- agent,
273
- item,
274
- createdAt: now,
275
- updatedAt: now,
276
- user,
277
- session,
278
- ...embedder && { embedder },
279
- ...workflow && { workflow },
280
- ...configuration && { configuration },
281
- ...steps && { steps },
282
- ...updater && { updater },
283
- ...context && { context },
284
- ...source && { source },
285
- ...documents && { documents: documents.map((doc2) => doc2.id) },
286
- ...trigger && { trigger }
287
- };
288
- await db3("jobs").insert(insertData).onConflict("redis").merge({
289
- ...insertData,
290
- updatedAt: now
291
- // Only updatedAt changes on updates
292
- });
293
- const doc = await db3.from("jobs").where({ redis: job.id }).first();
294
- if (!doc?.id) {
295
- throw new Error("Failed to get job ID after insert/update");
296
- }
297
- console.log("[EXULU] created job", doc?.id);
298
261
  return {
299
262
  ...job,
300
- id: doc?.id,
301
263
  redis: job.id
302
264
  };
303
265
  };
@@ -469,6 +431,7 @@ var ExuluEvalUtils = {
469
431
  // src/registry/classes.ts
470
432
  var import_crypto_js = __toESM(require("crypto-js"), 1);
471
433
  var import_express = require("express");
434
+ var import_api = require("@opentelemetry/api");
472
435
  function sanitizeToolName(name) {
473
436
  if (typeof name !== "string") return "";
474
437
  let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
@@ -478,7 +441,7 @@ function sanitizeToolName(name) {
478
441
  }
479
442
  return sanitized;
480
443
  }
481
- var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
444
+ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) => {
482
445
  if (!tools) return {};
483
446
  const sanitizedTools = tools ? tools.map((tool2) => ({
484
447
  ...tool2,
@@ -513,6 +476,8 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey) => {
513
476
  // is available, after we added the .value property
514
477
  // by hydrating it from the variables table.
515
478
  providerApiKey,
479
+ user,
480
+ role,
516
481
  config: config ? config.config.reduce((acc, curr) => {
517
482
  acc[curr.name] = curr.value;
518
483
  return acc;
@@ -618,10 +583,12 @@ var ExuluAgent = class {
618
583
  }),
619
584
  description: `A function that calls an AI agent named: ${this.name}. The agent does the following: ${this.description}.`,
620
585
  config: [],
621
- execute: async ({ prompt, config, providerApiKey }) => {
586
+ execute: async ({ prompt, config, providerApiKey, user, role }) => {
622
587
  return await this.generateSync({
623
588
  prompt,
624
589
  providerApiKey,
590
+ user,
591
+ role,
625
592
  statistics: {
626
593
  label: "",
627
594
  trigger: "tool"
@@ -630,7 +597,7 @@ var ExuluAgent = class {
630
597
  }
631
598
  });
632
599
  };
633
- generateSync = async ({ prompt, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
600
+ generateSync = async ({ prompt, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
634
601
  if (!this.model) {
635
602
  throw new Error("Model is required for streaming.");
636
603
  }
@@ -640,6 +607,9 @@ var ExuluAgent = class {
640
607
  if (prompt && message) {
641
608
  throw new Error("Message and prompt cannot be provided at the same time.");
642
609
  }
610
+ if (!prompt && !message) {
611
+ throw new Error("Prompt or message is required for generating.");
612
+ }
643
613
  const model = this.model.create({
644
614
  apiKey: providerApiKey
645
615
  });
@@ -659,28 +629,54 @@ var ExuluAgent = class {
659
629
  }
660
630
  console.log("[EXULU] Model provider key", providerApiKey);
661
631
  console.log("[EXULU] Tool configs", toolConfigs);
662
- const { text } = await (0, import_ai.generateText)({
663
- model,
664
- // Should be a LanguageModelV1
665
- 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.",
666
- messages: messages ? (0, import_ai.convertToModelMessages)(messages) : void 0,
667
- prompt,
668
- maxRetries: 2,
669
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
670
- stopWhen: [(0, import_ai.stepCountIs)(5)]
671
- });
672
- if (statistics) {
673
- await updateStatistic({
674
- name: "count",
675
- label: statistics.label,
676
- type: STATISTICS_TYPE_ENUM.AGENT_RUN,
677
- trigger: statistics.trigger,
678
- count: 1
632
+ if (prompt) {
633
+ const { text } = await (0, import_ai.generateText)({
634
+ model,
635
+ // Should be a LanguageModelV1
636
+ 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.",
637
+ prompt,
638
+ maxRetries: 2,
639
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
640
+ stopWhen: [(0, import_ai.stepCountIs)(5)]
641
+ });
642
+ if (statistics) {
643
+ await updateStatistic({
644
+ name: "count",
645
+ label: statistics.label,
646
+ type: STATISTICS_TYPE_ENUM.AGENT_RUN,
647
+ trigger: statistics.trigger,
648
+ count: 1,
649
+ user,
650
+ role
651
+ });
652
+ }
653
+ return text;
654
+ }
655
+ if (messages) {
656
+ const { text } = await (0, import_ai.generateText)({
657
+ model,
658
+ // Should be a LanguageModelV1
659
+ 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.",
660
+ messages: (0, import_ai.convertToModelMessages)(messages),
661
+ maxRetries: 2,
662
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
663
+ stopWhen: [(0, import_ai.stepCountIs)(5)]
679
664
  });
665
+ if (statistics) {
666
+ await updateStatistic({
667
+ name: "count",
668
+ label: statistics.label,
669
+ type: STATISTICS_TYPE_ENUM.AGENT_RUN,
670
+ trigger: statistics.trigger,
671
+ count: 1,
672
+ user,
673
+ role
674
+ });
675
+ }
676
+ return text;
680
677
  }
681
- return text;
682
678
  };
683
- generateStream = async ({ express: express3, user, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
679
+ generateStream = async ({ express: express3, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
684
680
  if (!this.model) {
685
681
  throw new Error("Model is required for streaming.");
686
682
  }
@@ -710,10 +706,10 @@ var ExuluAgent = class {
710
706
  const result = (0, import_ai.streamText)({
711
707
  model,
712
708
  // Should be a LanguageModelV1
713
- messages: messages ? (0, import_ai.convertToModelMessages)(messages) : void 0,
709
+ messages: (0, import_ai.convertToModelMessages)(messages),
714
710
  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.",
715
711
  maxRetries: 2,
716
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey),
712
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
717
713
  onError: (error) => console.error("[EXULU] chat stream error.", error),
718
714
  stopWhen: [(0, import_ai.stepCountIs)(5)]
719
715
  });
@@ -741,7 +737,9 @@ var ExuluAgent = class {
741
737
  label: statistics.label,
742
738
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
743
739
  trigger: statistics.trigger,
744
- count: 1
740
+ count: 1,
741
+ user,
742
+ role
745
743
  });
746
744
  }
747
745
  }
@@ -785,14 +783,16 @@ var ExuluEmbedder = class {
785
783
  this.queue = queue;
786
784
  this.generateEmbeddings = generateEmbeddings;
787
785
  }
788
- async generateFromQuery(query, statistics) {
786
+ async generateFromQuery(query, statistics, user, role) {
789
787
  if (statistics) {
790
788
  await updateStatistic({
791
789
  name: "count",
792
790
  label: statistics.label,
793
791
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
794
792
  trigger: statistics.trigger,
795
- count: 1
793
+ count: 1,
794
+ user,
795
+ role
796
796
  });
797
797
  }
798
798
  return await this.generateEmbeddings({
@@ -805,14 +805,16 @@ var ExuluEmbedder = class {
805
805
  }]
806
806
  });
807
807
  }
808
- async generateFromDocument(input, statistics) {
808
+ async generateFromDocument(input, statistics, user, role) {
809
809
  if (statistics) {
810
810
  await updateStatistic({
811
811
  name: "count",
812
812
  label: statistics.label,
813
813
  type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
814
814
  trigger: statistics.trigger,
815
- count: 1
815
+ count: 1,
816
+ user,
817
+ role
816
818
  });
817
819
  }
818
820
  if (!this.chunker) {
@@ -1066,8 +1068,36 @@ var ExuluContext = class {
1066
1068
  const tableExists = await db3.schema.hasTable(this.getTableName());
1067
1069
  return tableExists;
1068
1070
  };
1069
- async updateItem(user, id, item) {
1070
- if (!id) {
1071
+ createAndUpsertEmbeddings = async (item, user, statistics, role) => {
1072
+ const { db: db3 } = await postgresClient();
1073
+ const { id: source, chunks } = await this.embedder.generateFromDocument({
1074
+ ...item,
1075
+ id: item.id
1076
+ }, {
1077
+ label: statistics.label || this.name,
1078
+ trigger: statistics.trigger || "agent"
1079
+ }, user, role);
1080
+ const exists = await db3.schema.hasTable(this.getChunksTableName());
1081
+ if (!exists) {
1082
+ await this.createChunksTable();
1083
+ }
1084
+ await db3.from(this.getChunksTableName()).where({ source }).delete();
1085
+ await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1086
+ source,
1087
+ content: chunk.content,
1088
+ chunk_index: chunk.index,
1089
+ embedding: import_knex4.default.toSql(chunk.vector)
1090
+ })));
1091
+ await db3.from(this.getTableName()).where({ id: item.id }).update({
1092
+ embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1093
+ }).returning("id");
1094
+ return {
1095
+ id: item.id,
1096
+ chunks: chunks?.length || 0
1097
+ };
1098
+ };
1099
+ async updateItem(user, item, role, trigger) {
1100
+ if (!item.id) {
1071
1101
  throw new Error("Id is required for updating an item.");
1072
1102
  }
1073
1103
  const { db: db3 } = await postgresClient();
@@ -1084,51 +1114,37 @@ var ExuluContext = class {
1084
1114
  delete item.created_at;
1085
1115
  delete item.upsert;
1086
1116
  item.updated_at = db3.fn.now();
1087
- const result = await db3.from(this.getTableName()).where({ id }).update(item).returning("id");
1117
+ const result = await db3.from(this.getTableName()).where({ id: item.id }).update(item).returning("id");
1088
1118
  if (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always") {
1089
1119
  if (this.embedder.queue?.name) {
1090
1120
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1091
1121
  const job = await bullmqDecorator({
1092
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1122
+ label: `${this.embedder.name}`,
1093
1123
  embedder: this.embedder.id,
1094
- type: "embedder",
1124
+ context: this.id,
1095
1125
  inputs: item,
1126
+ item: item.id,
1096
1127
  queue: this.embedder.queue,
1097
- user
1128
+ user,
1129
+ role,
1130
+ trigger: trigger || "agent"
1098
1131
  });
1099
1132
  return {
1100
1133
  id: result[0].id,
1101
1134
  job: job.id
1102
1135
  };
1103
1136
  }
1104
- const { id: source, chunks } = await this.embedder.generateFromDocument({
1105
- ...item,
1106
- id
1107
- }, {
1108
- label: this.name,
1109
- trigger: "agent"
1110
- });
1111
- const exists = await db3.schema.hasTable(this.getChunksTableName());
1112
- if (!exists) {
1113
- await this.createChunksTable();
1114
- }
1115
- await db3.from(this.getChunksTableName()).where({ source }).delete();
1116
- await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1117
- source,
1118
- content: chunk.content,
1119
- chunk_index: chunk.index,
1120
- embedding: import_knex4.default.toSql(chunk.vector)
1121
- })));
1122
- await db3.from(this.getTableName()).where({ id }).update({
1123
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1124
- }).returning("id");
1137
+ await this.createAndUpsertEmbeddings(item, user, {
1138
+ label: this.embedder.name,
1139
+ trigger: trigger || "agent"
1140
+ }, role);
1125
1141
  }
1126
1142
  return {
1127
1143
  id: result[0].id,
1128
1144
  job: void 0
1129
1145
  };
1130
1146
  }
1131
- async insertItem(user, item, upsert = false) {
1147
+ async insertItem(user, item, upsert = false, role, trigger) {
1132
1148
  if (!item.name) {
1133
1149
  throw new Error("Name field is required.");
1134
1150
  }
@@ -1139,14 +1155,20 @@ var ExuluContext = class {
1139
1155
  throw new Error("Item with external id " + item.external_id + " already exists.");
1140
1156
  }
1141
1157
  if (existingItem && upsert) {
1142
- await this.updateItem(user, existingItem.id, item);
1158
+ await this.updateItem(user, {
1159
+ ...item,
1160
+ id: existingItem.id
1161
+ }, role, trigger);
1143
1162
  return existingItem.id;
1144
1163
  }
1145
1164
  }
1146
1165
  if (upsert && item.id) {
1147
1166
  const existingItem = await db3.from(this.getTableName()).where({ id: item.id }).first();
1148
1167
  if (existingItem && upsert) {
1149
- await this.updateItem(user, existingItem.id, item);
1168
+ await this.updateItem(user, {
1169
+ ...item,
1170
+ id: existingItem.id
1171
+ }, role, trigger);
1150
1172
  return existingItem.id;
1151
1173
  }
1152
1174
  }
@@ -1171,12 +1193,15 @@ var ExuluContext = class {
1171
1193
  if (this.embedder.queue?.name) {
1172
1194
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1173
1195
  const job = await bullmqDecorator({
1174
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1196
+ label: `${this.embedder.name}`,
1175
1197
  embedder: this.embedder.id,
1176
- type: "embedder",
1198
+ context: this.id,
1177
1199
  inputs: item,
1200
+ item: item.id,
1178
1201
  queue: this.embedder.queue,
1179
- user
1202
+ user,
1203
+ role,
1204
+ trigger: trigger || "agent"
1180
1205
  });
1181
1206
  return {
1182
1207
  id: result[0].id,
@@ -1184,27 +1209,13 @@ var ExuluContext = class {
1184
1209
  };
1185
1210
  }
1186
1211
  console.log("[EXULU] embedder is not in queue mode, calculating vectors directly.");
1187
- const { id: source, chunks } = await this.embedder.generateFromDocument({
1212
+ await this.createAndUpsertEmbeddings({
1188
1213
  ...item,
1189
1214
  id: result[0].id
1190
- }, {
1191
- label: this.name,
1192
- trigger: "agent"
1193
- });
1194
- const exists = await db3.schema.hasTable(this.getChunksTableName());
1195
- if (!exists) {
1196
- await this.createChunksTable();
1197
- }
1198
- console.log("[EXULU] Inserting chunks.");
1199
- await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1200
- source,
1201
- content: chunk.content,
1202
- chunk_index: chunk.index,
1203
- embedding: import_knex4.default.toSql(chunk.vector)
1204
- })));
1205
- await db3.from(this.getTableName()).where({ id: result[0].id }).update({
1206
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1207
- }).returning("id");
1215
+ }, user, {
1216
+ label: this.embedder.name,
1217
+ trigger: trigger || "agent"
1218
+ }, role);
1208
1219
  }
1209
1220
  return {
1210
1221
  id: result[0].id,
@@ -1218,6 +1229,8 @@ var ExuluContext = class {
1218
1229
  order,
1219
1230
  page,
1220
1231
  name,
1232
+ user,
1233
+ role,
1221
1234
  archived,
1222
1235
  query,
1223
1236
  method
@@ -1286,7 +1299,9 @@ var ExuluContext = class {
1286
1299
  name: "count",
1287
1300
  label: statistics.label,
1288
1301
  type: STATISTICS_TYPE_ENUM.CONTEXT_RETRIEVE,
1289
- trigger: statistics.trigger
1302
+ trigger: statistics.trigger,
1303
+ user,
1304
+ role
1290
1305
  });
1291
1306
  }
1292
1307
  if (this.queryRewriter) {
@@ -1302,7 +1317,10 @@ var ExuluContext = class {
1302
1317
  itemsQuery.select(chunksTable + ".chunk_index");
1303
1318
  itemsQuery.select(chunksTable + ".created_at as chunk_created_at");
1304
1319
  itemsQuery.select(chunksTable + ".updated_at as chunk_updated_at");
1305
- const { chunks } = await this.embedder.generateFromQuery(query);
1320
+ const { chunks } = await this.embedder.generateFromQuery(query, {
1321
+ label: this.name,
1322
+ trigger: "agent"
1323
+ }, user, role);
1306
1324
  if (!chunks?.[0]?.vector) {
1307
1325
  throw new Error("No vector generated for query.");
1308
1326
  }
@@ -1450,11 +1468,13 @@ var ExuluContext = class {
1450
1468
  }),
1451
1469
  config: [],
1452
1470
  description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
1453
- execute: async ({ query }) => {
1471
+ execute: async ({ query, user, role }) => {
1454
1472
  return await this.getItems({
1455
1473
  page: 1,
1456
1474
  limit: 10,
1457
1475
  query,
1476
+ user,
1477
+ role,
1458
1478
  statistics: {
1459
1479
  label: this.name,
1460
1480
  trigger: "agent"
@@ -1896,6 +1916,7 @@ var import_zodex = require("zodex");
1896
1916
 
1897
1917
  // src/bullmq/queues.ts
1898
1918
  var import_bullmq4 = require("bullmq");
1919
+ var import_bullmq_otel = require("bullmq-otel");
1899
1920
  var ExuluQueues = class {
1900
1921
  queues;
1901
1922
  constructor() {
@@ -1913,7 +1934,13 @@ var ExuluQueues = class {
1913
1934
  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() ).`);
1914
1935
  throw new Error(`[EXULU] no redis server configured.`);
1915
1936
  }
1916
- const newQueue = new import_bullmq4.Queue(`${name}`, { connection: redisServer });
1937
+ const newQueue = new import_bullmq4.Queue(
1938
+ `${name}`,
1939
+ {
1940
+ connection: redisServer,
1941
+ telemetry: new import_bullmq_otel.BullMQOtel("simple-guide")
1942
+ }
1943
+ );
1917
1944
  this.queues.push(newQueue);
1918
1945
  return newQueue;
1919
1946
  }
@@ -3700,6 +3727,7 @@ Intelligence Management Platform
3700
3727
  var import_openai = __toESM(require("openai"), 1);
3701
3728
  var import_fs = __toESM(require("fs"), 1);
3702
3729
  var import_node_crypto = require("crypto");
3730
+ var import_api2 = require("@opentelemetry/api");
3703
3731
  var REQUEST_SIZE_LIMIT = "50mb";
3704
3732
  var global_queues = {
3705
3733
  logs_cleaner: "logs-cleaner"
@@ -3738,7 +3766,7 @@ var createRecurringJobs = async () => {
3738
3766
  console.table(recurringJobSchedulersLogs);
3739
3767
  return queue;
3740
3768
  };
3741
- var createExpressRoutes = async (app, agents, tools, contexts) => {
3769
+ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
3742
3770
  const routeLogs = [];
3743
3771
  var corsOptions = {
3744
3772
  origin: "*",
@@ -3832,6 +3860,16 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3832
3860
  import_express4.default.json({ limit: REQUEST_SIZE_LIMIT }),
3833
3861
  (0, import_express5.expressMiddleware)(server, {
3834
3862
  context: async ({ req }) => {
3863
+ logger.info("================");
3864
+ logger.info({
3865
+ message: "Incoming Request",
3866
+ method: req.method,
3867
+ path: req.path,
3868
+ requestId: "req-" + Date.now(),
3869
+ ipAddress: req.ip,
3870
+ userAgent: req.get("User-Agent")
3871
+ });
3872
+ logger.info("================");
3835
3873
  const authenticationResult = await requestValidators.authenticate(req);
3836
3874
  if (!authenticationResult.user?.id) {
3837
3875
  throw new Error(authenticationResult.message);
@@ -4218,7 +4256,15 @@ Mood: friendly and intelligent.
4218
4256
  if (!exists) {
4219
4257
  await context.createItemsTable();
4220
4258
  }
4221
- const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body);
4259
+ const result = await context.updateItem(
4260
+ authenticationResult.user.id,
4261
+ {
4262
+ ...req.body,
4263
+ id: req.params.id
4264
+ },
4265
+ authenticationResult.user.role?.id,
4266
+ authenticationResult.user.type === "api" ? "api" : "user"
4267
+ );
4222
4268
  res.status(200).json({
4223
4269
  message: "Item updated successfully.",
4224
4270
  id: result
@@ -4265,7 +4311,13 @@ Mood: friendly and intelligent.
4265
4311
  await context.createItemsTable();
4266
4312
  }
4267
4313
  console.log("[EXULU] inserting item", req.body);
4268
- const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert);
4314
+ const result = await context.insertItem(
4315
+ authenticationResult.user.id,
4316
+ req.body,
4317
+ !!req.body.upsert,
4318
+ authenticationResult.user.role?.id,
4319
+ authenticationResult.user.type === "api" ? "api" : "user"
4320
+ );
4269
4321
  console.log("[EXULU] result", result);
4270
4322
  res.status(200).json({
4271
4323
  message: "Item created successfully.",
@@ -4326,6 +4378,8 @@ Mood: friendly and intelligent.
4326
4378
  const result = await context.getItems({
4327
4379
  sort,
4328
4380
  order,
4381
+ user: authenticationResult.user?.id,
4382
+ role: authenticationResult.user?.role?.id,
4329
4383
  page,
4330
4384
  limit,
4331
4385
  archived: req.query.archived === "true",
@@ -4487,6 +4541,8 @@ Mood: friendly and intelligent.
4487
4541
  const items = await context.getItems({
4488
4542
  page: 1,
4489
4543
  // todo add pagination
4544
+ user: authenticationResult.user?.id,
4545
+ role: authenticationResult.user?.role?.id,
4490
4546
  limit: 500
4491
4547
  });
4492
4548
  const csv = Papa.unparse(items);
@@ -4610,7 +4666,7 @@ Mood: friendly and intelligent.
4610
4666
  return;
4611
4667
  }
4612
4668
  console.log("[EXULU] agent tools", agentInstance.tools);
4613
- const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4669
+ const enabledTools = agentInstance.tools.map(({ config: config2, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4614
4670
  console.log("[EXULU] enabled tools", enabledTools);
4615
4671
  const variableName = agentInstance.providerApiKey;
4616
4672
  const variable = await db3.from("variables").where({ name: variableName }).first();
@@ -4637,7 +4693,8 @@ Mood: friendly and intelligent.
4637
4693
  res,
4638
4694
  req
4639
4695
  },
4640
- user: headers.user,
4696
+ user: user?.id,
4697
+ role: user?.role?.id,
4641
4698
  session: headers.session,
4642
4699
  message: req.body.message,
4643
4700
  tools: enabledTools,
@@ -4651,8 +4708,9 @@ Mood: friendly and intelligent.
4651
4708
  return;
4652
4709
  } else {
4653
4710
  const response = await agent.generateSync({
4654
- user: headers.user,
4711
+ user: user?.id,
4655
4712
  session: headers.session,
4713
+ role: user?.role?.id,
4656
4714
  message: req.body.message,
4657
4715
  tools: enabledTools.map((tool2) => tool2.tool),
4658
4716
  providerApiKey,
@@ -4751,7 +4809,9 @@ Mood: friendly and intelligent.
4751
4809
  label: "Claude Code",
4752
4810
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
4753
4811
  trigger: "claude-code",
4754
- count: 1
4812
+ count: 1,
4813
+ user: authenticationResult.user?.id,
4814
+ role: authenticationResult.user.role?.id
4755
4815
  });
4756
4816
  response.headers.forEach((value, key) => {
4757
4817
  res.setHeader(key, value);
@@ -4809,46 +4869,25 @@ var createCustomAnthropicStreamingMessage = (message) => {
4809
4869
 
4810
4870
  // src/registry/workers.ts
4811
4871
  var import_ioredis = __toESM(require("ioredis"), 1);
4812
- var import_bullmq6 = require("bullmq");
4872
+ var import_bullmq5 = require("bullmq");
4813
4873
 
4814
4874
  // src/registry/utils.ts
4815
- var import_bullmq5 = require("bullmq");
4816
4875
  var bullmq = {
4817
- validate: (bullmqJob) => {
4818
- if (!bullmqJob.data) {
4819
- throw new Error(`Missing job data for job ${bullmqJob.id}.`);
4820
- }
4821
- if (!bullmqJob.data.type) {
4822
- throw new Error(`Missing property "type" in data for job ${bullmqJob.id}.`);
4876
+ validate: (id, data) => {
4877
+ if (!data) {
4878
+ throw new Error(`Missing job data for job ${id}.`);
4823
4879
  }
4824
- if (!bullmqJob.data.inputs) {
4825
- throw new Error(`Missing property "inputs" in data for job ${bullmqJob.id}.`);
4880
+ if (!data.type) {
4881
+ throw new Error(`Missing property "type" in data for job ${id}.`);
4826
4882
  }
4827
- if (bullmqJob.data.type !== "embedder" && bullmqJob.data.type !== "workflow") {
4828
- throw new Error(`Property "type" in data for job ${bullmqJob.id} must be of value "embedder" or "workflow".`);
4883
+ if (!data.inputs) {
4884
+ throw new Error(`Missing property "inputs" in data for job ${id}.`);
4829
4885
  }
4830
- if (!bullmqJob.data.workflow && !bullmqJob.data.embedder) {
4831
- throw new Error(`Property "backend" in data for job ${bullmqJob.id} missing. Job data: ${JSON.stringify(bullmqJob)}`);
4886
+ if (data.type !== "embedder" && data.type !== "workflow") {
4887
+ throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
4832
4888
  }
4833
- },
4834
- process: {
4835
- workflow: async (bullmqJob, exuluJob, workflow, logsDir) => {
4836
- if (!workflow) {
4837
- throw new Error(`Workflow function with id: ${bullmqJob.data.backend} not found in registry.`);
4838
- }
4839
- console.log("[EXULU] starting workflow with job inputs.", bullmqJob.data.inputs);
4840
- const logger = new ExuluLogger(exuluJob, logsDir);
4841
- const output = await workflow.start({
4842
- job: exuluJob,
4843
- inputs: bullmqJob.data.inputs,
4844
- user: bullmqJob.data.user,
4845
- logger,
4846
- session: bullmqJob.data.session,
4847
- agent: bullmqJob.data.agent,
4848
- label: bullmqJob.data.label
4849
- });
4850
- await logger.write(`Workflow completed. ${JSON.stringify(output)}`, "INFO");
4851
- return output;
4889
+ if (!data.workflow && !data.embedder) {
4890
+ throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
4852
4891
  }
4853
4892
  }
4854
4893
  };
@@ -4856,9 +4895,10 @@ var bullmq = {
4856
4895
  // src/registry/workers.ts
4857
4896
  var fs3 = __toESM(require("fs"), 1);
4858
4897
  var import_path = __toESM(require("path"), 1);
4898
+ var import_api3 = require("@opentelemetry/api");
4859
4899
  var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
4860
4900
  var redisConnection;
4861
- var createWorkers = async (queues2, contexts, _logsDir) => {
4901
+ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
4862
4902
  if (!redisServer.host || !redisServer.port) {
4863
4903
  console.error("[EXULU] you are trying to start worker, but no redis server is configured in the environment.");
4864
4904
  throw new Error("No redis server configured in the environment, so cannot start worker.");
@@ -4872,87 +4912,33 @@ var createWorkers = async (queues2, contexts, _logsDir) => {
4872
4912
  const logsDir = _logsDir || defaultLogsDir;
4873
4913
  const workers = queues2.map((queue) => {
4874
4914
  console.log(`[EXULU] creating worker for queue ${queue}.`);
4875
- const worker = new import_bullmq6.Worker(
4915
+ const worker = new import_bullmq5.Worker(
4876
4916
  `${queue}`,
4877
4917
  async (bullmqJob) => {
4918
+ const logger2 = new ExuluLogger(bullmqJob, logsDir);
4878
4919
  const { db: db3 } = await postgresClient();
4879
4920
  try {
4880
- bullmq.validate(bullmqJob);
4881
- if (bullmqJob.data.type === "embedder") {
4882
- if (!bullmqJob.data.updater) {
4883
- throw new Error("No updater set for embedder job.");
4884
- }
4885
- const context = contexts.find((context2) => context2.id === bullmqJob.data.context);
4921
+ const data = bullmqJob.data;
4922
+ bullmq.validate(bullmqJob.id, data);
4923
+ if (data.type === "embedder") {
4924
+ const context = contexts.find((context2) => context2.id === data.context);
4886
4925
  if (!context) {
4887
- throw new Error(`Context ${bullmqJob.data.context} not found in the registry.`);
4926
+ throw new Error(`Context ${data.context} not found in the registry.`);
4888
4927
  }
4889
- if (!bullmqJob.data.embedder) {
4928
+ if (!data.embedder) {
4890
4929
  throw new Error(`No embedder set for embedder job.`);
4891
4930
  }
4892
- const embedder = contexts.find((context2) => context2.embedder?.id === bullmqJob.data.embedder);
4931
+ const embedder = contexts.find((context2) => context2.embedder?.id === data.embedder);
4893
4932
  if (!embedder) {
4894
- throw new Error(`Embedder ${bullmqJob.data.embedder} not found in the registry.`);
4895
- }
4896
- if (!bullmqJob.data.source) {
4897
- throw new Error("No source set for embedder job.");
4898
- }
4899
- const source = context.sources.get(bullmqJob.data.source);
4900
- if (!source) {
4901
- throw new Error(`Source ${bullmqJob.data.source} not found in the registry.`);
4902
- }
4903
- if (!bullmqJob.data.updater) {
4904
- throw new Error("No updater set for embedder job.");
4933
+ throw new Error(`Embedder ${data.embedder} not found in the registry.`);
4905
4934
  }
4906
- const updater = source.updaters.find((updater2) => updater2.id === bullmqJob.data.updater);
4907
- if (!updater) {
4908
- throw new Error(`Updater ${bullmqJob.data.updater} not found in the registry.`);
4909
- }
4910
- if (!bullmqJob.data.documents) {
4911
- throw new Error("No input documents set for embedder job.");
4912
- }
4913
- if (!Array.isArray(bullmqJob.data.documents)) {
4914
- throw new Error("Input documents must be an array.");
4915
- }
4916
- const result = await embedder.upsert(bullmqJob.data.context, bullmqJob.data.documents, {
4917
- label: context.name,
4918
- trigger: bullmqJob.data.trigger || "unknown"
4935
+ const result = await context.createAndUpsertEmbeddings(data.inputs, data.user, {
4936
+ label: embedder.name,
4937
+ trigger: data.trigger
4919
4938
  });
4920
- const mongoRecord = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
4921
- if (!mongoRecord) {
4922
- throw new Error("Job not found in the database.");
4923
- }
4924
- const finishedAt = /* @__PURE__ */ new Date();
4925
- const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
4926
- await db3.from("jobs").where({ redis: bullmqJob.id }).update({
4927
- status: "completed",
4928
- finishedAt,
4929
- duration,
4930
- result: JSON.stringify(result)
4931
- });
4932
- await db3.from((void 0).getTableName()).where({ id: result[0].id }).update({
4933
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
4934
- }).returning("id");
4935
4939
  return result;
4936
4940
  }
4937
4941
  if (bullmqJob.data.type === "workflow") {
4938
- const workflow = workflows.find((workflow2) => workflow2.id === bullmqJob.data.workflow);
4939
- if (!workflow) {
4940
- throw new Error(`Workflow ${bullmqJob.data.workflow} not found in the registry.`);
4941
- }
4942
- const exuluJob = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
4943
- if (!exuluJob) {
4944
- throw new Error("Job not found in the database.");
4945
- }
4946
- const result = await bullmq.process.workflow(bullmqJob, exuluJob, workflow, logsDir);
4947
- const finishedAt = /* @__PURE__ */ new Date();
4948
- const duration = (finishedAt.getTime() - new Date(exuluJob.createdAt).getTime()) / 1e3;
4949
- await db3.from("jobs").where({ redis: bullmqJob.id }).update({
4950
- status: "completed",
4951
- finishedAt,
4952
- duration,
4953
- result: JSON.stringify(result)
4954
- });
4955
- return result;
4956
4942
  }
4957
4943
  } catch (error) {
4958
4944
  await db3.from("jobs").where({ redis: bullmqJob.id }).update({
@@ -4984,7 +4970,7 @@ var createWorkers = async (queues2, contexts, _logsDir) => {
4984
4970
  return workers;
4985
4971
  };
4986
4972
  var createLogsCleanerWorker = (logsDir) => {
4987
- const logsCleaner = new import_bullmq6.Worker(
4973
+ const logsCleaner = new import_bullmq5.Worker(
4988
4974
  global_queues.logs_cleaner,
4989
4975
  async (job) => {
4990
4976
  console.log(`[EXULU] recurring job ${job.id}.`);
@@ -5022,6 +5008,7 @@ var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamable
5022
5008
  var import_types = require("@modelcontextprotocol/sdk/types.js");
5023
5009
  var import_zod3 = require("zod");
5024
5010
  var import_express6 = require("express");
5011
+ var import_api4 = require("@opentelemetry/api");
5025
5012
  var SESSION_ID_HEADER = "mcp-session-id";
5026
5013
  var ExuluMCP = class {
5027
5014
  server;
@@ -5029,7 +5016,7 @@ var ExuluMCP = class {
5029
5016
  express;
5030
5017
  constructor() {
5031
5018
  }
5032
- create = async ({ express: express3, contexts, agents, config, tools }) => {
5019
+ create = async ({ express: express3, contexts, agents, config, tools, tracer, logger }) => {
5033
5020
  this.express = express3;
5034
5021
  if (!this.server) {
5035
5022
  console.log("[EXULU] Creating MCP server.");
@@ -5207,6 +5194,38 @@ var defaultAgent = new ExuluAgent({
5207
5194
  }
5208
5195
  });
5209
5196
 
5197
+ // src/registry/index.ts
5198
+ var import_api5 = require("@opentelemetry/api");
5199
+
5200
+ // src/registry/logger.ts
5201
+ var import_winston_transport = require("@opentelemetry/winston-transport");
5202
+ var import_winston = __toESM(require("winston"), 1);
5203
+ var createLogger = ({
5204
+ enableOtel
5205
+ }) => {
5206
+ const logger = import_winston.default.createLogger({
5207
+ level: "debug",
5208
+ format: import_winston.default.format.combine(
5209
+ import_winston.default.format.timestamp(),
5210
+ import_winston.default.format.errors({
5211
+ stack: true
5212
+ }),
5213
+ import_winston.default.format.metadata(),
5214
+ import_winston.default.format.json()
5215
+ ),
5216
+ defaultMeta: {
5217
+ service: "Test-Exulu",
5218
+ environment: process.env.NODE_ENV || "development"
5219
+ },
5220
+ transports: [
5221
+ new import_winston.default.transports.Console(),
5222
+ ...enableOtel ? [new import_winston_transport.OpenTelemetryTransportV3()] : []
5223
+ ]
5224
+ });
5225
+ return logger;
5226
+ };
5227
+ var logger_default = createLogger;
5228
+
5210
5229
  // src/registry/index.ts
5211
5230
  var ExuluApp = class {
5212
5231
  _agents = [];
@@ -5273,10 +5292,20 @@ var ExuluApp = class {
5273
5292
  bullmq = {
5274
5293
  workers: {
5275
5294
  create: async () => {
5295
+ let tracer;
5296
+ if (this._config?.telemetry?.enabled) {
5297
+ console.log("[EXULU] telemetry enabled");
5298
+ tracer = import_api5.trace.getTracer("exulu", "1.0.0");
5299
+ }
5300
+ const logger = logger_default({
5301
+ enableOtel: this._config?.workers?.telemetry?.enabled ?? false
5302
+ });
5276
5303
  return await createWorkers(
5277
5304
  this._queues,
5305
+ logger,
5278
5306
  Object.values(this._contexts ?? {}),
5279
- this._config?.workers?.logsDir
5307
+ this._config?.workers?.logsDir,
5308
+ tracer
5280
5309
  );
5281
5310
  }
5282
5311
  }
@@ -5288,11 +5317,22 @@ var ExuluApp = class {
5288
5317
  throw new Error("Express app not initialized");
5289
5318
  }
5290
5319
  const app = this._expressApp;
5320
+ let tracer;
5321
+ if (this._config?.telemetry?.enabled) {
5322
+ console.log("[EXULU] telemetry enabled");
5323
+ tracer = import_api5.trace.getTracer("exulu", "1.0.0");
5324
+ }
5325
+ const logger = logger_default({
5326
+ enableOtel: this._config?.telemetry?.enabled ?? false
5327
+ });
5291
5328
  await createExpressRoutes(
5292
5329
  app,
5330
+ logger,
5293
5331
  this._agents,
5294
5332
  this._tools,
5295
- Object.values(this._contexts ?? {})
5333
+ Object.values(this._contexts ?? {}),
5334
+ this._config,
5335
+ tracer
5296
5336
  );
5297
5337
  if (this._config?.MCP.enabled) {
5298
5338
  const mcp = new ExuluMCP();
@@ -5301,7 +5341,9 @@ var ExuluApp = class {
5301
5341
  contexts: this._contexts,
5302
5342
  agents: this._agents,
5303
5343
  config: this._config,
5304
- tools: this._tools
5344
+ tools: this._tools,
5345
+ tracer,
5346
+ logger
5305
5347
  });
5306
5348
  await mcp.connect();
5307
5349
  }
@@ -6714,6 +6756,50 @@ var execute = async () => {
6714
6756
  return;
6715
6757
  };
6716
6758
 
6759
+ // src/registry/otel.ts
6760
+ var import_process = __toESM(require("process"), 1);
6761
+ var import_sdk_node = require("@opentelemetry/sdk-node");
6762
+ var import_auto_instrumentations_node = require("@opentelemetry/auto-instrumentations-node");
6763
+ var import_exporter_trace_otlp_http = require("@opentelemetry/exporter-trace-otlp-http");
6764
+ var import_exporter_logs_otlp_http = require("@opentelemetry/exporter-logs-otlp-http");
6765
+ var import_resources = require("@opentelemetry/resources");
6766
+ var import_semantic_conventions = require("@opentelemetry/semantic-conventions");
6767
+ var import_sdk_logs = require("@opentelemetry/sdk-logs");
6768
+ var create = ({
6769
+ SIGNOZ_ACCESS_TOKEN,
6770
+ SIGNOZ_TRACES_URL,
6771
+ SIGNOZ_LOGS_URL
6772
+ }) => {
6773
+ console.log("SIGNOZ_ACCESS_TOKEN", SIGNOZ_ACCESS_TOKEN);
6774
+ console.log("SIGNOZ_TRACES_URL", SIGNOZ_TRACES_URL);
6775
+ console.log("SIGNOZ_LOGS_URL", SIGNOZ_LOGS_URL);
6776
+ const exporterOptions = {
6777
+ url: SIGNOZ_TRACES_URL,
6778
+ headers: {
6779
+ "signoz-access-token": SIGNOZ_ACCESS_TOKEN
6780
+ }
6781
+ };
6782
+ const traceExporter = new import_exporter_trace_otlp_http.OTLPTraceExporter(exporterOptions);
6783
+ const logExporter = new import_exporter_logs_otlp_http.OTLPLogExporter({
6784
+ url: SIGNOZ_LOGS_URL,
6785
+ headers: {
6786
+ "signoz-access-token": SIGNOZ_ACCESS_TOKEN
6787
+ }
6788
+ });
6789
+ const sdk = new import_sdk_node.NodeSDK({
6790
+ traceExporter,
6791
+ logRecordProcessors: [new import_sdk_logs.BatchLogRecordProcessor(logExporter)],
6792
+ instrumentations: [(0, import_auto_instrumentations_node.getNodeAutoInstrumentations)()],
6793
+ resource: (0, import_resources.resourceFromAttributes)({
6794
+ [import_semantic_conventions.ATTR_SERVICE_NAME]: "Test-Exulu"
6795
+ })
6796
+ });
6797
+ import_process.default.on("SIGTERM", () => {
6798
+ sdk.shutdown().then(() => console.log("Tracing terminated")).catch((error) => console.log("Error terminating tracing", error)).finally(() => import_process.default.exit(0));
6799
+ });
6800
+ return sdk;
6801
+ };
6802
+
6717
6803
  // types/enums/jobs.ts
6718
6804
  var JOB_STATUS_ENUM = {
6719
6805
  completed: "completed",
@@ -6732,6 +6818,19 @@ var ExuluJobs = {
6732
6818
  validate: validateJob
6733
6819
  }
6734
6820
  };
6821
+ var ExuluOtel = {
6822
+ create: ({
6823
+ SIGNOZ_ACCESS_TOKEN,
6824
+ SIGNOZ_TRACES_URL,
6825
+ SIGNOZ_LOGS_URL
6826
+ }) => {
6827
+ return create({
6828
+ SIGNOZ_ACCESS_TOKEN,
6829
+ SIGNOZ_TRACES_URL,
6830
+ SIGNOZ_LOGS_URL
6831
+ });
6832
+ }
6833
+ };
6735
6834
  var db2 = {
6736
6835
  init: async () => {
6737
6836
  await execute();
@@ -6764,6 +6863,7 @@ var ExuluChunkers = {
6764
6863
  ExuluEval,
6765
6864
  ExuluJobs,
6766
6865
  ExuluLogger,
6866
+ ExuluOtel,
6767
6867
  ExuluQueues,
6768
6868
  ExuluSource,
6769
6869
  ExuluTool,