@exulu/backend 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -37,7 +37,6 @@ __export(index_exports, {
37
37
  ExuluAuthentication: () => authentication,
38
38
  ExuluChunkers: () => ExuluChunkers,
39
39
  ExuluContext: () => ExuluContext,
40
- ExuluDatabase: () => ExuluDatabase,
41
40
  ExuluEmbedder: () => ExuluEmbedder,
42
41
  ExuluEval: () => ExuluEval,
43
42
  ExuluJobs: () => ExuluJobs,
@@ -112,6 +111,32 @@ var validateJob = (job) => {
112
111
  return job;
113
112
  };
114
113
 
114
+ // src/registry/classes.ts
115
+ var import_zod = require("zod");
116
+ var import_bullmq2 = require("bullmq");
117
+ var import_zod2 = require("zod");
118
+ var fs = __toESM(require("fs"), 1);
119
+ var path = __toESM(require("path"), 1);
120
+ var import_ai = require("ai");
121
+
122
+ // types/enums/statistics.ts
123
+ var STATISTICS_TYPE_ENUM = {
124
+ CONTEXT_RETRIEVE: "context.retrieve",
125
+ SOURCE_UPDATE: "source.update",
126
+ EMBEDDER_UPSERT: "embedder.upsert",
127
+ EMBEDDER_GENERATE: "embedder.generate",
128
+ EMBEDDER_DELETE: "embedder.delete",
129
+ WORKFLOW_RUN: "workflow.run",
130
+ CONTEXT_UPSERT: "context.upsert",
131
+ TOOL_CALL: "tool.call",
132
+ AGENT_RUN: "agent.run"
133
+ };
134
+
135
+ // types/enums/eval-types.ts
136
+ var EVAL_TYPES_ENUM = {
137
+ llm_as_judge: "llm_as_judge"
138
+ };
139
+
115
140
  // src/postgres/client.ts
116
141
  var import_knex = __toESM(require("knex"), 1);
117
142
  var import_knex2 = require("knex");
@@ -120,7 +145,12 @@ var db = {};
120
145
  async function postgresClient() {
121
146
  if (!db["exulu"]) {
122
147
  try {
123
- console.log("[EXULU] Initializing exulu database.");
148
+ console.log("[EXULU] Connecting to exulu database.");
149
+ console.log("[EXULU] POSTGRES_DB_HOST:", process.env.POSTGRES_DB_HOST);
150
+ console.log("[EXULU] POSTGRES_DB_PORT:", process.env.POSTGRES_DB_PORT);
151
+ console.log("[EXULU] POSTGRES_DB_USER:", process.env.POSTGRES_DB_USER);
152
+ console.log("[EXULU] POSTGRES_DB_PASSWORD:", process.env.POSTGRES_DB_PASSWORD);
153
+ console.log("[EXULU] POSTGRES_DB_SSL:", process.env.POSTGRES_DB_SSL);
124
154
  const knex = (0, import_knex.default)({
125
155
  client: "pg",
126
156
  connection: {
@@ -144,745 +174,142 @@ async function postgresClient() {
144
174
  };
145
175
  }
146
176
 
147
- // src/postgres/core-schema.ts
148
- var agentMessagesSchema = {
149
- name: {
150
- plural: "agent_messages",
151
- singular: "agent_message"
152
- },
153
- fields: [
154
- {
155
- name: "content",
156
- type: "text"
157
- },
158
- {
159
- name: "title",
160
- type: "text"
161
- },
162
- {
163
- name: "session",
164
- type: "text"
165
- }
166
- ]
167
- };
168
- var agentSessionsSchema = {
169
- name: {
170
- plural: "agent_sessions",
171
- singular: "agent_session"
172
- },
173
- fields: [
174
- {
175
- name: "agent",
176
- type: "uuid"
177
- },
177
+ // src/registry/classes.ts
178
+ var import_knex4 = __toESM(require("pgvector/knex"), 1);
179
+
180
+ // src/registry/decoraters/bullmq.ts
181
+ var import_bullmq = require("bullmq");
182
+ var import_uuid = require("uuid");
183
+ var bullmqDecorator = async ({
184
+ label,
185
+ type,
186
+ workflow,
187
+ embedder,
188
+ inputs,
189
+ queue,
190
+ user,
191
+ agent,
192
+ session,
193
+ configuration,
194
+ updater,
195
+ context,
196
+ steps,
197
+ source,
198
+ documents,
199
+ trigger,
200
+ item
201
+ }) => {
202
+ const redisId = (0, import_uuid.v4)();
203
+ const job = await queue.add(
204
+ `${embedder || workflow}`,
178
205
  {
179
- name: "user",
180
- // next auth stores users with id type SERIAL, so we need to use number
181
- type: "number"
206
+ type: `${type}`,
207
+ ...embedder && { embedder },
208
+ ...workflow && { workflow },
209
+ ...configuration && { configuration },
210
+ ...updater && { updater },
211
+ ...context && { context },
212
+ ...source && { source },
213
+ ...documents && { documents },
214
+ ...steps && { steps },
215
+ ...trigger && { trigger },
216
+ ...item && { item },
217
+ agent,
218
+ user,
219
+ inputs,
220
+ label,
221
+ session
182
222
  },
183
223
  {
184
- name: "title",
185
- type: "text"
224
+ jobId: redisId
186
225
  }
187
- ]
226
+ );
227
+ const { db: db2 } = await postgresClient();
228
+ const now = /* @__PURE__ */ new Date();
229
+ console.log("[EXULU] scheduling new job", inputs);
230
+ const insertData = {
231
+ name: `${label}`,
232
+ redis: job.id,
233
+ status: "waiting",
234
+ type,
235
+ inputs,
236
+ agent,
237
+ item,
238
+ createdAt: now,
239
+ updatedAt: now,
240
+ user,
241
+ session,
242
+ ...embedder && { embedder },
243
+ ...workflow && { workflow },
244
+ ...configuration && { configuration },
245
+ ...steps && { steps },
246
+ ...updater && { updater },
247
+ ...context && { context },
248
+ ...source && { source },
249
+ ...documents && { documents: documents.map((doc2) => doc2.id) },
250
+ ...trigger && { trigger }
251
+ };
252
+ await db2("jobs").insert(insertData).onConflict("redis").merge({
253
+ ...insertData,
254
+ updatedAt: now
255
+ // Only updatedAt changes on updates
256
+ });
257
+ const doc = await db2.from("jobs").where({ redis: job.id }).first();
258
+ if (!doc?.id) {
259
+ throw new Error("Failed to get job ID after insert/update");
260
+ }
261
+ console.log("[EXULU] created job", doc?.id);
262
+ return {
263
+ ...job,
264
+ id: doc?.id,
265
+ redis: job.id
266
+ };
188
267
  };
189
- var usersSchema = {
190
- name: {
191
- plural: "users",
192
- singular: "user"
193
- },
194
- fields: [
195
- {
196
- name: "firstname",
197
- type: "text"
198
- },
199
- {
200
- name: "name",
201
- type: "text"
202
- },
203
- {
204
- name: "lastname",
205
- type: "text"
206
- },
207
- {
208
- name: "email",
209
- type: "text",
210
- index: true
211
- },
212
- {
213
- name: "temporary_token",
214
- type: "text"
215
- },
216
- {
217
- name: "type",
218
- type: "text",
219
- index: true
220
- },
221
- {
222
- name: "profile_image",
223
- type: "text"
224
- },
225
- {
226
- name: "super_admin",
227
- type: "boolean",
228
- default: false
229
- },
230
- {
231
- name: "status",
232
- type: "text"
233
- },
234
- {
235
- name: "emailVerified",
236
- type: "text"
237
- },
238
- {
239
- name: "apikey",
240
- type: "text"
241
- },
242
- {
243
- name: "last_used",
244
- type: "date"
245
- },
246
- {
247
- name: "anthropic_token",
248
- type: "text"
249
- },
250
- {
251
- name: "role",
252
- type: "uuid"
253
- }
254
- ]
268
+
269
+ // src/registry/utils/map-types.ts
270
+ var mapType = (t, type, name, defaultValue) => {
271
+ if (type === "text") {
272
+ t.text(name);
273
+ return;
274
+ }
275
+ if (type === "longText") {
276
+ t.text(name);
277
+ return;
278
+ }
279
+ if (type === "shortText") {
280
+ t.string(name, 100);
281
+ return;
282
+ }
283
+ if (type === "number") {
284
+ t.float(name);
285
+ return;
286
+ }
287
+ if (type === "boolean") {
288
+ t.boolean(name).defaultTo(defaultValue || false);
289
+ return;
290
+ }
291
+ if (type === "code") {
292
+ t.text(name);
293
+ return;
294
+ }
295
+ if (type === "json") {
296
+ t.jsonb(name);
297
+ return;
298
+ }
299
+ if (type === "date") {
300
+ t.timestamp(name);
301
+ return;
302
+ }
303
+ if (type === "uuid") {
304
+ t.uuid(name);
305
+ return;
306
+ }
307
+ throw new Error("Invalid type: " + type);
255
308
  };
256
- var rolesSchema = {
257
- name: {
258
- plural: "roles",
259
- singular: "role"
260
- },
261
- fields: [
262
- {
263
- name: "name",
264
- type: "text"
265
- },
266
- {
267
- name: "is_admin",
268
- type: "boolean",
269
- default: false
270
- },
271
- {
272
- name: "agents",
273
- type: "json"
274
- }
275
- ]
276
- };
277
- var statisticsSchema = {
278
- name: {
279
- plural: "statistics",
280
- singular: "statistic"
281
- },
282
- fields: [
283
- {
284
- name: "name",
285
- type: "text"
286
- },
287
- {
288
- name: "label",
289
- type: "text"
290
- },
291
- {
292
- name: "type",
293
- type: "text"
294
- },
295
- {
296
- name: "total",
297
- type: "number"
298
- }
299
- ]
300
- };
301
- var workflowSchema = {
302
- name: {
303
- plural: "workflows",
304
- singular: "workflow"
305
- },
306
- fields: [
307
- {
308
- name: "workflow_name",
309
- type: "text"
310
- },
311
- {
312
- name: "run_id",
313
- type: "text"
314
- },
315
- {
316
- name: "snapshot",
317
- type: "text"
318
- }
319
- ]
320
- };
321
- var evalResultsSchema = {
322
- name: {
323
- plural: "eval_results",
324
- singular: "eval_result"
325
- },
326
- fields: [
327
- {
328
- name: "input",
329
- type: "longText"
330
- },
331
- {
332
- name: "output",
333
- type: "longText"
334
- },
335
- {
336
- name: "duration",
337
- type: "number"
338
- },
339
- {
340
- name: "category",
341
- type: "text"
342
- },
343
- {
344
- name: "metadata",
345
- type: "json"
346
- },
347
- {
348
- name: "result",
349
- type: "number"
350
- },
351
- {
352
- name: "agent_id",
353
- type: "uuid"
354
- },
355
- {
356
- name: "workflow_id",
357
- type: "uuid"
358
- },
359
- {
360
- name: "eval_type",
361
- type: "text"
362
- },
363
- {
364
- name: "eval_name",
365
- type: "text"
366
- },
367
- {
368
- name: "comment",
369
- type: "longText"
370
- }
371
- ]
372
- };
373
- var jobsSchema = {
374
- name: {
375
- plural: "jobs",
376
- singular: "job"
377
- },
378
- fields: [
379
- {
380
- name: "redis",
381
- type: "text"
382
- },
383
- {
384
- name: "session",
385
- type: "text"
386
- },
387
- {
388
- name: "status",
389
- type: "text"
390
- },
391
- {
392
- name: "type",
393
- type: "text"
394
- },
395
- {
396
- name: "result",
397
- type: "longText"
398
- },
399
- {
400
- name: "name",
401
- type: "text"
402
- },
403
- {
404
- name: "agent",
405
- type: "uuid"
406
- },
407
- {
408
- name: "workflow",
409
- type: "uuid"
410
- },
411
- {
412
- name: "user",
413
- // next auth stores users with id type SERIAL, so we need to use number
414
- type: "number"
415
- },
416
- {
417
- name: "item",
418
- type: "uuid"
419
- },
420
- {
421
- name: "steps",
422
- type: "number"
423
- },
424
- {
425
- name: "inputs",
426
- type: "json"
427
- },
428
- {
429
- name: "finished_at",
430
- type: "date"
431
- },
432
- {
433
- name: "duration",
434
- type: "number"
435
- }
436
- ]
437
- };
438
- var agentsSchema = {
439
- name: {
440
- plural: "agents",
441
- singular: "agent"
442
- },
443
- fields: [
444
- {
445
- name: "name",
446
- type: "text"
447
- },
448
- {
449
- name: "description",
450
- type: "text"
451
- },
452
- {
453
- name: "extensions",
454
- type: "json"
455
- },
456
- {
457
- name: "backend",
458
- type: "text"
459
- },
460
- {
461
- name: "type",
462
- type: "text"
463
- },
464
- {
465
- name: "active",
466
- type: "boolean",
467
- default: false
468
- },
469
- {
470
- name: "public",
471
- type: "boolean",
472
- default: false
473
- },
474
- {
475
- name: "tools",
476
- type: "json"
477
- }
478
- ]
479
- };
480
-
481
- // src/registry/utils/map-types.ts
482
- var mapType = (t, type, name, defaultValue) => {
483
- if (type === "text") {
484
- t.text(name);
485
- return;
486
- }
487
- if (type === "longText") {
488
- t.text(name);
489
- return;
490
- }
491
- if (type === "shortText") {
492
- t.string(name, 100);
493
- return;
494
- }
495
- if (type === "number") {
496
- t.float(name);
497
- return;
498
- }
499
- if (type === "boolean") {
500
- t.boolean(name).defaultTo(defaultValue || false);
501
- return;
502
- }
503
- if (type === "code") {
504
- t.text(name);
505
- return;
506
- }
507
- if (type === "json") {
508
- t.jsonb(name);
509
- return;
510
- }
511
- if (type === "date") {
512
- t.timestamp(name);
513
- return;
514
- }
515
- if (type === "uuid") {
516
- t.uuid(name);
517
- return;
518
- }
519
- throw new Error("Invalid type: " + type);
520
- };
521
-
522
- // src/registry/utils/sanitize-name.ts
523
- var sanitizeName = (name) => {
524
- return name.toLowerCase().replace(/ /g, "_");
525
- };
526
-
527
- // src/auth/generate-key.ts
528
- var import_bcryptjs = __toESM(require("bcryptjs"), 1);
529
- var SALT_ROUNDS = 12;
530
- async function encryptString(string) {
531
- const hash = await import_bcryptjs.default.hash(string, SALT_ROUNDS);
532
- return hash;
533
- }
534
- var generateApiKey = async (name, email) => {
535
- const { db: db2 } = await postgresClient();
536
- console.log("[EXULU] Inserting default user and admin role.");
537
- const existingRole = await db2.from("roles").where({ name: "admin" }).first();
538
- let roleId;
539
- if (!existingRole) {
540
- console.log("[EXULU] Creating default admin role.");
541
- const role = await db2.from("roles").insert({
542
- name: "admin",
543
- is_admin: true,
544
- agents: []
545
- }).returning("id");
546
- roleId = role[0].id;
547
- } else {
548
- roleId = existingRole.id;
549
- }
550
- const newKeyName = name;
551
- const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
552
- const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
553
- const encryptedKey = await encryptString(plainKey);
554
- const existingApiUser = await db2.from("users").where({ email }).first();
555
- if (!existingApiUser) {
556
- console.log("[EXULU] Creating default api user.");
557
- await db2.from("users").insert({
558
- name,
559
- email,
560
- super_admin: true,
561
- createdAt: /* @__PURE__ */ new Date(),
562
- updatedAt: /* @__PURE__ */ new Date(),
563
- type: "api",
564
- emailVerified: /* @__PURE__ */ new Date(),
565
- apikey: `${encryptedKey}${postFix}`,
566
- // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
567
- role: roleId
568
- });
569
- console.log("[EXULU] Default api user created. Key: ", `${plainKey}${postFix}`);
570
- } else {
571
- console.log("[EXULU] API user with that name already exists.");
572
- }
573
- console.log("[EXULU] Key generated, copy and use the plain key from here, you will not be able to access it again.");
574
- console.log("[EXULU] Key: ", `${plainKey}${postFix}`);
575
- return {
576
- key: `${plainKey}${postFix}`
577
- };
578
- };
579
-
580
- // src/postgres/init-db.ts
581
- var up = async function(knex) {
582
- if (!await knex.schema.hasTable("agent_sessions")) {
583
- await knex.schema.createTable("agent_sessions", (table) => {
584
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
585
- table.timestamp("createdAt").defaultTo(knex.fn.now());
586
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
587
- for (const field of agentSessionsSchema.fields) {
588
- const { type, name, default: defaultValue } = field;
589
- if (!type || !name) {
590
- continue;
591
- }
592
- mapType(table, type, sanitizeName(name), defaultValue);
593
- }
594
- });
595
- }
596
- if (!await knex.schema.hasTable("agent_messages")) {
597
- await knex.schema.createTable("agent_messages", (table) => {
598
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
599
- table.timestamp("createdAt").defaultTo(knex.fn.now());
600
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
601
- for (const field of agentMessagesSchema.fields) {
602
- const { type, name, default: defaultValue } = field;
603
- if (!type || !name) {
604
- continue;
605
- }
606
- mapType(table, type, sanitizeName(name), defaultValue);
607
- }
608
- });
609
- }
610
- if (!await knex.schema.hasTable("roles")) {
611
- await knex.schema.createTable("roles", (table) => {
612
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
613
- table.timestamp("createdAt").defaultTo(knex.fn.now());
614
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
615
- for (const field of rolesSchema.fields) {
616
- const { type, name, default: defaultValue } = field;
617
- if (!type || !name) {
618
- continue;
619
- }
620
- mapType(table, type, sanitizeName(name), defaultValue);
621
- }
622
- });
623
- }
624
- if (!await knex.schema.hasTable("eval_results")) {
625
- await knex.schema.createTable("eval_results", (table) => {
626
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
627
- table.timestamp("createdAt").defaultTo(knex.fn.now());
628
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
629
- for (const field of evalResultsSchema.fields) {
630
- const { type, name, default: defaultValue } = field;
631
- if (!type || !name) {
632
- continue;
633
- }
634
- mapType(table, type, sanitizeName(name), defaultValue);
635
- }
636
- });
637
- }
638
- if (!await knex.schema.hasTable("statistics")) {
639
- await knex.schema.createTable("statistics", (table) => {
640
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
641
- table.timestamp("createdAt").defaultTo(knex.fn.now());
642
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
643
- for (const field of statisticsSchema.fields) {
644
- const { type, name, default: defaultValue } = field;
645
- if (!type || !name) {
646
- continue;
647
- }
648
- mapType(table, type, sanitizeName(name), defaultValue);
649
- }
650
- });
651
- }
652
- if (!await knex.schema.hasTable("jobs")) {
653
- await knex.schema.createTable("jobs", (table) => {
654
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
655
- table.timestamp("createdAt").defaultTo(knex.fn.now());
656
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
657
- for (const field of jobsSchema.fields) {
658
- const { type, name, default: defaultValue } = field;
659
- if (!type || !name) {
660
- continue;
661
- }
662
- mapType(table, type, sanitizeName(name), defaultValue);
663
- }
664
- });
665
- }
666
- if (!await knex.schema.hasTable("agents")) {
667
- await knex.schema.createTable("agents", (table) => {
668
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
669
- table.timestamp("createdAt").defaultTo(knex.fn.now());
670
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
671
- for (const field of agentsSchema.fields) {
672
- const { type, name, default: defaultValue } = field;
673
- if (!type || !name) {
674
- continue;
675
- }
676
- mapType(table, type, sanitizeName(name), defaultValue);
677
- }
678
- });
679
- }
680
- if (!await knex.schema.hasTable("verification_token")) {
681
- await knex.schema.createTable("verification_token", (table) => {
682
- table.text("identifier").notNullable();
683
- table.timestamp("expires", { useTz: true }).notNullable();
684
- table.text("token").notNullable();
685
- table.primary(["identifier", "token"]);
686
- });
687
- }
688
- if (!await knex.schema.hasTable("users")) {
689
- await knex.schema.createTable("users", (table) => {
690
- table.increments("id").primary();
691
- table.timestamp("createdAt").defaultTo(knex.fn.now());
692
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
693
- table.string("name", 255);
694
- table.string("password", 255);
695
- table.string("email", 255);
696
- table.timestamp("emailVerified", { useTz: true });
697
- table.text("image");
698
- for (const field of usersSchema.fields) {
699
- console.log("[EXULU] field", field);
700
- const { type, name, default: defaultValue } = field;
701
- if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
702
- continue;
703
- }
704
- if (!type || !name) {
705
- continue;
706
- }
707
- mapType(table, type, sanitizeName(name), defaultValue);
708
- }
709
- });
710
- }
711
- if (!await knex.schema.hasTable("accounts")) {
712
- await knex.schema.createTable("accounts", (table) => {
713
- table.increments("id").primary();
714
- table.integer("userId").notNullable();
715
- table.string("type", 255).notNullable();
716
- table.string("provider", 255).notNullable();
717
- table.string("providerAccountId", 255).notNullable();
718
- table.text("refresh_token");
719
- table.text("access_token");
720
- table.bigInteger("expires_at");
721
- table.text("id_token");
722
- table.text("scope");
723
- table.text("session_state");
724
- table.text("token_type");
725
- });
726
- }
727
- };
728
- var execute = async () => {
729
- console.log("[EXULU] Initializing database.");
730
- const { db: db2 } = await postgresClient();
731
- await up(db2);
732
- console.log("[EXULU] Inserting default user and admin role.");
733
- const existingRole = await db2.from("roles").where({ name: "admin" }).first();
734
- let roleId;
735
- if (!existingRole) {
736
- console.log("[EXULU] Creating default admin role.");
737
- const role = await db2.from("roles").insert({
738
- name: "admin",
739
- is_admin: true,
740
- agents: JSON.stringify([])
741
- }).returning("id");
742
- roleId = role[0].id;
743
- } else {
744
- roleId = existingRole.id;
745
- }
746
- const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
747
- if (!existingUser) {
748
- const password = await encryptString("admin");
749
- console.log("[EXULU] Creating default admin user.");
750
- await db2.from("users").insert({
751
- name: "exulu",
752
- email: "admin@exulu.com",
753
- super_admin: true,
754
- createdAt: /* @__PURE__ */ new Date(),
755
- emailVerified: /* @__PURE__ */ new Date(),
756
- updatedAt: /* @__PURE__ */ new Date(),
757
- password,
758
- type: "user",
759
- role: roleId
760
- });
761
- }
762
- const { key } = await generateApiKey("exulu", "api@exulu.com");
763
- console.log("[EXULU] Database initialized.");
764
- console.log("[EXULU] Default api key: ", `${key}`);
765
- console.log("[EXULU] Default password if using password auth: ", `admin`);
766
- console.log("[EXULU] Default email if using password auth: ", `admin@exulu.com`);
767
- return;
768
- };
769
-
770
- // src/registry/classes.ts
771
- var import_zod = require("zod");
772
- var import_bullmq2 = require("bullmq");
773
- var import_zod2 = require("zod");
774
- var fs = __toESM(require("fs"), 1);
775
- var path = __toESM(require("path"), 1);
776
- var import_ai = require("ai");
777
-
778
- // types/enums/statistics.ts
779
- var STATISTICS_TYPE_ENUM = {
780
- CONTEXT_RETRIEVE: "context.retrieve",
781
- SOURCE_UPDATE: "source.update",
782
- EMBEDDER_UPSERT: "embedder.upsert",
783
- EMBEDDER_GENERATE: "embedder.generate",
784
- EMBEDDER_DELETE: "embedder.delete",
785
- WORKFLOW_RUN: "workflow.run",
786
- CONTEXT_UPSERT: "context.upsert",
787
- TOOL_CALL: "tool.call",
788
- AGENT_RUN: "agent.run"
789
- };
790
-
791
- // types/enums/eval-types.ts
792
- var EVAL_TYPES_ENUM = {
793
- llm_as_judge: "llm_as_judge"
794
- };
795
-
796
- // src/registry/classes.ts
797
- var import_knex4 = __toESM(require("pgvector/knex"), 1);
798
309
 
799
- // src/registry/decoraters/bullmq.ts
800
- var import_bullmq = require("bullmq");
801
- var import_uuid = require("uuid");
802
- var bullmqDecorator = async ({
803
- label,
804
- type,
805
- workflow,
806
- embedder,
807
- inputs,
808
- queue,
809
- user,
810
- agent,
811
- session,
812
- configuration,
813
- updater,
814
- context,
815
- steps,
816
- source,
817
- documents,
818
- trigger,
819
- item
820
- }) => {
821
- const redisId = (0, import_uuid.v4)();
822
- const job = await queue.add(
823
- `${embedder || workflow}`,
824
- {
825
- type: `${type}`,
826
- ...embedder && { embedder },
827
- ...workflow && { workflow },
828
- ...configuration && { configuration },
829
- ...updater && { updater },
830
- ...context && { context },
831
- ...source && { source },
832
- ...documents && { documents },
833
- ...steps && { steps },
834
- ...trigger && { trigger },
835
- ...item && { item },
836
- agent,
837
- user,
838
- inputs,
839
- label,
840
- session
841
- },
842
- {
843
- jobId: redisId
844
- }
845
- );
846
- const { db: db2 } = await postgresClient();
847
- const now = /* @__PURE__ */ new Date();
848
- console.log("[EXULU] scheduling new job", inputs);
849
- const insertData = {
850
- name: `${label}`,
851
- redis: job.id,
852
- status: "waiting",
853
- type,
854
- inputs,
855
- agent,
856
- item,
857
- createdAt: now,
858
- updatedAt: now,
859
- user,
860
- session,
861
- ...embedder && { embedder },
862
- ...workflow && { workflow },
863
- ...configuration && { configuration },
864
- ...steps && { steps },
865
- ...updater && { updater },
866
- ...context && { context },
867
- ...source && { source },
868
- ...documents && { documents: documents.map((doc2) => doc2.id) },
869
- ...trigger && { trigger }
870
- };
871
- await db2("jobs").insert(insertData).onConflict("redis").merge({
872
- ...insertData,
873
- updatedAt: now
874
- // Only updatedAt changes on updates
875
- });
876
- const doc = await db2.from("jobs").where({ redis: job.id }).first();
877
- if (!doc?.id) {
878
- throw new Error("Failed to get job ID after insert/update");
879
- }
880
- console.log("[EXULU] created job", doc?.id);
881
- return {
882
- ...job,
883
- id: doc?.id,
884
- redis: job.id
885
- };
310
+ // src/registry/utils/sanitize-name.ts
311
+ var sanitizeName = (name) => {
312
+ return name.toLowerCase().replace(/ /g, "_");
886
313
  };
887
314
 
888
315
  // types/enums/jobs.ts
@@ -2063,7 +1490,7 @@ var getToken = async (authHeader) => {
2063
1490
  };
2064
1491
 
2065
1492
  // src/auth/auth.ts
2066
- var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
1493
+ var import_bcryptjs = __toESM(require("bcryptjs"), 1);
2067
1494
  var authentication = async ({
2068
1495
  apikey,
2069
1496
  authtoken,
@@ -2158,7 +1585,7 @@ var authentication = async ({
2158
1585
  for (const user of filtered) {
2159
1586
  const user_key_last_slash_index = user.apikey.lastIndexOf("/");
2160
1587
  const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
2161
- const isMatch = await import_bcryptjs2.default.compare(request_key_compare_value, user_key_compare_value);
1588
+ const isMatch = await import_bcryptjs.default.compare(request_key_compare_value, user_key_compare_value);
2162
1589
  if (isMatch) {
2163
1590
  await db2.from("users").where({ id: user.id }).update({
2164
1591
  last_used: /* @__PURE__ */ new Date()
@@ -2215,595 +1642,929 @@ var requestValidators = {
2215
1642
  message: "Missing body."
2216
1643
  };
2217
1644
  }
2218
- if (!req.body.agent) {
2219
- return {
2220
- error: true,
2221
- code: 400,
2222
- message: "Missing agent in body."
2223
- };
1645
+ if (!req.body.agent) {
1646
+ return {
1647
+ error: true,
1648
+ code: 400,
1649
+ message: "Missing agent in body."
1650
+ };
1651
+ }
1652
+ if (!req.body.session) {
1653
+ return {
1654
+ error: true,
1655
+ code: 400,
1656
+ message: "Missing session in body."
1657
+ };
1658
+ }
1659
+ if (!req.body.inputs) {
1660
+ return {
1661
+ error: true,
1662
+ code: 400,
1663
+ message: "Missing inputs in body."
1664
+ };
1665
+ }
1666
+ if (!req.body.label) {
1667
+ return {
1668
+ error: true,
1669
+ code: 400,
1670
+ message: "Missing label for job in body."
1671
+ };
1672
+ }
1673
+ return {
1674
+ error: false
1675
+ };
1676
+ },
1677
+ embedders: (req, configuration) => {
1678
+ const contentType = req.headers["content-type"] || "";
1679
+ if (!contentType.includes("application/json")) {
1680
+ return {
1681
+ error: true,
1682
+ code: 400,
1683
+ message: "Unsupported content type."
1684
+ };
1685
+ }
1686
+ if (!req.body) {
1687
+ return {
1688
+ error: true,
1689
+ code: 400,
1690
+ message: "Missing body."
1691
+ };
1692
+ }
1693
+ if (!req.body.inputs) {
1694
+ return {
1695
+ error: true,
1696
+ code: 400,
1697
+ message: "Missing inputs."
1698
+ };
1699
+ }
1700
+ if (!req.body.label) {
1701
+ return {
1702
+ error: true,
1703
+ code: 400,
1704
+ message: "Missing label for job in body."
1705
+ };
1706
+ }
1707
+ if (configuration) {
1708
+ for (const key in configuration) {
1709
+ if (!req.body.configuration[key]) {
1710
+ return {
1711
+ error: true,
1712
+ code: 400,
1713
+ message: `Missing ${key} in body.configuration.`
1714
+ };
1715
+ }
1716
+ }
1717
+ }
1718
+ return {
1719
+ error: false
1720
+ };
1721
+ },
1722
+ agents: (req) => {
1723
+ const contentType = req.headers["content-type"] || "";
1724
+ if (!contentType.includes("application/json")) {
1725
+ return {
1726
+ error: true,
1727
+ code: 400,
1728
+ message: "Unsupported content type."
1729
+ };
1730
+ }
1731
+ if (!req.body) {
1732
+ return {
1733
+ error: true,
1734
+ code: 400,
1735
+ message: "Missing body."
1736
+ };
1737
+ }
1738
+ if (!req.body.threadId) {
1739
+ return {
1740
+ error: true,
1741
+ code: 400,
1742
+ message: "Missing threadId in body."
1743
+ };
1744
+ }
1745
+ if (!req.body.resourceId) {
1746
+ return {
1747
+ error: true,
1748
+ code: 400,
1749
+ message: "Missing resourceId in body."
1750
+ };
1751
+ }
1752
+ if (!req.body.messages) {
1753
+ return {
1754
+ error: true,
1755
+ code: 400,
1756
+ message: 'Missing "messages" property in body.'
1757
+ };
1758
+ }
1759
+ return {
1760
+ error: false
1761
+ };
1762
+ }
1763
+ };
1764
+
1765
+ // src/registry/routes.ts
1766
+ var import_zodex = require("zodex");
1767
+
1768
+ // src/bullmq/queues.ts
1769
+ var import_bullmq4 = require("bullmq");
1770
+ var ExuluQueues = class {
1771
+ queues;
1772
+ constructor() {
1773
+ this.queues = [];
1774
+ }
1775
+ queue(name) {
1776
+ return this.queues.find((x) => x.name === name);
1777
+ }
1778
+ use(name) {
1779
+ const existing = this.queues.find((x) => x.name === name);
1780
+ if (existing) {
1781
+ return existing;
1782
+ }
1783
+ if (!redisServer.host?.length || !redisServer.port?.length) {
1784
+ console.error(`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or workflow (look for ExuluQueues.use() ).`);
1785
+ throw new Error(`[EXULU] no redis server configured.`);
1786
+ }
1787
+ const newQueue = new import_bullmq4.Queue(`${name}`, { connection: redisServer });
1788
+ this.queues.push(newQueue);
1789
+ return newQueue;
1790
+ }
1791
+ };
1792
+ var queues = new ExuluQueues();
1793
+
1794
+ // types/models/vector-methods.ts
1795
+ var VectorMethodEnum = {
1796
+ "cosineDistance": "cosineDistance",
1797
+ "l1Distance": "l1Distance",
1798
+ "l2Distance": "l2Distance",
1799
+ "hammingDistance": "hammingDistance",
1800
+ "jaccardDistance": "jaccardDistance",
1801
+ "maxInnerProduct": "maxInnerProduct"
1802
+ };
1803
+
1804
+ // src/registry/routes.ts
1805
+ var import_express3 = __toESM(require("express"), 1);
1806
+ var import_server3 = require("@apollo/server");
1807
+ var Papa = __toESM(require("papaparse"), 1);
1808
+ var import_cors = __toESM(require("cors"), 1);
1809
+ var import_reflect_metadata = require("reflect-metadata");
1810
+
1811
+ // src/registry/utils/graphql.ts
1812
+ var import_schema = require("@graphql-tools/schema");
1813
+ var import_graphql_type_json = __toESM(require("graphql-type-json"), 1);
1814
+ var import_graphql = require("graphql");
1815
+ var import_crypto_js = __toESM(require("crypto-js"), 1);
1816
+ var GraphQLDate = new import_graphql.GraphQLScalarType({
1817
+ name: "Date",
1818
+ description: "Date custom scalar type",
1819
+ serialize(value) {
1820
+ if (value instanceof Date) {
1821
+ return value.toISOString();
1822
+ }
1823
+ if (typeof value === "number") {
1824
+ return new Date(value).toISOString();
2224
1825
  }
2225
- if (!req.body.session) {
2226
- return {
2227
- error: true,
2228
- code: 400,
2229
- message: "Missing session in body."
2230
- };
1826
+ if (typeof value === "string") {
1827
+ return new Date(value).toISOString();
2231
1828
  }
2232
- if (!req.body.inputs) {
2233
- return {
2234
- error: true,
2235
- code: 400,
2236
- message: "Missing inputs in body."
2237
- };
1829
+ return value;
1830
+ },
1831
+ parseValue(value) {
1832
+ if (typeof value === "string") {
1833
+ return new Date(value);
2238
1834
  }
2239
- if (!req.body.label) {
2240
- return {
2241
- error: true,
2242
- code: 400,
2243
- message: "Missing label for job in body."
2244
- };
1835
+ if (typeof value === "number") {
1836
+ return new Date(value);
2245
1837
  }
2246
- return {
2247
- error: false
2248
- };
1838
+ return value;
2249
1839
  },
2250
- embedders: (req, configuration) => {
2251
- const contentType = req.headers["content-type"] || "";
2252
- if (!contentType.includes("application/json")) {
2253
- return {
2254
- error: true,
2255
- code: 400,
2256
- message: "Unsupported content type."
2257
- };
1840
+ parseLiteral(ast) {
1841
+ if (ast.kind === import_graphql.Kind.STRING) {
1842
+ return new Date(ast.value);
2258
1843
  }
2259
- if (!req.body) {
2260
- return {
2261
- error: true,
2262
- code: 400,
2263
- message: "Missing body."
2264
- };
1844
+ if (ast.kind === import_graphql.Kind.INT) {
1845
+ return new Date(parseInt(ast.value, 10));
1846
+ }
1847
+ return null;
1848
+ }
1849
+ });
1850
+ var map = (field) => {
1851
+ let type;
1852
+ switch (field.type) {
1853
+ case "text":
1854
+ case "shortText":
1855
+ case "longText":
1856
+ case "code":
1857
+ type = "String";
1858
+ break;
1859
+ case "number":
1860
+ type = "Float";
1861
+ break;
1862
+ case "boolean":
1863
+ type = "Boolean";
1864
+ break;
1865
+ case "json":
1866
+ type = "JSON";
1867
+ break;
1868
+ case "date":
1869
+ type = "Date";
1870
+ break;
1871
+ default:
1872
+ type = "String";
1873
+ }
1874
+ return type;
1875
+ };
1876
+ function createTypeDefs(table) {
1877
+ const fields = table.fields.map((field) => {
1878
+ let type;
1879
+ type = map(field);
1880
+ const required = field.required ? "!" : "";
1881
+ return ` ${field.name}: ${type}${required}`;
1882
+ });
1883
+ const typeDef = `
1884
+ type ${table.name.singular} {
1885
+ ${fields.join("\n")}
1886
+ id: ID!
1887
+ createdAt: Date!
1888
+ updatedAt: Date!
1889
+ }
1890
+ `;
1891
+ const inputDef = `
1892
+ input ${table.name.singular}Input {
1893
+ ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
1894
+ }
1895
+ `;
1896
+ return typeDef + inputDef;
1897
+ }
1898
+ function createFilterTypeDefs(table) {
1899
+ const fieldFilters = table.fields.map((field) => {
1900
+ let type;
1901
+ type = map(field);
1902
+ return `
1903
+ ${field.name}: FilterOperator${type}`;
1904
+ });
1905
+ const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
1906
+ const operatorTypes = `
1907
+ input FilterOperatorString {
1908
+ eq: String
1909
+ ne: String
1910
+ in: [String]
1911
+ contains: String
1912
+ }
1913
+
1914
+ input FilterOperatorDate {
1915
+ lte: Date
1916
+ gte: Date
1917
+ }
1918
+
1919
+ input FilterOperatorFloat {
1920
+ eq: Float
1921
+ ne: Float
1922
+ in: [Float]
1923
+ }
1924
+
1925
+ input FilterOperatorBoolean {
1926
+ eq: Boolean
1927
+ ne: Boolean
1928
+ in: [Boolean]
1929
+ }
1930
+
1931
+ input FilterOperatorJSON {
1932
+ eq: JSON
1933
+ ne: JSON
1934
+ in: [JSON]
1935
+ }
1936
+
1937
+ input SortBy {
1938
+ field: String!
1939
+ direction: SortDirection!
1940
+ }
1941
+
1942
+ enum SortDirection {
1943
+ ASC
1944
+ DESC
1945
+ }
1946
+
1947
+ input Filter${tableNameSingularUpperCaseFirst} {
1948
+ ${fieldFilters.join("\n")}
1949
+ }`;
1950
+ return operatorTypes;
1951
+ }
1952
+ var getRequestedFields = (info) => {
1953
+ const selections = info.operation.selectionSet.selections[0].selectionSet.selections;
1954
+ const itemsSelection = selections.find((s) => s.name.value === "items");
1955
+ const fields = itemsSelection ? Object.keys(itemsSelection.selectionSet.selections.reduce((acc, field) => {
1956
+ acc[field.name.value] = true;
1957
+ return acc;
1958
+ }, {})) : Object.keys(selections.reduce((acc, field) => {
1959
+ acc[field.name.value] = true;
1960
+ return acc;
1961
+ }, {}));
1962
+ return fields.filter((field) => field !== "pageInfo" && field !== "items");
1963
+ };
1964
+ function createMutations(table) {
1965
+ const tableNamePlural = table.name.plural.toLowerCase();
1966
+ const tableNameSingular = table.name.singular.toLowerCase();
1967
+ return {
1968
+ [`${tableNamePlural}CreateOne`]: async (_, args, context, info) => {
1969
+ const { db: db2 } = context;
1970
+ const requestedFields = getRequestedFields(info);
1971
+ let { input } = args;
1972
+ input = encryptSensitiveFields(input);
1973
+ const results = await db2(tableNamePlural).insert({
1974
+ ...input,
1975
+ createdAt: /* @__PURE__ */ new Date(),
1976
+ updatedAt: /* @__PURE__ */ new Date()
1977
+ }).returning(requestedFields);
1978
+ return results[0];
1979
+ },
1980
+ [`${tableNamePlural}UpdateOne`]: async (_, args, context, info) => {
1981
+ const { db: db2 } = context;
1982
+ let { where, input } = args;
1983
+ input = encryptSensitiveFields(input);
1984
+ await db2(tableNamePlural).where(where).update({
1985
+ ...input,
1986
+ updatedAt: /* @__PURE__ */ new Date()
1987
+ });
1988
+ const requestedFields = getRequestedFields(info);
1989
+ const result = await db2.from(tableNamePlural).select(requestedFields).where(where).first();
1990
+ return result;
1991
+ },
1992
+ [`${tableNamePlural}UpdateOneById`]: async (_, args, context, info) => {
1993
+ let { id, input } = args;
1994
+ input = encryptSensitiveFields(input);
1995
+ const { db: db2 } = context;
1996
+ await db2(tableNamePlural).where({ id }).update({
1997
+ ...input,
1998
+ updatedAt: /* @__PURE__ */ new Date()
1999
+ });
2000
+ const requestedFields = getRequestedFields(info);
2001
+ const result = await db2.from(tableNamePlural).select(requestedFields).where({ id }).first();
2002
+ return result;
2003
+ },
2004
+ [`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
2005
+ const { db: db2 } = context;
2006
+ const { where } = args;
2007
+ const requestedFields = getRequestedFields(info);
2008
+ const result = await db2.from(tableNamePlural).select(requestedFields).where(where).first();
2009
+ await db2(tableNamePlural).where(where).del();
2010
+ return result;
2011
+ },
2012
+ [`${tableNamePlural}RemoveOneById`]: async (_, args, context, info) => {
2013
+ const { id } = args;
2014
+ const { db: db2 } = context;
2015
+ const requestedFields = getRequestedFields(info);
2016
+ const result = await db2.from(tableNamePlural).select(requestedFields).where({ id }).first();
2017
+ await db2(tableNamePlural).where({ id }).del();
2018
+ return result;
2265
2019
  }
2266
- if (!req.body.inputs) {
2267
- return {
2268
- error: true,
2269
- code: 400,
2270
- message: "Missing inputs."
2271
- };
2020
+ };
2021
+ }
2022
+ function createQueries(table) {
2023
+ const tableNamePlural = table.name.plural.toLowerCase();
2024
+ const tableNameSingular = table.name.singular.toLowerCase();
2025
+ const applyFilters = (query, filters) => {
2026
+ filters.forEach((filter) => {
2027
+ Object.entries(filter).forEach(([fieldName, operators]) => {
2028
+ if (operators) {
2029
+ if (operators.eq !== void 0) {
2030
+ query = query.where(fieldName, operators.eq);
2031
+ }
2032
+ if (operators.ne !== void 0) {
2033
+ query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2034
+ }
2035
+ if (operators.in !== void 0) {
2036
+ query = query.whereIn(fieldName, operators.in);
2037
+ }
2038
+ if (operators.contains !== void 0) {
2039
+ query = query.where(fieldName, "like", `%${operators.contains}%`);
2040
+ }
2041
+ }
2042
+ });
2043
+ });
2044
+ return query;
2045
+ };
2046
+ const applySorting = (query, sort) => {
2047
+ if (sort) {
2048
+ query = query.orderBy(sort.field, sort.direction.toLowerCase());
2272
2049
  }
2273
- if (!req.body.label) {
2050
+ return query;
2051
+ };
2052
+ return {
2053
+ [`${tableNameSingular}ById`]: async (_, args, context, info) => {
2054
+ const { db: db2 } = context;
2055
+ const requestedFields = getRequestedFields(info);
2056
+ const result = await db2.from(tableNamePlural).select(requestedFields).where({ id: args.id }).first();
2057
+ return result;
2058
+ },
2059
+ [`${tableNameSingular}One`]: async (_, args, context, info) => {
2060
+ const { filters = [], sort } = args;
2061
+ const { db: db2 } = context;
2062
+ const requestedFields = getRequestedFields(info);
2063
+ let query = db2.from(tableNamePlural).select(requestedFields);
2064
+ query = applyFilters(query, filters);
2065
+ query = applySorting(query, sort);
2066
+ const result = await query.first();
2067
+ return result;
2068
+ },
2069
+ [`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
2070
+ const { limit = 10, page = 0, filters = [], sort } = args;
2071
+ const { db: db2 } = context;
2072
+ let baseQuery = db2(tableNamePlural);
2073
+ baseQuery = applyFilters(baseQuery, filters);
2074
+ const [{ count }] = await baseQuery.clone().count("* as count");
2075
+ const itemCount = Number(count);
2076
+ const pageCount = Math.ceil(itemCount / limit);
2077
+ const currentPage = page;
2078
+ const hasPreviousPage = currentPage > 1;
2079
+ const hasNextPage = currentPage < pageCount - 1;
2080
+ let dataQuery = baseQuery.clone();
2081
+ const requestedFields = getRequestedFields(info);
2082
+ dataQuery = applySorting(dataQuery, sort);
2083
+ if (page > 1) {
2084
+ dataQuery = dataQuery.offset((page - 1) * limit);
2085
+ }
2086
+ const items = await dataQuery.select(requestedFields).limit(limit);
2274
2087
  return {
2275
- error: true,
2276
- code: 400,
2277
- message: "Missing label for job in body."
2088
+ pageInfo: {
2089
+ pageCount,
2090
+ itemCount,
2091
+ currentPage,
2092
+ hasPreviousPage,
2093
+ hasNextPage
2094
+ },
2095
+ items
2278
2096
  };
2279
- }
2280
- if (configuration) {
2281
- for (const key in configuration) {
2282
- if (!req.body.configuration[key]) {
2283
- return {
2284
- error: true,
2285
- code: 400,
2286
- message: `Missing ${key} in body.configuration.`
2287
- };
2097
+ },
2098
+ // Add jobStatistics query for jobs table
2099
+ ...tableNamePlural === "jobs" ? {
2100
+ jobStatistics: async (_, args, context, info) => {
2101
+ const { user, agent, from, to } = args;
2102
+ const { db: db2 } = context;
2103
+ let query = db2("jobs");
2104
+ if (user) {
2105
+ query = query.where("user", user);
2106
+ }
2107
+ if (agent) {
2108
+ query = query.where("agent", agent);
2109
+ }
2110
+ if (from) {
2111
+ query = query.where("createdAt", ">=", from);
2112
+ }
2113
+ if (to) {
2114
+ query = query.where("createdAt", "<=", to);
2288
2115
  }
2116
+ const completedQuery = query.clone().where("status", "completed");
2117
+ const [{ completedCount }] = await completedQuery.count("* as completedCount");
2118
+ const failedQuery = query.clone().where("status", "failed");
2119
+ const [{ failedCount }] = await failedQuery.count("* as failedCount");
2120
+ const durationQuery = query.clone().where("status", "completed").whereNotNull("duration").select(db2.raw('AVG("duration") as averageDuration'));
2121
+ const [{ averageDuration }] = await durationQuery;
2122
+ return {
2123
+ completedCount: Number(completedCount),
2124
+ failedCount: Number(failedCount),
2125
+ averageDuration: averageDuration ? Number(averageDuration) : 0
2126
+ };
2289
2127
  }
2128
+ } : {}
2129
+ };
2130
+ }
2131
+ function createSDL(tables) {
2132
+ let typeDefs = `
2133
+ scalar JSON
2134
+ scalar Date
2135
+
2136
+ type Query {
2137
+ `;
2138
+ let mutationDefs = `
2139
+ type Mutation {
2140
+ `;
2141
+ let modelDefs = "";
2142
+ const resolvers = { JSON: import_graphql_type_json.default, Date: GraphQLDate, Query: {}, Mutation: {} };
2143
+ for (const table of tables) {
2144
+ const tableNamePlural = table.name.plural.toLowerCase();
2145
+ const tableNameSingular = table.name.singular.toLowerCase();
2146
+ const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2147
+ typeDefs += `
2148
+ ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2149
+ ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2150
+ ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2151
+ ${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
2152
+ `;
2153
+ mutationDefs += `
2154
+ ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
2155
+ ${tableNamePlural}UpdateOne(where: JSON!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2156
+ ${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2157
+ ${tableNamePlural}RemoveOne(where: JSON!): ${tableNameSingular}
2158
+ ${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
2159
+ `;
2160
+ modelDefs += createTypeDefs(table);
2161
+ modelDefs += createFilterTypeDefs(table);
2162
+ modelDefs += `
2163
+ type ${tableNameSingularUpperCaseFirst}PaginationResult {
2164
+ pageInfo: PageInfo!
2165
+ items: [${tableNameSingular}]!
2166
+ }
2167
+
2168
+ type PageInfo {
2169
+ pageCount: Int!
2170
+ itemCount: Int!
2171
+ currentPage: Int!
2172
+ hasPreviousPage: Boolean!
2173
+ hasNextPage: Boolean!
2174
+ }
2175
+ `;
2176
+ if (tableNamePlural === "jobs") {
2177
+ modelDefs += `
2178
+ type JobStatistics {
2179
+ completedCount: Int!
2180
+ failedCount: Int!
2181
+ averageDuration: Float!
2182
+ }
2183
+ `;
2290
2184
  }
2291
- return {
2292
- error: false
2293
- };
2294
- },
2295
- agents: (req) => {
2296
- const contentType = req.headers["content-type"] || "";
2297
- if (!contentType.includes("application/json")) {
2298
- return {
2299
- error: true,
2300
- code: 400,
2301
- message: "Unsupported content type."
2302
- };
2303
- }
2304
- if (!req.body) {
2305
- return {
2306
- error: true,
2307
- code: 400,
2308
- message: "Missing body."
2309
- };
2310
- }
2311
- if (!req.body.threadId) {
2312
- return {
2313
- error: true,
2314
- code: 400,
2315
- message: "Missing threadId in body."
2316
- };
2317
- }
2318
- if (!req.body.resourceId) {
2319
- return {
2320
- error: true,
2321
- code: 400,
2322
- message: "Missing resourceId in body."
2323
- };
2324
- }
2325
- if (!req.body.messages) {
2326
- return {
2327
- error: true,
2328
- code: 400,
2329
- message: 'Missing "messages" property in body.'
2330
- };
2185
+ Object.assign(resolvers.Query, createQueries(table));
2186
+ Object.assign(resolvers.Mutation, createMutations(table));
2187
+ }
2188
+ typeDefs += "}\n";
2189
+ mutationDefs += "}\n";
2190
+ const fullSDL = typeDefs + mutationDefs + modelDefs;
2191
+ const schema = (0, import_schema.makeExecutableSchema)({
2192
+ typeDefs: fullSDL,
2193
+ resolvers
2194
+ });
2195
+ console.log("\n\u{1F4CA} GraphQL Schema Overview\n");
2196
+ const queriesTable = Object.keys(resolvers.Query).map((query) => ({
2197
+ "Operation Type": "Query",
2198
+ "Name": query,
2199
+ "Description": "Retrieves data"
2200
+ }));
2201
+ const mutationsTable = Object.keys(resolvers.Mutation).map((mutation) => ({
2202
+ "Operation Type": "Mutation",
2203
+ "Name": mutation,
2204
+ "Description": "Modifies data"
2205
+ }));
2206
+ const typesTable = tables.flatMap(
2207
+ (table) => table.fields.map((field) => ({
2208
+ "Type": table.name.singular,
2209
+ "Field": field.name,
2210
+ "Field Type": field.type,
2211
+ "Required": field.required ? "Yes" : "No"
2212
+ }))
2213
+ );
2214
+ console.log("\u{1F50D} Operations:");
2215
+ console.table([...queriesTable, ...mutationsTable]);
2216
+ console.log("\n\u{1F4DD} Types and Fields:");
2217
+ console.table(typesTable);
2218
+ console.log("\n");
2219
+ return schema;
2220
+ }
2221
+ var sensitiveFields = ["anthropic_token"];
2222
+ var encryptSensitiveFields = (input) => {
2223
+ sensitiveFields.forEach((field) => {
2224
+ if (input[field]) {
2225
+ input[field] = import_crypto_js.default.AES.encrypt(input[field], process.env.NEXTAUTH_SECRET).toString();
2331
2226
  }
2332
- return {
2333
- error: false
2334
- };
2335
- }
2227
+ });
2228
+ return input;
2336
2229
  };
2337
2230
 
2338
2231
  // src/registry/routes.ts
2339
- var import_zodex = require("zodex");
2232
+ var import_express5 = require("@as-integrations/express5");
2340
2233
 
2341
- // src/bullmq/queues.ts
2342
- var import_bullmq4 = require("bullmq");
2343
- var ExuluQueues = class {
2344
- queues;
2345
- constructor() {
2346
- this.queues = [];
2347
- }
2348
- queue(name) {
2349
- return this.queues.find((x) => x.name === name);
2350
- }
2351
- use(name) {
2352
- const existing = this.queues.find((x) => x.name === name);
2353
- if (existing) {
2354
- return existing;
2355
- }
2356
- if (!redisServer.host?.length || !redisServer.port?.length) {
2357
- console.error(`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or workflow (look for ExuluQueues.use() ).`);
2358
- throw new Error(`[EXULU] no redis server configured.`);
2234
+ // src/postgres/core-schema.ts
2235
+ var agentMessagesSchema = {
2236
+ name: {
2237
+ plural: "agent_messages",
2238
+ singular: "agent_message"
2239
+ },
2240
+ fields: [
2241
+ {
2242
+ name: "content",
2243
+ type: "text"
2244
+ },
2245
+ {
2246
+ name: "title",
2247
+ type: "text"
2248
+ },
2249
+ {
2250
+ name: "session",
2251
+ type: "text"
2359
2252
  }
2360
- const newQueue = new import_bullmq4.Queue(`${name}`, { connection: redisServer });
2361
- this.queues.push(newQueue);
2362
- return newQueue;
2363
- }
2253
+ ]
2364
2254
  };
2365
- var queues = new ExuluQueues();
2366
-
2367
- // types/models/vector-methods.ts
2368
- var VectorMethodEnum = {
2369
- "cosineDistance": "cosineDistance",
2370
- "l1Distance": "l1Distance",
2371
- "l2Distance": "l2Distance",
2372
- "hammingDistance": "hammingDistance",
2373
- "jaccardDistance": "jaccardDistance",
2374
- "maxInnerProduct": "maxInnerProduct"
2255
+ var agentSessionsSchema = {
2256
+ name: {
2257
+ plural: "agent_sessions",
2258
+ singular: "agent_session"
2259
+ },
2260
+ fields: [
2261
+ {
2262
+ name: "agent",
2263
+ type: "uuid"
2264
+ },
2265
+ {
2266
+ name: "user",
2267
+ // next auth stores users with id type SERIAL, so we need to use number
2268
+ type: "number"
2269
+ },
2270
+ {
2271
+ name: "title",
2272
+ type: "text"
2273
+ }
2274
+ ]
2375
2275
  };
2376
-
2377
- // src/registry/routes.ts
2378
- var import_express3 = __toESM(require("express"), 1);
2379
- var import_server3 = require("@apollo/server");
2380
- var Papa = __toESM(require("papaparse"), 1);
2381
- var import_cors = __toESM(require("cors"), 1);
2382
- var import_reflect_metadata = require("reflect-metadata");
2383
-
2384
- // src/registry/utils/graphql.ts
2385
- var import_schema = require("@graphql-tools/schema");
2386
- var import_graphql_type_json = __toESM(require("graphql-type-json"), 1);
2387
- var import_graphql = require("graphql");
2388
- var import_crypto_js = __toESM(require("crypto-js"), 1);
2389
- var GraphQLDate = new import_graphql.GraphQLScalarType({
2390
- name: "Date",
2391
- description: "Date custom scalar type",
2392
- serialize(value) {
2393
- if (value instanceof Date) {
2394
- return value.toISOString();
2276
+ var usersSchema = {
2277
+ name: {
2278
+ plural: "users",
2279
+ singular: "user"
2280
+ },
2281
+ fields: [
2282
+ {
2283
+ name: "firstname",
2284
+ type: "text"
2285
+ },
2286
+ {
2287
+ name: "name",
2288
+ type: "text"
2289
+ },
2290
+ {
2291
+ name: "lastname",
2292
+ type: "text"
2293
+ },
2294
+ {
2295
+ name: "email",
2296
+ type: "text",
2297
+ index: true
2298
+ },
2299
+ {
2300
+ name: "temporary_token",
2301
+ type: "text"
2302
+ },
2303
+ {
2304
+ name: "type",
2305
+ type: "text",
2306
+ index: true
2307
+ },
2308
+ {
2309
+ name: "profile_image",
2310
+ type: "text"
2311
+ },
2312
+ {
2313
+ name: "super_admin",
2314
+ type: "boolean",
2315
+ default: false
2316
+ },
2317
+ {
2318
+ name: "status",
2319
+ type: "text"
2320
+ },
2321
+ {
2322
+ name: "emailVerified",
2323
+ type: "text"
2324
+ },
2325
+ {
2326
+ name: "apikey",
2327
+ type: "text"
2328
+ },
2329
+ {
2330
+ name: "last_used",
2331
+ type: "date"
2332
+ },
2333
+ {
2334
+ name: "anthropic_token",
2335
+ type: "text"
2336
+ },
2337
+ {
2338
+ name: "role",
2339
+ type: "uuid"
2395
2340
  }
2396
- if (typeof value === "number") {
2397
- return new Date(value).toISOString();
2341
+ ]
2342
+ };
2343
+ var rolesSchema = {
2344
+ name: {
2345
+ plural: "roles",
2346
+ singular: "role"
2347
+ },
2348
+ fields: [
2349
+ {
2350
+ name: "name",
2351
+ type: "text"
2352
+ },
2353
+ {
2354
+ name: "is_admin",
2355
+ type: "boolean",
2356
+ default: false
2357
+ },
2358
+ {
2359
+ name: "agents",
2360
+ type: "json"
2398
2361
  }
2399
- if (typeof value === "string") {
2400
- return new Date(value).toISOString();
2362
+ ]
2363
+ };
2364
+ var statisticsSchema = {
2365
+ name: {
2366
+ plural: "statistics",
2367
+ singular: "statistic"
2368
+ },
2369
+ fields: [
2370
+ {
2371
+ name: "name",
2372
+ type: "text"
2373
+ },
2374
+ {
2375
+ name: "label",
2376
+ type: "text"
2377
+ },
2378
+ {
2379
+ name: "type",
2380
+ type: "text"
2381
+ },
2382
+ {
2383
+ name: "total",
2384
+ type: "number"
2401
2385
  }
2402
- return value;
2386
+ ]
2387
+ };
2388
+ var workflowSchema = {
2389
+ name: {
2390
+ plural: "workflows",
2391
+ singular: "workflow"
2403
2392
  },
2404
- parseValue(value) {
2405
- if (typeof value === "string") {
2406
- return new Date(value);
2393
+ fields: [
2394
+ {
2395
+ name: "workflow_name",
2396
+ type: "text"
2397
+ },
2398
+ {
2399
+ name: "run_id",
2400
+ type: "text"
2401
+ },
2402
+ {
2403
+ name: "snapshot",
2404
+ type: "text"
2407
2405
  }
2408
- if (typeof value === "number") {
2409
- return new Date(value);
2406
+ ]
2407
+ };
2408
+ var evalResultsSchema = {
2409
+ name: {
2410
+ plural: "eval_results",
2411
+ singular: "eval_result"
2412
+ },
2413
+ fields: [
2414
+ {
2415
+ name: "input",
2416
+ type: "longText"
2417
+ },
2418
+ {
2419
+ name: "output",
2420
+ type: "longText"
2421
+ },
2422
+ {
2423
+ name: "duration",
2424
+ type: "number"
2425
+ },
2426
+ {
2427
+ name: "category",
2428
+ type: "text"
2429
+ },
2430
+ {
2431
+ name: "metadata",
2432
+ type: "json"
2433
+ },
2434
+ {
2435
+ name: "result",
2436
+ type: "number"
2437
+ },
2438
+ {
2439
+ name: "agent_id",
2440
+ type: "uuid"
2441
+ },
2442
+ {
2443
+ name: "workflow_id",
2444
+ type: "uuid"
2445
+ },
2446
+ {
2447
+ name: "eval_type",
2448
+ type: "text"
2449
+ },
2450
+ {
2451
+ name: "eval_name",
2452
+ type: "text"
2453
+ },
2454
+ {
2455
+ name: "comment",
2456
+ type: "longText"
2410
2457
  }
2411
- return value;
2458
+ ]
2459
+ };
2460
+ var jobsSchema = {
2461
+ name: {
2462
+ plural: "jobs",
2463
+ singular: "job"
2412
2464
  },
2413
- parseLiteral(ast) {
2414
- if (ast.kind === import_graphql.Kind.STRING) {
2415
- return new Date(ast.value);
2416
- }
2417
- if (ast.kind === import_graphql.Kind.INT) {
2418
- return new Date(parseInt(ast.value, 10));
2465
+ fields: [
2466
+ {
2467
+ name: "redis",
2468
+ type: "text"
2469
+ },
2470
+ {
2471
+ name: "session",
2472
+ type: "text"
2473
+ },
2474
+ {
2475
+ name: "status",
2476
+ type: "text"
2477
+ },
2478
+ {
2479
+ name: "type",
2480
+ type: "text"
2481
+ },
2482
+ {
2483
+ name: "result",
2484
+ type: "longText"
2485
+ },
2486
+ {
2487
+ name: "name",
2488
+ type: "text"
2489
+ },
2490
+ {
2491
+ name: "agent",
2492
+ type: "uuid"
2493
+ },
2494
+ {
2495
+ name: "workflow",
2496
+ type: "uuid"
2497
+ },
2498
+ {
2499
+ name: "user",
2500
+ // next auth stores users with id type SERIAL, so we need to use number
2501
+ type: "number"
2502
+ },
2503
+ {
2504
+ name: "item",
2505
+ type: "uuid"
2506
+ },
2507
+ {
2508
+ name: "steps",
2509
+ type: "number"
2510
+ },
2511
+ {
2512
+ name: "inputs",
2513
+ type: "json"
2514
+ },
2515
+ {
2516
+ name: "finished_at",
2517
+ type: "date"
2518
+ },
2519
+ {
2520
+ name: "duration",
2521
+ type: "number"
2419
2522
  }
2420
- return null;
2421
- }
2422
- });
2423
- var map = (field) => {
2424
- let type;
2425
- switch (field.type) {
2426
- case "text":
2427
- case "shortText":
2428
- case "longText":
2429
- case "code":
2430
- type = "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 fields = table.fields.map((field) => {
2451
- let type;
2452
- type = map(field);
2453
- const required = field.required ? "!" : "";
2454
- return ` ${field.name}: ${type}${required}`;
2455
- });
2456
- const typeDef = `
2457
- type ${table.name.singular} {
2458
- ${fields.join("\n")}
2459
- id: ID!
2460
- createdAt: Date!
2461
- updatedAt: Date!
2462
- }
2463
- `;
2464
- const inputDef = `
2465
- input ${table.name.singular}Input {
2466
- ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
2467
- }
2468
- `;
2469
- return typeDef + inputDef;
2470
- }
2471
- function createFilterTypeDefs(table) {
2472
- const fieldFilters = table.fields.map((field) => {
2473
- let type;
2474
- type = map(field);
2475
- return `
2476
- ${field.name}: FilterOperator${type}`;
2477
- });
2478
- const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2479
- const operatorTypes = `
2480
- input FilterOperatorString {
2481
- eq: String
2482
- ne: String
2483
- in: [String]
2484
- contains: String
2485
- }
2486
-
2487
- input FilterOperatorDate {
2488
- lte: Date
2489
- gte: Date
2490
- }
2491
-
2492
- input FilterOperatorFloat {
2493
- eq: Float
2494
- ne: Float
2495
- in: [Float]
2496
- }
2497
-
2498
- input FilterOperatorBoolean {
2499
- eq: Boolean
2500
- ne: Boolean
2501
- in: [Boolean]
2502
- }
2503
-
2504
- input FilterOperatorJSON {
2505
- eq: JSON
2506
- ne: JSON
2507
- in: [JSON]
2508
- }
2509
-
2510
- input SortBy {
2511
- field: String!
2512
- direction: SortDirection!
2513
- }
2514
-
2515
- enum SortDirection {
2516
- ASC
2517
- DESC
2518
- }
2519
-
2520
- input Filter${tableNameSingularUpperCaseFirst} {
2521
- ${fieldFilters.join("\n")}
2522
- }`;
2523
- return operatorTypes;
2524
- }
2525
- var getRequestedFields = (info) => {
2526
- const selections = info.operation.selectionSet.selections[0].selectionSet.selections;
2527
- const itemsSelection = selections.find((s) => s.name.value === "items");
2528
- const fields = itemsSelection ? Object.keys(itemsSelection.selectionSet.selections.reduce((acc, field) => {
2529
- acc[field.name.value] = true;
2530
- return acc;
2531
- }, {})) : Object.keys(selections.reduce((acc, field) => {
2532
- acc[field.name.value] = true;
2533
- return acc;
2534
- }, {}));
2535
- return fields.filter((field) => field !== "pageInfo" && field !== "items");
2523
+ ]
2536
2524
  };
2537
- function createMutations(table) {
2538
- const tableNamePlural = table.name.plural.toLowerCase();
2539
- const tableNameSingular = table.name.singular.toLowerCase();
2540
- return {
2541
- [`${tableNamePlural}CreateOne`]: async (_, args, context, info) => {
2542
- const { db: db2 } = context;
2543
- const requestedFields = getRequestedFields(info);
2544
- let { input } = args;
2545
- input = encryptSensitiveFields(input);
2546
- const results = await db2(tableNamePlural).insert({
2547
- ...input,
2548
- createdAt: /* @__PURE__ */ new Date(),
2549
- updatedAt: /* @__PURE__ */ new Date()
2550
- }).returning(requestedFields);
2551
- return results[0];
2552
- },
2553
- [`${tableNamePlural}UpdateOne`]: async (_, args, context, info) => {
2554
- const { db: db2 } = context;
2555
- let { where, input } = args;
2556
- input = encryptSensitiveFields(input);
2557
- await db2(tableNamePlural).where(where).update({
2558
- ...input,
2559
- updatedAt: /* @__PURE__ */ new Date()
2560
- });
2561
- const requestedFields = getRequestedFields(info);
2562
- const result = await db2.from(tableNamePlural).select(requestedFields).where(where).first();
2563
- return result;
2525
+ var agentsSchema = {
2526
+ name: {
2527
+ plural: "agents",
2528
+ singular: "agent"
2529
+ },
2530
+ fields: [
2531
+ {
2532
+ name: "name",
2533
+ type: "text"
2564
2534
  },
2565
- [`${tableNamePlural}UpdateOneById`]: async (_, args, context, info) => {
2566
- let { id, input } = args;
2567
- input = encryptSensitiveFields(input);
2568
- const { db: db2 } = context;
2569
- await db2(tableNamePlural).where({ id }).update({
2570
- ...input,
2571
- updatedAt: /* @__PURE__ */ new Date()
2572
- });
2573
- const requestedFields = getRequestedFields(info);
2574
- const result = await db2.from(tableNamePlural).select(requestedFields).where({ id }).first();
2575
- return result;
2535
+ {
2536
+ name: "description",
2537
+ type: "text"
2576
2538
  },
2577
- [`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
2578
- const { db: db2 } = context;
2579
- const { where } = args;
2580
- const requestedFields = getRequestedFields(info);
2581
- const result = await db2.from(tableNamePlural).select(requestedFields).where(where).first();
2582
- await db2(tableNamePlural).where(where).del();
2583
- return result;
2539
+ {
2540
+ name: "extensions",
2541
+ type: "json"
2584
2542
  },
2585
- [`${tableNamePlural}RemoveOneById`]: async (_, args, context, info) => {
2586
- const { id } = args;
2587
- const { db: db2 } = context;
2588
- const requestedFields = getRequestedFields(info);
2589
- const result = await db2.from(tableNamePlural).select(requestedFields).where({ id }).first();
2590
- await db2(tableNamePlural).where({ id }).del();
2591
- return result;
2592
- }
2593
- };
2594
- }
2595
- function createQueries(table) {
2596
- const tableNamePlural = table.name.plural.toLowerCase();
2597
- const tableNameSingular = table.name.singular.toLowerCase();
2598
- const applyFilters = (query, filters) => {
2599
- filters.forEach((filter) => {
2600
- Object.entries(filter).forEach(([fieldName, operators]) => {
2601
- if (operators) {
2602
- if (operators.eq !== void 0) {
2603
- query = query.where(fieldName, operators.eq);
2604
- }
2605
- if (operators.ne !== void 0) {
2606
- query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2607
- }
2608
- if (operators.in !== void 0) {
2609
- query = query.whereIn(fieldName, operators.in);
2610
- }
2611
- if (operators.contains !== void 0) {
2612
- query = query.where(fieldName, "like", `%${operators.contains}%`);
2613
- }
2614
- }
2615
- });
2616
- });
2617
- return query;
2618
- };
2619
- const applySorting = (query, sort) => {
2620
- if (sort) {
2621
- query = query.orderBy(sort.field, sort.direction.toLowerCase());
2622
- }
2623
- return query;
2624
- };
2625
- return {
2626
- [`${tableNameSingular}ById`]: async (_, args, context, info) => {
2627
- const { db: db2 } = context;
2628
- const requestedFields = getRequestedFields(info);
2629
- const result = await db2.from(tableNamePlural).select(requestedFields).where({ id: args.id }).first();
2630
- return result;
2543
+ {
2544
+ name: "backend",
2545
+ type: "text"
2631
2546
  },
2632
- [`${tableNameSingular}One`]: async (_, args, context, info) => {
2633
- const { filters = [], sort } = args;
2634
- const { db: db2 } = context;
2635
- const requestedFields = getRequestedFields(info);
2636
- let query = db2.from(tableNamePlural).select(requestedFields);
2637
- query = applyFilters(query, filters);
2638
- query = applySorting(query, sort);
2639
- const result = await query.first();
2640
- return result;
2547
+ {
2548
+ name: "type",
2549
+ type: "text"
2641
2550
  },
2642
- [`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
2643
- const { limit = 10, page = 0, filters = [], sort } = args;
2644
- const { db: db2 } = context;
2645
- let baseQuery = db2(tableNamePlural);
2646
- baseQuery = applyFilters(baseQuery, filters);
2647
- const [{ count }] = await baseQuery.clone().count("* as count");
2648
- const itemCount = Number(count);
2649
- const pageCount = Math.ceil(itemCount / limit);
2650
- const currentPage = page;
2651
- const hasPreviousPage = currentPage > 1;
2652
- const hasNextPage = currentPage < pageCount - 1;
2653
- let dataQuery = baseQuery.clone();
2654
- const requestedFields = getRequestedFields(info);
2655
- dataQuery = applySorting(dataQuery, sort);
2656
- if (page > 1) {
2657
- dataQuery = dataQuery.offset((page - 1) * limit);
2658
- }
2659
- const items = await dataQuery.select(requestedFields).limit(limit);
2660
- return {
2661
- pageInfo: {
2662
- pageCount,
2663
- itemCount,
2664
- currentPage,
2665
- hasPreviousPage,
2666
- hasNextPage
2667
- },
2668
- items
2669
- };
2551
+ {
2552
+ name: "active",
2553
+ type: "boolean",
2554
+ default: false
2670
2555
  },
2671
- // Add jobStatistics query for jobs table
2672
- ...tableNamePlural === "jobs" ? {
2673
- jobStatistics: async (_, args, context, info) => {
2674
- const { user, agent, from, to } = args;
2675
- const { db: db2 } = context;
2676
- let query = db2("jobs");
2677
- if (user) {
2678
- query = query.where("user", user);
2679
- }
2680
- if (agent) {
2681
- query = query.where("agent", agent);
2682
- }
2683
- if (from) {
2684
- query = query.where("createdAt", ">=", from);
2685
- }
2686
- if (to) {
2687
- query = query.where("createdAt", "<=", to);
2688
- }
2689
- const completedQuery = query.clone().where("status", "completed");
2690
- const [{ completedCount }] = await completedQuery.count("* as completedCount");
2691
- const failedQuery = query.clone().where("status", "failed");
2692
- const [{ failedCount }] = await failedQuery.count("* as failedCount");
2693
- const durationQuery = query.clone().where("status", "completed").whereNotNull("duration").select(db2.raw('AVG("duration") as averageDuration'));
2694
- const [{ averageDuration }] = await durationQuery;
2695
- return {
2696
- completedCount: Number(completedCount),
2697
- failedCount: Number(failedCount),
2698
- averageDuration: averageDuration ? Number(averageDuration) : 0
2699
- };
2700
- }
2701
- } : {}
2702
- };
2703
- }
2704
- function createSDL(tables) {
2705
- let typeDefs = `
2706
- scalar JSON
2707
- scalar Date
2708
-
2709
- type Query {
2710
- `;
2711
- let mutationDefs = `
2712
- type Mutation {
2713
- `;
2714
- let modelDefs = "";
2715
- const resolvers = { JSON: import_graphql_type_json.default, Date: GraphQLDate, Query: {}, Mutation: {} };
2716
- for (const table of tables) {
2717
- const tableNamePlural = table.name.plural.toLowerCase();
2718
- const tableNameSingular = table.name.singular.toLowerCase();
2719
- const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2720
- typeDefs += `
2721
- ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2722
- ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2723
- ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2724
- ${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
2725
- `;
2726
- mutationDefs += `
2727
- ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
2728
- ${tableNamePlural}UpdateOne(where: JSON!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2729
- ${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2730
- ${tableNamePlural}RemoveOne(where: JSON!): ${tableNameSingular}
2731
- ${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
2732
- `;
2733
- modelDefs += createTypeDefs(table);
2734
- modelDefs += createFilterTypeDefs(table);
2735
- modelDefs += `
2736
- type ${tableNameSingularUpperCaseFirst}PaginationResult {
2737
- pageInfo: PageInfo!
2738
- items: [${tableNameSingular}]!
2739
- }
2740
-
2741
- type PageInfo {
2742
- pageCount: Int!
2743
- itemCount: Int!
2744
- currentPage: Int!
2745
- hasPreviousPage: Boolean!
2746
- hasNextPage: Boolean!
2747
- }
2748
- `;
2749
- if (tableNamePlural === "jobs") {
2750
- modelDefs += `
2751
- type JobStatistics {
2752
- completedCount: Int!
2753
- failedCount: Int!
2754
- averageDuration: Float!
2755
- }
2756
- `;
2757
- }
2758
- Object.assign(resolvers.Query, createQueries(table));
2759
- Object.assign(resolvers.Mutation, createMutations(table));
2760
- }
2761
- typeDefs += "}\n";
2762
- mutationDefs += "}\n";
2763
- const fullSDL = typeDefs + mutationDefs + modelDefs;
2764
- const schema = (0, import_schema.makeExecutableSchema)({
2765
- typeDefs: fullSDL,
2766
- resolvers
2767
- });
2768
- console.log("\n\u{1F4CA} GraphQL Schema Overview\n");
2769
- const queriesTable = Object.keys(resolvers.Query).map((query) => ({
2770
- "Operation Type": "Query",
2771
- "Name": query,
2772
- "Description": "Retrieves data"
2773
- }));
2774
- const mutationsTable = Object.keys(resolvers.Mutation).map((mutation) => ({
2775
- "Operation Type": "Mutation",
2776
- "Name": mutation,
2777
- "Description": "Modifies data"
2778
- }));
2779
- const typesTable = tables.flatMap(
2780
- (table) => table.fields.map((field) => ({
2781
- "Type": table.name.singular,
2782
- "Field": field.name,
2783
- "Field Type": field.type,
2784
- "Required": field.required ? "Yes" : "No"
2785
- }))
2786
- );
2787
- console.log("\u{1F50D} Operations:");
2788
- console.table([...queriesTable, ...mutationsTable]);
2789
- console.log("\n\u{1F4DD} Types and Fields:");
2790
- console.table(typesTable);
2791
- console.log("\n");
2792
- return schema;
2793
- }
2794
- var sensitiveFields = ["anthropic_token"];
2795
- var encryptSensitiveFields = (input) => {
2796
- sensitiveFields.forEach((field) => {
2797
- if (input[field]) {
2798
- input[field] = import_crypto_js.default.AES.encrypt(input[field], process.env.NEXTAUTH_SECRET).toString();
2556
+ {
2557
+ name: "public",
2558
+ type: "boolean",
2559
+ default: false
2560
+ },
2561
+ {
2562
+ name: "tools",
2563
+ type: "json"
2799
2564
  }
2800
- });
2801
- return input;
2565
+ ]
2802
2566
  };
2803
2567
 
2804
- // src/registry/routes.ts
2805
- var import_express5 = require("@as-integrations/express5");
2806
-
2807
2568
  // src/registry/uppy.ts
2808
2569
  var import_express = require("express");
2809
2570
  var createUppyRoutes = async (app) => {
@@ -4385,14 +4146,14 @@ var preprocessInputs = async (data) => {
4385
4146
  return data;
4386
4147
  };
4387
4148
  var getPresignedFileUrl = async (key) => {
4388
- if (!process.env.NEXT_PUBLIC_UPLOAD_URL) {
4389
- throw new Error("Missing process.env.NEXT_PUBLIC_UPLOAD_URL");
4149
+ if (!process.env.NEXT_BACKEND) {
4150
+ throw new Error("Missing process.env.NEXT_BACKEND");
4390
4151
  }
4391
4152
  if (!process.env.INTERNAL_SECRET) {
4392
- throw new Error("Missing process.env.NEXT_PUBLIC_UPLOAD_URL");
4153
+ throw new Error("Missing process.env.NEXT_BACKEND");
4393
4154
  }
4394
4155
  console.log(`[EXULU] fetching presigned url for file with key: ${key}`);
4395
- let url = `${process.env.NEXT_PUBLIC_UPLOAD_URL}/s3/download?key=${key}`;
4156
+ let url = `${process.env.NEXT_BACKEND}/s3/download?key=${key}`;
4396
4157
  const response = await fetch(url, {
4397
4158
  method: "GET",
4398
4159
  headers: {
@@ -4942,6 +4703,259 @@ var getTicket = new ExuluTool({
4942
4703
  }
4943
4704
  });
4944
4705
 
4706
+ // src/auth/generate-key.ts
4707
+ var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
4708
+ var SALT_ROUNDS = 12;
4709
+ async function encryptString(string) {
4710
+ const hash = await import_bcryptjs2.default.hash(string, SALT_ROUNDS);
4711
+ return hash;
4712
+ }
4713
+ var generateApiKey = async (name, email) => {
4714
+ const { db: db2 } = await postgresClient();
4715
+ console.log("[EXULU] Inserting default user and admin role.");
4716
+ const existingRole = await db2.from("roles").where({ name: "admin" }).first();
4717
+ let roleId;
4718
+ if (!existingRole) {
4719
+ console.log("[EXULU] Creating default admin role.");
4720
+ const role = await db2.from("roles").insert({
4721
+ name: "admin",
4722
+ is_admin: true,
4723
+ agents: []
4724
+ }).returning("id");
4725
+ roleId = role[0].id;
4726
+ } else {
4727
+ roleId = existingRole.id;
4728
+ }
4729
+ const newKeyName = name;
4730
+ const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
4731
+ const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
4732
+ const encryptedKey = await encryptString(plainKey);
4733
+ const existingApiUser = await db2.from("users").where({ email }).first();
4734
+ if (!existingApiUser) {
4735
+ console.log("[EXULU] Creating default api user.");
4736
+ await db2.from("users").insert({
4737
+ name,
4738
+ email,
4739
+ super_admin: true,
4740
+ createdAt: /* @__PURE__ */ new Date(),
4741
+ updatedAt: /* @__PURE__ */ new Date(),
4742
+ type: "api",
4743
+ emailVerified: /* @__PURE__ */ new Date(),
4744
+ apikey: `${encryptedKey}${postFix}`,
4745
+ // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
4746
+ role: roleId
4747
+ });
4748
+ console.log("[EXULU] Default api user created. Key: ", `${plainKey}${postFix}`);
4749
+ } else {
4750
+ console.log("[EXULU] API user with that name already exists.");
4751
+ }
4752
+ console.log("[EXULU] Key generated, copy and use the plain key from here, you will not be able to access it again.");
4753
+ console.log("[EXULU] Key: ", `${plainKey}${postFix}`);
4754
+ return {
4755
+ key: `${plainKey}${postFix}`
4756
+ };
4757
+ };
4758
+
4759
+ // src/postgres/init-db.ts
4760
+ var up = async function(knex) {
4761
+ if (!await knex.schema.hasTable("agent_sessions")) {
4762
+ console.log("[EXULU] Creating agent_sessions table.");
4763
+ await knex.schema.createTable("agent_sessions", (table) => {
4764
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
4765
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4766
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4767
+ for (const field of agentSessionsSchema.fields) {
4768
+ const { type, name, default: defaultValue } = field;
4769
+ if (!type || !name) {
4770
+ continue;
4771
+ }
4772
+ mapType(table, type, sanitizeName(name), defaultValue);
4773
+ }
4774
+ });
4775
+ }
4776
+ if (!await knex.schema.hasTable("agent_messages")) {
4777
+ console.log("[EXULU] Creating agent_messages table.");
4778
+ await knex.schema.createTable("agent_messages", (table) => {
4779
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
4780
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4781
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4782
+ for (const field of agentMessagesSchema.fields) {
4783
+ const { type, name, default: defaultValue } = field;
4784
+ if (!type || !name) {
4785
+ continue;
4786
+ }
4787
+ mapType(table, type, sanitizeName(name), defaultValue);
4788
+ }
4789
+ });
4790
+ }
4791
+ if (!await knex.schema.hasTable("roles")) {
4792
+ console.log("[EXULU] Creating roles table.");
4793
+ await knex.schema.createTable("roles", (table) => {
4794
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
4795
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4796
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4797
+ for (const field of rolesSchema.fields) {
4798
+ const { type, name, default: defaultValue } = field;
4799
+ if (!type || !name) {
4800
+ continue;
4801
+ }
4802
+ mapType(table, type, sanitizeName(name), defaultValue);
4803
+ }
4804
+ });
4805
+ }
4806
+ if (!await knex.schema.hasTable("eval_results")) {
4807
+ console.log("[EXULU] Creating eval_results table.");
4808
+ await knex.schema.createTable("eval_results", (table) => {
4809
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
4810
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4811
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4812
+ for (const field of evalResultsSchema.fields) {
4813
+ const { type, name, default: defaultValue } = field;
4814
+ if (!type || !name) {
4815
+ continue;
4816
+ }
4817
+ mapType(table, type, sanitizeName(name), defaultValue);
4818
+ }
4819
+ });
4820
+ }
4821
+ if (!await knex.schema.hasTable("statistics")) {
4822
+ console.log("[EXULU] Creating statistics table.");
4823
+ await knex.schema.createTable("statistics", (table) => {
4824
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
4825
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4826
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4827
+ for (const field of statisticsSchema.fields) {
4828
+ const { type, name, default: defaultValue } = field;
4829
+ if (!type || !name) {
4830
+ continue;
4831
+ }
4832
+ mapType(table, type, sanitizeName(name), defaultValue);
4833
+ }
4834
+ });
4835
+ }
4836
+ if (!await knex.schema.hasTable("jobs")) {
4837
+ console.log("[EXULU] Creating jobs table.");
4838
+ await knex.schema.createTable("jobs", (table) => {
4839
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
4840
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4841
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4842
+ for (const field of jobsSchema.fields) {
4843
+ const { type, name, default: defaultValue } = field;
4844
+ if (!type || !name) {
4845
+ continue;
4846
+ }
4847
+ mapType(table, type, sanitizeName(name), defaultValue);
4848
+ }
4849
+ });
4850
+ }
4851
+ if (!await knex.schema.hasTable("agents")) {
4852
+ console.log("[EXULU] Creating agents table.");
4853
+ await knex.schema.createTable("agents", (table) => {
4854
+ table.uuid("id").primary().defaultTo(knex.fn.uuid());
4855
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4856
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4857
+ for (const field of agentsSchema.fields) {
4858
+ const { type, name, default: defaultValue } = field;
4859
+ if (!type || !name) {
4860
+ continue;
4861
+ }
4862
+ mapType(table, type, sanitizeName(name), defaultValue);
4863
+ }
4864
+ });
4865
+ }
4866
+ if (!await knex.schema.hasTable("verification_token")) {
4867
+ console.log("[EXULU] Creating verification_token table.");
4868
+ await knex.schema.createTable("verification_token", (table) => {
4869
+ table.text("identifier").notNullable();
4870
+ table.timestamp("expires", { useTz: true }).notNullable();
4871
+ table.text("token").notNullable();
4872
+ table.primary(["identifier", "token"]);
4873
+ });
4874
+ }
4875
+ if (!await knex.schema.hasTable("users")) {
4876
+ console.log("[EXULU] Creating users table.");
4877
+ await knex.schema.createTable("users", (table) => {
4878
+ table.increments("id").primary();
4879
+ table.timestamp("createdAt").defaultTo(knex.fn.now());
4880
+ table.timestamp("updatedAt").defaultTo(knex.fn.now());
4881
+ table.string("name", 255);
4882
+ table.string("password", 255);
4883
+ table.string("email", 255);
4884
+ table.timestamp("emailVerified", { useTz: true });
4885
+ table.text("image");
4886
+ for (const field of usersSchema.fields) {
4887
+ console.log("[EXULU] field", field);
4888
+ const { type, name, default: defaultValue } = field;
4889
+ if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
4890
+ continue;
4891
+ }
4892
+ if (!type || !name) {
4893
+ continue;
4894
+ }
4895
+ mapType(table, type, sanitizeName(name), defaultValue);
4896
+ }
4897
+ });
4898
+ }
4899
+ if (!await knex.schema.hasTable("accounts")) {
4900
+ console.log("[EXULU] Creating accounts table.");
4901
+ await knex.schema.createTable("accounts", (table) => {
4902
+ table.increments("id").primary();
4903
+ table.integer("userId").notNullable();
4904
+ table.string("type", 255).notNullable();
4905
+ table.string("provider", 255).notNullable();
4906
+ table.string("providerAccountId", 255).notNullable();
4907
+ table.text("refresh_token");
4908
+ table.text("access_token");
4909
+ table.bigInteger("expires_at");
4910
+ table.text("id_token");
4911
+ table.text("scope");
4912
+ table.text("session_state");
4913
+ table.text("token_type");
4914
+ });
4915
+ }
4916
+ };
4917
+ var execute = async () => {
4918
+ const { db: db2 } = await postgresClient();
4919
+ console.log("[EXULU] Checking Exulu IMP database status.");
4920
+ await up(db2);
4921
+ console.log("[EXULU] Inserting default user and admin role.");
4922
+ const existingRole = await db2.from("roles").where({ name: "admin" }).first();
4923
+ let roleId;
4924
+ if (!existingRole) {
4925
+ console.log("[EXULU] Creating default admin role.");
4926
+ const role = await db2.from("roles").insert({
4927
+ name: "admin",
4928
+ is_admin: true,
4929
+ agents: JSON.stringify([])
4930
+ }).returning("id");
4931
+ roleId = role[0].id;
4932
+ } else {
4933
+ roleId = existingRole.id;
4934
+ }
4935
+ const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
4936
+ if (!existingUser) {
4937
+ const password = await encryptString("admin");
4938
+ console.log("[EXULU] Creating default admin user.");
4939
+ await db2.from("users").insert({
4940
+ name: "exulu",
4941
+ email: "admin@exulu.com",
4942
+ super_admin: true,
4943
+ createdAt: /* @__PURE__ */ new Date(),
4944
+ emailVerified: /* @__PURE__ */ new Date(),
4945
+ updatedAt: /* @__PURE__ */ new Date(),
4946
+ password,
4947
+ type: "user",
4948
+ role: roleId
4949
+ });
4950
+ }
4951
+ const { key } = await generateApiKey("exulu", "api@exulu.com");
4952
+ console.log("[EXULU] Database initialized.");
4953
+ console.log("[EXULU] Default api key: ", `${key}`);
4954
+ console.log("[EXULU] Default password if using password auth: ", `admin`);
4955
+ console.log("[EXULU] Default email if using password auth: ", `admin@exulu.com`);
4956
+ return;
4957
+ };
4958
+
4945
4959
  // src/registry/index.ts
4946
4960
  var ExuluApp = class {
4947
4961
  _agents = [];
@@ -4956,6 +4970,7 @@ var ExuluApp = class {
4956
4970
  // Factory function so we can async
4957
4971
  // initialize the MCP server if needed.
4958
4972
  create = async ({ contexts, agents, workflows, config, tools }) => {
4973
+ await execute();
4959
4974
  this._workflows = workflows ?? [];
4960
4975
  this._contexts = contexts ?? {};
4961
4976
  this._agents = [
@@ -6271,14 +6286,6 @@ var ExuluChunkers = {
6271
6286
  rules: RecursiveRules
6272
6287
  }
6273
6288
  };
6274
- var ExuluDatabase = {
6275
- init: async () => {
6276
- await execute();
6277
- },
6278
- generateApiKey: async (name, email) => {
6279
- return await generateApiKey(name, email);
6280
- }
6281
- };
6282
6289
  // Annotate the CommonJS export names for ESM import in node:
6283
6290
  0 && (module.exports = {
6284
6291
  EXULU_JOB_STATUS_ENUM,
@@ -6288,7 +6295,6 @@ var ExuluDatabase = {
6288
6295
  ExuluAuthentication,
6289
6296
  ExuluChunkers,
6290
6297
  ExuluContext,
6291
- ExuluDatabase,
6292
6298
  ExuluEmbedder,
6293
6299
  ExuluEval,
6294
6300
  ExuluJobs,