@exulu/backend 1.16.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, "_");
@@ -602,6 +564,9 @@ var ExuluAgent = class {
602
564
  if (prompt && message) {
603
565
  throw new Error("Message and prompt cannot be provided at the same time.");
604
566
  }
567
+ if (!prompt && !message) {
568
+ throw new Error("Prompt or message is required for generating.");
569
+ }
605
570
  const model = this.model.create({
606
571
  apiKey: providerApiKey
607
572
  });
@@ -621,28 +586,52 @@ var ExuluAgent = class {
621
586
  }
622
587
  console.log("[EXULU] Model provider key", providerApiKey);
623
588
  console.log("[EXULU] Tool configs", toolConfigs);
624
- const { text } = await generateText({
625
- model,
626
- // Should be a LanguageModelV1
627
- 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.",
628
- messages: messages ? convertToModelMessages(messages) : void 0,
629
- prompt,
630
- maxRetries: 2,
631
- tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
632
- stopWhen: [stepCountIs(5)]
633
- });
634
- if (statistics) {
635
- await updateStatistic({
636
- name: "count",
637
- label: statistics.label,
638
- type: STATISTICS_TYPE_ENUM.AGENT_RUN,
639
- trigger: statistics.trigger,
640
- count: 1,
641
- user,
642
- role
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)]
643
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;
644
634
  }
645
- return text;
646
635
  };
647
636
  generateStream = async ({ express: express3, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
648
637
  if (!this.model) {
@@ -674,7 +663,7 @@ var ExuluAgent = class {
674
663
  const result = streamText({
675
664
  model,
676
665
  // Should be a LanguageModelV1
677
- messages: messages ? convertToModelMessages(messages) : void 0,
666
+ messages: convertToModelMessages(messages),
678
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.",
679
668
  maxRetries: 2,
680
669
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
@@ -1036,8 +1025,36 @@ var ExuluContext = class {
1036
1025
  const tableExists = await db3.schema.hasTable(this.getTableName());
1037
1026
  return tableExists;
1038
1027
  };
1039
- async updateItem(user, id, item, role) {
1040
- 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) {
1041
1058
  throw new Error("Id is required for updating an item.");
1042
1059
  }
1043
1060
  const { db: db3 } = await postgresClient();
@@ -1054,51 +1071,37 @@ var ExuluContext = class {
1054
1071
  delete item.created_at;
1055
1072
  delete item.upsert;
1056
1073
  item.updated_at = db3.fn.now();
1057
- 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");
1058
1075
  if (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always") {
1059
1076
  if (this.embedder.queue?.name) {
1060
1077
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1061
1078
  const job = await bullmqDecorator({
1062
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1079
+ label: `${this.embedder.name}`,
1063
1080
  embedder: this.embedder.id,
1064
- type: "embedder",
1081
+ context: this.id,
1065
1082
  inputs: item,
1083
+ item: item.id,
1066
1084
  queue: this.embedder.queue,
1067
- user
1085
+ user,
1086
+ role,
1087
+ trigger: trigger || "agent"
1068
1088
  });
1069
1089
  return {
1070
1090
  id: result[0].id,
1071
1091
  job: job.id
1072
1092
  };
1073
1093
  }
1074
- const { id: source, chunks } = await this.embedder.generateFromDocument({
1075
- ...item,
1076
- id
1077
- }, {
1078
- label: this.name,
1079
- trigger: "agent"
1080
- }, user, role);
1081
- const exists = await db3.schema.hasTable(this.getChunksTableName());
1082
- if (!exists) {
1083
- await this.createChunksTable();
1084
- }
1085
- await db3.from(this.getChunksTableName()).where({ source }).delete();
1086
- await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1087
- source,
1088
- content: chunk.content,
1089
- chunk_index: chunk.index,
1090
- embedding: pgvector2.toSql(chunk.vector)
1091
- })));
1092
- await db3.from(this.getTableName()).where({ id }).update({
1093
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1094
- }).returning("id");
1094
+ await this.createAndUpsertEmbeddings(item, user, {
1095
+ label: this.embedder.name,
1096
+ trigger: trigger || "agent"
1097
+ }, role);
1095
1098
  }
1096
1099
  return {
1097
1100
  id: result[0].id,
1098
1101
  job: void 0
1099
1102
  };
1100
1103
  }
1101
- async insertItem(user, item, upsert = false, role) {
1104
+ async insertItem(user, item, upsert = false, role, trigger) {
1102
1105
  if (!item.name) {
1103
1106
  throw new Error("Name field is required.");
1104
1107
  }
@@ -1109,14 +1112,20 @@ var ExuluContext = class {
1109
1112
  throw new Error("Item with external id " + item.external_id + " already exists.");
1110
1113
  }
1111
1114
  if (existingItem && upsert) {
1112
- await this.updateItem(user, existingItem.id, item, role);
1115
+ await this.updateItem(user, {
1116
+ ...item,
1117
+ id: existingItem.id
1118
+ }, role, trigger);
1113
1119
  return existingItem.id;
1114
1120
  }
1115
1121
  }
1116
1122
  if (upsert && item.id) {
1117
1123
  const existingItem = await db3.from(this.getTableName()).where({ id: item.id }).first();
1118
1124
  if (existingItem && upsert) {
1119
- await this.updateItem(user, existingItem.id, item, role);
1125
+ await this.updateItem(user, {
1126
+ ...item,
1127
+ id: existingItem.id
1128
+ }, role, trigger);
1120
1129
  return existingItem.id;
1121
1130
  }
1122
1131
  }
@@ -1141,12 +1150,15 @@ var ExuluContext = class {
1141
1150
  if (this.embedder.queue?.name) {
1142
1151
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1143
1152
  const job = await bullmqDecorator({
1144
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1153
+ label: `${this.embedder.name}`,
1145
1154
  embedder: this.embedder.id,
1146
- type: "embedder",
1155
+ context: this.id,
1147
1156
  inputs: item,
1157
+ item: item.id,
1148
1158
  queue: this.embedder.queue,
1149
- user
1159
+ user,
1160
+ role,
1161
+ trigger: trigger || "agent"
1150
1162
  });
1151
1163
  return {
1152
1164
  id: result[0].id,
@@ -1154,27 +1166,13 @@ var ExuluContext = class {
1154
1166
  };
1155
1167
  }
1156
1168
  console.log("[EXULU] embedder is not in queue mode, calculating vectors directly.");
1157
- const { id: source, chunks } = await this.embedder.generateFromDocument({
1169
+ await this.createAndUpsertEmbeddings({
1158
1170
  ...item,
1159
1171
  id: result[0].id
1160
- }, {
1161
- label: this.name,
1162
- trigger: "agent"
1163
- }, user, role);
1164
- const exists = await db3.schema.hasTable(this.getChunksTableName());
1165
- if (!exists) {
1166
- await this.createChunksTable();
1167
- }
1168
- console.log("[EXULU] Inserting chunks.");
1169
- await db3.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
1170
- source,
1171
- content: chunk.content,
1172
- chunk_index: chunk.index,
1173
- embedding: pgvector2.toSql(chunk.vector)
1174
- })));
1175
- await db3.from(this.getTableName()).where({ id: result[0].id }).update({
1176
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1177
- }).returning("id");
1172
+ }, user, {
1173
+ label: this.embedder.name,
1174
+ trigger: trigger || "agent"
1175
+ }, role);
1178
1176
  }
