@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/CHANGELOG.md +3 -35
- package/dist/index.cjs +1942 -1914
- package/dist/index.d.cts +33 -79
- package/dist/index.d.ts +33 -79
- package/dist/index.js +1938 -1909
- package/documentation/logging.md +122 -0
- package/documentation/otel.md +145 -0
- package/package.json +15 -4
- package/types/models/agent.ts +0 -1
- package/types/models/context.ts +2 -22
- package/types/models/item.ts +1 -1
- package/types/models/variable.ts +8 -0
- package/lms.md +0 -3
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
|
|
77
|
-
import
|
|
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
|
-
|
|
186
|
-
session,
|
|
187
|
-
configuration,
|
|
188
|
-
updater,
|
|
189
|
-
context,
|
|
190
|
-
steps,
|
|
191
|
-
source,
|
|
192
|
-
documents,
|
|
183
|
+
role,
|
|
193
184
|
trigger,
|
|
194
|
-
|
|
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
|
-
|
|
203
|
+
label,
|
|
201
204
|
...embedder && { embedder },
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
...
|
|
205
|
-
...
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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:
|
|
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
|
|
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(
|
|
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
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
return
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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.
|
|
996
|
+
const tableExists = await db3.schema.hasTable(getTableName(this.name));
|
|
1037
997
|
return tableExists;
|
|
1038
998
|
};
|
|
1039
|
-
async
|
|
1040
|
-
if (!
|
|
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 === "
|
|
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
|
|
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
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
|
|
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:
|
|
1058
|
+
label: `${this.embedder.name}`,
|
|
1063
1059
|
embedder: this.embedder.id,
|
|
1064
|
-
|
|
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
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
1102
|
+
const existingItem = await db3.from(getTableName(this.name)).where({ id: item.id }).first();
|
|
1118
1103
|
if (existingItem && upsert) {
|
|
1119
|
-
await this.updateItem(user,
|
|
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 === "
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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:
|
|
1136
|
+
label: `${this.embedder.name}`,
|
|
1145
1137
|
embedder: this.embedder.id,
|
|
1146
|
-
|
|
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
|
-
|
|
1152
|
+
await this.createAndUpsertEmbeddings({
|
|
1158
1153
|
...item,
|
|
1159
1154
|
id: result[0].id
|
|
1160
|
-
}, {
|
|
1161
|
-
label: this.name,
|
|
1162
|
-
trigger: "agent"
|
|
1163
|
-
},
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
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
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
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
|
-
|
|
1903
|
+
]
|
|
1904
|
+
};
|
|
1905
|
+
var agentSessionsSchema = {
|
|
1906
|
+
name: {
|
|
1907
|
+
plural: "agent_sessions",
|
|
1908
|
+
singular: "agent_session"
|
|
1948
1909
|
},
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
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
|
-
|
|
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
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
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
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
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
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
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
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
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
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
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
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
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
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
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
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
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
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
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
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
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
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
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
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
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
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
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: "
|
|
2747
|
-
singular: "
|
|
2163
|
+
plural: "tracking",
|
|
2164
|
+
singular: "tracking"
|
|
2748
2165
|
},
|
|
2749
2166
|
fields: [
|
|
2750
2167
|
{
|
|
2751
|
-
name: "
|
|
2168
|
+
name: "name",
|
|
2752
2169
|
type: "text"
|
|
2753
2170
|
},
|
|
2754
2171
|
{
|
|
2755
|
-
name: "
|
|
2172
|
+
name: "label",
|
|
2756
2173
|
type: "text"
|
|
2757
2174
|
},
|
|
2758
2175
|
{
|
|
2759
|
-
name: "
|
|
2760
|
-
type: "
|
|
2176
|
+
name: "type",
|
|
2177
|
+
type: "enum",
|
|
2178
|
+
enumValues: Object.values(STATISTICS_TYPE_ENUM)
|
|
2761
2179
|
},
|
|
2762
2180
|
{
|
|
2763
|
-
name: "
|
|
2764
|
-
type: "
|
|
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
|
|
2194
|
+
var evalResultsSchema = {
|
|
2794
2195
|
name: {
|
|
2795
|
-
plural: "
|
|
2796
|
-
singular: "
|
|
2196
|
+
plural: "eval_results",
|
|
2197
|
+
singular: "eval_result"
|
|
2797
2198
|
},
|
|
2798
2199
|
fields: [
|
|
2799
2200
|
{
|
|
2800
|
-
name: "
|
|
2801
|
-
type: "
|
|
2802
|
-
index: true,
|
|
2803
|
-
unique: true
|
|
2201
|
+
name: "input",
|
|
2202
|
+
type: "longText"
|
|
2804
2203
|
},
|
|
2805
2204
|
{
|
|
2806
|
-
name: "
|
|
2205
|
+
name: "output",
|
|
2807
2206
|
type: "longText"
|
|
2808
2207
|
},
|
|
2809
2208
|
{
|
|
2810
|
-
name: "
|
|
2811
|
-
type: "
|
|
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: "
|
|
2213
|
+
name: "category",
|
|
2830
2214
|
type: "text"
|
|
2831
2215
|
},
|
|
2832
2216
|
{
|
|
2833
|
-
name: "
|
|
2834
|
-
type: "
|
|
2835
|
-
required: true
|
|
2217
|
+
name: "metadata",
|
|
2218
|
+
type: "json"
|
|
2836
2219
|
},
|
|
2837
2220
|
{
|
|
2838
|
-
name: "
|
|
2839
|
-
type: "
|
|
2840
|
-
required: true
|
|
2221
|
+
name: "result",
|
|
2222
|
+
type: "number"
|
|
2841
2223
|
},
|
|
2842
2224
|
{
|
|
2843
|
-
name: "
|
|
2844
|
-
type: "
|
|
2225
|
+
name: "agent_id",
|
|
2226
|
+
type: "uuid"
|
|
2845
2227
|
},
|
|
2846
2228
|
{
|
|
2847
|
-
name: "
|
|
2848
|
-
type: "
|
|
2229
|
+
name: "workflow_id",
|
|
2230
|
+
type: "uuid"
|
|
2849
2231
|
},
|
|
2850
2232
|
{
|
|
2851
|
-
name: "
|
|
2852
|
-
type: "
|
|
2233
|
+
name: "eval_type",
|
|
2234
|
+
type: "text"
|
|
2853
2235
|
},
|
|
2854
2236
|
{
|
|
2855
|
-
name: "
|
|
2856
|
-
type: "
|
|
2857
|
-
required: true
|
|
2237
|
+
name: "eval_name",
|
|
2238
|
+
type: "text"
|
|
2858
2239
|
},
|
|
2859
2240
|
{
|
|
2860
|
-
name: "
|
|
2861
|
-
type: "
|
|
2241
|
+
name: "comment",
|
|
2242
|
+
type: "longText"
|
|
2862
2243
|
}
|
|
2863
2244
|
]
|
|
2864
2245
|
};
|
|
2865
|
-
var
|
|
2246
|
+
var jobsSchema = {
|
|
2866
2247
|
name: {
|
|
2867
|
-
plural: "
|
|
2868
|
-
singular: "
|
|
2248
|
+
plural: "jobs",
|
|
2249
|
+
singular: "job"
|
|
2869
2250
|
},
|
|
2870
2251
|
RBAC: true,
|
|
2871
2252
|
fields: [
|
|
2872
2253
|
{
|
|
2873
|
-
name: "
|
|
2254
|
+
name: "redis",
|
|
2874
2255
|
type: "text"
|
|
2875
2256
|
},
|
|
2876
2257
|
{
|
|
2877
|
-
name: "
|
|
2258
|
+
name: "session",
|
|
2878
2259
|
type: "text"
|
|
2879
2260
|
},
|
|
2880
2261
|
{
|
|
2881
|
-
name: "
|
|
2262
|
+
name: "status",
|
|
2882
2263
|
type: "text"
|
|
2883
2264
|
},
|
|
2884
2265
|
{
|
|
2885
|
-
name: "
|
|
2266
|
+
name: "type",
|
|
2886
2267
|
type: "text"
|
|
2887
2268
|
},
|
|
2888
2269
|
{
|
|
2889
|
-
name: "
|
|
2890
|
-
type: "
|
|
2891
|
-
},
|
|
2892
|
-
{
|
|
2893
|
-
name: "backend",
|
|
2894
|
-
type: "text"
|
|
2270
|
+
name: "result",
|
|
2271
|
+
type: "longText"
|
|
2895
2272
|
},
|
|
2896
2273
|
{
|
|
2897
|
-
name: "
|
|
2274
|
+
name: "name",
|
|
2898
2275
|
type: "text"
|
|
2899
2276
|
},
|
|
2900
2277
|
{
|
|
2901
|
-
name: "
|
|
2902
|
-
type: "
|
|
2903
|
-
default: false
|
|
2278
|
+
name: "agent",
|
|
2279
|
+
type: "uuid"
|
|
2904
2280
|
},
|
|
2905
2281
|
{
|
|
2906
|
-
name: "
|
|
2907
|
-
type: "
|
|
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: "
|
|
2924
|
-
type
|
|
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: "
|
|
2928
|
-
type: "
|
|
2291
|
+
name: "item",
|
|
2292
|
+
type: "uuid"
|
|
2929
2293
|
},
|
|
2930
2294
|
{
|
|
2931
|
-
name: "
|
|
2932
|
-
type: "
|
|
2295
|
+
name: "steps",
|
|
2296
|
+
type: "number"
|
|
2933
2297
|
},
|
|
2934
2298
|
{
|
|
2935
|
-
name: "
|
|
2936
|
-
type: "
|
|
2299
|
+
name: "inputs",
|
|
2300
|
+
type: "json"
|
|
2937
2301
|
},
|
|
2938
2302
|
{
|
|
2939
|
-
name: "
|
|
2940
|
-
type: "
|
|
2941
|
-
index: true
|
|
2303
|
+
name: "finished_at",
|
|
2304
|
+
type: "date"
|
|
2942
2305
|
},
|
|
2943
2306
|
{
|
|
2944
|
-
name: "
|
|
2945
|
-
type: "
|
|
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: "
|
|
2320
|
+
name: "entity",
|
|
2949
2321
|
type: "text",
|
|
2950
|
-
|
|
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: "
|
|
2971
|
-
type: "text"
|
|
2325
|
+
name: "access_type",
|
|
2326
|
+
type: "text",
|
|
2327
|
+
required: true
|
|
2972
2328
|
},
|
|
2973
2329
|
{
|
|
2974
|
-
name: "
|
|
2975
|
-
type: "
|
|
2330
|
+
name: "target_resource_id",
|
|
2331
|
+
type: "uuid",
|
|
2332
|
+
required: true
|
|
2976
2333
|
},
|
|
2977
2334
|
{
|
|
2978
|
-
name: "
|
|
2979
|
-
type: "
|
|
2335
|
+
name: "role_id",
|
|
2336
|
+
type: "uuid"
|
|
2980
2337
|
},
|
|
2981
2338
|
{
|
|
2982
|
-
name: "
|
|
2983
|
-
type: "
|
|
2339
|
+
name: "user_id",
|
|
2340
|
+
type: "number"
|
|
2984
2341
|
},
|
|
2985
2342
|
{
|
|
2986
|
-
name: "
|
|
2987
|
-
type: "
|
|
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
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
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
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
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
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
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
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
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
|
|
3061
|
-
name
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
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
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
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
|
-
|
|
3179
|
-
name
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
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
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
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
|
-
|
|
3202
|
-
|
|
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
|
-
|
|
3206
|
-
|
|
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
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
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
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
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
|
-
|
|
3225
|
-
name: "
|
|
3226
|
-
type: "
|
|
3227
|
-
|
|
3228
|
-
|
|
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
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
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.
|
|
4185
|
+
throw new Error("Table with name " + getTableName(context.name) + " does not exist.");
|
|
4061
4186
|
}
|
|
4062
|
-
const query = db3.from(context.
|
|
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
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
4156
|
-
|
|
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(
|
|
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(
|
|
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: (
|
|
4805
|
-
if (!
|
|
4806
|
-
throw new Error(`Missing job data for job ${
|
|
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 (!
|
|
4812
|
-
throw new Error(`Missing property "
|
|
4758
|
+
if (!data.type) {
|
|
4759
|
+
throw new Error(`Missing property "type" in data for job ${id}.`);
|
|
4813
4760
|
}
|
|
4814
|
-
if (
|
|
4815
|
-
throw new Error(`
|
|
4761
|
+
if (!data.inputs) {
|
|
4762
|
+
throw new Error(`Missing property "inputs" in data for job ${id}.`);
|
|
4816
4763
|
}
|
|
4817
|
-
if (
|
|
4818
|
-
throw new Error(`Property "
|
|
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
|
-
|
|
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
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
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 ${
|
|
4803
|
+
throw new Error(`Context ${data.context} not found in the registry.`);
|
|
4875
4804
|
}
|
|
4876
|
-
if (!
|
|
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 ===
|
|
4808
|
+
const embedder = contexts.find((context2) => context2.embedder?.id === data.embedder);
|
|
4880
4809
|
if (!embedder) {
|
|
4881
|
-
throw new Error(`Embedder ${
|
|
4810
|
+
throw new Error(`Embedder ${data.embedder} not found in the registry.`);
|
|
4882
4811
|
}
|
|
4883
|
-
|
|
4884
|
-
|
|
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 =
|
|
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
|
|
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
|
|
5637
|
-
return await Promise.all(
|
|
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
|
|
5654
|
-
return await Promise.all(
|
|
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
|
-
|
|
6782
|
+
ExuluOtel,
|
|
6753
6783
|
queues as ExuluQueues,
|
|
6754
|
-
ExuluSource,
|
|
6755
6784
|
ExuluTool,
|
|
6756
6785
|
ExuluZodFileType,
|
|
6757
6786
|
db2 as db
|