@exulu/backend 1.16.0 → 1.18.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
@@ -73,8 +73,8 @@ var validateJob = (job) => {
73
73
  import "zod";
74
74
  import "bullmq";
75
75
  import { z } from "zod";
76
- import * as fs from "fs";
77
- import * as path from "path";
76
+ import "fs";
77
+ import "path";
78
78
  import { convertToModelMessages, createIdGenerator, generateObject, generateText, streamText, tool, validateUIMessages, stepCountIs } from "ai";
79
79
 
80
80
  // types/enums/statistics.ts
@@ -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
  };
@@ -344,7 +305,7 @@ var mapType = (t, type, name, defaultValue, unique) => {
344
305
 
345
306
  // src/registry/utils/sanitize-name.ts
346
307
  var sanitizeName = (name) => {
347
- return name.toLowerCase().replace(/ /g, "_");
308
+ return name.toLowerCase().replace(/ /g, "_")?.trim();
348
309
  };
349
310
 
350
311
  // src/evals/utils/index.tsx
@@ -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, "_");
@@ -488,7 +450,7 @@ var convertToolsArrayToObject = (tools, configs, providerApiKey, user, role) =>
488
450
  };
489
451
  var hydrateVariables = async (tool2) => {
490
452
  const { db: db3 } = await postgresClient();
491
- const promises2 = tool2.config.map(async (toolConfig) => {
453
+ const promises = tool2.config.map(async (toolConfig) => {
492
454
  const variableName = toolConfig.variable;
493
455
  const variable = await db3.from("variables").where({ name: variableName }).first();
494
456
  if (!variable) {
@@ -502,7 +464,7 @@ var hydrateVariables = async (tool2) => {
502
464
  }
503
465
  toolConfig.value = value;
504
466
  });
505
- await Promise.all(promises2);
467
+ await Promise.all(promises);
506
468
  console.log("[EXULU] Variable values retrieved and added to tool config.");
507
469
  return tool2;
508
470
  };
@@ -559,10 +521,16 @@ var ExuluAgent = class {
559
521
  this.model = this.config?.model;
560
522
  }
561
523
  get providerName() {
524
+ if (!this.config?.model?.create) {
525
+ return "";
526
+ }
562
527
  const model = this.config?.model?.create({ apiKey: "" });
563
528
  return typeof model === "string" ? model : model?.provider || "";
564
529
  }
565
530
  get modelName() {
531
+ if (!this.config?.model?.create) {
532
+ return "";
533
+ }
566
534
  const model = this.config?.model?.create({ apiKey: "" });
567
535
  return typeof model === "string" ? model : model?.modelId || "";
568
536
  }
@@ -602,6 +570,9 @@ var ExuluAgent = class {
602
570
  if (prompt && message) {
603
571
  throw new Error("Message and prompt cannot be provided at the same time.");
604
572
  }
573
+ if (!prompt && !message) {
574
+ throw new Error("Prompt or message is required for generating.");
575
+ }
605
576
  const model = this.model.create({
606
577
  apiKey: providerApiKey
607
578
  });
@@ -621,28 +592,52 @@ var ExuluAgent = class {
621
592
  }
622
593
  console.log("[EXULU] Model provider key", providerApiKey);
623
594
  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
595
+ if (prompt) {
596
+ const { text } = await generateText({
597
+ model,
598
+ // Should be a LanguageModelV1
599
+ 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.",
600
+ prompt,
601
+ maxRetries: 2,
602
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
603
+ stopWhen: [stepCountIs(5)]
604
+ });
605
+ if (statistics) {
606
+ await updateStatistic({
607
+ name: "count",
608
+ label: statistics.label,
609
+ type: STATISTICS_TYPE_ENUM.AGENT_RUN,
610
+ trigger: statistics.trigger,
611
+ count: 1,
612
+ user,
613
+ role
614
+ });
615
+ }
616
+ return text;
617
+ }
618
+ if (messages) {
619
+ const { text } = await generateText({
620
+ model,
621
+ // Should be a LanguageModelV1
622
+ 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.",
623
+ messages: convertToModelMessages(messages),
624
+ maxRetries: 2,
625
+ tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
626
+ stopWhen: [stepCountIs(5)]
643
627
  });
628
+ if (statistics) {
629
+ await updateStatistic({
630
+ name: "count",
631
+ label: statistics.label,
632
+ type: STATISTICS_TYPE_ENUM.AGENT_RUN,
633
+ trigger: statistics.trigger,
634
+ count: 1,
635
+ user,
636
+ role
637
+ });
638
+ }
639
+ return text;
644
640
  }
645
- return text;
646
641
  };
647
642
  generateStream = async ({ express: express3, user, role, session, message, tools, statistics, toolConfigs, providerApiKey }) => {
648
643
  if (!this.model) {
@@ -674,7 +669,7 @@ var ExuluAgent = class {
674
669
  const result = streamText({
675
670
  model,
676
671
  // Should be a LanguageModelV1
677
- messages: messages ? convertToModelMessages(messages) : void 0,
672
+ messages: convertToModelMessages(messages),
678
673
  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
674
  maxRetries: 2,
680
675
  tools: convertToolsArrayToObject(tools, toolConfigs, providerApiKey, user, role),
@@ -722,7 +717,7 @@ var getAgentMessages = async ({ session, user, limit, page }) => {
722
717
  };
723
718
  var saveChat = async ({ session, user, messages }) => {
724
719
  const { db: db3 } = await postgresClient();
725
- const promises2 = messages.map((message) => {
720
+ const promises = messages.map((message) => {
726
721
  return db3.from("agent_messages").insert({
727
722
  session,
728
723
  user,
@@ -730,7 +725,7 @@ var saveChat = async ({ session, user, messages }) => {
730
725
  title: message.role === "user" ? "User" : "Assistant"
731
726
  });
732
727
  });
733
- await Promise.all(promises2);
728
+ await Promise.all(promises);
734
729
  };
735
730
  var ExuluEmbedder = class {
736
731
  id;
@@ -796,42 +791,6 @@ var ExuluEmbedder = class {
796
791
  return await this.generateEmbeddings(output);
797
792
  }
798
793
  };
799
- var ExuluLogger = class {
800
- logPath;
801
- job;
802
- constructor(job, logsDir) {
803
- this.job = job;
804
- if (logsDir && job) {
805
- if (!fs.existsSync(logsDir)) {
806
- fs.mkdirSync(logsDir, { recursive: true });
807
- }
808
- this.logPath = path.join(logsDir, `${job.id}_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.txt`);
809
- }
810
- }
811
- async write(message, level) {
812
- const logMessage = message.endsWith("\n") ? message : message + "\n";
813
- if (!this.logPath) {
814
- switch (level) {
815
- case "INFO":
816
- console.log(message);
817
- break;
818
- case "WARNING":
819
- console.warn(message);
820
- break;
821
- case "ERROR":
822
- console.error(message);
823
- break;
824
- }
825
- return;
826
- }
827
- try {
828
- await fs.promises.appendFile(this.logPath, `[EXULU][${level}] - ${(/* @__PURE__ */ new Date()).toISOString()}: ${logMessage}`);
829
- } catch (error) {
830
- console.error(`Error writing to log file ${this.job ? this.job.id : "unknown job"}:`, error);
831
- throw error;
832
- }
833
- }
834
- };
835
794
  var ExuluEval = class {
836
795
  name;
837
796
  description;
@@ -991,6 +950,12 @@ var ExuluTool = class {
991
950
  });
992
951
  }
993
952
  };
953
+ var getTableName = (name) => {
954
+ return sanitizeName(name) + "_items";
955
+ };
956
+ var getChunksTableName = (name) => {
957
+ return sanitizeName(name) + "_chunks";
958
+ };
994
959
  var ExuluContext = class {
995
960
  id;
996
961
  name;
@@ -1002,7 +967,6 @@ var ExuluContext = class {
1002
967
  queryRewriter;
1003
968
  resultReranker;
1004
969
  // todo typings
1005
- _sources = [];
1006
970
  configuration;
1007
971
  constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration }) {
1008
972
  this.id = id;
@@ -1015,138 +979,169 @@ var ExuluContext = class {
1015
979
  this.embedder = embedder;
1016
980
  this.active = active;
1017
981
  this.rateLimit = rateLimit;
1018
- this._sources = [];
1019
982
  this.queryRewriter = queryRewriter;
1020
983
  this.resultReranker = resultReranker;
1021
984
  }
1022
- deleteOne = async (id) => {
1023
- return {};
1024
- };
1025
985
  deleteAll = async () => {
1026
- return {};
1027
- };
1028
- getTableName = () => {
1029
- return sanitizeName(this.name) + "_items";
1030
- };
1031
- getChunksTableName = () => {
1032
- return sanitizeName(this.name) + "_chunks";
986
+ const { db: db3 } = await postgresClient();
987
+ await db3.from(getTableName(this.name)).delete();
988
+ await db3.from(getChunksTableName(this.name)).delete();
989
+ return {
990
+ count: 0,
991
+ results: []
992
+ };
1033
993
  };
1034
994
  tableExists = async () => {
1035
995
  const { db: db3 } = await postgresClient();
1036
- const tableExists = await db3.schema.hasTable(this.getTableName());
996
+ const tableExists = await db3.schema.hasTable(getTableName(this.name));
1037
997
  return tableExists;
1038
998
  };
1039
- async updateItem(user, id, item, role) {
1040
- if (!id) {
999
+ createAndUpsertEmbeddings = async (item, user, statistics, role) => {
1000
+ if (!this.embedder) {
1001
+ throw new Error("Embedder is not set for this context.");
1002
+ }
1003
+ const { db: db3 } = await postgresClient();
1004
+ const { id: source, chunks } = await this.embedder.generateFromDocument({
1005
+ ...item,
1006
+ id: item.id
1007
+ }, {
1008
+ label: statistics.label || this.name,
1009
+ trigger: statistics.trigger || "agent"
1010
+ }, user, role);
1011
+ const exists = await db3.schema.hasTable(getChunksTableName(this.name));
1012
+ if (!exists) {
1013
+ await this.createChunksTable();
1014
+ }
1015
+ await db3.from(getChunksTableName(this.name)).where({ source }).delete();
1016
+ await db3.from(getChunksTableName(this.name)).insert(chunks.map((chunk) => ({
1017
+ source,
1018
+ content: chunk.content,
1019
+ chunk_index: chunk.index,
1020
+ embedding: pgvector2.toSql(chunk.vector)
1021
+ })));
1022
+ await db3.from(getTableName(this.name)).where({ id: item.id }).update({
1023
+ embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
1024
+ }).returning("id");
1025
+ return {
1026
+ id: item.id,
1027
+ chunks: chunks?.length || 0
1028
+ };
1029
+ };
1030
+ async updateItem(user, item, role, trigger) {
1031
+ if (!item.id) {
1041
1032
  throw new Error("Id is required for updating an item.");
1042
1033
  }
1043
1034
  const { db: db3 } = await postgresClient();
1044
1035
  Object.keys(item).forEach((key) => {
1045
- if (key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textLength" || key === "upsert" || key === "archived") {
1036
+ if (key === "id" || key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textlength" || key === "upsert" || key === "archived") {
1046
1037
  return;
1047
1038
  }
1048
- const field = this.fields.find((field2) => field2.name === key);
1039
+ const field = this.fields.find((field2) => sanitizeName(field2.name) === sanitizeName(key));
1049
1040
  if (!field) {
1050
- throw new Error("Trying to uppdate value for field '" + key + "' that does not exist on the context fields definition. Available fields: " + this.fields.map((field2) => sanitizeName(field2.name)).join(", ") + " ,name, description, external_id");
1041
+ throw new Error("Trying to update value for field '" + key + "' that does not exist on the context fields definition. Available fields: " + this.fields.map((field2) => sanitizeName(field2.name)).join(", ") + " ,name, description, external_id");
1051
1042
  }
1052
1043
  });
1053
- delete item.id;
1054
- delete item.created_at;
1055
- delete item.upsert;
1056
- item.updated_at = db3.fn.now();
1057
- const result = await db3.from(this.getTableName()).where({ id }).update(item).returning("id");
1058
- if (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always") {
1044
+ const payloadWithSanitizedPropertyNames = {};
1045
+ Object.keys(item).forEach((key) => {
1046
+ payloadWithSanitizedPropertyNames[sanitizeName(key)] = item[key];
1047
+ });
1048
+ const id = payloadWithSanitizedPropertyNames.id;
1049
+ delete payloadWithSanitizedPropertyNames.id;
1050
+ delete payloadWithSanitizedPropertyNames.created_at;
1051
+ delete payloadWithSanitizedPropertyNames.upsert;
1052
+ payloadWithSanitizedPropertyNames.updated_at = db3.fn.now();
1053
+ const result = await db3.from(getTableName(this.name)).where({ id }).update(payloadWithSanitizedPropertyNames).returning("id");
1054
+ if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
1059
1055
  if (this.embedder.queue?.name) {
1060
1056
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1061
1057
  const job = await bullmqDecorator({
1062
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1058
+ label: `${this.embedder.name}`,
1063
1059
  embedder: this.embedder.id,
1064
- type: "embedder",
1060
+ context: this.id,
1065
1061
  inputs: item,
1062
+ item: item.id,
1066
1063
  queue: this.embedder.queue,
1067
- user
1064
+ user,
1065
+ role,
1066
+ trigger: trigger || "agent"
1068
1067
  });
1069
1068
  return {
1070
1069
  id: result[0].id,
1071
1070
  job: job.id
1072
1071
  };
1073
1072
  }
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");
1073
+ await this.createAndUpsertEmbeddings(item, user, {
1074
+ label: this.embedder.name,
1075
+ trigger: trigger || "agent"
1076
+ }, role);
1095
1077
  }
1096
1078
  return {
1097
1079
  id: result[0].id,
1098
1080
  job: void 0
1099
1081
  };
1100
1082
  }
1101
- async insertItem(user, item, upsert = false, role) {
1083
+ async insertItem(user, item, upsert = false, role, trigger) {
1102
1084
  if (!item.name) {
1103
1085
  throw new Error("Name field is required.");
1104
1086
  }
1105
1087
  const { db: db3 } = await postgresClient();
1106
1088
  if (item.external_id) {
1107
- const existingItem = await db3.from(this.getTableName()).where({ external_id: item.external_id }).first();
1089
+ const existingItem = await db3.from(getTableName(this.name)).where({ external_id: item.external_id }).first();
1108
1090
  if (existingItem && !upsert) {
1109
1091
  throw new Error("Item with external id " + item.external_id + " already exists.");
1110
1092
  }
1111
1093
  if (existingItem && upsert) {
1112
- await this.updateItem(user, existingItem.id, item, role);
1094
+ await this.updateItem(user, {
1095
+ ...item,
1096
+ id: existingItem.id
1097
+ }, role, trigger);
1113
1098
  return existingItem.id;
1114
1099
  }
1115
1100
  }
1116
1101
  if (upsert && item.id) {
1117
- const existingItem = await db3.from(this.getTableName()).where({ id: item.id }).first();
1102
+ const existingItem = await db3.from(getTableName(this.name)).where({ id: item.id }).first();
1118
1103
  if (existingItem && upsert) {
1119
- await this.updateItem(user, existingItem.id, item, role);
1104
+ await this.updateItem(user, {
1105
+ ...item,
1106
+ id: existingItem.id
1107
+ }, role, trigger);
1120
1108
  return existingItem.id;
1121
1109
  }
1122
1110
  }
1123
1111
  Object.keys(item).forEach((key) => {
1124
- if (key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textLength" || key === "upsert" || key === "archived") {
1112
+ if (key === "id" || key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textlength" || key === "upsert" || key === "archived") {
1125
1113
  return;
1126
1114
  }
1127
- const field = this.fields.find((field2) => field2.name === key);
1115
+ const field = this.fields.find((field2) => sanitizeName(field2.name) === sanitizeName(key));
1128
1116
  if (!field) {
1129
1117
  throw new Error("Trying to insert value for field '" + key + "' that does not exist on the context fields definition. Available fields: " + this.fields.map((field2) => sanitizeName(field2.name)).join(", ") + " ,name, description, external_id");
1130
1118
  }
1131
1119
  });
1132
- delete item.id;
1133
- delete item.upsert;
1134
- const result = await db3.from(this.getTableName()).insert({
1135
- ...item,
1120
+ const payloadWithSanitizedPropertyNames = {};
1121
+ Object.keys(item).forEach((key) => {
1122
+ payloadWithSanitizedPropertyNames[sanitizeName(key)] = item[key];
1123
+ });
1124
+ delete payloadWithSanitizedPropertyNames.id;
1125
+ delete payloadWithSanitizedPropertyNames.upsert;
1126
+ const result = await db3.from(getTableName(this.name)).insert({
1127
+ ...payloadWithSanitizedPropertyNames,
1136
1128
  id: db3.fn.uuid(),
1137
1129
  created_at: db3.fn.now(),
1138
1130
  updated_at: db3.fn.now()
1139
1131
  }).returning("id");
1140
- if (this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always") {
1132
+ if (this.embedder && (this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always")) {
1141
1133
  if (this.embedder.queue?.name) {
1142
1134
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
1143
1135
  const job = await bullmqDecorator({
1144
- label: `Job running '${this.embedder.name}' for '${item.name} (${item.id}).'`,
1136
+ label: `${this.embedder.name}`,
1145
1137
  embedder: this.embedder.id,
1146
- type: "embedder",
1138
+ context: this.id,
1147
1139
  inputs: item,
1140
+ item: item.id,
1148
1141
  queue: this.embedder.queue,
1149
- user
1142
+ user,
1143
+ role,
1144
+ trigger: trigger || "agent"
1150
1145
  });
1151
1146
  return {
1152
1147
  id: result[0].id,
@@ -1154,27 +1149,13 @@ var ExuluContext = class {
1154
1149
  };
1155
1150
  }
1156
1151
  console.log("[EXULU] embedder is not in queue mode, calculating vectors directly.");
1157
- const { id: source, chunks } = await this.embedder.generateFromDocument({
1152
+ await this.createAndUpsertEmbeddings({
1158
1153
  ...item,
1159
1154
  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");
1155
+ }, user, {
1156
+ label: this.embedder.name,
1157
+ trigger: trigger || "agent"
1158
+ }, role);
1178
1159
  }
1179
1160
  return {
1180
1161
  id: result[0].id,
@@ -1203,7 +1184,7 @@ var ExuluContext = class {
1203
1184
  if (page < 1) page = 1;
1204
1185
  if (limit < 1) limit = 10;
1205
1186
  let offset = (page - 1) * limit;
1206
- const mainTable = this.getTableName();
1187
+ const mainTable = getTableName(this.name);
1207
1188
  const { db: db3 } = await postgresClient();
1208
1189
  const columns = await db3(mainTable).columnInfo();
1209
1190
  const totalQuery = db3.count("* as count").from(mainTable).first();
@@ -1243,12 +1224,12 @@ var ExuluContext = class {
1243
1224
  context: {
1244
1225
  name: this.name,
1245
1226
  id: this.id,
1246
- embedder: this.embedder.name
1227
+ embedder: this.embedder?.name || void 0
1247
1228
  },
1248
1229
  items
1249
1230
  };
1250
1231
  }
1251
- if (typeof query === "string") {
1232
+ if (typeof query === "string" && this.embedder) {
1252
1233
  if (!method) {
1253
1234
  method = "cosineDistance";
1254
1235
  }
@@ -1266,7 +1247,7 @@ var ExuluContext = class {
1266
1247
  if (this.queryRewriter) {
1267
1248
  query = await this.queryRewriter(query);
1268
1249
  }
1269
- const chunksTable = this.getChunksTableName();
1250
+ const chunksTable = getChunksTableName(this.name);
1270
1251
  itemsQuery.leftJoin(chunksTable, function() {
1271
1252
  this.on(chunksTable + ".source", "=", mainTable + ".id");
1272
1253
  });
@@ -1380,7 +1361,7 @@ var ExuluContext = class {
1380
1361
  };
1381
1362
  createItemsTable = async () => {
1382
1363
  const { db: db3 } = await postgresClient();
1383
- const tableName = this.getTableName();
1364
+ const tableName = getTableName(this.name);
1384
1365
  console.log("[EXULU] Creating table: " + tableName);
1385
1366
  return await db3.schema.createTable(tableName, (table) => {
1386
1367
  console.log("[EXULU] Creating fields for table.", this.fields);
@@ -1390,7 +1371,8 @@ var ExuluContext = class {
1390
1371
  table.text("tags");
1391
1372
  table.boolean("archived").defaultTo(false);
1392
1373
  table.text("external_id");
1393
- table.integer("textLength");
1374
+ table.text("rights_mode").defaultTo(this.configuration?.defaultRightsMode ?? "private");
1375
+ table.integer("textlength");
1394
1376
  table.text("source");
1395
1377
  table.timestamp("embeddings_updated_at");
1396
1378
  for (const field of this.fields) {
@@ -1405,11 +1387,14 @@ var ExuluContext = class {
1405
1387
  };
1406
1388
  createChunksTable = async () => {
1407
1389
  const { db: db3 } = await postgresClient();
1408
- const tableName = this.getChunksTableName();
1390
+ const tableName = getChunksTableName(this.name);
1409
1391
  console.log("[EXULU] Creating table: " + tableName);
1410
1392
  return await db3.schema.createTable(tableName, (table) => {
1393
+ if (!this.embedder) {
1394
+ throw new Error("Embedder must be set for context " + this.name + " to create chunks table.");
1395
+ }
1411
1396
  table.uuid("id").primary().defaultTo(db3.fn.uuid());
1412
- table.uuid("source").references("id").inTable(this.getTableName());
1397
+ table.uuid("source").references("id").inTable(getTableName(this.name));
1413
1398
  table.text("content");
1414
1399
  table.integer("chunk_index");
1415
1400
  table.specificType("embedding", `vector(${this.embedder.vectorDimensions})`);
@@ -1442,44 +1427,6 @@ var ExuluContext = class {
1442
1427
  }
1443
1428
  });
1444
1429
  };
1445
- sources = {
1446
- add: (inputs) => {
1447
- const source = new ExuluSource({
1448
- ...inputs,
1449
- context: this.id
1450
- });
1451
- this._sources.push(source);
1452
- return source;
1453
- },
1454
- get: (id) => {
1455
- if (id) {
1456
- return this._sources.find((source) => source.id === id);
1457
- }
1458
- return this._sources ?? [];
1459
- }
1460
- };
1461
- };
1462
- var ExuluSource = class {
1463
- id;
1464
- name;
1465
- description;
1466
- updaters;
1467
- context;
1468
- constructor({ id, name, description, updaters, context }) {
1469
- this.id = id;
1470
- this.name = name;
1471
- this.description = description;
1472
- this.context = context;
1473
- this.updaters = updaters.map((updater) => {
1474
- if (updater.type === "webhook") {
1475
- return {
1476
- ...updater,
1477
- slug: `/contexts/${context}/sources/${this.id}/${updater.id}/webhook`
1478
- };
1479
- }
1480
- return updater;
1481
- });
1482
- }
1483
1430
  };
1484
1431
  var updateStatistic = async (statistic) => {
1485
1432
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
@@ -1871,10 +1818,11 @@ var requestValidators = {
1871
1818
  };
1872
1819
 
1873
1820
  // src/registry/routes.ts
1874
- import { zerialize } from "zodex";
1821
+ import "zodex";
1875
1822
 
1876
1823
  // src/bullmq/queues.ts
1877
1824
  import { Queue as Queue3 } from "bullmq";
1825
+ import { BullMQOtel } from "bullmq-otel";
1878
1826
  var ExuluQueues = class {
1879
1827
  queues;
1880
1828
  constructor() {
@@ -1892,7 +1840,13 @@ var ExuluQueues = class {
1892
1840
  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
1841
  throw new Error(`[EXULU] no redis server configured.`);
1894
1842
  }
1895
- const newQueue = new Queue3(`${name}`, { connection: redisServer });
1843
+ const newQueue = new Queue3(
1844
+ `${name}`,
1845
+ {
1846
+ connection: redisServer,
1847
+ telemetry: new BullMQOtel("simple-guide")
1848
+ }
1849
+ );
1896
1850
  this.queues.push(newQueue);
1897
1851
  return newQueue;
1898
1852
  }
@@ -1912,7 +1866,7 @@ var VectorMethodEnum = {
1912
1866
  // src/registry/routes.ts
1913
1867
  import express from "express";
1914
1868
  import { ApolloServer } from "@apollo/server";
1915
- import * as Papa from "papaparse";
1869
+ import "papaparse";
1916
1870
  import cors from "cors";
1917
1871
  import "reflect-metadata";
1918
1872
 
@@ -1922,1332 +1876,1612 @@ import GraphQLJSON from "graphql-type-json";
1922
1876
  import { GraphQLScalarType, Kind } from "graphql";
1923
1877
  import CryptoJS2 from "crypto-js";
1924
1878
  import bcrypt2 from "bcryptjs";
1925
- var GraphQLDate = new GraphQLScalarType({
1926
- name: "Date",
1927
- description: "Date custom scalar type",
1928
- serialize(value) {
1929
- if (value instanceof Date) {
1930
- return value.toISOString();
1931
- }
1932
- if (typeof value === "number") {
1933
- return new Date(value).toISOString();
1934
- }
1935
- if (typeof value === "string") {
1936
- return new Date(value).toISOString();
1937
- }
1938
- return value;
1879
+
1880
+ // src/postgres/core-schema.ts
1881
+ var agentMessagesSchema = {
1882
+ name: {
1883
+ plural: "agent_messages",
1884
+ singular: "agent_message"
1939
1885
  },
1940
- parseValue(value) {
1941
- if (typeof value === "string") {
1942
- return new Date(value);
1943
- }
1944
- if (typeof value === "number") {
1945
- return new Date(value);
1886
+ fields: [
1887
+ {
1888
+ name: "content",
1889
+ type: "text"
1890
+ },
1891
+ {
1892
+ name: "title",
1893
+ type: "text"
1894
+ },
1895
+ {
1896
+ name: "user",
1897
+ type: "number"
1898
+ },
1899
+ {
1900
+ name: "session",
1901
+ type: "text"
1946
1902
  }
1947
- return value;
1903
+ ]
1904
+ };
1905
+ var agentSessionsSchema = {
1906
+ name: {
1907
+ plural: "agent_sessions",
1908
+ singular: "agent_session"
1948
1909
  },
1949
- parseLiteral(ast) {
1950
- if (ast.kind === Kind.STRING) {
1951
- return new Date(ast.value);
1952
- }
1953
- if (ast.kind === Kind.INT) {
1954
- return new Date(parseInt(ast.value, 10));
1910
+ RBAC: true,
1911
+ fields: [
1912
+ {
1913
+ name: "agent",
1914
+ type: "uuid"
1915
+ },
1916
+ {
1917
+ name: "user",
1918
+ // next auth stores users with id type SERIAL, so we need to use number
1919
+ type: "number"
1920
+ },
1921
+ {
1922
+ name: "role",
1923
+ type: "uuid"
1924
+ },
1925
+ {
1926
+ name: "title",
1927
+ type: "text"
1955
1928
  }
1956
- return null;
1957
- }
1958
- });
1959
- var map = (field) => {
1960
- let type;
1961
- switch (field.type) {
1962
- case "text":
1963
- case "shortText":
1964
- case "longText":
1965
- case "code":
1966
- type = "String";
1967
- break;
1968
- case "enum":
1969
- type = field.enumValues ? `${field.name}Enum` : "String";
1970
- break;
1971
- case "number":
1972
- type = "Float";
1973
- break;
1974
- case "boolean":
1975
- type = "Boolean";
1976
- break;
1977
- case "json":
1978
- type = "JSON";
1979
- break;
1980
- case "date":
1981
- type = "Date";
1982
- break;
1983
- default:
1984
- type = "String";
1985
- }
1986
- return type;
1929
+ ]
1987
1930
  };
1988
- function createTypeDefs(table) {
1989
- const enumDefs = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
1990
- const enumValues = field.enumValues.map((value) => {
1991
- const sanitized = String(value).replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[0-9]/, "_$&").toUpperCase();
1992
- return ` ${sanitized}`;
1993
- }).join("\n");
1994
- return `
1995
- enum ${field.name}Enum {
1996
- ${enumValues}
1997
- }`;
1998
- }).join("\n");
1999
- const fields = table.fields.map((field) => {
2000
- let type;
2001
- type = map(field);
2002
- const required = field.required ? "!" : "";
2003
- return ` ${field.name}: ${type}${required}`;
2004
- });
2005
- const rbacField = table.RBAC ? " RBAC: RBACData" : "";
2006
- const typeDef = `
2007
- type ${table.name.singular} {
2008
- ${fields.join("\n")}
2009
- ${table.fields.find((field) => field.name === "id") ? "" : "id: ID!"}
2010
- ${rbacField}
2011
- }
2012
- `;
2013
- const rbacInputField = table.RBAC ? " RBAC: RBACInput" : "";
2014
- const inputDef = `
2015
- input ${table.name.singular}Input {
2016
- ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
2017
- ${rbacInputField}
2018
- }
2019
- `;
2020
- return enumDefs + typeDef + inputDef;
2021
- }
2022
- function createFilterTypeDefs(table) {
2023
- const fieldFilters = table.fields.map((field) => {
2024
- let type;
2025
- if (field.type === "enum" && field.enumValues) {
2026
- type = `${field.name}Enum`;
2027
- } else {
2028
- type = map(field);
1931
+ var variablesSchema = {
1932
+ name: {
1933
+ plural: "variables",
1934
+ singular: "variable"
1935
+ },
1936
+ fields: [
1937
+ {
1938
+ name: "name",
1939
+ type: "text",
1940
+ index: true,
1941
+ unique: true
1942
+ },
1943
+ {
1944
+ name: "value",
1945
+ type: "longText"
1946
+ },
1947
+ {
1948
+ name: "encrypted",
1949
+ type: "boolean",
1950
+ default: false
2029
1951
  }
2030
- return `
2031
- ${field.name}: FilterOperator${type}`;
2032
- });
2033
- const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2034
- const enumFilterOperators = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
2035
- const enumTypeName = `${field.name}Enum`;
2036
- return `
2037
- input FilterOperator${enumTypeName} {
2038
- eq: ${enumTypeName}
2039
- ne: ${enumTypeName}
2040
- in: [${enumTypeName}]
2041
- and: [FilterOperator${enumTypeName}]
2042
- or: [FilterOperator${enumTypeName}]
2043
- }`;
2044
- }).join("\n");
2045
- const operatorTypes = `
2046
- input FilterOperatorString {
2047
- eq: String
2048
- ne: String
2049
- in: [String]
2050
- contains: String
2051
- and: [FilterOperatorString]
2052
- or: [FilterOperatorString]
2053
- }
2054
-
2055
- input FilterOperatorDate {
2056
- lte: Date
2057
- gte: Date
2058
- and: [FilterOperatorDate]
2059
- or: [FilterOperatorDate]
2060
- }
2061
-
2062
- input FilterOperatorFloat {
2063
- eq: Float
2064
- ne: Float
2065
- in: [Float]
2066
- and: [FilterOperatorFloat]
2067
- or: [FilterOperatorFloat]
2068
- }
2069
-
2070
- input FilterOperatorBoolean {
2071
- eq: Boolean
2072
- ne: Boolean
2073
- in: [Boolean]
2074
- and: [FilterOperatorBoolean]
2075
- or: [FilterOperatorBoolean]
2076
- }
2077
-
2078
- input FilterOperatorJSON {
2079
- eq: JSON
2080
- ne: JSON
2081
- in: [JSON]
2082
- }
2083
-
2084
- input SortBy {
2085
- field: String!
2086
- direction: SortDirection!
2087
- }
2088
-
2089
- enum SortDirection {
2090
- ASC
2091
- DESC
2092
- }
2093
-
2094
- ${enumFilterOperators}
2095
-
2096
- input Filter${tableNameSingularUpperCaseFirst} {
2097
- ${fieldFilters.join("\n")}
2098
- }`;
2099
- return operatorTypes;
2100
- }
2101
- var getRequestedFields = (info) => {
2102
- const selections = info.operation.selectionSet.selections[0].selectionSet.selections;
2103
- const itemsSelection = selections.find((s) => s.name.value === "items");
2104
- const fields = itemsSelection ? Object.keys(itemsSelection.selectionSet.selections.reduce((acc, field) => {
2105
- acc[field.name.value] = true;
2106
- return acc;
2107
- }, {})) : Object.keys(selections.reduce((acc, field) => {
2108
- acc[field.name.value] = true;
2109
- return acc;
2110
- }, {}));
2111
- return fields.filter((field) => field !== "pageInfo" && field !== "items" && field !== "RBAC");
1952
+ ]
2112
1953
  };
2113
- var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRbacRecords) => {
2114
- const { users = [], roles = [] } = rbacData;
2115
- if (!existingRbacRecords) {
2116
- existingRbacRecords = await db3.from("rbac").where({
2117
- entity: entityName,
2118
- target_resource_id: resourceId
2119
- }).select("*");
2120
- }
2121
- const newUserRecords = new Set(users.map((u) => `${u.id}:${u.rights}`));
2122
- const newRoleRecords = new Set(roles.map((r) => `${r.id}:${r.rights}`));
2123
- const existingUserRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "User").map((r) => `${r.user_id}:${r.rights}`));
2124
- const existingRoleRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "Role").map((r) => `${r.role_id}:${r.rights}`));
2125
- const usersToCreate = users.filter((u) => !existingUserRecords.has(`${u.id}:${u.rights}`));
2126
- const rolesToCreate = roles.filter((r) => !existingRoleRecords.has(`${r.id}:${r.rights}`));
2127
- const usersToRemove = existingRbacRecords.filter((r) => r.access_type === "User" && !newUserRecords.has(`${r.user_id}:${r.rights}`));
2128
- const rolesToRemove = existingRbacRecords.filter((r) => r.access_type === "Role" && !newRoleRecords.has(`${r.role_id}:${r.rights}`));
2129
- if (usersToRemove.length > 0) {
2130
- await db3.from("rbac").whereIn("id", usersToRemove.map((r) => r.id)).del();
2131
- }
2132
- if (rolesToRemove.length > 0) {
2133
- await db3.from("rbac").whereIn("id", rolesToRemove.map((r) => r.id)).del();
2134
- }
2135
- const recordsToInsert = [];
2136
- usersToCreate.forEach((user) => {
2137
- recordsToInsert.push({
2138
- entity: entityName,
2139
- access_type: "User",
2140
- target_resource_id: resourceId,
2141
- user_id: user.id,
2142
- rights: user.rights,
2143
- createdAt: /* @__PURE__ */ new Date(),
2144
- updatedAt: /* @__PURE__ */ new Date()
2145
- });
2146
- });
2147
- rolesToCreate.forEach((role) => {
2148
- recordsToInsert.push({
2149
- entity: entityName,
2150
- access_type: "Role",
2151
- target_resource_id: resourceId,
2152
- role_id: role.id,
2153
- rights: role.rights,
2154
- createdAt: /* @__PURE__ */ new Date(),
2155
- updatedAt: /* @__PURE__ */ new Date()
2156
- });
2157
- });
2158
- if (recordsToInsert.length > 0) {
2159
- await db3.from("rbac").insert(recordsToInsert);
2160
- }
1954
+ var workflowTemplatesSchema = {
1955
+ name: {
1956
+ plural: "workflow_templates",
1957
+ singular: "workflow_template"
1958
+ },
1959
+ RBAC: true,
1960
+ fields: [
1961
+ {
1962
+ name: "name",
1963
+ type: "text",
1964
+ required: true
1965
+ },
1966
+ {
1967
+ name: "description",
1968
+ type: "text"
1969
+ },
1970
+ {
1971
+ name: "owner",
1972
+ type: "number",
1973
+ required: true
1974
+ },
1975
+ {
1976
+ name: "visibility",
1977
+ type: "text",
1978
+ required: true
1979
+ },
1980
+ {
1981
+ name: "shared_user_ids",
1982
+ type: "json"
1983
+ },
1984
+ {
1985
+ name: "shared_role_ids",
1986
+ type: "json"
1987
+ },
1988
+ {
1989
+ name: "variables",
1990
+ type: "json"
1991
+ },
1992
+ {
1993
+ name: "steps_json",
1994
+ type: "json",
1995
+ required: true
1996
+ },
1997
+ {
1998
+ name: "example_metadata_json",
1999
+ type: "json"
2000
+ }
2001
+ ]
2161
2002
  };
2162
- function createMutations(table) {
2163
- const tableNamePlural = table.name.plural.toLowerCase();
2164
- const validateWriteAccess = async (id, context) => {
2165
- try {
2166
- const { db: db3, req, user } = context;
2167
- if (user.super_admin === true) {
2168
- return true;
2169
- }
2170
- if (!user.role || !(table.name.plural === "agents" && user.role.agents === "write") && !(table.name.plural === "workflow_templates" && user.role.workflows === "write") && !(table.name.plural === "variables" && user.role.variables === "write") && !(table.name.plural === "users" && user.role.users === "write")) {
2171
- console.error("Access control error: no role found for current user or no access to entity type.");
2172
- throw new Error("Access control error: no role found for current user or no access to entity type.");
2173
- }
2174
- const hasRBAC = table.RBAC === true;
2175
- if (!hasRBAC) {
2176
- return true;
2177
- }
2178
- const record = await db3.from(tableNamePlural).select(["rights_mode", "created_by"]).where({ id }).first();
2179
- if (!record) {
2180
- throw new Error("Record not found");
2181
- }
2182
- if (tableNamePlural === "jobs") {
2183
- if (!user.super_admin && record.created_by !== user.id) {
2184
- throw new Error("You are not authorized to edit this record");
2185
- }
2186
- }
2187
- if (record.rights_mode === "public") {
2188
- return true;
2189
- }
2190
- if (record.rights_mode === "private") {
2191
- if (record.created_by === user.id) {
2192
- return true;
2193
- }
2194
- throw new Error("Only the creator can edit this private record");
2195
- }
2196
- if (record.rights_mode === "users") {
2197
- const rbacRecord = await db3.from("rbac").where({
2198
- entity: table.name.singular,
2199
- target_resource_id: id,
2200
- access_type: "User",
2201
- user_id: user.id,
2202
- rights: "write"
2203
- }).first();
2204
- if (rbacRecord) {
2205
- return true;
2206
- }
2207
- throw new Error("Insufficient user permissions to edit this record");
2208
- }
2209
- if (record.rights_mode === "roles" && user.role) {
2210
- const rbacRecord = await db3.from("rbac").where({
2211
- entity: table.name.singular,
2212
- target_resource_id: id,
2213
- access_type: "Role",
2214
- role_id: user.role,
2215
- rights: "write"
2216
- }).first();
2217
- if (rbacRecord) {
2218
- return true;
2219
- }
2220
- throw new Error("Insufficient role permissions to edit this record");
2221
- }
2222
- throw new Error("Insufficient permissions to edit this record");
2223
- } catch (error) {
2224
- console.error("Write access validation error:", error);
2225
- throw error;
2226
- }
2227
- };
2228
- return {
2229
- [`${tableNamePlural}CreateOne`]: async (_, args, context, info) => {
2230
- const { db: db3 } = context;
2231
- const requestedFields = getRequestedFields(info);
2232
- let { input } = args;
2233
- const rbacData = input.RBAC;
2234
- delete input.RBAC;
2235
- delete input.created_by;
2236
- input = encryptSensitiveFields(input);
2237
- if (table.RBAC) {
2238
- input.created_by = context.user.id;
2239
- }
2240
- if (table.name.singular === "user" && context.user?.super_admin !== true) {
2241
- throw new Error("You are not authorized to create users");
2242
- }
2243
- if (table.name.singular === "user" && input.password) {
2244
- input.password = await bcrypt2.hash(input.password, 10);
2245
- }
2246
- Object.keys(input).forEach((key) => {
2247
- if (table.fields.find((field) => field.name === key)?.type === "json") {
2248
- if (typeof input[key] === "object" || Array.isArray(input[key])) {
2249
- input[key] = JSON.stringify(input[key]);
2250
- }
2251
- }
2252
- });
2253
- const results = await db3(tableNamePlural).insert({
2254
- ...input,
2255
- ...table.RBAC ? { rights_mode: "private" } : {},
2256
- createdAt: /* @__PURE__ */ new Date(),
2257
- updatedAt: /* @__PURE__ */ new Date()
2258
- }).returning(requestedFields);
2259
- if (table.RBAC && rbacData && results[0]) {
2260
- await handleRBACUpdate(db3, table.name.singular, results[0].id, rbacData, []);
2261
- }
2262
- return results[0];
2003
+ var agentsSchema = {
2004
+ name: {
2005
+ plural: "agents",
2006
+ singular: "agent"
2007
+ },
2008
+ RBAC: true,
2009
+ fields: [
2010
+ {
2011
+ name: "name",
2012
+ type: "text"
2263
2013
  },
2264
- [`${tableNamePlural}UpdateOne`]: async (_, args, context, info) => {
2265
- const { db: db3, req } = context;
2266
- let { where, input } = args;
2267
- await validateCreateOrRemoveSuperAdminPermission(tableNamePlural, input, req);
2268
- if (where.id) {
2269
- await validateWriteAccess(where.id, context);
2270
- }
2271
- const rbacData = input.RBAC;
2272
- delete input.RBAC;
2273
- delete input.created_by;
2274
- input = encryptSensitiveFields(input);
2275
- Object.keys(input).forEach((key) => {
2276
- if (table.fields.find((field) => field.name === key)?.type === "json") {
2277
- if (typeof input[key] === "object" || Array.isArray(input[key])) {
2278
- input[key] = JSON.stringify(input[key]);
2279
- }
2280
- }
2281
- });
2282
- await db3(tableNamePlural).where(where).update({
2283
- ...input,
2284
- updatedAt: /* @__PURE__ */ new Date()
2285
- });
2286
- if (table.RBAC && rbacData && where.id) {
2287
- const existingRbacRecords = await db3.from("rbac").where({
2288
- entity: table.name.singular,
2289
- target_resource_id: where.id
2290
- }).select("*");
2291
- await handleRBACUpdate(db3, table.name.singular, where.id, rbacData, existingRbacRecords);
2292
- }
2293
- const requestedFields = getRequestedFields(info);
2294
- const result = await db3.from(tableNamePlural).select(requestedFields).where(where).first();
2295
- return result;
2014
+ {
2015
+ name: "image",
2016
+ type: "text"
2296
2017
  },
2297
- [`${tableNamePlural}UpdateOneById`]: async (_, args, context, info) => {
2298
- const { db: db3, req } = context;
2299
- let { id, input } = args;
2300
- await validateCreateOrRemoveSuperAdminPermission(tableNamePlural, input, req);
2301
- await validateWriteAccess(id, context);
2302
- const rbacData = input.RBAC;
2303
- delete input.RBAC;
2304
- delete input.created_by;
2305
- input = encryptSensitiveFields(input);
2306
- Object.keys(input).forEach((key) => {
2307
- if (table.fields.find((field) => field.name === key)?.type === "json") {
2308
- if (typeof input[key] === "object" || Array.isArray(input[key])) {
2309
- input[key] = JSON.stringify(input[key]);
2310
- }
2311
- }
2312
- });
2313
- await db3(tableNamePlural).where({ id }).update({
2314
- ...input,
2315
- updatedAt: /* @__PURE__ */ new Date()
2316
- });
2317
- if (table.RBAC && rbacData) {
2318
- const existingRbacRecords = await db3.from("rbac").where({
2319
- entity: table.name.singular,
2320
- target_resource_id: id
2321
- }).select("*");
2322
- await handleRBACUpdate(db3, table.name.singular, id, rbacData, existingRbacRecords);
2323
- }
2324
- const requestedFields = getRequestedFields(info);
2325
- const result = await db3.from(tableNamePlural).select(requestedFields).where({ id }).first();
2326
- return result;
2018
+ {
2019
+ name: "description",
2020
+ type: "text"
2327
2021
  },
2328
- [`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
2329
- const { db: db3 } = context;
2330
- const { where } = args;
2331
- const requestedFields = getRequestedFields(info);
2332
- const result = await db3.from(tableNamePlural).select(requestedFields).where(where).first();
2333
- if (!result) {
2334
- throw new Error("Record not found");
2335
- }
2336
- await db3(tableNamePlural).where(where).del();
2337
- if (table.RBAC) {
2338
- await db3.from("rbac").where({
2339
- entity: table.name.singular,
2340
- target_resource_id: result.id
2341
- }).del();
2342
- }
2343
- return result;
2022
+ {
2023
+ name: "providerApiKey",
2024
+ type: "text"
2344
2025
  },
2345
- [`${tableNamePlural}RemoveOneById`]: async (_, args, context, info) => {
2346
- const { id } = args;
2347
- const { db: db3 } = context;
2348
- const requestedFields = getRequestedFields(info);
2349
- const result = await db3.from(tableNamePlural).select(requestedFields).where({ id }).first();
2350
- if (!result) {
2351
- throw new Error("Record not found");
2352
- }
2353
- await db3(tableNamePlural).where({ id }).del();
2354
- if (table.RBAC) {
2355
- await db3.from("rbac").where({
2356
- entity: table.name.singular,
2357
- target_resource_id: id
2358
- }).del();
2359
- }
2360
- return result;
2026
+ {
2027
+ name: "backend",
2028
+ type: "text"
2029
+ },
2030
+ {
2031
+ name: "type",
2032
+ type: "text"
2033
+ },
2034
+ {
2035
+ name: "active",
2036
+ type: "boolean",
2037
+ default: false
2038
+ },
2039
+ {
2040
+ name: "tools",
2041
+ type: "json"
2361
2042
  }
2362
- };
2363
- }
2364
- var applyAccessControl = (table, user, query) => {
2365
- console.log("table", table);
2366
- const tableNamePlural = table.name.plural.toLowerCase();
2367
- if (!user.super_admin && table.name.plural === "jobs") {
2368
- query = query.where("created_by", user.id);
2369
- return query;
2370
- }
2371
- const hasRBAC = table.RBAC === true;
2372
- if (!hasRBAC) {
2373
- return query;
2374
- }
2375
- if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2376
- return query;
2377
- }
2378
- if (!user.role || !(table.name.plural === "agents" && (user.role.agents === "read" || user.role.agents === "write")) && !(table.name.plural === "workflow_templates" && (user.role.workflows === "read" || user.role.workflows === "write")) && !(table.name.plural === "variables" && (user.role.variables === "read" || user.role.variables === "write")) && !(table.name.plural === "users" && (user.role.users === "read" || user.role.users === "write"))) {
2379
- console.error("Access control error: no role found or no access to entity type.");
2380
- return query.where("1", "=", "0");
2381
- }
2382
- try {
2383
- query = query.where(function() {
2384
- this.where("rights_mode", "public");
2385
- this.orWhere("created_by", user.id);
2386
- this.orWhere(function() {
2387
- this.where("rights_mode", "users").whereExists(function() {
2388
- this.select("*").from("rbac").whereRaw("rbac.target_resource_id = " + tableNamePlural + ".id").where("rbac.entity", table.name.singular).where("rbac.access_type", "User").where("rbac.user_id", user.id);
2389
- });
2390
- });
2391
- if (user.role) {
2392
- console.log("user.role", user.role);
2393
- this.orWhere(function() {
2394
- this.where("rights_mode", "roles").whereExists(function() {
2395
- this.select("*").from("rbac").whereRaw("rbac.target_resource_id = " + tableNamePlural + ".id").where("rbac.entity", table.name.singular).where("rbac.access_type", "Role").where("rbac.role_id", user.role);
2396
- });
2397
- });
2398
- }
2399
- });
2400
- } catch (error) {
2401
- console.error("Access control error:", error);
2402
- return query.where("1", "=", "0");
2403
- }
2404
- return query;
2405
- };
2406
- var converOperatorToQuery = (query, fieldName, operators) => {
2407
- if (operators.eq !== void 0) {
2408
- query = query.where(fieldName, operators.eq);
2409
- }
2410
- if (operators.ne !== void 0) {
2411
- query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2412
- }
2413
- if (operators.in !== void 0) {
2414
- query = query.whereIn(fieldName, operators.in);
2415
- }
2416
- if (operators.contains !== void 0) {
2417
- query = query.where(fieldName, "like", `%${operators.contains}%`);
2418
- }
2419
- if (operators.lte !== void 0) {
2420
- query = query.where(fieldName, "<=", operators.lte);
2421
- }
2422
- if (operators.gte !== void 0) {
2423
- query = query.where(fieldName, ">=", operators.gte);
2424
- }
2425
- return query;
2043
+ ]
2426
2044
  };
2427
- function createQueries(table) {
2428
- const tableNamePlural = table.name.plural.toLowerCase();
2429
- const tableNameSingular = table.name.singular.toLowerCase();
2430
- const applyFilters = (query, filters) => {
2431
- filters.forEach((filter) => {
2432
- Object.entries(filter).forEach(([fieldName, operators]) => {
2433
- if (operators) {
2434
- if (operators.and !== void 0) {
2435
- console.log("operators.and", operators.and);
2436
- operators.and.forEach((operator) => {
2437
- query = converOperatorToQuery(query, fieldName, operator);
2438
- });
2439
- }
2440
- if (operators.or !== void 0) {
2441
- operators.or.forEach((operator) => {
2442
- query = converOperatorToQuery(query, fieldName, operator);
2443
- });
2444
- }
2445
- query = converOperatorToQuery(query, fieldName, operators);
2446
- console.log("query", query);
2447
- }
2448
- });
2449
- });
2450
- return query;
2451
- };
2452
- const applySorting = (query, sort) => {
2453
- if (sort) {
2454
- query = query.orderBy(sort.field, sort.direction.toLowerCase());
2455
- }
2456
- return query;
2457
- };
2458
- return {
2459
- [`${tableNameSingular}ById`]: async (_, args, context, info) => {
2460
- const { db: db3 } = context;
2461
- const requestedFields = getRequestedFields(info);
2462
- let query = db3.from(tableNamePlural).select(requestedFields).where({ id: args.id });
2463
- query = applyAccessControl(table, context.user, query);
2464
- const result = await query.first();
2465
- return result;
2045
+ var usersSchema = {
2046
+ name: {
2047
+ plural: "users",
2048
+ singular: "user"
2049
+ },
2050
+ fields: [
2051
+ {
2052
+ name: "id",
2053
+ type: "number",
2054
+ index: true
2466
2055
  },
2467
- [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2468
- const { db: db3 } = context;
2469
- const requestedFields = getRequestedFields(info);
2470
- let query = db3.from(tableNamePlural).select(requestedFields).whereIn("id", args.ids);
2471
- query = applyAccessControl(table, context.user, query);
2472
- const result = await query;
2473
- return result;
2056
+ {
2057
+ name: "favourite_agents",
2058
+ type: "json"
2474
2059
  },
2475
- [`${tableNameSingular}One`]: async (_, args, context, info) => {
2476
- const { filters = [], sort } = args;
2477
- const { db: db3 } = context;
2478
- const requestedFields = getRequestedFields(info);
2479
- let query = db3.from(tableNamePlural).select(requestedFields);
2480
- query = applyFilters(query, filters);
2481
- query = applyAccessControl(table, context.user, query);
2482
- query = applySorting(query, sort);
2483
- const result = await query.first();
2484
- return result;
2060
+ {
2061
+ name: "firstname",
2062
+ type: "text"
2485
2063
  },
2486
- [`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
2487
- const { limit = 10, page = 0, filters = [], sort } = args;
2488
- const { db: db3 } = context;
2489
- let countQuery = db3(tableNamePlural);
2490
- countQuery = applyFilters(countQuery, filters);
2491
- countQuery = applyAccessControl(table, context.user, countQuery);
2492
- console.log("countQuery", countQuery);
2493
- const countResult = await countQuery.count("* as count");
2494
- const itemCount = Number(countResult[0]?.count || 0);
2495
- const pageCount = Math.ceil(itemCount / limit);
2496
- const currentPage = page;
2497
- const hasPreviousPage = currentPage > 1;
2498
- const hasNextPage = currentPage < pageCount - 1;
2499
- let dataQuery = db3(tableNamePlural);
2500
- dataQuery = applyFilters(dataQuery, filters);
2501
- dataQuery = applyAccessControl(table, context.user, dataQuery);
2502
- const requestedFields = getRequestedFields(info);
2503
- dataQuery = applySorting(dataQuery, sort);
2504
- if (page > 1) {
2505
- dataQuery = dataQuery.offset((page - 1) * limit);
2506
- }
2507
- const items = await dataQuery.select(requestedFields).limit(limit);
2508
- return {
2509
- pageInfo: {
2510
- pageCount,
2511
- itemCount,
2512
- currentPage,
2513
- hasPreviousPage,
2514
- hasNextPage
2515
- },
2516
- items
2517
- };
2064
+ {
2065
+ name: "name",
2066
+ type: "text"
2518
2067
  },
2519
- // Add generic statistics query for all tables
2520
- [`${tableNamePlural}Statistics`]: async (_, args, context, info) => {
2521
- const { filters = [], groupBy } = args;
2522
- const { db: db3 } = context;
2523
- let query = db3(tableNamePlural);
2524
- query = applyFilters(query, filters);
2525
- query = applyAccessControl(table, context.user, query);
2526
- if (groupBy) {
2527
- query = query.select(groupBy).groupBy(groupBy);
2528
- if (tableNamePlural === "tracking") {
2529
- query = query.sum("total as count");
2530
- } else {
2531
- query = query.count("* as count");
2532
- }
2533
- const results = await query;
2534
- console.log("!!! results !!!", results);
2535
- return results.map((r) => ({
2536
- group: r[groupBy],
2537
- count: r.count ? Number(r.count) : 0
2538
- }));
2539
- } else {
2540
- if (tableNamePlural === "tracking") {
2541
- query = query.sum("total as count");
2542
- const [{ count }] = await query.sum("total as count");
2543
- console.log("!!! count !!!", count);
2544
- return [{
2545
- group: "total",
2546
- count: count ? Number(count) : 0
2547
- }];
2548
- } else {
2549
- const [{ count }] = await query.count("* as count");
2550
- return [{
2551
- group: "total",
2552
- count: count ? Number(count) : 0
2553
- }];
2554
- }
2555
- }
2556
- }
2557
- };
2558
- }
2559
- var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
2560
- const rbacRecords = await db3.from("rbac").where({
2561
- entity: entityName,
2562
- target_resource_id: resourceId
2563
- }).select("*");
2564
- const users = rbacRecords.filter((r) => r.access_type === "User").map((r) => ({ id: r.user_id, rights: r.rights }));
2565
- const roles = rbacRecords.filter((r) => r.access_type === "Role").map((r) => ({ id: r.role_id, rights: r.rights }));
2566
- let type = rights_mode || "private";
2567
- if (type === "users" && users.length === 0) type = "private";
2568
- if (type === "roles" && roles.length === 0) type = "private";
2569
- return {
2570
- type,
2571
- users,
2572
- roles
2573
- };
2574
- };
2575
- function createSDL(tables) {
2576
- tables.forEach((table) => {
2577
- table.fields.push({
2578
- name: "createdAt",
2579
- type: "date"
2580
- });
2581
- table.fields.push({
2582
- name: "updatedAt",
2068
+ {
2069
+ name: "lastname",
2070
+ type: "text"
2071
+ },
2072
+ {
2073
+ name: "email",
2074
+ type: "text",
2075
+ index: true
2076
+ },
2077
+ {
2078
+ name: "temporary_token",
2079
+ type: "text"
2080
+ },
2081
+ {
2082
+ name: "type",
2083
+ type: "text",
2084
+ index: true
2085
+ },
2086
+ {
2087
+ name: "profile_image",
2088
+ type: "text"
2089
+ },
2090
+ {
2091
+ name: "super_admin",
2092
+ type: "boolean",
2093
+ default: false
2094
+ },
2095
+ {
2096
+ name: "status",
2097
+ type: "text"
2098
+ },
2099
+ {
2100
+ name: "emailVerified",
2101
+ type: "text"
2102
+ },
2103
+ {
2104
+ name: "apikey",
2105
+ type: "text"
2106
+ },
2107
+ {
2108
+ name: "last_used",
2583
2109
  type: "date"
2584
- });
2585
- });
2586
- console.log("[EXULU] Creating SDL");
2587
- let typeDefs = `
2588
- scalar JSON
2589
- scalar Date
2590
-
2591
- type RBACData {
2592
- type: String!
2593
- users: [RBACUser!]
2594
- roles: [RBACRole!]
2110
+ },
2111
+ {
2112
+ name: "password",
2113
+ type: "text"
2114
+ },
2115
+ {
2116
+ name: "anthropic_token",
2117
+ type: "text"
2118
+ },
2119
+ {
2120
+ name: "role",
2121
+ type: "uuid"
2595
2122
  }
2596
-
2597
- type RBACUser {
2598
- id: ID!
2599
- rights: String!
2123
+ ]
2124
+ };
2125
+ var rolesSchema = {
2126
+ name: {
2127
+ plural: "roles",
2128
+ singular: "role"
2129
+ },
2130
+ fields: [
2131
+ {
2132
+ name: "name",
2133
+ type: "text"
2134
+ },
2135
+ {
2136
+ name: agentsSchema.name.plural,
2137
+ type: "text"
2138
+ // write | read access to agents
2139
+ },
2140
+ {
2141
+ name: "api",
2142
+ type: "text"
2143
+ },
2144
+ {
2145
+ name: "workflows",
2146
+ type: "text"
2147
+ // write | read access to workflows
2148
+ },
2149
+ {
2150
+ name: variablesSchema.name.plural,
2151
+ type: "text"
2152
+ // write | read access to variables
2153
+ },
2154
+ {
2155
+ name: usersSchema.name.plural,
2156
+ type: "text"
2157
+ // write | read access to users
2600
2158
  }
2601
-
2602
- type RBACRole {
2603
- id: ID!
2604
- rights: String!
2605
- }
2606
-
2607
- input RBACInput {
2608
- users: [RBACUserInput!]
2609
- roles: [RBACRoleInput!]
2610
- }
2611
-
2612
- input RBACUserInput {
2613
- id: ID!
2614
- rights: String!
2615
- }
2616
-
2617
- input RBACRoleInput {
2618
- id: ID!
2619
- rights: String!
2620
- }
2621
-
2622
- type Query {
2623
- `;
2624
- let mutationDefs = `
2625
- type Mutation {
2626
- `;
2627
- let modelDefs = "";
2628
- const resolvers = { JSON: GraphQLJSON, Date: GraphQLDate, Query: {}, Mutation: {} };
2629
- for (const table of tables) {
2630
- if (table.graphql === false) {
2631
- continue;
2632
- }
2633
- const tableNamePlural = table.name.plural.toLowerCase();
2634
- const tableNameSingular = table.name.singular.toLowerCase();
2635
- const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2636
- console.log("[EXULU] Adding table >>>>>", tableNamePlural);
2637
- typeDefs += `
2638
- ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2639
- ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
2640
- ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2641
- ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2642
- ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
2643
- `;
2644
- mutationDefs += `
2645
- ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
2646
- ${tableNamePlural}UpdateOne(where: JSON!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2647
- ${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2648
- ${tableNamePlural}RemoveOne(where: JSON!): ${tableNameSingular}
2649
- ${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
2650
- `;
2651
- modelDefs += createTypeDefs(table);
2652
- modelDefs += createFilterTypeDefs(table);
2653
- modelDefs += `
2654
- type ${tableNameSingularUpperCaseFirst}PaginationResult {
2655
- pageInfo: PageInfo!
2656
- items: [${tableNameSingular}]!
2657
- }
2658
-
2659
- type PageInfo {
2660
- pageCount: Int!
2661
- itemCount: Int!
2662
- currentPage: Int!
2663
- hasPreviousPage: Boolean!
2664
- hasNextPage: Boolean!
2665
- }
2666
- `;
2667
- Object.assign(resolvers.Query, createQueries(table));
2668
- Object.assign(resolvers.Mutation, createMutations(table));
2669
- if (table.RBAC) {
2670
- const rbacResolverName = table.name.singular;
2671
- if (!resolvers[rbacResolverName]) {
2672
- resolvers[rbacResolverName] = {};
2673
- }
2674
- resolvers[rbacResolverName].RBAC = async (parent, args, context) => {
2675
- const { db: db3 } = context;
2676
- const resourceId = parent.id;
2677
- const entityName = table.name.singular;
2678
- const rights_mode = parent.rights_mode;
2679
- return RBACResolver(db3, table, entityName, resourceId, rights_mode);
2680
- };
2681
- }
2682
- }
2683
- typeDefs += "}\n";
2684
- mutationDefs += "}\n";
2685
- const genericTypes = `
2686
- type StatisticsResult {
2687
- group: String!
2688
- count: Int!
2689
- }
2690
- `;
2691
- const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
2692
- const schema = makeExecutableSchema({
2693
- typeDefs: fullSDL,
2694
- resolvers
2695
- });
2696
- console.log("\n\u{1F4CA} GraphQL Schema Overview\n");
2697
- const queriesTable = Object.keys(resolvers.Query).map((query) => ({
2698
- "Operation Type": "Query",
2699
- "Name": query,
2700
- "Description": "Retrieves data"
2701
- }));
2702
- const mutationsTable = Object.keys(resolvers.Mutation).map((mutation) => ({
2703
- "Operation Type": "Mutation",
2704
- "Name": mutation,
2705
- "Description": "Modifies data"
2706
- }));
2707
- const typesTable = tables.flatMap(
2708
- (table) => table.fields.map((field) => ({
2709
- "Type": table.name.singular,
2710
- "Field": field.name,
2711
- "Field Type": field.type,
2712
- "Required": field.required ? "Yes" : "No"
2713
- }))
2714
- );
2715
- console.log("\u{1F50D} Operations:");
2716
- console.table([...queriesTable, ...mutationsTable]);
2717
- console.log("\n\u{1F4DD} Types and Fields:");
2718
- console.table(typesTable);
2719
- console.log("\n");
2720
- return schema;
2721
- }
2722
- var encryptSensitiveFields = (input) => {
2723
- if (input.value && input.encrypted === true) {
2724
- input.value = CryptoJS2.AES.encrypt(input.value, process.env.NEXTAUTH_SECRET).toString();
2725
- }
2726
- return input;
2727
- };
2728
- var validateCreateOrRemoveSuperAdminPermission = async (tableNamePlural, input, req) => {
2729
- if (tableNamePlural === "users" && input.super_admin !== void 0) {
2730
- const authResult = await requestValidators.authenticate(req);
2731
- if (authResult.error || !authResult.user) {
2732
- throw new Error("Authentication failed");
2733
- }
2734
- if (!authResult.user.super_admin) {
2735
- throw new Error("Only super administrators can modify super_admin status");
2736
- }
2737
- }
2159
+ ]
2738
2160
  };
2739
-
2740
- // src/registry/routes.ts
2741
- import { expressMiddleware } from "@as-integrations/express5";
2742
-
2743
- // src/postgres/core-schema.ts
2744
- var agentMessagesSchema = {
2161
+ var statisticsSchema = {
2745
2162
  name: {
2746
- plural: "agent_messages",
2747
- singular: "agent_message"
2163
+ plural: "tracking",
2164
+ singular: "tracking"
2748
2165
  },
2749
2166
  fields: [
2750
2167
  {
2751
- name: "content",
2168
+ name: "name",
2752
2169
  type: "text"
2753
2170
  },
2754
2171
  {
2755
- name: "title",
2172
+ name: "label",
2756
2173
  type: "text"
2757
2174
  },
2758
2175
  {
2759
- name: "user",
2760
- type: "number"
2176
+ name: "type",
2177
+ type: "enum",
2178
+ enumValues: Object.values(STATISTICS_TYPE_ENUM)
2761
2179
  },
2762
2180
  {
2763
- name: "session",
2764
- type: "text"
2765
- }
2766
- ]
2767
- };
2768
- var agentSessionsSchema = {
2769
- name: {
2770
- plural: "agent_sessions",
2771
- singular: "agent_session"
2772
- },
2773
- fields: [
2774
- {
2775
- name: "agent",
2776
- type: "uuid"
2181
+ name: "total",
2182
+ type: "number"
2777
2183
  },
2778
2184
  {
2779
2185
  name: "user",
2780
- // next auth stores users with id type SERIAL, so we need to use number
2781
2186
  type: "number"
2782
2187
  },
2783
2188
  {
2784
2189
  name: "role",
2785
2190
  type: "uuid"
2786
- },
2787
- {
2788
- name: "title",
2789
- type: "text"
2790
2191
  }
2791
2192
  ]
2792
2193
  };
2793
- var variablesSchema = {
2194
+ var evalResultsSchema = {
2794
2195
  name: {
2795
- plural: "variables",
2796
- singular: "variable"
2196
+ plural: "eval_results",
2197
+ singular: "eval_result"
2797
2198
  },
2798
2199
  fields: [
2799
2200
  {
2800
- name: "name",
2801
- type: "text",
2802
- index: true,
2803
- unique: true
2201
+ name: "input",
2202
+ type: "longText"
2804
2203
  },
2805
2204
  {
2806
- name: "value",
2205
+ name: "output",
2807
2206
  type: "longText"
2808
2207
  },
2809
2208
  {
2810
- name: "encrypted",
2811
- type: "boolean",
2812
- default: false
2813
- }
2814
- ]
2815
- };
2816
- var workflowTemplatesSchema = {
2817
- name: {
2818
- plural: "workflow_templates",
2819
- singular: "workflow_template"
2820
- },
2821
- RBAC: true,
2822
- fields: [
2823
- {
2824
- name: "name",
2825
- type: "text",
2826
- required: true
2209
+ name: "duration",
2210
+ type: "number"
2827
2211
  },
2828
2212
  {
2829
- name: "description",
2213
+ name: "category",
2830
2214
  type: "text"
2831
2215
  },
2832
2216
  {
2833
- name: "owner",
2834
- type: "number",
2835
- required: true
2217
+ name: "metadata",
2218
+ type: "json"
2836
2219
  },
2837
2220
  {
2838
- name: "visibility",
2839
- type: "text",
2840
- required: true
2221
+ name: "result",
2222
+ type: "number"
2841
2223
  },
2842
2224
  {
2843
- name: "shared_user_ids",
2844
- type: "json"
2225
+ name: "agent_id",
2226
+ type: "uuid"
2845
2227
  },
2846
2228
  {
2847
- name: "shared_role_ids",
2848
- type: "json"
2229
+ name: "workflow_id",
2230
+ type: "uuid"
2849
2231
  },
2850
2232
  {
2851
- name: "variables",
2852
- type: "json"
2233
+ name: "eval_type",
2234
+ type: "text"
2853
2235
  },
2854
2236
  {
2855
- name: "steps_json",
2856
- type: "json",
2857
- required: true
2237
+ name: "eval_name",
2238
+ type: "text"
2858
2239
  },
2859
2240
  {
2860
- name: "example_metadata_json",
2861
- type: "json"
2241
+ name: "comment",
2242
+ type: "longText"
2862
2243
  }
2863
2244
  ]
2864
2245
  };
2865
- var agentsSchema = {
2246
+ var jobsSchema = {
2866
2247
  name: {
2867
- plural: "agents",
2868
- singular: "agent"
2248
+ plural: "jobs",
2249
+ singular: "job"
2869
2250
  },
2870
2251
  RBAC: true,
2871
2252
  fields: [
2872
2253
  {
2873
- name: "name",
2254
+ name: "redis",
2874
2255
  type: "text"
2875
2256
  },
2876
2257
  {
2877
- name: "image",
2258
+ name: "session",
2878
2259
  type: "text"
2879
2260
  },
2880
2261
  {
2881
- name: "description",
2262
+ name: "status",
2882
2263
  type: "text"
2883
2264
  },
2884
2265
  {
2885
- name: "providerApiKey",
2266
+ name: "type",
2886
2267
  type: "text"
2887
2268
  },
2888
2269
  {
2889
- name: "extensions",
2890
- type: "json"
2891
- },
2892
- {
2893
- name: "backend",
2894
- type: "text"
2270
+ name: "result",
2271
+ type: "longText"
2895
2272
  },
2896
2273
  {
2897
- name: "type",
2274
+ name: "name",
2898
2275
  type: "text"
2899
2276
  },
2900
2277
  {
2901
- name: "active",
2902
- type: "boolean",
2903
- default: false
2278
+ name: "agent",
2279
+ type: "uuid"
2904
2280
  },
2905
2281
  {
2906
- name: "tools",
2907
- type: "json"
2908
- }
2909
- ]
2910
- };
2911
- var usersSchema = {
2912
- name: {
2913
- plural: "users",
2914
- singular: "user"
2915
- },
2916
- fields: [
2917
- {
2918
- name: "id",
2919
- type: "number",
2920
- index: true
2282
+ name: "workflow",
2283
+ type: "uuid"
2921
2284
  },
2922
2285
  {
2923
- name: "favourite_agents",
2924
- type: "json"
2286
+ name: "user",
2287
+ // next auth stores users with id type SERIAL, so we need to use number
2288
+ type: "number"
2925
2289
  },
2926
2290
  {
2927
- name: "firstname",
2928
- type: "text"
2291
+ name: "item",
2292
+ type: "uuid"
2929
2293
  },
2930
2294
  {
2931
- name: "name",
2932
- type: "text"
2295
+ name: "steps",
2296
+ type: "number"
2933
2297
  },
2934
2298
  {
2935
- name: "lastname",
2936
- type: "text"
2299
+ name: "inputs",
2300
+ type: "json"
2937
2301
  },
2938
2302
  {
2939
- name: "email",
2940
- type: "text",
2941
- index: true
2303
+ name: "finished_at",
2304
+ type: "date"
2942
2305
  },
2943
2306
  {
2944
- name: "temporary_token",
2945
- type: "text"
2946
- },
2307
+ name: "duration",
2308
+ type: "number"
2309
+ }
2310
+ ]
2311
+ };
2312
+ var rbacSchema = {
2313
+ name: {
2314
+ plural: "rbac",
2315
+ singular: "rbac"
2316
+ },
2317
+ graphql: false,
2318
+ fields: [
2947
2319
  {
2948
- name: "type",
2320
+ name: "entity",
2949
2321
  type: "text",
2950
- index: true
2951
- },
2952
- {
2953
- name: "profile_image",
2954
- type: "text"
2955
- },
2956
- {
2957
- name: "super_admin",
2958
- type: "boolean",
2959
- default: false
2960
- },
2961
- {
2962
- name: "status",
2963
- type: "text"
2964
- },
2965
- {
2966
- name: "emailVerified",
2967
- type: "text"
2322
+ required: true
2968
2323
  },
2969
2324
  {
2970
- name: "apikey",
2971
- type: "text"
2325
+ name: "access_type",
2326
+ type: "text",
2327
+ required: true
2972
2328
  },
2973
2329
  {
2974
- name: "last_used",
2975
- type: "date"
2330
+ name: "target_resource_id",
2331
+ type: "uuid",
2332
+ required: true
2976
2333
  },
2977
2334
  {
2978
- name: "password",
2979
- type: "text"
2335
+ name: "role_id",
2336
+ type: "uuid"
2980
2337
  },
2981
2338
  {
2982
- name: "anthropic_token",
2983
- type: "text"
2339
+ name: "user_id",
2340
+ type: "number"
2984
2341
  },
2985
2342
  {
2986
- name: "role",
2987
- type: "uuid"
2343
+ name: "rights",
2344
+ type: "text",
2345
+ required: true
2346
+ }
2347
+ ]
2348
+ };
2349
+ var addRBACfields = (schema) => {
2350
+ if (schema.RBAC) {
2351
+ console.log(`[EXULU] Adding rights_mode field to ${schema.name.plural} table.`);
2352
+ schema.fields.push({
2353
+ name: "rights_mode",
2354
+ type: "text",
2355
+ required: false,
2356
+ default: "private"
2357
+ });
2358
+ schema.fields.push({
2359
+ name: "created_by",
2360
+ type: "number",
2361
+ required: true,
2362
+ default: 0
2363
+ });
2364
+ }
2365
+ return schema;
2366
+ };
2367
+ var coreSchemas = {
2368
+ get: () => {
2369
+ return {
2370
+ agentsSchema: () => addRBACfields(agentsSchema),
2371
+ agentMessagesSchema: () => addRBACfields(agentMessagesSchema),
2372
+ agentSessionsSchema: () => addRBACfields(agentSessionsSchema),
2373
+ usersSchema: () => addRBACfields(usersSchema),
2374
+ rolesSchema: () => addRBACfields(rolesSchema),
2375
+ statisticsSchema: () => addRBACfields(statisticsSchema),
2376
+ evalResultsSchema: () => addRBACfields(evalResultsSchema),
2377
+ jobsSchema: () => addRBACfields(jobsSchema),
2378
+ variablesSchema: () => addRBACfields(variablesSchema),
2379
+ rbacSchema: () => addRBACfields(rbacSchema),
2380
+ workflowTemplatesSchema: () => addRBACfields(workflowTemplatesSchema)
2381
+ };
2382
+ }
2383
+ };
2384
+
2385
+ // src/registry/utils/graphql.ts
2386
+ var GraphQLDate = new GraphQLScalarType({
2387
+ name: "Date",
2388
+ description: "Date custom scalar type",
2389
+ serialize(value) {
2390
+ if (value instanceof Date) {
2391
+ return value.toISOString();
2392
+ }
2393
+ if (typeof value === "number") {
2394
+ return new Date(value).toISOString();
2395
+ }
2396
+ if (typeof value === "string") {
2397
+ return new Date(value).toISOString();
2398
+ }
2399
+ return value;
2400
+ },
2401
+ parseValue(value) {
2402
+ if (typeof value === "string") {
2403
+ return new Date(value);
2404
+ }
2405
+ if (typeof value === "number") {
2406
+ return new Date(value);
2407
+ }
2408
+ return value;
2409
+ },
2410
+ parseLiteral(ast) {
2411
+ if (ast.kind === Kind.STRING) {
2412
+ return new Date(ast.value);
2413
+ }
2414
+ if (ast.kind === Kind.INT) {
2415
+ return new Date(parseInt(ast.value, 10));
2416
+ }
2417
+ return null;
2418
+ }
2419
+ });
2420
+ var map = (field) => {
2421
+ let type;
2422
+ switch (field.type) {
2423
+ case "text":
2424
+ case "shortText":
2425
+ case "longText":
2426
+ case "code":
2427
+ type = "String";
2428
+ break;
2429
+ case "enum":
2430
+ type = field.enumValues ? `${field.name}Enum` : "String";
2431
+ break;
2432
+ case "number":
2433
+ type = "Float";
2434
+ break;
2435
+ case "boolean":
2436
+ type = "Boolean";
2437
+ break;
2438
+ case "json":
2439
+ type = "JSON";
2440
+ break;
2441
+ case "date":
2442
+ type = "Date";
2443
+ break;
2444
+ default:
2445
+ type = "String";
2446
+ }
2447
+ return type;
2448
+ };
2449
+ function createTypeDefs(table) {
2450
+ const enumDefs = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
2451
+ const enumValues = field.enumValues.map((value) => {
2452
+ const sanitized = String(value).replace(/[^a-zA-Z0-9_]/g, "_").replace(/^[0-9]/, "_$&").toUpperCase();
2453
+ return ` ${sanitized}`;
2454
+ }).join("\n");
2455
+ return `
2456
+ enum ${field.name}Enum {
2457
+ ${enumValues}
2458
+ }`;
2459
+ }).join("\n");
2460
+ let fields = table.fields.map((field) => {
2461
+ let type;
2462
+ type = map(field);
2463
+ const required = field.required ? "!" : "";
2464
+ return ` ${field.name}: ${type}${required}`;
2465
+ });
2466
+ if (table.name.singular === "agent") {
2467
+ fields.push(" providerName: String");
2468
+ fields.push(" modelName: String");
2469
+ fields.push(" rateLimit: RateLimiterRule");
2470
+ fields.push(" streaming: Boolean");
2471
+ fields.push(" capabilities: AgentCapabilities");
2472
+ fields.push(" slug: String");
2473
+ }
2474
+ const rbacField = table.RBAC ? " RBAC: RBACData" : "";
2475
+ const typeDef = `
2476
+ type ${table.name.singular} {
2477
+ ${fields.join("\n")}
2478
+ ${table.fields.find((field) => field.name === "id") ? "" : "id: ID!"}
2479
+ ${rbacField}
2480
+ }
2481
+ `;
2482
+ const rbacInputField = table.RBAC ? " RBAC: RBACInput" : "";
2483
+ const inputDef = `
2484
+ input ${table.name.singular}Input {
2485
+ ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
2486
+ ${rbacInputField}
2487
+ }
2488
+ `;
2489
+ return enumDefs + typeDef + inputDef;
2490
+ }
2491
+ function createFilterTypeDefs(table) {
2492
+ const fieldFilters = table.fields.map((field) => {
2493
+ let type;
2494
+ if (field.type === "enum" && field.enumValues) {
2495
+ type = `${field.name}Enum`;
2496
+ } else {
2497
+ type = map(field);
2498
+ }
2499
+ return `
2500
+ ${field.name}: FilterOperator${type}`;
2501
+ });
2502
+ const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2503
+ const enumFilterOperators = table.fields.filter((field) => field.type === "enum" && field.enumValues).map((field) => {
2504
+ const enumTypeName = `${field.name}Enum`;
2505
+ return `
2506
+ input FilterOperator${enumTypeName} {
2507
+ eq: ${enumTypeName}
2508
+ ne: ${enumTypeName}
2509
+ in: [${enumTypeName}]
2510
+ and: [FilterOperator${enumTypeName}]
2511
+ or: [FilterOperator${enumTypeName}]
2512
+ }`;
2513
+ }).join("\n");
2514
+ const operatorTypes = `
2515
+ input FilterOperatorString {
2516
+ eq: String
2517
+ ne: String
2518
+ in: [String]
2519
+ contains: String
2520
+ and: [FilterOperatorString]
2521
+ or: [FilterOperatorString]
2522
+ }
2523
+
2524
+ input FilterOperatorDate {
2525
+ lte: Date
2526
+ gte: Date
2527
+ and: [FilterOperatorDate]
2528
+ or: [FilterOperatorDate]
2529
+ }
2530
+
2531
+ input FilterOperatorFloat {
2532
+ eq: Float
2533
+ ne: Float
2534
+ in: [Float]
2535
+ and: [FilterOperatorFloat]
2536
+ or: [FilterOperatorFloat]
2537
+ }
2538
+
2539
+ input FilterOperatorBoolean {
2540
+ eq: Boolean
2541
+ ne: Boolean
2542
+ in: [Boolean]
2543
+ and: [FilterOperatorBoolean]
2544
+ or: [FilterOperatorBoolean]
2545
+ }
2546
+
2547
+ input FilterOperatorJSON {
2548
+ eq: JSON
2549
+ ne: JSON
2550
+ in: [JSON]
2551
+ }
2552
+
2553
+ input SortBy {
2554
+ field: String!
2555
+ direction: SortDirection!
2556
+ }
2557
+
2558
+ enum SortDirection {
2559
+ ASC
2560
+ DESC
2561
+ }
2562
+
2563
+ ${enumFilterOperators}
2564
+
2565
+ input Filter${tableNameSingularUpperCaseFirst} {
2566
+ ${fieldFilters.join("\n")}
2567
+ }`;
2568
+ return operatorTypes;
2569
+ }
2570
+ var getRequestedFields = (info) => {
2571
+ const selections = info.operation.selectionSet.selections[0].selectionSet.selections;
2572
+ const itemsSelection = selections.find((s) => s.name.value === "items");
2573
+ const fields = itemsSelection ? Object.keys(itemsSelection.selectionSet.selections.reduce((acc, field) => {
2574
+ acc[field.name.value] = true;
2575
+ return acc;
2576
+ }, {})) : Object.keys(selections.reduce((acc, field) => {
2577
+ acc[field.name.value] = true;
2578
+ return acc;
2579
+ }, {}));
2580
+ return fields.filter((field) => field !== "pageInfo" && field !== "items" && field !== "RBAC");
2581
+ };
2582
+ var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRbacRecords) => {
2583
+ const { users = [], roles = [] } = rbacData;
2584
+ if (!existingRbacRecords) {
2585
+ existingRbacRecords = await db3.from("rbac").where({
2586
+ entity: entityName,
2587
+ target_resource_id: resourceId
2588
+ }).select("*");
2589
+ }
2590
+ const newUserRecords = new Set(users.map((u) => `${u.id}:${u.rights}`));
2591
+ const newRoleRecords = new Set(roles.map((r) => `${r.id}:${r.rights}`));
2592
+ const existingUserRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "User").map((r) => `${r.user_id}:${r.rights}`));
2593
+ const existingRoleRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "Role").map((r) => `${r.role_id}:${r.rights}`));
2594
+ const usersToCreate = users.filter((u) => !existingUserRecords.has(`${u.id}:${u.rights}`));
2595
+ const rolesToCreate = roles.filter((r) => !existingRoleRecords.has(`${r.id}:${r.rights}`));
2596
+ const usersToRemove = existingRbacRecords.filter((r) => r.access_type === "User" && !newUserRecords.has(`${r.user_id}:${r.rights}`));
2597
+ const rolesToRemove = existingRbacRecords.filter((r) => r.access_type === "Role" && !newRoleRecords.has(`${r.role_id}:${r.rights}`));
2598
+ if (usersToRemove.length > 0) {
2599
+ await db3.from("rbac").whereIn("id", usersToRemove.map((r) => r.id)).del();
2600
+ }
2601
+ if (rolesToRemove.length > 0) {
2602
+ await db3.from("rbac").whereIn("id", rolesToRemove.map((r) => r.id)).del();
2603
+ }
2604
+ const recordsToInsert = [];
2605
+ usersToCreate.forEach((user) => {
2606
+ recordsToInsert.push({
2607
+ entity: entityName,
2608
+ access_type: "User",
2609
+ target_resource_id: resourceId,
2610
+ user_id: user.id,
2611
+ rights: user.rights,
2612
+ createdAt: /* @__PURE__ */ new Date(),
2613
+ updatedAt: /* @__PURE__ */ new Date()
2614
+ });
2615
+ });
2616
+ rolesToCreate.forEach((role) => {
2617
+ recordsToInsert.push({
2618
+ entity: entityName,
2619
+ access_type: "Role",
2620
+ target_resource_id: resourceId,
2621
+ role_id: role.id,
2622
+ rights: role.rights,
2623
+ createdAt: /* @__PURE__ */ new Date(),
2624
+ updatedAt: /* @__PURE__ */ new Date()
2625
+ });
2626
+ });
2627
+ if (recordsToInsert.length > 0) {
2628
+ await db3.from("rbac").insert(recordsToInsert);
2629
+ }
2630
+ };
2631
+ function createMutations(table, agents, contexts, tools) {
2632
+ const tableNamePlural = table.name.plural.toLowerCase();
2633
+ const validateWriteAccess = async (id, context) => {
2634
+ try {
2635
+ const { db: db3, req, user } = context;
2636
+ if (user.super_admin === true) {
2637
+ return true;
2638
+ }
2639
+ if (!user.role || !(table.name.plural === "agents" && user.role.agents === "write") && !(table.name.plural === "workflow_templates" && user.role.workflows === "write") && !(table.name.plural === "variables" && user.role.variables === "write") && !(table.name.plural === "users" && user.role.users === "write")) {
2640
+ console.error("Access control error: no role found for current user or no access to entity type.");
2641
+ throw new Error("Access control error: no role found for current user or no access to entity type.");
2642
+ }
2643
+ const hasRBAC = table.RBAC === true;
2644
+ if (!hasRBAC) {
2645
+ return true;
2646
+ }
2647
+ const record = await db3.from(tableNamePlural).select(["rights_mode", "created_by"]).where({ id }).first();
2648
+ if (!record) {
2649
+ throw new Error("Record not found");
2650
+ }
2651
+ if (tableNamePlural === "jobs") {
2652
+ if (!user.super_admin && record.created_by !== user.id) {
2653
+ throw new Error("You are not authorized to edit this record");
2654
+ }
2655
+ }
2656
+ if (record.rights_mode === "public") {
2657
+ return true;
2658
+ }
2659
+ if (record.rights_mode === "private") {
2660
+ if (record.created_by === user.id) {
2661
+ return true;
2662
+ }
2663
+ throw new Error("Only the creator can edit this private record");
2664
+ }
2665
+ if (record.rights_mode === "users") {
2666
+ const rbacRecord = await db3.from("rbac").where({
2667
+ entity: table.name.singular,
2668
+ target_resource_id: id,
2669
+ access_type: "User",
2670
+ user_id: user.id,
2671
+ rights: "write"
2672
+ }).first();
2673
+ if (rbacRecord) {
2674
+ return true;
2675
+ }
2676
+ throw new Error("Insufficient user permissions to edit this record");
2677
+ }
2678
+ if (record.rights_mode === "roles" && user.role) {
2679
+ const rbacRecord = await db3.from("rbac").where({
2680
+ entity: table.name.singular,
2681
+ target_resource_id: id,
2682
+ access_type: "Role",
2683
+ role_id: user.role,
2684
+ rights: "write"
2685
+ }).first();
2686
+ if (rbacRecord) {
2687
+ return true;
2688
+ }
2689
+ throw new Error("Insufficient role permissions to edit this record");
2690
+ }
2691
+ throw new Error("Insufficient permissions to edit this record");
2692
+ } catch (error) {
2693
+ console.error("Write access validation error:", error);
2694
+ throw error;
2988
2695
  }
2989
- ]
2990
- };
2991
- var rolesSchema = {
2992
- name: {
2993
- plural: "roles",
2994
- singular: "role"
2995
- },
2996
- fields: [
2997
- {
2998
- name: "name",
2999
- type: "text"
3000
- },
3001
- {
3002
- name: agentsSchema.name.plural,
3003
- type: "text"
3004
- // write | read access to agents
3005
- },
3006
- {
3007
- name: "api",
3008
- type: "text"
2696
+ };
2697
+ return {
2698
+ [`${tableNamePlural}CreateOne`]: async (_, args, context, info) => {
2699
+ const { db: db3 } = context;
2700
+ const requestedFields = getRequestedFields(info);
2701
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2702
+ let { input } = args;
2703
+ const rbacData = input.RBAC;
2704
+ delete input.RBAC;
2705
+ delete input.created_by;
2706
+ input = encryptSensitiveFields(input);
2707
+ if (table.RBAC) {
2708
+ input.created_by = context.user.id;
2709
+ }
2710
+ if (table.name.singular === "user" && context.user?.super_admin !== true) {
2711
+ throw new Error("You are not authorized to create users");
2712
+ }
2713
+ if (table.name.singular === "user" && input.password) {
2714
+ input.password = await bcrypt2.hash(input.password, 10);
2715
+ }
2716
+ Object.keys(input).forEach((key) => {
2717
+ if (table.fields.find((field) => field.name === key)?.type === "json") {
2718
+ if (typeof input[key] === "object" || Array.isArray(input[key])) {
2719
+ input[key] = JSON.stringify(input[key]);
2720
+ }
2721
+ }
2722
+ });
2723
+ const results = await db3(tableNamePlural).insert({
2724
+ ...input,
2725
+ ...table.RBAC ? { rights_mode: "private" } : {},
2726
+ createdAt: /* @__PURE__ */ new Date(),
2727
+ updatedAt: /* @__PURE__ */ new Date()
2728
+ }).returning(sanitizedFields);
2729
+ if (table.RBAC && rbacData && results[0]) {
2730
+ await handleRBACUpdate(db3, table.name.singular, results[0].id, rbacData, []);
2731
+ }
2732
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0] });
3009
2733
  },
3010
- {
3011
- name: "workflows",
3012
- type: "text"
3013
- // write | read access to workflows
2734
+ [`${tableNamePlural}UpdateOne`]: async (_, args, context, info) => {
2735
+ const { db: db3, req } = context;
2736
+ let { where, input } = args;
2737
+ await validateCreateOrRemoveSuperAdminPermission(tableNamePlural, input, req);
2738
+ if (where.id) {
2739
+ await validateWriteAccess(where.id, context);
2740
+ }
2741
+ const rbacData = input.RBAC;
2742
+ delete input.RBAC;
2743
+ delete input.created_by;
2744
+ input = encryptSensitiveFields(input);
2745
+ Object.keys(input).forEach((key) => {
2746
+ if (table.fields.find((field) => field.name === key)?.type === "json") {
2747
+ if (typeof input[key] === "object" || Array.isArray(input[key])) {
2748
+ input[key] = JSON.stringify(input[key]);
2749
+ }
2750
+ }
2751
+ });
2752
+ await db3(tableNamePlural).where(where).update({
2753
+ ...input,
2754
+ updatedAt: /* @__PURE__ */ new Date()
2755
+ });
2756
+ if (table.RBAC && rbacData && where.id) {
2757
+ const existingRbacRecords = await db3.from("rbac").where({
2758
+ entity: table.name.singular,
2759
+ target_resource_id: where.id
2760
+ }).select("*");
2761
+ await handleRBACUpdate(db3, table.name.singular, where.id, rbacData, existingRbacRecords);
2762
+ }
2763
+ const requestedFields = getRequestedFields(info);
2764
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2765
+ const result = await db3.from(tableNamePlural).select(sanitizedFields).where(where).first();
2766
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
3014
2767
  },
3015
- {
3016
- name: variablesSchema.name.plural,
3017
- type: "text"
3018
- // write | read access to variables
2768
+ [`${tableNamePlural}UpdateOneById`]: async (_, args, context, info) => {
2769
+ const { db: db3, req } = context;
2770
+ let { id, input } = args;
2771
+ await validateCreateOrRemoveSuperAdminPermission(tableNamePlural, input, req);
2772
+ await validateWriteAccess(id, context);
2773
+ const rbacData = input.RBAC;
2774
+ delete input.RBAC;
2775
+ delete input.created_by;
2776
+ input = encryptSensitiveFields(input);
2777
+ Object.keys(input).forEach((key) => {
2778
+ if (table.fields.find((field) => field.name === key)?.type === "json") {
2779
+ if (typeof input[key] === "object" || Array.isArray(input[key])) {
2780
+ input[key] = JSON.stringify(input[key]);
2781
+ }
2782
+ }
2783
+ });
2784
+ await db3(tableNamePlural).where({ id }).update({
2785
+ ...input,
2786
+ updatedAt: /* @__PURE__ */ new Date()
2787
+ });
2788
+ if (table.RBAC && rbacData) {
2789
+ const existingRbacRecords = await db3.from("rbac").where({
2790
+ entity: table.name.singular,
2791
+ target_resource_id: id
2792
+ }).select("*");
2793
+ await handleRBACUpdate(db3, table.name.singular, id, rbacData, existingRbacRecords);
2794
+ }
2795
+ const requestedFields = getRequestedFields(info);
2796
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2797
+ const result = await db3.from(tableNamePlural).select(sanitizedFields).where({ id }).first();
2798
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
3019
2799
  },
3020
- {
3021
- name: usersSchema.name.plural,
3022
- type: "text"
3023
- // write | read access to users
2800
+ [`${tableNamePlural}RemoveOneById`]: async (_, args, context, info) => {
2801
+ const { id } = args;
2802
+ const { db: db3 } = context;
2803
+ const requestedFields = getRequestedFields(info);
2804
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2805
+ const result = await db3.from(tableNamePlural).select(sanitizedFields).where({ id }).first();
2806
+ if (!result) {
2807
+ throw new Error("Record not found");
2808
+ }
2809
+ await db3(tableNamePlural).where({ id }).del();
2810
+ if (table.RBAC) {
2811
+ await db3.from("rbac").where({
2812
+ entity: table.name.singular,
2813
+ target_resource_id: id
2814
+ }).del();
2815
+ }
2816
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
3024
2817
  }
3025
- ]
2818
+ };
2819
+ }
2820
+ var applyAccessControl = (table, user, query) => {
2821
+ console.log("table", table);
2822
+ const tableNamePlural = table.name.plural.toLowerCase();
2823
+ if (!user.super_admin && table.name.plural === "jobs") {
2824
+ query = query.where("created_by", user.id);
2825
+ return query;
2826
+ }
2827
+ const hasRBAC = table.RBAC === true;
2828
+ if (!hasRBAC) {
2829
+ return query;
2830
+ }
2831
+ if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2832
+ return query;
2833
+ }
2834
+ if (!user.role || !(table.name.plural === "agents" && (user.role.agents === "read" || user.role.agents === "write")) && !(table.name.plural === "workflow_templates" && (user.role.workflows === "read" || user.role.workflows === "write")) && !(table.name.plural === "variables" && (user.role.variables === "read" || user.role.variables === "write")) && !(table.name.plural === "users" && (user.role.users === "read" || user.role.users === "write"))) {
2835
+ console.error("Access control error: no role found or no access to entity type.");
2836
+ return query.where("1", "=", "0");
2837
+ }
2838
+ try {
2839
+ query = query.where(function() {
2840
+ this.where("rights_mode", "public");
2841
+ this.orWhere("created_by", user.id);
2842
+ this.orWhere(function() {
2843
+ this.where("rights_mode", "users").whereExists(function() {
2844
+ this.select("*").from("rbac").whereRaw("rbac.target_resource_id = " + tableNamePlural + ".id").where("rbac.entity", table.name.singular).where("rbac.access_type", "User").where("rbac.user_id", user.id);
2845
+ });
2846
+ });
2847
+ if (user.role) {
2848
+ console.log("user.role", user.role);
2849
+ this.orWhere(function() {
2850
+ this.where("rights_mode", "roles").whereExists(function() {
2851
+ this.select("*").from("rbac").whereRaw("rbac.target_resource_id = " + tableNamePlural + ".id").where("rbac.entity", table.name.singular).where("rbac.access_type", "Role").where("rbac.role_id", user.role);
2852
+ });
2853
+ });
2854
+ }
2855
+ });
2856
+ } catch (error) {
2857
+ console.error("Access control error:", error);
2858
+ return query.where("1", "=", "0");
2859
+ }
2860
+ return query;
3026
2861
  };
3027
- var statisticsSchema = {
3028
- name: {
3029
- plural: "tracking",
3030
- singular: "tracking"
3031
- },
3032
- fields: [
3033
- {
3034
- name: "name",
3035
- type: "text"
3036
- },
3037
- {
3038
- name: "label",
3039
- type: "text"
3040
- },
3041
- {
3042
- name: "type",
3043
- type: "enum",
3044
- enumValues: Object.values(STATISTICS_TYPE_ENUM)
3045
- },
3046
- {
3047
- name: "total",
3048
- type: "number"
3049
- },
3050
- {
3051
- name: "user",
3052
- type: "number"
3053
- },
3054
- {
3055
- name: "role",
3056
- type: "uuid"
3057
- }
3058
- ]
2862
+ var converOperatorToQuery = (query, fieldName, operators) => {
2863
+ if (operators.eq !== void 0) {
2864
+ query = query.where(fieldName, operators.eq);
2865
+ }
2866
+ if (operators.ne !== void 0) {
2867
+ query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2868
+ }
2869
+ if (operators.in !== void 0) {
2870
+ query = query.whereIn(fieldName, operators.in);
2871
+ }
2872
+ if (operators.contains !== void 0) {
2873
+ query = query.where(fieldName, "like", `%${operators.contains}%`);
2874
+ }
2875
+ if (operators.lte !== void 0) {
2876
+ query = query.where(fieldName, "<=", operators.lte);
2877
+ }
2878
+ if (operators.gte !== void 0) {
2879
+ query = query.where(fieldName, ">=", operators.gte);
2880
+ }
2881
+ return query;
2882
+ };
2883
+ var backendAgentFields = [
2884
+ "providerName",
2885
+ "modelName",
2886
+ "slug",
2887
+ "rateLimit",
2888
+ "streaming",
2889
+ "capabilities"
2890
+ ];
2891
+ var removeAgentFields = (requestedFields) => {
2892
+ const filtered = requestedFields.filter((field) => !backendAgentFields.includes(field));
2893
+ filtered.push("backend");
2894
+ return filtered;
2895
+ };
2896
+ var addAgentFields = (requestedFields, agents, result) => {
2897
+ let backend = agents.find((a) => a.id === result?.backend);
2898
+ if (requestedFields.includes("providerName")) {
2899
+ result.providerName = backend?.providerName || "";
2900
+ }
2901
+ if (requestedFields.includes("modelName")) {
2902
+ result.modelName = backend?.modelName || "";
2903
+ }
2904
+ if (requestedFields.includes("slug")) {
2905
+ result.slug = backend?.slug || "";
2906
+ }
2907
+ if (requestedFields.includes("rateLimit")) {
2908
+ result.rateLimit = backend?.rateLimit || "";
2909
+ }
2910
+ if (requestedFields.includes("streaming")) {
2911
+ result.streaming = backend?.streaming || false;
2912
+ }
2913
+ if (requestedFields.includes("capabilities")) {
2914
+ result.capabilities = backend?.capabilities || [];
2915
+ }
2916
+ if (!requestedFields.includes("backend")) {
2917
+ delete result.backend;
2918
+ }
2919
+ return result;
3059
2920
  };
3060
- var evalResultsSchema = {
3061
- name: {
3062
- plural: "eval_results",
3063
- singular: "eval_result"
3064
- },
3065
- fields: [
3066
- {
3067
- name: "input",
3068
- type: "longText"
3069
- },
3070
- {
3071
- name: "output",
3072
- type: "longText"
3073
- },
3074
- {
3075
- name: "duration",
3076
- type: "number"
3077
- },
3078
- {
3079
- name: "category",
3080
- type: "text"
3081
- },
3082
- {
3083
- name: "metadata",
3084
- type: "json"
3085
- },
3086
- {
3087
- name: "result",
3088
- type: "number"
3089
- },
3090
- {
3091
- name: "agent_id",
3092
- type: "uuid"
3093
- },
3094
- {
3095
- name: "workflow_id",
3096
- type: "uuid"
3097
- },
3098
- {
3099
- name: "eval_type",
3100
- type: "text"
3101
- },
3102
- {
3103
- name: "eval_name",
3104
- type: "text"
3105
- },
3106
- {
3107
- name: "comment",
3108
- type: "longText"
3109
- }
3110
- ]
2921
+ var sanitizeRequestedFields = (table, requestedFields) => {
2922
+ if (table.name.singular === "agent") {
2923
+ requestedFields = removeAgentFields(requestedFields);
2924
+ }
2925
+ if (!requestedFields.includes("id")) {
2926
+ requestedFields.push("id");
2927
+ }
2928
+ return requestedFields;
3111
2929
  };
3112
- var jobsSchema = {
3113
- name: {
3114
- plural: "jobs",
3115
- singular: "job"
3116
- },
3117
- RBAC: true,
3118
- fields: [
3119
- {
3120
- name: "redis",
3121
- type: "text"
3122
- },
3123
- {
3124
- name: "session",
3125
- type: "text"
3126
- },
3127
- {
3128
- name: "status",
3129
- type: "text"
3130
- },
3131
- {
3132
- name: "type",
3133
- type: "text"
3134
- },
3135
- {
3136
- name: "result",
3137
- type: "longText"
3138
- },
3139
- {
3140
- name: "name",
3141
- type: "text"
3142
- },
3143
- {
3144
- name: "agent",
3145
- type: "uuid"
3146
- },
3147
- {
3148
- name: "workflow",
3149
- type: "uuid"
3150
- },
3151
- {
3152
- name: "user",
3153
- // next auth stores users with id type SERIAL, so we need to use number
3154
- type: "number"
3155
- },
3156
- {
3157
- name: "item",
3158
- type: "uuid"
3159
- },
3160
- {
3161
- name: "steps",
3162
- type: "number"
3163
- },
3164
- {
3165
- name: "inputs",
3166
- type: "json"
3167
- },
3168
- {
3169
- name: "finished_at",
3170
- type: "date"
3171
- },
3172
- {
3173
- name: "duration",
3174
- type: "number"
2930
+ var finalizeRequestedFields = ({
2931
+ table,
2932
+ requestedFields,
2933
+ agents,
2934
+ contexts,
2935
+ tools,
2936
+ result
2937
+ }) => {
2938
+ if (!result) {
2939
+ return result;
2940
+ }
2941
+ if (Array.isArray(result)) {
2942
+ result = result.map((item) => {
2943
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item });
2944
+ });
2945
+ } else {
2946
+ if (table.name.singular === "agent") {
2947
+ result = addAgentFields(requestedFields, agents, result);
2948
+ if (!requestedFields.includes("backend")) {
2949
+ delete result.backend;
2950
+ }
3175
2951
  }
3176
- ]
2952
+ }
2953
+ return result;
3177
2954
  };
3178
- var rbacSchema = {
3179
- name: {
3180
- plural: "rbac",
3181
- singular: "rbac"
3182
- },
3183
- graphql: false,
3184
- fields: [
3185
- {
3186
- name: "entity",
3187
- type: "text",
3188
- required: true
3189
- },
3190
- {
3191
- name: "access_type",
3192
- type: "text",
3193
- required: true
2955
+ function createQueries(table, agents, tools, contexts) {
2956
+ const tableNamePlural = table.name.plural.toLowerCase();
2957
+ const tableNameSingular = table.name.singular.toLowerCase();
2958
+ const applyFilters = (query, filters) => {
2959
+ filters.forEach((filter) => {
2960
+ Object.entries(filter).forEach(([fieldName, operators]) => {
2961
+ if (operators) {
2962
+ if (operators.and !== void 0) {
2963
+ console.log("operators.and", operators.and);
2964
+ operators.and.forEach((operator) => {
2965
+ query = converOperatorToQuery(query, fieldName, operator);
2966
+ });
2967
+ }
2968
+ if (operators.or !== void 0) {
2969
+ operators.or.forEach((operator) => {
2970
+ query = converOperatorToQuery(query, fieldName, operator);
2971
+ });
2972
+ }
2973
+ query = converOperatorToQuery(query, fieldName, operators);
2974
+ console.log("query", query);
2975
+ }
2976
+ });
2977
+ });
2978
+ return query;
2979
+ };
2980
+ const applySorting = (query, sort) => {
2981
+ if (sort) {
2982
+ query = query.orderBy(sort.field, sort.direction.toLowerCase());
2983
+ }
2984
+ return query;
2985
+ };
2986
+ return {
2987
+ [`${tableNameSingular}ById`]: async (_, args, context, info) => {
2988
+ const { db: db3 } = context;
2989
+ const requestedFields = getRequestedFields(info);
2990
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2991
+ let query = db3.from(tableNamePlural).select(sanitizedFields).where({ id: args.id });
2992
+ query = applyAccessControl(table, context.user, query);
2993
+ let result = await query.first();
2994
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
3194
2995
  },
3195
- {
3196
- name: "target_resource_id",
3197
- type: "uuid",
3198
- required: true
2996
+ [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2997
+ const { db: db3 } = context;
2998
+ const requestedFields = getRequestedFields(info);
2999
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
3000
+ let query = db3.from(tableNamePlural).select(sanitizedFields).whereIn("id", args.ids);
3001
+ query = applyAccessControl(table, context.user, query);
3002
+ let result = await query;
3003
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
3199
3004
  },
3200
- {
3201
- name: "role_id",
3202
- type: "uuid"
3005
+ [`${tableNameSingular}One`]: async (_, args, context, info) => {
3006
+ const { filters = [], sort } = args;
3007
+ const { db: db3 } = context;
3008
+ const requestedFields = getRequestedFields(info);
3009
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
3010
+ let query = db3.from(tableNamePlural).select(sanitizedFields);
3011
+ query = applyFilters(query, filters);
3012
+ query = applyAccessControl(table, context.user, query);
3013
+ query = applySorting(query, sort);
3014
+ let result = await query.first();
3015
+ return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result });
3203
3016
  },
3204
- {
3205
- name: "user_id",
3206
- type: "number"
3017
+ [`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
3018
+ const { limit = 10, page = 0, filters = [], sort } = args;
3019
+ const { db: db3 } = context;
3020
+ let countQuery = db3(tableNamePlural);
3021
+ countQuery = applyFilters(countQuery, filters);
3022
+ countQuery = applyAccessControl(table, context.user, countQuery);
3023
+ console.log("countQuery", countQuery);
3024
+ const countResult = await countQuery.count("* as count");
3025
+ const itemCount = Number(countResult[0]?.count || 0);
3026
+ const pageCount = Math.ceil(itemCount / limit);
3027
+ const currentPage = page;
3028
+ const hasPreviousPage = currentPage > 1;
3029
+ const hasNextPage = currentPage < pageCount - 1;
3030
+ let dataQuery = db3(tableNamePlural);
3031
+ dataQuery = applyFilters(dataQuery, filters);
3032
+ dataQuery = applyAccessControl(table, context.user, dataQuery);
3033
+ const requestedFields = getRequestedFields(info);
3034
+ dataQuery = applySorting(dataQuery, sort);
3035
+ if (page > 1) {
3036
+ dataQuery = dataQuery.offset((page - 1) * limit);
3037
+ }
3038
+ const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
3039
+ let items = await dataQuery.select(sanitizedFields).limit(limit);
3040
+ return {
3041
+ pageInfo: {
3042
+ pageCount,
3043
+ itemCount,
3044
+ currentPage,
3045
+ hasPreviousPage,
3046
+ hasNextPage
3047
+ },
3048
+ items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items })
3049
+ };
3207
3050
  },
3208
- {
3209
- name: "rights",
3210
- type: "text",
3211
- required: true
3051
+ // Add generic statistics query for all tables
3052
+ [`${tableNamePlural}Statistics`]: async (_, args, context, info) => {
3053
+ const { filters = [], groupBy } = args;
3054
+ const { db: db3 } = context;
3055
+ let query = db3(tableNamePlural);
3056
+ query = applyFilters(query, filters);
3057
+ query = applyAccessControl(table, context.user, query);
3058
+ if (groupBy) {
3059
+ query = query.select(groupBy).groupBy(groupBy);
3060
+ if (tableNamePlural === "tracking") {
3061
+ query = query.sum("total as count");
3062
+ } else {
3063
+ query = query.count("* as count");
3064
+ }
3065
+ const results = await query;
3066
+ console.log("!!! results !!!", results);
3067
+ return results.map((r) => ({
3068
+ group: r[groupBy],
3069
+ count: r.count ? Number(r.count) : 0
3070
+ }));
3071
+ } else {
3072
+ if (tableNamePlural === "tracking") {
3073
+ query = query.sum("total as count");
3074
+ const [{ count }] = await query.sum("total as count");
3075
+ console.log("!!! count !!!", count);
3076
+ return [{
3077
+ group: "total",
3078
+ count: count ? Number(count) : 0
3079
+ }];
3080
+ } else {
3081
+ const [{ count }] = await query.count("* as count");
3082
+ return [{
3083
+ group: "total",
3084
+ count: count ? Number(count) : 0
3085
+ }];
3086
+ }
3087
+ }
3212
3088
  }
3213
- ]
3089
+ };
3090
+ }
3091
+ var RBACResolver = async (db3, table, entityName, resourceId, rights_mode) => {
3092
+ const rbacRecords = await db3.from("rbac").where({
3093
+ entity: entityName,
3094
+ target_resource_id: resourceId
3095
+ }).select("*");
3096
+ const users = rbacRecords.filter((r) => r.access_type === "User").map((r) => ({ id: r.user_id, rights: r.rights }));
3097
+ const roles = rbacRecords.filter((r) => r.access_type === "Role").map((r) => ({ id: r.role_id, rights: r.rights }));
3098
+ let type = rights_mode || "private";
3099
+ if (type === "users" && users.length === 0) type = "private";
3100
+ if (type === "roles" && roles.length === 0) type = "private";
3101
+ return {
3102
+ type,
3103
+ users,
3104
+ roles
3105
+ };
3214
3106
  };
3215
- var addRBACfields = (schema) => {
3216
- if (schema.RBAC) {
3217
- console.log(`[EXULU] Adding rights_mode field to ${schema.name.plural} table.`);
3218
- schema.fields.push({
3219
- name: "rights_mode",
3220
- type: "text",
3221
- required: false,
3222
- default: "private"
3107
+ function createSDL(tables, contexts, agents, tools) {
3108
+ const contextSchemas = [];
3109
+ console.log("============= Agents =============", agents?.length);
3110
+ contexts.forEach((context) => {
3111
+ const tableName = getTableName(context.name);
3112
+ const definition = {
3113
+ name: {
3114
+ singular: tableName,
3115
+ plural: tableName?.endsWith("s") ? tableName : tableName + "s"
3116
+ },
3117
+ RBAC: true,
3118
+ fields: context.fields.map((field) => ({
3119
+ name: sanitizeName(field.name),
3120
+ type: field.type
3121
+ }))
3122
+ };
3123
+ contextSchemas.push(addRBACfields(definition));
3124
+ });
3125
+ contextSchemas.forEach((contextSchema) => {
3126
+ contextSchema.fields.push({
3127
+ // important: the contexts use the default knex timestamp
3128
+ // fields which are different to the regular
3129
+ // ExuluTableDefinition, i.e. created_at vs. createdAt.
3130
+ name: "created_at",
3131
+ type: "date"
3223
3132
  });
3224
- schema.fields.push({
3225
- name: "created_by",
3226
- type: "number",
3227
- required: true,
3228
- default: 0
3133
+ contextSchema.fields.push({
3134
+ name: "updated_at",
3135
+ type: "date"
3136
+ });
3137
+ contextSchema.fields.push({
3138
+ name: "name",
3139
+ type: "text"
3140
+ });
3141
+ contextSchema.fields.push({
3142
+ name: "description",
3143
+ type: "text"
3144
+ });
3145
+ contextSchema.fields.push({
3146
+ name: "tags",
3147
+ type: "text"
3148
+ });
3149
+ contextSchema.fields.push({
3150
+ name: "archived",
3151
+ type: "boolean"
3152
+ });
3153
+ });
3154
+ tables.forEach((table) => {
3155
+ table.fields.push({
3156
+ name: "createdAt",
3157
+ type: "date"
3158
+ });
3159
+ table.fields.push({
3160
+ name: "updatedAt",
3161
+ type: "date"
3229
3162
  });
3163
+ });
3164
+ tables = [...tables, ...contextSchemas];
3165
+ console.log("[EXULU] Creating SDL");
3166
+ let typeDefs = `
3167
+ scalar JSON
3168
+ scalar Date
3169
+
3170
+ type RBACData {
3171
+ type: String!
3172
+ users: [RBACUser!]
3173
+ roles: [RBACRole!]
3174
+ }
3175
+
3176
+ type RBACUser {
3177
+ id: ID!
3178
+ rights: String!
3179
+ }
3180
+
3181
+ type RBACRole {
3182
+ id: ID!
3183
+ rights: String!
3184
+ }
3185
+
3186
+ input RBACInput {
3187
+ users: [RBACUserInput!]
3188
+ roles: [RBACRoleInput!]
3189
+ }
3190
+
3191
+ input RBACUserInput {
3192
+ id: ID!
3193
+ rights: String!
3194
+ }
3195
+
3196
+ input RBACRoleInput {
3197
+ id: ID!
3198
+ rights: String!
3199
+ }
3200
+
3201
+ type Query {
3202
+ `;
3203
+ let mutationDefs = `
3204
+ type Mutation {
3205
+ `;
3206
+ let modelDefs = "";
3207
+ const resolvers = { JSON: GraphQLJSON, Date: GraphQLDate, Query: {}, Mutation: {} };
3208
+ for (const table of tables) {
3209
+ if (table.graphql === false) {
3210
+ continue;
3211
+ }
3212
+ const tableNamePlural = table.name.plural.toLowerCase();
3213
+ const tableNameSingular = table.name.singular.toLowerCase();
3214
+ const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
3215
+ console.log("[EXULU] Adding table >>>>>", tableNamePlural);
3216
+ typeDefs += `
3217
+ ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
3218
+ ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
3219
+ ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
3220
+ ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
3221
+ ${tableNamePlural}Statistics(filters: [Filter${tableNameSingularUpperCaseFirst}], groupBy: String): [StatisticsResult]!
3222
+ `;
3223
+ mutationDefs += `
3224
+ ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
3225
+ ${tableNamePlural}UpdateOne(where: JSON!, input: ${tableNameSingular}Input!): ${tableNameSingular}
3226
+ ${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!): ${tableNameSingular}
3227
+ ${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
3228
+ `;
3229
+ modelDefs += createTypeDefs(table);
3230
+ modelDefs += createFilterTypeDefs(table);
3231
+ modelDefs += `
3232
+ type ${tableNameSingularUpperCaseFirst}PaginationResult {
3233
+ pageInfo: PageInfo!
3234
+ items: [${tableNameSingular}]!
3235
+ }
3236
+ type PageInfo {
3237
+ pageCount: Int!
3238
+ itemCount: Int!
3239
+ currentPage: Int!
3240
+ hasPreviousPage: Boolean!
3241
+ hasNextPage: Boolean!
3242
+ }
3243
+ `;
3244
+ Object.assign(resolvers.Query, createQueries(table, agents, tools, contexts));
3245
+ Object.assign(resolvers.Mutation, createMutations(table, agents, contexts, tools));
3246
+ if (table.RBAC) {
3247
+ const rbacResolverName = table.name.singular;
3248
+ if (!resolvers[rbacResolverName]) {
3249
+ resolvers[rbacResolverName] = {};
3250
+ }
3251
+ resolvers[rbacResolverName].RBAC = async (parent, args, context) => {
3252
+ const { db: db3 } = context;
3253
+ const resourceId = parent.id;
3254
+ const entityName = table.name.singular;
3255
+ const rights_mode = parent.rights_mode;
3256
+ return RBACResolver(db3, table, entityName, resourceId, rights_mode);
3257
+ };
3258
+ }
3230
3259
  }
3260
+ typeDefs += `
3261
+ providers: ProviderPaginationResult
3262
+ `;
3263
+ typeDefs += `
3264
+ contexts: ContextPaginationResult
3265
+ `;
3266
+ typeDefs += `
3267
+ contextById(id: ID!): Context
3268
+ `;
3269
+ typeDefs += `
3270
+ tools: ToolPaginationResult
3271
+ `;
3272
+ resolvers.Query["providers"] = async (_, args, context, info) => {
3273
+ const requestedFields = getRequestedFields(info);
3274
+ return {
3275
+ items: agents.map((agent) => {
3276
+ const object = {};
3277
+ requestedFields.forEach((field) => {
3278
+ object[field] = agent[field];
3279
+ });
3280
+ return object;
3281
+ })
3282
+ };
3283
+ };
3284
+ resolvers.Query["contexts"] = async (_, args, context, info) => {
3285
+ const data = contexts.map((context2) => ({
3286
+ id: context2.id,
3287
+ name: context2.name,
3288
+ description: context2.description,
3289
+ embedder: context2.embedder?.name || void 0,
3290
+ slug: "/contexts/" + context2.id,
3291
+ active: context2.active,
3292
+ fields: context2.fields.map((field) => {
3293
+ return {
3294
+ ...field,
3295
+ name: sanitizeName(field.name),
3296
+ label: field.name
3297
+ };
3298
+ })
3299
+ }));
3300
+ const requestedFields = getRequestedFields(info);
3301
+ return {
3302
+ items: data.map((context2) => {
3303
+ const object = {};
3304
+ requestedFields.forEach((field) => {
3305
+ object[field] = context2[field];
3306
+ });
3307
+ return object;
3308
+ })
3309
+ };
3310
+ };
3311
+ resolvers.Query["contextById"] = async (_, args, context, info) => {
3312
+ let data = contexts.find((context2) => context2.id === args.id);
3313
+ if (!data) {
3314
+ return null;
3315
+ }
3316
+ const clean = {
3317
+ id: data.id,
3318
+ name: data.name,
3319
+ description: data.description,
3320
+ embedder: data.embedder?.name || void 0,
3321
+ slug: "/contexts/" + data.id,
3322
+ active: data.active,
3323
+ fields: data.fields.map((field) => {
3324
+ return {
3325
+ ...field,
3326
+ name: sanitizeName(field.name),
3327
+ label: field.name
3328
+ };
3329
+ }),
3330
+ configuration: data.configuration
3331
+ };
3332
+ const requestedFields = getRequestedFields(info);
3333
+ const mapped = {};
3334
+ requestedFields.forEach((field) => {
3335
+ mapped[field] = clean[field];
3336
+ });
3337
+ return mapped;
3338
+ };
3339
+ resolvers.Query["tools"] = async (_, args, context, info) => {
3340
+ const requestedFields = getRequestedFields(info);
3341
+ return {
3342
+ items: tools.map((tool2) => {
3343
+ const object = {};
3344
+ requestedFields.forEach((field) => {
3345
+ object[field] = tool2[field];
3346
+ });
3347
+ return object;
3348
+ })
3349
+ };
3350
+ };
3351
+ modelDefs += `
3352
+ type ProviderPaginationResult {
3353
+ items: [Provider]!
3354
+ }
3355
+ `;
3356
+ modelDefs += `
3357
+ type ContextPaginationResult {
3358
+ items: [Context]!
3359
+ }
3360
+ `;
3361
+ modelDefs += `
3362
+ type ToolPaginationResult {
3363
+ items: [Tool]!
3364
+ }
3365
+ `;
3366
+ typeDefs += "}\n";
3367
+ mutationDefs += "}\n";
3368
+ const genericTypes = `
3369
+
3370
+ type RateLimiterRule {
3371
+ name: String
3372
+ rate_limit: RateLimiterRuleRateLimit
3373
+ }
3374
+
3375
+ type RateLimiterRuleRateLimit {
3376
+ time: Int
3377
+ limit: Int
3378
+ }
3379
+
3380
+ type AgentCapabilities {
3381
+ text: Boolean
3382
+ images: [String]
3383
+ files: [String]
3384
+ audio: [String]
3385
+ video: [String]
3386
+ }
3387
+
3388
+ type Provider {
3389
+ id: ID!
3390
+ name: String!
3391
+ description: String
3392
+ providerName: String
3393
+ modelName: String
3394
+ type: EnumProviderType!
3395
+ }
3396
+
3397
+ type Context {
3398
+ id: ID!
3399
+ name: String!
3400
+ description: String
3401
+ embedder: String
3402
+ slug: String
3403
+ active: Boolean
3404
+ fields: JSON
3405
+ configuration: JSON
3406
+ }
3407
+
3408
+ type ContextField {
3409
+ name: String!
3410
+ type: String!
3411
+ unique: Boolean
3412
+ label: String
3413
+ }
3414
+
3415
+ type Tool {
3416
+ id: ID!
3417
+ name: String!
3418
+ description: String
3419
+ type: String
3420
+ config: JSON
3421
+ }
3422
+
3423
+ enum EnumProviderType {
3424
+ agent
3425
+ custom
3426
+ }
3427
+
3428
+ type StatisticsResult {
3429
+ group: String!
3430
+ count: Int!
3431
+ }
3432
+ `;
3433
+ const fullSDL = typeDefs + mutationDefs + modelDefs + genericTypes;
3434
+ const schema = makeExecutableSchema({
3435
+ typeDefs: fullSDL,
3436
+ resolvers
3437
+ });
3438
+ console.log("\n\u{1F4CA} GraphQL Schema Overview\n");
3439
+ const queriesTable = Object.keys(resolvers.Query).map((query) => ({
3440
+ "Operation Type": "Query",
3441
+ "Name": query,
3442
+ "Description": "Retrieves data"
3443
+ }));
3444
+ const mutationsTable = Object.keys(resolvers.Mutation).map((mutation) => ({
3445
+ "Operation Type": "Mutation",
3446
+ "Name": mutation,
3447
+ "Description": "Modifies data"
3448
+ }));
3449
+ const typesTable = tables.flatMap(
3450
+ (table) => table.fields.map((field) => ({
3451
+ "Type": table.name.singular,
3452
+ "Field": field.name,
3453
+ "Field Type": field.type,
3454
+ "Required": field.required ? "Yes" : "No"
3455
+ }))
3456
+ );
3457
+ console.log("\u{1F50D} Operations:");
3458
+ console.table([...queriesTable, ...mutationsTable]);
3459
+ console.log("\n\u{1F4DD} Types and Fields:");
3460
+ console.table(typesTable);
3461
+ console.log("\n");
3231
3462
  return schema;
3463
+ }
3464
+ var encryptSensitiveFields = (input) => {
3465
+ if (input.value && input.encrypted === true) {
3466
+ input.value = CryptoJS2.AES.encrypt(input.value, process.env.NEXTAUTH_SECRET).toString();
3467
+ }
3468
+ return input;
3232
3469
  };
3233
- var coreSchemas = {
3234
- get: () => {
3235
- return {
3236
- agentsSchema: () => addRBACfields(agentsSchema),
3237
- agentMessagesSchema: () => addRBACfields(agentMessagesSchema),
3238
- agentSessionsSchema: () => addRBACfields(agentSessionsSchema),
3239
- usersSchema: () => addRBACfields(usersSchema),
3240
- rolesSchema: () => addRBACfields(rolesSchema),
3241
- statisticsSchema: () => addRBACfields(statisticsSchema),
3242
- evalResultsSchema: () => addRBACfields(evalResultsSchema),
3243
- jobsSchema: () => addRBACfields(jobsSchema),
3244
- variablesSchema: () => addRBACfields(variablesSchema),
3245
- rbacSchema: () => addRBACfields(rbacSchema),
3246
- workflowTemplatesSchema: () => addRBACfields(workflowTemplatesSchema)
3247
- };
3470
+ var validateCreateOrRemoveSuperAdminPermission = async (tableNamePlural, input, req) => {
3471
+ if (tableNamePlural === "users" && input.super_admin !== void 0) {
3472
+ const authResult = await requestValidators.authenticate(req);
3473
+ if (authResult.error || !authResult.user) {
3474
+ throw new Error("Authentication failed");
3475
+ }
3476
+ if (!authResult.user.super_admin) {
3477
+ throw new Error("Only super administrators can modify super_admin status");
3478
+ }
3248
3479
  }
3249
3480
  };
3250
3481
 
3482
+ // src/registry/routes.ts
3483
+ import { expressMiddleware } from "@as-integrations/express5";
3484
+
3251
3485
  // src/registry/uppy.ts
3252
3486
  import "express";
3253
3487
  var createUppyRoutes = async (app) => {
@@ -3650,7 +3884,7 @@ var CLAUDE_MESSAGES = {
3650
3884
  \x1B[41m -- Anthropic token variable set by your admin is not encrypted. This poses a security risk. Please contact your admin to fix the variable used for your key. --
3651
3885
  \x1B[0m`,
3652
3886
  anthropic_token_variable_not_found: `
3653
- \x1B[41m -- Anthropic token variable not found. Please contact to fix the variable used for your key. --
3887
+ \x1B[41m -- Anthropic token variable not found. Please contact your Exulu adminto fix the variable used for your key. --
3654
3888
  \x1B[0m`,
3655
3889
  authentication_error: `
3656
3890
  \x1B[41m -- Authentication error please check your IMP token and try again. --
@@ -3679,11 +3913,24 @@ Intelligence Management Platform
3679
3913
  import OpenAI from "openai";
3680
3914
  import fs2 from "fs";
3681
3915
  import { randomUUID } from "crypto";
3916
+ import "@opentelemetry/api";
3682
3917
  var REQUEST_SIZE_LIMIT = "50mb";
3683
3918
  var global_queues = {
3684
3919
  logs_cleaner: "logs-cleaner"
3685
3920
  };
3686
- var { agentsSchema: agentsSchema2, evalResultsSchema: evalResultsSchema2, jobsSchema: jobsSchema2, agentSessionsSchema: agentSessionsSchema2, agentMessagesSchema: agentMessagesSchema2, rolesSchema: rolesSchema2, usersSchema: usersSchema2, variablesSchema: variablesSchema2, workflowTemplatesSchema: workflowTemplatesSchema2, rbacSchema: rbacSchema2, statisticsSchema: statisticsSchema2 } = coreSchemas.get();
3921
+ var {
3922
+ agentsSchema: agentsSchema2,
3923
+ evalResultsSchema: evalResultsSchema2,
3924
+ jobsSchema: jobsSchema2,
3925
+ agentSessionsSchema: agentSessionsSchema2,
3926
+ agentMessagesSchema: agentMessagesSchema2,
3927
+ rolesSchema: rolesSchema2,
3928
+ usersSchema: usersSchema2,
3929
+ variablesSchema: variablesSchema2,
3930
+ workflowTemplatesSchema: workflowTemplatesSchema2,
3931
+ rbacSchema: rbacSchema2,
3932
+ statisticsSchema: statisticsSchema2
3933
+ } = coreSchemas.get();
3687
3934
  var createRecurringJobs = async () => {
3688
3935
  console.log("[EXULU] creating recurring jobs.");
3689
3936
  const recurringJobSchedulersLogs = [];
@@ -3717,8 +3964,9 @@ var createRecurringJobs = async () => {
3717
3964
  console.table(recurringJobSchedulersLogs);
3718
3965
  return queue;
3719
3966
  };
3720
- var createExpressRoutes = async (app, agents, tools, contexts) => {
3967
+ var createExpressRoutes = async (app, logger, agents, tools, contexts, config, tracer) => {
3721
3968
  const routeLogs = [];
3969
+ console.log("============= agents =============", agents?.length);
3722
3970
  var corsOptions = {
3723
3971
  origin: "*",
3724
3972
  exposedHeaders: "*",
@@ -3752,25 +4000,18 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3752
4000
  }));
3753
4001
  console.log("Contexts:");
3754
4002
  console.table(contexts.map((context) => {
3755
- const sources = context.sources.get();
3756
4003
  return {
3757
4004
  id: context.id,
3758
4005
  name: context.name,
3759
4006
  description: context.description,
3760
- embedder: context.embedder.name,
4007
+ embedder: context.embedder?.name || void 0,
3761
4008
  slug: "/contexts/" + context.id,
3762
- active: context.active,
3763
- sources: Array.isArray(sources) ? sources.length : 0,
3764
- sources_details: Array.isArray(sources) ? sources.map((source) => `${source.name} (${source.id})`).join(", ") : "No sources"
4009
+ active: context.active
3765
4010
  };
3766
4011
  }));
3767
4012
  routeLogs.push(
3768
- { route: "/agents", method: "GET", note: "List all agents" },
3769
- { route: "/agents/:id", method: "GET", note: "Get specific agent" },
3770
4013
  { route: "/contexts", method: "GET", note: "List all contexts" },
3771
4014
  { route: "/contexts/:id", method: "GET", note: "Get specific context" },
3772
- { route: "/tools", method: "GET", note: "List all tools" },
3773
- { route: "/tools/:id", method: "GET", note: "Get specific tool" },
3774
4015
  { route: "/items/:context", method: "POST", note: "Create new item in context" },
3775
4016
  { route: "/items/:context", method: "GET", note: "Get items from context" },
3776
4017
  { route: "/items/export/:context", method: "GET", note: "Export items from context" },
@@ -3781,21 +4022,19 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3781
4022
  } else {
3782
4023
  console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
3783
4024
  }
3784
- const schema = createSDL(
3785
- [
3786
- usersSchema2(),
3787
- rolesSchema2(),
3788
- agentsSchema2(),
3789
- jobsSchema2(),
3790
- evalResultsSchema2(),
3791
- agentSessionsSchema2(),
3792
- agentMessagesSchema2(),
3793
- variablesSchema2(),
3794
- workflowTemplatesSchema2(),
3795
- statisticsSchema2(),
3796
- rbacSchema2()
3797
- ]
3798
- );
4025
+ const schema = createSDL([
4026
+ usersSchema2(),
4027
+ rolesSchema2(),
4028
+ agentsSchema2(),
4029
+ jobsSchema2(),
4030
+ evalResultsSchema2(),
4031
+ agentSessionsSchema2(),
4032
+ agentMessagesSchema2(),
4033
+ variablesSchema2(),
4034
+ workflowTemplatesSchema2(),
4035
+ statisticsSchema2(),
4036
+ rbacSchema2()
4037
+ ], contexts, agents, tools);
3799
4038
  console.log("[EXULU] graphql server");
3800
4039
  const server = new ApolloServer({
3801
4040
  cache: new InMemoryLRUCache(),
@@ -3811,6 +4050,16 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3811
4050
  express.json({ limit: REQUEST_SIZE_LIMIT }),
3812
4051
  expressMiddleware(server, {
3813
4052
  context: async ({ req }) => {
4053
+ logger.info("================");
4054
+ logger.info({
4055
+ message: "Incoming Request",
4056
+ method: req.method,
4057
+ path: req.path,
4058
+ requestId: "req-" + Date.now(),
4059
+ ipAddress: req.ip,
4060
+ userAgent: req.get("User-Agent")
4061
+ });
4062
+ logger.info("================");
3814
4063
  const authenticationResult = await requestValidators.authenticate(req);
3815
4064
  if (!authenticationResult.user?.id) {
3816
4065
  throw new Error(authenticationResult.message);
@@ -3824,113 +4073,6 @@ var createExpressRoutes = async (app, agents, tools, contexts) => {
3824
4073
  }
3825
4074
  })
3826
4075
  );
3827
- app.get(`/providers`, async (req, res) => {
3828
- const authenticationResult = await requestValidators.authenticate(req);
3829
- if (!authenticationResult.user?.id) {
3830
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3831
- return;
3832
- }
3833
- res.status(200).json(agents);
3834
- });
3835
- app.get(`/agents`, async (req, res) => {
3836
- const authenticationResult = await requestValidators.authenticate(req);
3837
- if (!authenticationResult.user?.id) {
3838
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3839
- return;
3840
- }
3841
- const { db: db3 } = await postgresClient();
3842
- let query = db3("agents");
3843
- query.select("*");
3844
- query = applyAccessControl(agentsSchema2(), authenticationResult.user, query);
3845
- const agentsFromDb = await query;
3846
- res.status(200).json(agentsFromDb.map((agent) => {
3847
- const backend = agents.find((a) => a.id === agent.backend);
3848
- if (!backend) {
3849
- return null;
3850
- }
3851
- return {
3852
- name: agent.name,
3853
- id: agent.id,
3854
- description: agent.description,
3855
- provider: backend.providerName,
3856
- model: backend.modelName,
3857
- active: agent.active,
3858
- type: agent.type,
3859
- rights_mode: agent.rights_mode,
3860
- slug: backend?.slug,
3861
- rateLimit: backend?.rateLimit,
3862
- image: agent.image,
3863
- streaming: backend?.streaming,
3864
- capabilities: backend?.capabilities,
3865
- RBAC: agent.RBAC,
3866
- // todo add contexts
3867
- availableTools: tools,
3868
- enabledTools: agent.tools
3869
- };
3870
- }).filter(Boolean));
3871
- });
3872
- app.get(`/agents/:id`, async (req, res) => {
3873
- const authenticationResult = await requestValidators.authenticate(req);
3874
- if (!authenticationResult.user?.id) {
3875
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3876
- return;
3877
- }
3878
- const { db: db3 } = await postgresClient();
3879
- const id = req.params.id;
3880
- if (!id) {
3881
- res.status(400).json({
3882
- message: "Missing id in request."
3883
- });
3884
- return;
3885
- }
3886
- let query = db3("agents");
3887
- query.select("*");
3888
- query = applyAccessControl(agentsSchema2(), authenticationResult.user, query);
3889
- query.where({ id });
3890
- const agent = await query.first();
3891
- if (!agent) {
3892
- res.status(400).json({
3893
- message: "Agent not found in database."
3894
- });
3895
- return;
3896
- }
3897
- console.log("[EXULU] agent", agent);
3898
- const backend = agents.find((a) => a.id === agent.backend);
3899
- console.log("[EXULU] agent", agent);
3900
- const RBAC = await RBACResolver(db3, agentsSchema2(), agentsSchema2().name.singular, agent.id, agent.rights_mode);
3901
- res.status(200).json({
3902
- ...{
3903
- name: agent.name,
3904
- id: agent.id,
3905
- description: agent.description,
3906
- provider: backend?.model?.create({ apiKey: "" }).provider,
3907
- model: backend?.model?.create({ apiKey: "" }).modelId,
3908
- active: agent.active,
3909
- public: agent.public,
3910
- image: agent.image,
3911
- type: agent.type,
3912
- rights_mode: agent.rights_mode,
3913
- slug: backend?.slug,
3914
- rateLimit: backend?.rateLimit,
3915
- streaming: backend?.streaming,
3916
- RBAC,
3917
- capabilities: backend?.capabilities,
3918
- providerApiKey: agent.providerApiKey,
3919
- // todo add contexts
3920
- availableTools: tools,
3921
- enabledTools: agent.tools
3922
- }
3923
- });
3924
- });
3925
- app.get("/tools", async (req, res) => {
3926
- res.status(200).json(tools.map((tool2) => ({
3927
- id: tool2.id,
3928
- name: tool2.name,
3929
- description: tool2.description,
3930
- type: tool2.type || "tool",
3931
- inputSchema: tool2.inputSchema ? zerialize(tool2.inputSchema) : null
3932
- })));
3933
- });
3934
4076
  app.post("/generate/agent/image", async (req, res) => {
3935
4077
  console.log("[EXULU] generate/agent/image", req.body);
3936
4078
  const authenticationResult = await requestValidators.authenticate(req);
@@ -4022,23 +4164,6 @@ Mood: friendly and intelligent.
4022
4164
  image: `${process.env.BACKEND}/${uuid}.png`
4023
4165
  });
4024
4166
  });
4025
- app.get("/tools/:id", async (req, res) => {
4026
- const id = req.params.id;
4027
- if (!id) {
4028
- res.status(400).json({
4029
- message: "Missing id in request."
4030
- });
4031
- return;
4032
- }
4033
- const tool2 = tools.find((tool3) => tool3.id === id);
4034
- if (!tool2) {
4035
- res.status(400).json({
4036
- message: "Tool not found."
4037
- });
4038
- return;
4039
- }
4040
- res.status(200).json(tool2);
4041
- });
4042
4167
  const deleteItem = async ({
4043
4168
  id,
4044
4169
  external_id,
@@ -4057,9 +4182,9 @@ Mood: friendly and intelligent.
4057
4182
  }
4058
4183
  const exists = await context.tableExists();
4059
4184
  if (!exists) {
4060
- throw new Error("Table with name " + context.getTableName() + " does not exist.");
4185
+ throw new Error("Table with name " + getTableName(context.name) + " does not exist.");
4061
4186
  }
4062
- const query = db3.from(context.getTableName()).select("id");
4187
+ const query = db3.from(getTableName(context.name)).select("id");
4063
4188
  if (id) {
4064
4189
  query.where({ id });
4065
4190
  }
@@ -4070,11 +4195,13 @@ Mood: friendly and intelligent.
4070
4195
  if (!item) {
4071
4196
  return null;
4072
4197
  }
4073
- const chunks = await db3.from(context.getChunksTableName()).where({ source: item.id }).select("id");
4074
- if (chunks.length > 0) {
4075
- await db3.from(context.getChunksTableName()).where({ source: item.id }).delete();
4198
+ if (context.embedder) {
4199
+ const chunks = await db3.from(getChunksTableName(context.name)).where({ source: item.id }).select("id");
4200
+ if (chunks.length > 0) {
4201
+ await db3.from(getChunksTableName(context.name)).where({ source: item.id }).delete();
4202
+ }
4076
4203
  }
4077
- const mutation = db3.from(context.getTableName()).where({ id: item.id }).delete().returning("id");
4204
+ const mutation = db3.from(getTableName(context.name)).where({ id: item.id }).delete().returning("id");
4078
4205
  const result = await mutation;
4079
4206
  return result;
4080
4207
  };
@@ -4141,19 +4268,25 @@ Mood: friendly and intelligent.
4141
4268
  if (!itemsTableExists) {
4142
4269
  await context.createItemsTable();
4143
4270
  }
4144
- const chunksTableExists = await db3.schema.hasTable(context.getChunksTableName());
4145
- if (!chunksTableExists) {
4271
+ const chunksTableExists = await db3.schema.hasTable(getChunksTableName(context.name));
4272
+ if (!chunksTableExists && context.embedder) {
4146
4273
  await context.createChunksTable();
4147
4274
  }
4148
- const item = await db3.from(context.getTableName()).where({ id: req.params.id }).select("*").first();
4275
+ const item = await db3.from(getTableName(context.name)).where({ id: req.params.id }).select("*").first();
4149
4276
  if (!item) {
4150
4277
  res.status(404).json({
4151
4278
  message: "Item not found."
4152
4279
  });
4153
4280
  return;
4154
4281
  }
4155
- console.log("[EXULU] chunks table name.", context.getChunksTableName());
4156
- const chunks = await db3.from(context.getChunksTableName()).where({ source: req.params.id }).select("id", "content", "source", "embedding", "chunk_index", "created_at", "updated_at");
4282
+ if (!context.embedder) {
4283
+ res.status(200).json({
4284
+ ...item
4285
+ });
4286
+ return;
4287
+ }
4288
+ console.log("[EXULU] chunks table name.", getChunksTableName(context.name));
4289
+ const chunks = await db3.from(getChunksTableName(context.name)).where({ source: req.params.id }).select("id", "content", "source", "embedding", "chunk_index", "created_at", "updated_at");
4157
4290
  console.log("[EXULU] chunks", chunks);
4158
4291
  res.status(200).json({
4159
4292
  ...item,
@@ -4197,7 +4330,15 @@ Mood: friendly and intelligent.
4197
4330
  if (!exists) {
4198
4331
  await context.createItemsTable();
4199
4332
  }
4200
- const result = await context.updateItem(authenticationResult.user.id, req.params.id, req.body, authenticationResult.user.role?.id);
4333
+ const result = await context.updateItem(
4334
+ authenticationResult.user.id,
4335
+ {
4336
+ ...req.body,
4337
+ id: req.params.id
4338
+ },
4339
+ authenticationResult.user.role?.id,
4340
+ authenticationResult.user.type === "api" ? "api" : "user"
4341
+ );
4201
4342
  res.status(200).json({
4202
4343
  message: "Item updated successfully.",
4203
4344
  id: result
@@ -4244,7 +4385,13 @@ Mood: friendly and intelligent.
4244
4385
  await context.createItemsTable();
4245
4386
  }
4246
4387
  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);
4388
+ const result = await context.insertItem(
4389
+ authenticationResult.user.id,
4390
+ req.body,
4391
+ !!req.body.upsert,
4392
+ authenticationResult.user.role?.id,
4393
+ authenticationResult.user.type === "api" ? "api" : "user"
4394
+ );
4248
4395
  console.log("[EXULU] result", result);
4249
4396
  res.status(200).json({
4250
4397
  message: "Item created successfully.",
@@ -4320,72 +4467,6 @@ Mood: friendly and intelligent.
4320
4467
  });
4321
4468
  res.status(200).json(result);
4322
4469
  });
4323
- routeLogs.push({
4324
- route: "/items/:context",
4325
- method: "DELETE",
4326
- note: `Delete all embeddings for a context.`
4327
- });
4328
- app.delete(`items/:context`, async (req, res) => {
4329
- if (!req.params.context) {
4330
- res.status(400).json({
4331
- message: "Missing context in request."
4332
- });
4333
- return;
4334
- }
4335
- const context = contexts.find((context2) => context2.id === req.params.context);
4336
- if (!context) {
4337
- res.status(400).json({
4338
- message: "Context not found in registry."
4339
- });
4340
- return;
4341
- }
4342
- const authenticationResult = await requestValidators.authenticate(req);
4343
- if (!authenticationResult.user?.id) {
4344
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4345
- return;
4346
- }
4347
- await context.deleteAll();
4348
- res.status(200).json({
4349
- message: "All embeddings deleted."
4350
- });
4351
- });
4352
- routeLogs.push({
4353
- route: `/items/:context/:id`,
4354
- method: "DELETE",
4355
- note: `Delete specific embedding for a context.`
4356
- });
4357
- console.log("[EXULU] delete embedding by id");
4358
- app.delete(`items/:context/:id`, async (req, res) => {
4359
- const id = req.params.id;
4360
- if (!req.params.context) {
4361
- res.status(400).json({
4362
- message: "Missing context in request."
4363
- });
4364
- return;
4365
- }
4366
- const context = contexts.find((context2) => context2.id === req.params.context);
4367
- if (!context) {
4368
- res.status(400).json({
4369
- message: "Context not found in registry."
4370
- });
4371
- return;
4372
- }
4373
- if (!id) {
4374
- res.status(400).json({
4375
- message: "Missing id in request."
4376
- });
4377
- return;
4378
- }
4379
- const authenticationResult = await requestValidators.authenticate(req);
4380
- if (!authenticationResult.user?.id) {
4381
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4382
- return;
4383
- }
4384
- await context.deleteOne(id);
4385
- res.status(200).json({
4386
- message: "Embedding deleted."
4387
- });
4388
- });
4389
4470
  app.get("/ping", async (req, res) => {
4390
4471
  const authenticationResult = await requestValidators.authenticate(req);
4391
4472
  if (!authenticationResult.user?.id) {
@@ -4398,136 +4479,6 @@ Mood: friendly and intelligent.
4398
4479
  authenticated: true
4399
4480
  });
4400
4481
  });
4401
- console.log("[EXULU] context by id");
4402
- app.get(`/contexts/:id`, async (req, res) => {
4403
- const authenticationResult = await requestValidators.authenticate(req);
4404
- if (!authenticationResult.user?.id) {
4405
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4406
- return;
4407
- }
4408
- const id = req.params.id;
4409
- if (!id) {
4410
- res.status(400).json({
4411
- message: "Missing id in request."
4412
- });
4413
- return;
4414
- }
4415
- const context = contexts.find((context2) => context2.id === id);
4416
- if (!context) {
4417
- res.status(400).json({
4418
- message: "Context not found."
4419
- });
4420
- return;
4421
- }
4422
- res.status(200).json({
4423
- ...{
4424
- id: context.id,
4425
- name: context.name,
4426
- description: context.description,
4427
- embedder: context.embedder.name,
4428
- slug: "/contexts/" + context.id,
4429
- active: context.active,
4430
- fields: context.fields,
4431
- configuration: context.configuration,
4432
- sources: context.sources.get().map((source) => ({
4433
- id: source.id,
4434
- name: source.name,
4435
- description: source.description,
4436
- updaters: source.updaters.map((updater) => ({
4437
- id: updater.id,
4438
- slug: updater.slug,
4439
- type: updater.type,
4440
- configuration: updater.configuration
4441
- }))
4442
- }))
4443
- },
4444
- agents: []
4445
- // todo
4446
- });
4447
- });
4448
- console.log("[EXULU] items export by context");
4449
- app.get(`/items/export/:context`, async (req, res) => {
4450
- if (!req.params.context) {
4451
- res.status(400).json({
4452
- message: "Missing context in request."
4453
- });
4454
- return;
4455
- }
4456
- const authenticationResult = await requestValidators.authenticate(req);
4457
- if (!authenticationResult.user?.id) {
4458
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4459
- return;
4460
- }
4461
- const context = contexts.find((context2) => context2.id === req.params.context);
4462
- if (!context) {
4463
- res.status(400).json({
4464
- message: "Context not found."
4465
- });
4466
- return;
4467
- }
4468
- const items = await context.getItems({
4469
- page: 1,
4470
- // todo add pagination
4471
- user: authenticationResult.user?.id,
4472
- role: authenticationResult.user?.role?.id,
4473
- limit: 500
4474
- });
4475
- const csv = Papa.unparse(items);
4476
- const ISOTime = (/* @__PURE__ */ new Date()).toISOString();
4477
- res.status(200).attachment(`${context.name}-items-export-${ISOTime}.csv`).send(csv);
4478
- });
4479
- console.log("[EXULU] contexts get list");
4480
- app.get(`/contexts`, async (req, res) => {
4481
- const authenticationResult = await requestValidators.authenticate(req);
4482
- if (!authenticationResult.user?.id) {
4483
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4484
- return;
4485
- }
4486
- res.status(200).json(contexts.map((context) => ({
4487
- id: context.id,
4488
- name: context.name,
4489
- description: context.description,
4490
- embedder: context.embedder.name,
4491
- slug: "/contexts/" + context.id,
4492
- active: context.active,
4493
- fields: context.fields,
4494
- sources: context.sources.get().map((source) => ({
4495
- id: source.id,
4496
- name: source.name,
4497
- description: source.description,
4498
- updaters: source.updaters.map((updater) => ({
4499
- id: updater.id,
4500
- slug: updater.slug,
4501
- type: updater.type,
4502
- configuration: updater.configuration
4503
- }))
4504
- }))
4505
- })));
4506
- });
4507
- console.log("[EXULU] contexts");
4508
- contexts.forEach((context) => {
4509
- const sources = context.sources.get();
4510
- if (!Array.isArray(sources)) {
4511
- return;
4512
- }
4513
- sources.forEach((source) => {
4514
- source.updaters.forEach((updater) => {
4515
- if (!updater.slug) return;
4516
- if (updater.type === "webhook" || updater.type === "manual") {
4517
- routeLogs.push({
4518
- route: `${updater.slug}/${updater.type}/:context`,
4519
- method: "POST",
4520
- note: `Webhook updater for ${context.name}`
4521
- });
4522
- app.post(`${updater.slug}/${updater.type}/:context`, async (req, res) => {
4523
- res.status(200).json([]);
4524
- return;
4525
- });
4526
- }
4527
- });
4528
- });
4529
- });
4530
- console.log("[EXULU] agents");
4531
4482
  agents.forEach((agent) => {
4532
4483
  const slug = agent.slug;
4533
4484
  if (!slug) return;
@@ -4593,7 +4544,7 @@ Mood: friendly and intelligent.
4593
4544
  return;
4594
4545
  }
4595
4546
  console.log("[EXULU] agent tools", agentInstance.tools);
4596
- const enabledTools = agentInstance.tools.map(({ config, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean);
4547
+ const enabledTools = agentInstance.tools ? agentInstance.tools.map(({ config: config2, toolId }) => tools.find(({ id }) => id === toolId)).filter(Boolean) : [];
4597
4548
  console.log("[EXULU] enabled tools", enabledTools);
4598
4549
  const variableName = agentInstance.providerApiKey;
4599
4550
  const variable = await db3.from("variables").where({ name: variableName }).first();
@@ -4799,43 +4750,22 @@ import IORedis from "ioredis";
4799
4750
  import { Worker } from "bullmq";
4800
4751
 
4801
4752
  // src/registry/utils.ts
4802
- import "bullmq";
4803
4753
  var bullmq = {
4804
- validate: (bullmqJob) => {
4805
- if (!bullmqJob.data) {
4806
- throw new Error(`Missing job data for job ${bullmqJob.id}.`);
4807
- }
4808
- if (!bullmqJob.data.type) {
4809
- throw new Error(`Missing property "type" in data for job ${bullmqJob.id}.`);
4754
+ validate: (id, data) => {
4755
+ if (!data) {
4756
+ throw new Error(`Missing job data for job ${id}.`);
4810
4757
  }
4811
- if (!bullmqJob.data.inputs) {
4812
- throw new Error(`Missing property "inputs" in data for job ${bullmqJob.id}.`);
4758
+ if (!data.type) {
4759
+ throw new Error(`Missing property "type" in data for job ${id}.`);
4813
4760
  }
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".`);
4761
+ if (!data.inputs) {
4762
+ throw new Error(`Missing property "inputs" in data for job ${id}.`);
4816
4763
  }
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)}`);
4764
+ if (data.type !== "embedder" && data.type !== "workflow") {
4765
+ throw new Error(`Property "type" in data for job ${id} must be of value "embedder" or "workflow".`);
4819
4766
  }
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;
4767
+ if (!data.workflow && !data.embedder) {
4768
+ throw new Error(`Either a workflow or embedder must be set for job ${id}.`);
4839
4769
  }
4840
4770
  }
4841
4771
  };
@@ -4843,9 +4773,10 @@ var bullmq = {
4843
4773
  // src/registry/workers.ts
4844
4774
  import * as fs3 from "fs";
4845
4775
  import path2 from "path";
4776
+ import "@opentelemetry/api";
4846
4777
  var defaultLogsDir = path2.join(process.cwd(), "logs");
4847
4778
  var redisConnection;
4848
- var createWorkers = async (queues2, contexts, _logsDir) => {
4779
+ var createWorkers = async (queues2, logger, contexts, _logsDir, tracer) => {
4849
4780
  if (!redisServer.host || !redisServer.port) {
4850
4781
  console.error("[EXULU] you are trying to start worker, but no redis server is configured in the environment.");
4851
4782
  throw new Error("No redis server configured in the environment, so cannot start worker.");
@@ -4864,82 +4795,27 @@ var createWorkers = async (queues2, contexts, _logsDir) => {
4864
4795
  async (bullmqJob) => {
4865
4796
  const { db: db3 } = await postgresClient();
4866
4797
  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);
4798
+ const data = bullmqJob.data;
4799
+ bullmq.validate(bullmqJob.id, data);
4800
+ if (data.type === "embedder") {
4801
+ const context = contexts.find((context2) => context2.id === data.context);
4873
4802
  if (!context) {
4874
- throw new Error(`Context ${bullmqJob.data.context} not found in the registry.`);
4803
+ throw new Error(`Context ${data.context} not found in the registry.`);
4875
4804
  }
4876
- if (!bullmqJob.data.embedder) {
4805
+ if (!data.embedder) {
4877
4806
  throw new Error(`No embedder set for embedder job.`);
4878
4807
  }
4879
- const embedder = contexts.find((context2) => context2.embedder?.id === bullmqJob.data.embedder);
4808
+ const embedder = contexts.find((context2) => context2.embedder?.id === data.embedder);
4880
4809
  if (!embedder) {
4881
- throw new Error(`Embedder ${bullmqJob.data.embedder} not found in the registry.`);
4810
+ throw new Error(`Embedder ${data.embedder} not found in the registry.`);
4882
4811
  }
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.");
4902
- }
4903
- const result = await embedder.upsert(bullmqJob.data.context, bullmqJob.data.documents, {
4904
- label: context.name,
4905
- trigger: bullmqJob.data.trigger || "unknown"
4906
- });
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)
4812
+ const result = await context.createAndUpsertEmbeddings(data.inputs, data.user, {
4813
+ label: embedder.name,
4814
+ trigger: data.trigger
4918
4815
  });
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
4816
  return result;
4923
4817
  }
4924
4818
  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
4819
  }
4944
4820
  } catch (error) {
4945
4821
  await db3.from("jobs").where({ redis: bullmqJob.id }).update({
@@ -5009,6 +4885,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
5009
4885
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
5010
4886
  import { z as z2 } from "zod";
5011
4887
  import "express";
4888
+ import "@opentelemetry/api";
5012
4889
  var SESSION_ID_HEADER = "mcp-session-id";
5013
4890
  var ExuluMCP = class {
5014
4891
  server;
@@ -5016,7 +4893,7 @@ var ExuluMCP = class {
5016
4893
  express;
5017
4894
  constructor() {
5018
4895
  }
5019
- create = async ({ express: express3, contexts, agents, config, tools }) => {
4896
+ create = async ({ express: express3, contexts, agents, config, tools, tracer, logger }) => {
5020
4897
  this.express = express3;
5021
4898
  if (!this.server) {
5022
4899
  console.log("[EXULU] Creating MCP server.");
@@ -5194,6 +5071,77 @@ var defaultAgent = new ExuluAgent({
5194
5071
  }
5195
5072
  });
5196
5073
 
5074
+ // src/registry/index.ts
5075
+ import { trace as trace2 } from "@opentelemetry/api";
5076
+
5077
+ // src/registry/logger.ts
5078
+ import { OpenTelemetryTransportV3 } from "@opentelemetry/winston-transport";
5079
+ import winston from "winston";
5080
+ var createLogger = ({
5081
+ enableOtel
5082
+ }) => {
5083
+ const logger = winston.createLogger({
5084
+ level: "debug",
5085
+ format: winston.format.combine(
5086
+ winston.format.timestamp(),
5087
+ winston.format.errors({
5088
+ stack: true
5089
+ }),
5090
+ winston.format.metadata(),
5091
+ winston.format.json()
5092
+ ),
5093
+ defaultMeta: {
5094
+ service: "Exulu",
5095
+ environment: process.env.NODE_ENV || "development"
5096
+ },
5097
+ transports: [
5098
+ new winston.transports.Console(),
5099
+ ...enableOtel ? [new OpenTelemetryTransportV3()] : []
5100
+ ]
5101
+ });
5102
+ return logger;
5103
+ };
5104
+ var logger_default = createLogger;
5105
+
5106
+ // src/templates/contexts/code-standards.ts
5107
+ var codeStandardsContext = new ExuluContext({
5108
+ id: "code-standards",
5109
+ name: "Code Standards",
5110
+ description: "Code standards that can be used with the Exulu CLI.",
5111
+ configuration: {
5112
+ defaultRightsMode: "public"
5113
+ },
5114
+ fields: [{
5115
+ name: "Best practices",
5116
+ type: "longText"
5117
+ }, {
5118
+ name: "Code style",
5119
+ type: "longText"
5120
+ }, {
5121
+ name: "Tech stack",
5122
+ type: "longText"
5123
+ }],
5124
+ active: true
5125
+ });
5126
+
5127
+ // src/templates/contexts/projects.ts
5128
+ var projectsContext = new ExuluContext({
5129
+ id: "projects",
5130
+ name: "Projects",
5131
+ description: "Default context that stores files and data related to projects in Exulu.",
5132
+ configuration: {
5133
+ defaultRightsMode: "projects"
5134
+ },
5135
+ fields: [{
5136
+ name: "Type",
5137
+ type: "text"
5138
+ }, {
5139
+ name: "Summary",
5140
+ type: "longText"
5141
+ }],
5142
+ active: true
5143
+ });
5144
+
5197
5145
  // src/registry/index.ts
5198
5146
  var ExuluApp = class {
5199
5147
  _agents = [];
@@ -5207,7 +5155,11 @@ var ExuluApp = class {
5207
5155
  // Factory function so we can async
5208
5156
  // initialize the MCP server if needed.
5209
5157
  create = async ({ contexts, agents, config, tools }) => {
5210
- this._contexts = contexts ?? {};
5158
+ this._contexts = {
5159
+ ...contexts,
5160
+ projectsContext,
5161
+ codeStandardsContext
5162
+ };
5211
5163
  this._agents = [
5212
5164
  claudeCodeAgent,
5213
5165
  defaultAgent,
@@ -5223,7 +5175,7 @@ var ExuluApp = class {
5223
5175
  ];
5224
5176
  const contextsArray = Object.values(contexts || {});
5225
5177
  const queues2 = [
5226
- ...contextsArray?.length ? contextsArray.map((context) => context.embedder.queue?.name || null) : []
5178
+ ...contextsArray?.length ? contextsArray.map((context) => context.embedder?.queue?.name || null) : []
5227
5179
  ];
5228
5180
  this._queues = [...new Set(queues2.filter((o) => !!o))];
5229
5181
  if (!this._expressApp) {
@@ -5260,10 +5212,20 @@ var ExuluApp = class {
5260
5212
  bullmq = {
5261
5213
  workers: {
5262
5214
  create: async () => {
5215
+ let tracer;
5216
+ if (this._config?.telemetry?.enabled) {
5217
+ console.log("[EXULU] telemetry enabled.");
5218
+ tracer = trace2.getTracer("exulu", "1.0.0");
5219
+ }
5220
+ const logger = logger_default({
5221
+ enableOtel: this._config?.workers?.telemetry?.enabled ?? false
5222
+ });
5263
5223
  return await createWorkers(
5264
5224
  this._queues,
5225
+ logger,
5265
5226
  Object.values(this._contexts ?? {}),
5266
- this._config?.workers?.logsDir
5227
+ this._config?.workers?.logsDir,
5228
+ tracer
5267
5229
  );
5268
5230
  }
5269
5231
  }
@@ -5272,14 +5234,25 @@ var ExuluApp = class {
5272
5234
  express: {
5273
5235
  init: async () => {
5274
5236
  if (!this._expressApp) {
5275
- throw new Error("Express app not initialized");
5237
+ throw new Error("Express app not initialized.");
5276
5238
  }
5277
5239
  const app = this._expressApp;
5240
+ let tracer;
5241
+ if (this._config?.telemetry?.enabled) {
5242
+ console.log("[EXULU] telemetry enabled");
5243
+ tracer = trace2.getTracer("exulu", "1.0.0");
5244
+ }
5245
+ const logger = logger_default({
5246
+ enableOtel: this._config?.telemetry?.enabled ?? false
5247
+ });
5278
5248
  await createExpressRoutes(
5279
5249
  app,
5250
+ logger,
5280
5251
  this._agents,
5281
5252
  this._tools,
5282
- Object.values(this._contexts ?? {})
5253
+ Object.values(this._contexts ?? {}),
5254
+ this._config,
5255
+ tracer
5283
5256
  );
5284
5257
  if (this._config?.MCP.enabled) {
5285
5258
  const mcp = new ExuluMCP();
@@ -5288,7 +5261,9 @@ var ExuluApp = class {
5288
5261
  contexts: this._contexts,
5289
5262
  agents: this._agents,
5290
5263
  config: this._config,
5291
- tools: this._tools
5264
+ tools: this._tools,
5265
+ tracer,
5266
+ logger
5292
5267
  });
5293
5268
  await mcp.connect();
5294
5269
  }
@@ -5633,8 +5608,8 @@ var ExuluTokenizer = class {
5633
5608
  if (!this.encoder) {
5634
5609
  throw new Error("Tokenizer not initialized");
5635
5610
  }
5636
- const promises2 = tokenSequences.map((tokens) => this.decode(tokens));
5637
- return await Promise.all(promises2);
5611
+ const promises = tokenSequences.map((tokens) => this.decode(tokens));
5612
+ return await Promise.all(promises);
5638
5613
  }
5639
5614
  encode(text) {
5640
5615
  if (!this.encoder) {
@@ -5650,8 +5625,8 @@ var ExuluTokenizer = class {
5650
5625
  if (!this.encoder) {
5651
5626
  throw new Error("Tokenizer not initialized");
5652
5627
  }
5653
- const promises2 = texts.map((text) => this.countTokens(text));
5654
- return await Promise.all(promises2);
5628
+ const promises = texts.map((text) => this.countTokens(text));
5629
+ return await Promise.all(promises);
5655
5630
  }
5656
5631
  countTokens(text) {
5657
5632
  if (!this.encoder) {
@@ -6701,6 +6676,48 @@ var execute = async () => {
6701
6676
  return;
6702
6677
  };
6703
6678
 
6679
+ // src/registry/otel.ts
6680
+ import process2 from "process";
6681
+ import { NodeSDK } from "@opentelemetry/sdk-node";
6682
+ import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
6683
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
6684
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
6685
+ import { resourceFromAttributes } from "@opentelemetry/resources";
6686
+ import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
6687
+ import { BatchLogRecordProcessor } from "@opentelemetry/sdk-logs";
6688
+ var create = ({
6689
+ SIGNOZ_ACCESS_TOKEN,
6690
+ SIGNOZ_TRACES_URL,
6691
+ SIGNOZ_LOGS_URL
6692
+ }) => {
6693
+ console.log("[EXULU] Setting up OpenTelemetry");
6694
+ const exporterOptions = {
6695
+ url: SIGNOZ_TRACES_URL,
6696
+ headers: {
6697
+ "signoz-access-token": SIGNOZ_ACCESS_TOKEN
6698
+ }
6699
+ };
6700
+ const traceExporter = new OTLPTraceExporter(exporterOptions);
6701
+ const logExporter = new OTLPLogExporter({
6702
+ url: SIGNOZ_LOGS_URL,
6703
+ headers: {
6704
+ "signoz-access-token": SIGNOZ_ACCESS_TOKEN
6705
+ }
6706
+ });
6707
+ const sdk = new NodeSDK({
6708
+ traceExporter,
6709
+ logRecordProcessors: [new BatchLogRecordProcessor(logExporter)],
6710
+ instrumentations: [getNodeAutoInstrumentations()],
6711
+ resource: resourceFromAttributes({
6712
+ [ATTR_SERVICE_NAME]: "Exulu"
6713
+ })
6714
+ });
6715
+ process2.on("SIGTERM", () => {
6716
+ sdk.shutdown().then(() => console.log("Tracing terminated")).catch((error) => console.log("Error terminating tracing", error)).finally(() => process2.exit(0));
6717
+ });
6718
+ return sdk;
6719
+ };
6720
+
6704
6721
  // types/enums/jobs.ts
6705
6722
  var JOB_STATUS_ENUM = {
6706
6723
  completed: "completed",
@@ -6719,6 +6736,19 @@ var ExuluJobs = {
6719
6736
  validate: validateJob
6720
6737
  }
6721
6738
  };
6739
+ var ExuluOtel = {
6740
+ create: ({
6741
+ SIGNOZ_ACCESS_TOKEN,
6742
+ SIGNOZ_TRACES_URL,
6743
+ SIGNOZ_LOGS_URL
6744
+ }) => {
6745
+ return create({
6746
+ SIGNOZ_ACCESS_TOKEN,
6747
+ SIGNOZ_TRACES_URL,
6748
+ SIGNOZ_LOGS_URL
6749
+ });
6750
+ }
6751
+ };
6722
6752
  var db2 = {
6723
6753
  init: async () => {
6724
6754
  await execute();
@@ -6749,9 +6779,8 @@ export {
6749
6779
  ExuluEmbedder,
6750
6780
  ExuluEval,
6751
6781
  ExuluJobs,
6752
- ExuluLogger,
6782
+ ExuluOtel,
6753
6783
  queues as ExuluQueues,
6754
- ExuluSource,
6755
6784
  ExuluTool,
6756
6785
  ExuluZodFileType,
6757
6786
  db2 as db