1179
1177
  return {
1180
1178
  id: result[0].id,
@@ -1875,6 +1873,7 @@ import { zerialize } from "zodex";
1875
1873
 
1876
1874
  // src/bullmq/queues.ts
1877
1875
  import { Queue as Queue3 } from "bullmq";
1876
+ import { BullMQOtel } from "bullmq-otel";
1878
1877
  var ExuluQueues = class {
1879
1878
  queues;
1880
1879
  constructor() {
@@ -1892,7 +1891,13 @@ var ExuluQueues = class {
1892
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() ).`);
1893
1892
  throw new Error(`[EXULU] no redis server configured.`);
1894
1893
  }
1895
- 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
+ );
1896
1901
  this.queues.push(newQueue);
1897
1902
  return newQueue;
1898
1903
  }
@@ -3679,6 +3684,7 @@ Intelligence Management Platform
3679
3684
  import OpenAI from "openai";
3680
3685
  import fs2 from "fs";
3681
3686
  import { randomUUID } from "crypto";
3687
+ import "@opentelemetry/api";
3682
3688
  var REQUEST_SIZE_LIMIT = "50mb";
3683
3689
  var global_queues = {
3684
3690
  logs_cleaner: "logs-cleaner"
@@ -3717,7 +3723,7 @@ var createRecurringJobs = async () => {
3717
3723
  console.table(recurringJobSchedulersLogs);
3718
3724
  return queue;
3719
3725
  };
3720
- var createExpressRoutes = async (app, agents, tools, contexts) => {
3726
+ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
3721
3727
  const routeLogs = [];
3722
3728
  var corsOptions = {
3723
3729
  origin: "*",
@@ -3811,6 +3817,16 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3811
3817
  express.json({ limit: REQUEST_SIZE_LIMIT }),
3812
3818
  expressMiddleware(server, {
3813
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("================");
3814
3830
  const authenticationResult = await requestValidators.authenticate(req);
3815
3831
  if (!authenticationResult.user?.id) {
3816
3832
  throw new Error(authenticationResult.message);
@@ -4197,7 +4213,15 @@ Mood: friendly and intelligent.
4197
4213
  if (!exists) {
4198
4214
  await context.createItemsTable();
4199
4215
  }
4200
- const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body, authenticationResult.user.role?.id);
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
+ );
4201
4225
  res.status(200).json({
4202
4226
  message: "Item updated successfully.",
4203
4227
  id: result
@@ -4244,7 +4268,13 @@ Mood: friendly and intelligent.
4244
4268
  await context.createItemsTable();
4245
4269
  }
4246
4270
  console.log("[EXULU] inserting item", req.body);
4247
- const result = await context.insertItem(authenticationResult.user.id, req.body, !!req.body.upsert, authenticationResult.user.role?.id);
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
+ );
4248
4278
  console.log("[EXULU] result", result);
4249
4279
  res.status(200).json({
4250
4280
  message: "Item created successfully.",
@@ -4593,7 +4623,7 @@ Mood: friendly and intelligent.
4593
4623
  return;
4594
4624
  }
4595
4625
  console.log("[EXULU] agent tools", agentInstance.tools);
4596
- 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);
4597
4627
  console.log("[EXULU] enabled tools", enabledTools);
4598
4628
  const variableName = agentInstance.providerApiKey;
4599
4629
  const variable = await db3.from("variables").where({ name: variableName }).first();
@@ -4799,43 +4829,22 @@ import IORedis from "ioredis";
4799
4829
  import { Worker } from "bullmq";
4800
4830
 
4801
4831
  // src/registry/utils.ts
4802
- import "bullmq";
4803
4832
  var bullmq = {
4804
- validate: (bullmqJob) => {
4805
- if (!bullmqJob.data) {
4806
- throw new Error(`Missing job data for job ${bullmqJob.id}.`);
4833
+ validate: (id, data) => {
4834
+ if (!data) {
4835
+ throw new Error(`Missing job data for job ${id}.`);
4807
4836
  }
4808
- if (!bullmqJob.data.type) {
4809
- throw new Error(`Missing property "type" in data for job ${bullmqJob.id}.`);
4837
+ if (!data.type) {
4838
+ throw new Error(`Missing property "type" in data for job ${id}.`);
4810
4839
  }
4811
- if (!bullmqJob.data.inputs) {
4812
- throw new Error(`Missing property "inputs" in data for job ${bullmqJob.id}.`);
4840
+ if (!data.inputs) {
4841
+ throw new Error(`Missing property "inputs" in data for job ${id}.`);
4813
4842
  }
4814
- if (bullmqJob.data.type !== "embedder" && bullmqJob.data.type !== "workflow") {
4815
- throw new Error(`Property "type" in data for job ${bullmqJob.id} must be of value "embedder" or "workflow".`);
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".`);
4816
4845
  }
4817
- if (!bullmqJob.data.workflow && !bullmqJob.data.embedder) {
4818
- throw new Error(`Property "backend" in data for job ${bullmqJob.id} missing. Job data: ${JSON.stringify(bullmqJob)}`);
4819
- }
4820
- },
4821
- process: {
4822
- workflow: async (bullmqJob, exuluJob, workflow, logsDir) => {
4823
- if (!workflow) {
4824
- throw new Error(`Workflow function with id: ${bullmqJob.data.backend} not found in registry.`);
4825
- }
4826
- console.log("[EXULU] starting workflow with job inputs.", bullmqJob.data.inputs);
4827
- const logger = new ExuluLogger(exuluJob, logsDir);
4828
- const output = await workflow.start({
4829
- job: exuluJob,
4830
- inputs: bullmqJob.data.inputs,
4831
- user: bullmqJob.data.user,
4832
- logger,
4833
- session: bullmqJob.data.session,
4834
- agent: bullmqJob.data.agent,
4835
- label: bullmqJob.data.label
4836
- });
4837
- await logger.write(`Workflow completed. ${JSON.stringify(output)}`, "INFO");
4838
- return output;
4846
+ if (!data.workflow && !data.embedder) {
4847
+ throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
4839
4848
  }
4840
4849
  }
4841
4850
  };
@@ -4843,9 +4852,10 @@ var bullmq = {
4843
4852
  // src/registry/workers.ts
4844
4853
  import * as fs3 from "fs";
4845
4854
  import path2 from "path";
4855
+ import "@opentelemetry/api";
4846
4856
  var defaultLogsDir = path2.join(process.cwd(), "logs");
4847
4857
  var redisConnection;
4848
- var createWorkers = async (queues2, contexts, _logsDir) => {
4858
+ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
4849
4859
  if (!redisServer.host || !redisServer.port) {
4850
4860
  console.error("[EXULU] you are trying to start worker, but no redis server is configured in the environment.");
4851
4861
  throw new Error("No redis server configured in the environment, so cannot start worker.");
@@ -4862,84 +4872,30 @@ var createWorkers = async (queues2, contexts, _logsDir) => {
4862
4872
  const worker = new Worker(
4863
4873
  `${queue}`,
4864
4874
  async (bullmqJob) => {
4875
+ const logger2 = new ExuluLogger(bullmqJob, logsDir);
4865
4876
  const { db: db3 } = await postgresClient();
4866
4877
  try {
4867
- bullmq.validate(bullmqJob);
4868
- if (bullmqJob.data.type === "embedder") {
4869
- if (!bullmqJob.data.updater) {
4870
- throw new Error("No updater set for embedder job.");
4871
- }
4872
- 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);
4873
4882
  if (!context) {
4874
- throw new Error(`Context ${bullmqJob.data.context} not found in the registry.`);
4883
+ throw new Error(`Context ${data.context} not found in the registry.`);
4875
4884
  }
4876
- if (!bullmqJob.data.embedder) {
4885
+ if (!data.embedder) {
4877
4886
  throw new Error(`No embedder set for embedder job.`);
4878
4887
  }
4879
- const embedder = contexts.find((context2) => context2.embedder?.id === bullmqJob.data.embedder);
4888
+ const embedder = contexts.find((context2) => context2.embedder?.id === data.embedder);
4880
4889
  if (!embedder) {
4881
- throw new Error(`Embedder ${bullmqJob.data.embedder} not found in the registry.`);
4882
- }
4883
- if (!bullmqJob.data.source) {
4884
- throw new Error("No source set for embedder job.");
4885
- }
4886
- const source = context.sources.get(bullmqJob.data.source);
4887
- if (!source) {
4888
- throw new Error(`Source ${bullmqJob.data.source} not found in the registry.`);
4889
- }
4890
- if (!bullmqJob.data.updater) {
4891
- throw new Error("No updater set for embedder job.");
4892
- }
4893
- const updater = source.updaters.find((updater2) => updater2.id === bullmqJob.data.updater);
4894
- if (!updater) {
4895
- throw new Error(`Updater ${bullmqJob.data.updater} not found in the registry.`);
4896
- }
4897
- if (!bullmqJob.data.documents) {
4898
- throw new Error("No input documents set for embedder job.");
4899
- }
4900
- if (!Array.isArray(bullmqJob.data.documents)) {
4901
- throw new Error("Input documents must be an array.");
4890
+ throw new Error(`Embedder ${data.embedder} not found in the registry.`);
4902
4891
  }
4903
- const result = await embedder.upsert(bullmqJob.data.context, bullmqJob.data.documents, {
4904
- label: context.name,
4905
- trigger: bullmqJob.data.trigger || "unknown"
4892
+ const result = await context.createAndUpsertEmbeddings(data.inputs, data.user, {
4893
+ label: embedder.name,
4894
+ trigger: data.trigger
4906
4895
  });
4907
- const mongoRecord = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
4908
- if (!mongoRecord) {
4909
- throw new Error("Job not found in the database.");
4910
- }
4911
- const finishedAt = /* @__PURE__ */ new Date();
4912
- const duration = (finishedAt.getTime() - new Date(mongoRecord.createdAt).getTime()) / 1e3;
4913
- await db3.from("jobs").where({ redis: bullmqJob.id }).update({
4914
- status: "completed",
4915
- finishedAt,
4916
- duration,
4917
- result: JSON.stringify(result)
4918
- });
4919
- await db3.from((void 0).getTableName()).where({ id: result[0].id }).update({
4920
- embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
4921
- }).returning("id");
4922
4896
  return result;
4923
4897
  }
4924
4898
  if (bullmqJob.data.type === "workflow") {
4925
- const workflow = workflows.find((workflow2) => workflow2.id === bullmqJob.data.workflow);
4926
- if (!workflow) {
4927
- throw new Error(`Workflow ${bullmqJob.data.workflow} not found in the registry.`);
4928
- }
4929
- const exuluJob = await db3.from("jobs").where({ redis: bullmqJob.id }).first();
4930
- if (!exuluJob) {
4931
- throw new Error("Job not found in the database.");
4932
- }
4933
- const result = await bullmq.process.workflow(bullmqJob, exuluJob, workflow, logsDir);
4934
- const finishedAt = /* @__PURE__ */ new Date();
4935
- const duration = (finishedAt.getTime() - new Date(exuluJob.createdAt).getTime()) / 1e3;
4936
- await db3.from("jobs").where({ redis: bullmqJob.id }).update({
4937
- status: "completed",
4938
- finishedAt,
4939
- duration,
4940
- result: JSON.stringify(result)
4941
- });
4942
- return result;
4943
4899
  }
4944
4900
  } catch (error) {
4945
4901
  await db3.from("jobs").where({ redis: bullmqJob.id }).update({
@@ -5009,6 +4965,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
5009
4965
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5010
4966
  import { z as z2 } from "zod";
5011
4967
  import "express";
4968
+ import "@opentelemetry/api";
5012
4969
  var SESSION_ID_HEADER = "mcp-session-id";
5013
4970
  var ExuluMCP = class {
5014
4971
  server;
@@ -5016,7 +4973,7 @@ var ExuluMCP = class {
5016
4973
  express;
5017
4974
  constructor() {
5018
4975
  }
5019
- create = async ({ express: express3, contexts, agents, config, tools }) => {
4976
+ create = async ({ express: express3, contexts, agents, config, tools, tracer, logger }) => {
5020
4977
  this.express = express3;
5021
4978
  if (!this.server) {
5022
4979
  console.log("[EXULU] Creating MCP server.");
@@ -5194,6 +5151,38 @@ var defaultAgent = new ExuluAgent({
5194
5151
  }
5195
5152
  });
5196
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
+
5197
5186
  // src/registry/index.ts
5198
5187
  var ExuluApp = class {
5199
5188
  _agents = [];
@@ -5260,10 +5249,20 @@ var ExuluApp = class {
5260
5249
  bullmq = {
5261
5250
  workers: {
5262
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
+ });
5263
5260
  return await createWorkers(
5264
5261
  this._queues,
5262
+ logger,
5265
5263
  Object.values(this._contexts ?? {}),
5266
- this._config?.workers?.logsDir
5264
+ this._config?.workers?.logsDir,
5265
+ tracer
5267
5266
  );
5268
5267
  }
5269
5268
  }
@@ -5275,11 +5274,22 @@ var ExuluApp = class {
5275
5274
  throw new Error("Express app not initialized");
5276
5275
  }
5277
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
+ });
5278
5285
  await createExpressRoutes(
5279
5286
  app,
5287
+ logger,
5280
5288
  this._agents,
5281
5289
  this._tools,
5282
- Object.values(this._contexts ?? {})
5290
+ Object.values(this._contexts ?? {}),
5291
+ this._config,
5292
+ tracer
5283
5293
  );
5284
5294
  if (this._config?.MCP.enabled) {
5285
5295
  const mcp = new ExuluMCP();
@@ -5288,7 +5298,9 @@ var ExuluApp = class {
5288
5298
  contexts: this._contexts,
5289
5299
  agents: this._agents,
5290
5300
  config: this._config,
5291
- tools: this._tools
5301
+ tools: this._tools,
5302
+ tracer,
5303
+ logger
5292
5304
  });
5293
5305
  await mcp.connect();
5294
5306
  }
@@ -6701,6 +6713,50 @@ var execute = async () => {
6701
6713
  return;
6702
6714
  };
6703
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
+
6704
6760
  // types/enums/jobs.ts
6705
6761
  var JOB_STATUS_ENUM = {
6706
6762
  completed: "completed",
@@ -6719,6 +6775,19 @@ var ExuluJobs = {
6719
6775
  validate: validateJob
6720
6776
  }
6721
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
+ };
6722
6791
  var db2 = {
6723
6792
  init: async () => {
6724
6793
  await execute();
@@ -6750,6 +6819,7 @@ export {
6750
6819
  ExuluEval,
6751
6820
  ExuluJobs,
6752
6821
  ExuluLogger,
6822
+ ExuluOtel,
6753
6823
  queues as ExuluQueues,
6754
6824
  ExuluSource,
6755
6825
  ExuluTool,