@exulu/backend 1.4.1 → 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");
@@ -149,745 +174,142 @@ async function postgresClient() {
149
174
  };
150
175
  }
151
176
 
152
- // src/postgres/core-schema.ts
153
- var agentMessagesSchema = {
154
- name: {
155
- plural: "agent_messages",
156
- singular: "agent_message"
157
- },
158
- fields: [
159
- {
160
- name: "content",
161
- type: "text"
162
- },
163
- {
164
- name: "title",
165
- type: "text"
166
- },
167
- {
168
- name: "session",
169
- type: "text"
170
- }
171
- ]
172
- };
173
- var agentSessionsSchema = {
174
- name: {
175
- plural: "agent_sessions",
176
- singular: "agent_session"
177
- },
178
- fields: [
179
- {
180
- name: "agent",
181
- type: "uuid"
182
- },
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}`,
183
205
  {
184
- name: "user",
185
- // next auth stores users with id type SERIAL, so we need to use number
186
- 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
187
222
  },
188
223
  {
189
- name: "title",
190
- type: "text"
224
+ jobId: redisId
191
225
  }
192
- ]
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
+ };
193
267
  };
194
- var usersSchema = {
195
- name: {
196
- plural: "users",
197
- singular: "user"
198
- },
199
- fields: [
200
- {
201
- name: "firstname",
202
- type: "text"
203
- },
204
- {
205
- name: "name",
206
- type: "text"
207
- },
208
- {
209
- name: "lastname",
210
- type: "text"
211
- },
212
- {
213
- name: "email",
214
- type: "text",
215
- index: true
216
- },
217
- {
218
- name: "temporary_token",
219
- type: "text"
220
- },
221
- {
222
- name: "type",
223
- type: "text",
224
- index: true
225
- },
226
- {
227
- name: "profile_image",
228
- type: "text"
229
- },
230
- {
231
- name: "super_admin",
232
- type: "boolean",
233
- default: false
234
- },
235
- {
236
- name: "status",
237
- type: "text"
238
- },
239
- {
240
- name: "emailVerified",
241
- type: "text"
242
- },
243
- {
244
- name: "apikey",
245
- type: "text"
246
- },
247
- {
248
- name: "last_used",
249
- type: "date"
250
- },
251
- {
252
- name: "anthropic_token",
253
- type: "text"
254
- },
255
- {
256
- name: "role",
257
- type: "uuid"
258
- }
259
- ]
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);
260
308
  };
261
- var rolesSchema = {
262
- name: {
263
- plural: "roles",
264
- singular: "role"
265
- },
266
- fields: [
267
- {
268
- name: "name",
269
- type: "text"
270
- },
271
- {
272
- name: "is_admin",
273
- type: "boolean",
274
- default: false
275
- },
276
- {
277
- name: "agents",
278
- type: "json"
279
- }
280
- ]
281
- };
282
- var statisticsSchema = {
283
- name: {
284
- plural: "statistics",
285
- singular: "statistic"
286
- },
287
- fields: [
288
- {
289
- name: "name",
290
- type: "text"
291
- },
292
- {
293
- name: "label",
294
- type: "text"
295
- },
296
- {
297
- name: "type",
298
- type: "text"
299
- },
300
- {
301
- name: "total",
302
- type: "number"
303
- }
304
- ]
305
- };
306
- var workflowSchema = {
307
- name: {
308
- plural: "workflows",
309
- singular: "workflow"
310
- },
311
- fields: [
312
- {
313
- name: "workflow_name",
314
- type: "text"
315
- },
316
- {
317
- name: "run_id",
318
- type: "text"
319
- },
320
- {
321
- name: "snapshot",
322
- type: "text"
323
- }
324
- ]
325
- };
326
- var evalResultsSchema = {
327
- name: {
328
- plural: "eval_results",
329
- singular: "eval_result"
330
- },
331
- fields: [
332
- {
333
- name: "input",
334
- type: "longText"
335
- },
336
- {
337
- name: "output",
338
- type: "longText"
339
- },
340
- {
341
- name: "duration",
342
- type: "number"
343
- },
344
- {
345
- name: "category",
346
- type: "text"
347
- },
348
- {
349
- name: "metadata",
350
- type: "json"
351
- },
352
- {
353
- name: "result",
354
- type: "number"
355
- },
356
- {
357
- name: "agent_id",
358
- type: "uuid"
359
- },
360
- {
361
- name: "workflow_id",
362
- type: "uuid"
363
- },
364
- {
365
- name: "eval_type",
366
- type: "text"
367
- },
368
- {
369
- name: "eval_name",
370
- type: "text"
371
- },
372
- {
373
- name: "comment",
374
- type: "longText"
375
- }
376
- ]
377
- };
378
- var jobsSchema = {
379
- name: {
380
- plural: "jobs",
381
- singular: "job"
382
- },
383
- fields: [
384
- {
385
- name: "redis",
386
- type: "text"
387
- },
388
- {
389
- name: "session",
390
- type: "text"
391
- },
392
- {
393
- name: "status",
394
- type: "text"
395
- },
396
- {
397
- name: "type",
398
- type: "text"
399
- },
400
- {
401
- name: "result",
402
- type: "longText"
403
- },
404
- {
405
- name: "name",
406
- type: "text"
407
- },
408
- {
409
- name: "agent",
410
- type: "uuid"
411
- },
412
- {
413
- name: "workflow",
414
- type: "uuid"
415
- },
416
- {
417
- name: "user",
418
- // next auth stores users with id type SERIAL, so we need to use number
419
- type: "number"
420
- },
421
- {
422
- name: "item",
423
- type: "uuid"
424
- },
425
- {
426
- name: "steps",
427
- type: "number"
428
- },
429
- {
430
- name: "inputs",
431
- type: "json"
432
- },
433
- {
434
- name: "finished_at",
435
- type: "date"
436
- },
437
- {
438
- name: "duration",
439
- type: "number"
440
- }
441
- ]
442
- };
443
- var agentsSchema = {
444
- name: {
445
- plural: "agents",
446
- singular: "agent"
447
- },
448
- fields: [
449
- {
450
- name: "name",
451
- type: "text"
452
- },
453
- {
454
- name: "description",
455
- type: "text"
456
- },
457
- {
458
- name: "extensions",
459
- type: "json"
460
- },
461
- {
462
- name: "backend",
463
- type: "text"
464
- },
465
- {
466
- name: "type",
467
- type: "text"
468
- },
469
- {
470
- name: "active",
471
- type: "boolean",
472
- default: false
473
- },
474
- {
475
- name: "public",
476
- type: "boolean",
477
- default: false
478
- },
479
- {
480
- name: "tools",
481
- type: "json"
482
- }
483
- ]
484
- };
485
-
486
- // src/registry/utils/map-types.ts
487
- var mapType = (t, type, name, defaultValue) => {
488
- if (type === "text") {
489
- t.text(name);
490
- return;
491
- }
492
- if (type === "longText") {
493
- t.text(name);
494
- return;
495
- }
496
- if (type === "shortText") {
497
- t.string(name, 100);
498
- return;
499
- }
500
- if (type === "number") {
501
- t.float(name);
502
- return;
503
- }
504
- if (type === "boolean") {
505
- t.boolean(name).defaultTo(defaultValue || false);
506
- return;
507
- }
508
- if (type === "code") {
509
- t.text(name);
510
- return;
511
- }
512
- if (type === "json") {
513
- t.jsonb(name);
514
- return;
515
- }
516
- if (type === "date") {
517
- t.timestamp(name);
518
- return;
519
- }
520
- if (type === "uuid") {
521
- t.uuid(name);
522
- return;
523
- }
524
- throw new Error("Invalid type: " + type);
525
- };
526
-
527
- // src/registry/utils/sanitize-name.ts
528
- var sanitizeName = (name) => {
529
- return name.toLowerCase().replace(/ /g, "_");
530
- };
531
-
532
- // src/auth/generate-key.ts
533
- var import_bcryptjs = __toESM(require("bcryptjs"), 1);
534
- var SALT_ROUNDS = 12;
535
- async function encryptString(string) {
536
- const hash = await import_bcryptjs.default.hash(string, SALT_ROUNDS);
537
- return hash;
538
- }
539
- var generateApiKey = async (name, email) => {
540
- const { db: db2 } = await postgresClient();
541
- console.log("[EXULU] Inserting default user and admin role.");
542
- const existingRole = await db2.from("roles").where({ name: "admin" }).first();
543
- let roleId;
544
- if (!existingRole) {
545
- console.log("[EXULU] Creating default admin role.");
546
- const role = await db2.from("roles").insert({
547
- name: "admin",
548
- is_admin: true,
549
- agents: []
550
- }).returning("id");
551
- roleId = role[0].id;
552
- } else {
553
- roleId = existingRole.id;
554
- }
555
- const newKeyName = name;
556
- const plainKey = `sk_${Math.random().toString(36).substring(2, 15)}_${Math.random().toString(36).substring(2, 15)}`;
557
- const postFix = `/${newKeyName.toLowerCase().trim().replaceAll(" ", "_")}`;
558
- const encryptedKey = await encryptString(plainKey);
559
- const existingApiUser = await db2.from("users").where({ email }).first();
560
- if (!existingApiUser) {
561
- console.log("[EXULU] Creating default api user.");
562
- await db2.from("users").insert({
563
- name,
564
- email,
565
- super_admin: true,
566
- createdAt: /* @__PURE__ */ new Date(),
567
- updatedAt: /* @__PURE__ */ new Date(),
568
- type: "api",
569
- emailVerified: /* @__PURE__ */ new Date(),
570
- apikey: `${encryptedKey}${postFix}`,
571
- // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
572
- role: roleId
573
- });
574
- console.log("[EXULU] Default api user created. Key: ", `${plainKey}${postFix}`);
575
- } else {
576
- console.log("[EXULU] API user with that name already exists.");
577
- }
578
- console.log("[EXULU] Key generated, copy and use the plain key from here, you will not be able to access it again.");
579
- console.log("[EXULU] Key: ", `${plainKey}${postFix}`);
580
- return {
581
- key: `${plainKey}${postFix}`
582
- };
583
- };
584
-
585
- // src/postgres/init-db.ts
586
- var up = async function(knex) {
587
- if (!await knex.schema.hasTable("agent_sessions")) {
588
- await knex.schema.createTable("agent_sessions", (table) => {
589
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
590
- table.timestamp("createdAt").defaultTo(knex.fn.now());
591
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
592
- for (const field of agentSessionsSchema.fields) {
593
- const { type, name, default: defaultValue } = field;
594
- if (!type || !name) {
595
- continue;
596
- }
597
- mapType(table, type, sanitizeName(name), defaultValue);
598
- }
599
- });
600
- }
601
- if (!await knex.schema.hasTable("agent_messages")) {
602
- await knex.schema.createTable("agent_messages", (table) => {
603
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
604
- table.timestamp("createdAt").defaultTo(knex.fn.now());
605
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
606
- for (const field of agentMessagesSchema.fields) {
607
- const { type, name, default: defaultValue } = field;
608
- if (!type || !name) {
609
- continue;
610
- }
611
- mapType(table, type, sanitizeName(name), defaultValue);
612
- }
613
- });
614
- }
615
- if (!await knex.schema.hasTable("roles")) {
616
- await knex.schema.createTable("roles", (table) => {
617
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
618
- table.timestamp("createdAt").defaultTo(knex.fn.now());
619
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
620
- for (const field of rolesSchema.fields) {
621
- const { type, name, default: defaultValue } = field;
622
- if (!type || !name) {
623
- continue;
624
- }
625
- mapType(table, type, sanitizeName(name), defaultValue);
626
- }
627
- });
628
- }
629
- if (!await knex.schema.hasTable("eval_results")) {
630
- await knex.schema.createTable("eval_results", (table) => {
631
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
632
- table.timestamp("createdAt").defaultTo(knex.fn.now());
633
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
634
- for (const field of evalResultsSchema.fields) {
635
- const { type, name, default: defaultValue } = field;
636
- if (!type || !name) {
637
- continue;
638
- }
639
- mapType(table, type, sanitizeName(name), defaultValue);
640
- }
641
- });
642
- }
643
- if (!await knex.schema.hasTable("statistics")) {
644
- await knex.schema.createTable("statistics", (table) => {
645
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
646
- table.timestamp("createdAt").defaultTo(knex.fn.now());
647
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
648
- for (const field of statisticsSchema.fields) {
649
- const { type, name, default: defaultValue } = field;
650
- if (!type || !name) {
651
- continue;
652
- }
653
- mapType(table, type, sanitizeName(name), defaultValue);
654
- }
655
- });
656
- }
657
- if (!await knex.schema.hasTable("jobs")) {
658
- await knex.schema.createTable("jobs", (table) => {
659
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
660
- table.timestamp("createdAt").defaultTo(knex.fn.now());
661
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
662
- for (const field of jobsSchema.fields) {
663
- const { type, name, default: defaultValue } = field;
664
- if (!type || !name) {
665
- continue;
666
- }
667
- mapType(table, type, sanitizeName(name), defaultValue);
668
- }
669
- });
670
- }
671
- if (!await knex.schema.hasTable("agents")) {
672
- await knex.schema.createTable("agents", (table) => {
673
- table.uuid("id").primary().defaultTo(knex.fn.uuid());
674
- table.timestamp("createdAt").defaultTo(knex.fn.now());
675
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
676
- for (const field of agentsSchema.fields) {
677
- const { type, name, default: defaultValue } = field;
678
- if (!type || !name) {
679
- continue;
680
- }
681
- mapType(table, type, sanitizeName(name), defaultValue);
682
- }
683
- });
684
- }
685
- if (!await knex.schema.hasTable("verification_token")) {
686
- await knex.schema.createTable("verification_token", (table) => {
687
- table.text("identifier").notNullable();
688
- table.timestamp("expires", { useTz: true }).notNullable();
689
- table.text("token").notNullable();
690
- table.primary(["identifier", "token"]);
691
- });
692
- }
693
- if (!await knex.schema.hasTable("users")) {
694
- await knex.schema.createTable("users", (table) => {
695
- table.increments("id").primary();
696
- table.timestamp("createdAt").defaultTo(knex.fn.now());
697
- table.timestamp("updatedAt").defaultTo(knex.fn.now());
698
- table.string("name", 255);
699
- table.string("password", 255);
700
- table.string("email", 255);
701
- table.timestamp("emailVerified", { useTz: true });
702
- table.text("image");
703
- for (const field of usersSchema.fields) {
704
- console.log("[EXULU] field", field);
705
- const { type, name, default: defaultValue } = field;
706
- if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
707
- continue;
708
- }
709
- if (!type || !name) {
710
- continue;
711
- }
712
- mapType(table, type, sanitizeName(name), defaultValue);
713
- }
714
- });
715
- }
716
- if (!await knex.schema.hasTable("accounts")) {
717
- await knex.schema.createTable("accounts", (table) => {
718
- table.increments("id").primary();
719
- table.integer("userId").notNullable();
720
- table.string("type", 255).notNullable();
721
- table.string("provider", 255).notNullable();
722
- table.string("providerAccountId", 255).notNullable();
723
- table.text("refresh_token");
724
- table.text("access_token");
725
- table.bigInteger("expires_at");
726
- table.text("id_token");
727
- table.text("scope");
728
- table.text("session_state");
729
- table.text("token_type");
730
- });
731
- }
732
- };
733
- var execute = async () => {
734
- console.log("[EXULU] Initializing Exulu IMP database.");
735
- const { db: db2 } = await postgresClient();
736
- await up(db2);
737
- console.log("[EXULU] Inserting default user and admin role.");
738
- const existingRole = await db2.from("roles").where({ name: "admin" }).first();
739
- let roleId;
740
- if (!existingRole) {
741
- console.log("[EXULU] Creating default admin role.");
742
- const role = await db2.from("roles").insert({
743
- name: "admin",
744
- is_admin: true,
745
- agents: JSON.stringify([])
746
- }).returning("id");
747
- roleId = role[0].id;
748
- } else {
749
- roleId = existingRole.id;
750
- }
751
- const existingUser = await db2.from("users").where({ email: "admin@exulu.com" }).first();
752
- if (!existingUser) {
753
- const password = await encryptString("admin");
754
- console.log("[EXULU] Creating default admin user.");
755
- await db2.from("users").insert({
756
- name: "exulu",
757
- email: "admin@exulu.com",
758
- super_admin: true,
759
- createdAt: /* @__PURE__ */ new Date(),
760
- emailVerified: /* @__PURE__ */ new Date(),
761
- updatedAt: /* @__PURE__ */ new Date(),
762
- password,
763
- type: "user",
764
- role: roleId
765
- });
766
- }
767
- const { key } = await generateApiKey("exulu", "api@exulu.com");
768
- console.log("[EXULU] Database initialized.");
769
- console.log("[EXULU] Default api key: ", `${key}`);
770
- console.log("[EXULU] Default password if using password auth: ", `admin`);
771
- console.log("[EXULU] Default email if using password auth: ", `admin@exulu.com`);
772
- return;
773
- };
774
-
775
- // src/registry/classes.ts
776
- var import_zod = require("zod");
777
- var import_bullmq2 = require("bullmq");
778
- var import_zod2 = require("zod");
779
- var fs = __toESM(require("fs"), 1);
780
- var path = __toESM(require("path"), 1);
781
- var import_ai = require("ai");
782
-
783
- // types/enums/statistics.ts
784
- var STATISTICS_TYPE_ENUM = {
785
- CONTEXT_RETRIEVE: "context.retrieve",
786
- SOURCE_UPDATE: "source.update",
787
- EMBEDDER_UPSERT: "embedder.upsert",
788
- EMBEDDER_GENERATE: "embedder.generate",
789
- EMBEDDER_DELETE: "embedder.delete",
790
- WORKFLOW_RUN: "workflow.run",
791
- CONTEXT_UPSERT: "context.upsert",
792
- TOOL_CALL: "tool.call",
793
- AGENT_RUN: "agent.run"
794
- };
795
-
796
- // types/enums/eval-types.ts
797
- var EVAL_TYPES_ENUM = {
798
- llm_as_judge: "llm_as_judge"
799
- };
800
-
801
- // src/registry/classes.ts
802
- var import_knex4 = __toESM(require("pgvector/knex"), 1);
803
309
 
804
- // src/registry/decoraters/bullmq.ts
805
- var import_bullmq = require("bullmq");
806
- var import_uuid = require("uuid");
807
- var bullmqDecorator = async ({
808
- label,
809
- type,
810
- workflow,
811
- embedder,
812
- inputs,
813
- queue,
814
- user,
815
- agent,
816
- session,
817
- configuration,
818
- updater,
819
- context,
820
- steps,
821
- source,
822
- documents,
823
- trigger,
824
- item
825
- }) => {
826
- const redisId = (0, import_uuid.v4)();
827
- const job = await queue.add(
828
- `${embedder || workflow}`,
829
- {
830
- type: `${type}`,
831
- ...embedder && { embedder },
832
- ...workflow && { workflow },
833
- ...configuration && { configuration },
834
- ...updater && { updater },
835
- ...context && { context },
836
- ...source && { source },
837
- ...documents && { documents },
838
- ...steps && { steps },
839
- ...trigger && { trigger },
840
- ...item && { item },
841
- agent,
842
- user,
843
- inputs,
844
- label,
845
- session
846
- },
847
- {
848
- jobId: redisId
849
- }
850
- );
851
- const { db: db2 } = await postgresClient();
852
- const now = /* @__PURE__ */ new Date();
853
- console.log("[EXULU] scheduling new job", inputs);
854
- const insertData = {
855
- name: `${label}`,
856
- redis: job.id,
857
- status: "waiting",
858
- type,
859
- inputs,
860
- agent,
861
- item,
862
- createdAt: now,
863
- updatedAt: now,
864
- user,
865
- session,
866
- ...embedder && { embedder },
867
- ...workflow && { workflow },
868
- ...configuration && { configuration },
869
- ...steps && { steps },
870
- ...updater && { updater },
871
- ...context && { context },
872
- ...source && { source },
873
- ...documents && { documents: documents.map((doc2) => doc2.id) },
874
- ...trigger && { trigger }
875
- };
876
- await db2("jobs").insert(insertData).onConflict("redis").merge({
877
- ...insertData,
878
- updatedAt: now
879
- // Only updatedAt changes on updates
880
- });
881
- const doc = await db2.from("jobs").where({ redis: job.id }).first();
882
- if (!doc?.id) {
883
- throw new Error("Failed to get job ID after insert/update");
884
- }
885
- console.log("[EXULU] created job", doc?.id);
886
- return {
887
- ...job,
888
- id: doc?.id,
889
- redis: job.id
890
- };
310
+ // src/registry/utils/sanitize-name.ts
311
+ var sanitizeName = (name) => {
312
+ return name.toLowerCase().replace(/ /g, "_");
891
313
  };
892
314
 
893
315
  // types/enums/jobs.ts
@@ -2068,7 +1490,7 @@ var getToken = async (authHeader) => {
2068
1490
  };
2069
1491
 
2070
1492
  // src/auth/auth.ts
2071
- var import_bcryptjs2 = __toESM(require("bcryptjs"), 1);
1493
+ var import_bcryptjs = __toESM(require("bcryptjs"), 1);
2072
1494
  var authentication = async ({
2073
1495
  apikey,
2074
1496
  authtoken,
@@ -2163,7 +1585,7 @@ var authentication = async ({
2163
1585
  for (const user of filtered) {
2164
1586
  const user_key_last_slash_index = user.apikey.lastIndexOf("/");
2165
1587
  const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
2166
- 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);
2167
1589
  if (isMatch) {
2168
1590
  await db2.from("users").where({ id: user.id }).update({
2169
1591
  last_used: /* @__PURE__ */ new Date()
@@ -2220,595 +1642,929 @@ var requestValidators = {
2220
1642
  message: "Missing body."
2221
1643
  };
2222
1644
  }
2223
- if (!req.body.agent) {
2224
- return {
2225
- error: true,
2226
- code: 400,
2227
- message: "Missing agent in body."
2228
- };
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();
2229
1825
  }
2230
- if (!req.body.session) {
2231
- return {
2232
- error: true,
2233
- code: 400,
2234
- message: "Missing session in body."
2235
- };
1826
+ if (typeof value === "string") {
1827
+ return new Date(value).toISOString();
2236
1828
  }
2237
- if (!req.body.inputs) {
2238
- return {
2239
- error: true,
2240
- code: 400,
2241
- message: "Missing inputs in body."
2242
- };
1829
+ return value;
1830
+ },
1831
+ parseValue(value) {
1832
+ if (typeof value === "string") {
1833
+ return new Date(value);
2243
1834
  }
2244
- if (!req.body.label) {
2245
- return {
2246
- error: true,
2247
- code: 400,
2248
- message: "Missing label for job in body."
2249
- };
1835
+ if (typeof value === "number") {
1836
+ return new Date(value);
2250
1837
  }
2251
- return {
2252
- error: false
2253
- };
1838
+ return value;
2254
1839
  },
2255
- embedders: (req, configuration) => {
2256
- const contentType = req.headers["content-type"] || "";
2257
- if (!contentType.includes("application/json")) {
2258
- return {
2259
- error: true,
2260
- code: 400,
2261
- message: "Unsupported content type."
2262
- };
1840
+ parseLiteral(ast) {
1841
+ if (ast.kind === import_graphql.Kind.STRING) {
1842
+ return new Date(ast.value);
2263
1843
  }
2264
- if (!req.body) {
2265
- return {
2266
- error: true,
2267
- code: 400,
2268
- message: "Missing body."
2269
- };
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;
2270
2019
  }
2271
- if (!req.body.inputs) {
2272
- return {
2273
- error: true,
2274
- code: 400,
2275
- message: "Missing inputs."
2276
- };
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());
2277
2049
  }
2278
- 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);
2279
2087
  return {
2280
- error: true,
2281
- code: 400,
2282
- message: "Missing label for job in body."
2088
+ pageInfo: {
2089
+ pageCount,
2090
+ itemCount,
2091
+ currentPage,
2092
+ hasPreviousPage,
2093
+ hasNextPage
2094
+ },
2095
+ items
2283
2096
  };
2284
- }
2285
- if (configuration) {
2286
- for (const key in configuration) {
2287
- if (!req.body.configuration[key]) {
2288
- return {
2289
- error: true,
2290
- code: 400,
2291
- message: `Missing ${key} in body.configuration.`
2292
- };
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);
2293
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
+ };
2294
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
+ `;
2295
2184
  }
2296
- return {
2297
- error: false
2298
- };
2299
- },
2300
- agents: (req) => {
2301
- const contentType = req.headers["content-type"] || "";
2302
- if (!contentType.includes("application/json")) {
2303
- return {
2304
- error: true,
2305
- code: 400,
2306
- message: "Unsupported content type."
2307
- };
2308
- }
2309
- if (!req.body) {
2310
- return {
2311
- error: true,
2312
- code: 400,
2313
- message: "Missing body."
2314
- };
2315
- }
2316
- if (!req.body.threadId) {
2317
- return {
2318
- error: true,
2319
- code: 400,
2320
- message: "Missing threadId in body."
2321
- };
2322
- }
2323
- if (!req.body.resourceId) {
2324
- return {
2325
- error: true,
2326
- code: 400,
2327
- message: "Missing resourceId in body."
2328
- };
2329
- }
2330
- if (!req.body.messages) {
2331
- return {
2332
- error: true,
2333
- code: 400,
2334
- message: 'Missing "messages" property in body.'
2335
- };
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();
2336
2226
  }
2337
- return {
2338
- error: false
2339
- };
2340
- }
2227
+ });
2228
+ return input;
2341
2229
  };
2342
2230
 
2343
2231
  // src/registry/routes.ts
2344
- var import_zodex = require("zodex");
2232
+ var import_express5 = require("@as-integrations/express5");
2345
2233
 
2346
- // src/bullmq/queues.ts
2347
- var import_bullmq4 = require("bullmq");
2348
- var ExuluQueues = class {
2349
- queues;
2350
- constructor() {
2351
- this.queues = [];
2352
- }
2353
- queue(name) {
2354
- return this.queues.find((x) => x.name === name);
2355
- }
2356
- use(name) {
2357
- const existing = this.queues.find((x) => x.name === name);
2358
- if (existing) {
2359
- return existing;
2360
- }
2361
- if (!redisServer.host?.length || !redisServer.port?.length) {
2362
- 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() ).`);
2363
- 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"
2364
2252
  }
2365
- const newQueue = new import_bullmq4.Queue(`${name}`, { connection: redisServer });
2366
- this.queues.push(newQueue);
2367
- return newQueue;
2368
- }
2253
+ ]
2369
2254
  };
2370
- var queues = new ExuluQueues();
2371
-
2372
- // types/models/vector-methods.ts
2373
- var VectorMethodEnum = {
2374
- "cosineDistance": "cosineDistance",
2375
- "l1Distance": "l1Distance",
2376
- "l2Distance": "l2Distance",
2377
- "hammingDistance": "hammingDistance",
2378
- "jaccardDistance": "jaccardDistance",
2379
- "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
+ ]
2380
2275
  };
2381
-
2382
- // src/registry/routes.ts
2383
- var import_express3 = __toESM(require("express"), 1);
2384
- var import_server3 = require("@apollo/server");
2385
- var Papa = __toESM(require("papaparse"), 1);
2386
- var import_cors = __toESM(require("cors"), 1);
2387
- var import_reflect_metadata = require("reflect-metadata");
2388
-
2389
- // src/registry/utils/graphql.ts
2390
- var import_schema = require("@graphql-tools/schema");
2391
- var import_graphql_type_json = __toESM(require("graphql-type-json"), 1);
2392
- var import_graphql = require("graphql");
2393
- var import_crypto_js = __toESM(require("crypto-js"), 1);
2394
- var GraphQLDate = new import_graphql.GraphQLScalarType({
2395
- name: "Date",
2396
- description: "Date custom scalar type",
2397
- serialize(value) {
2398
- if (value instanceof Date) {
2399
- 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"
2400
2340
  }
2401
- if (typeof value === "number") {
2402
- 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"
2403
2361
  }
2404
- if (typeof value === "string") {
2405
- 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"
2406
2385
  }
2407
- return value;
2386
+ ]
2387
+ };
2388
+ var workflowSchema = {
2389
+ name: {
2390
+ plural: "workflows",
2391
+ singular: "workflow"
2408
2392
  },
2409
- parseValue(value) {
2410
- if (typeof value === "string") {
2411
- 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"
2412
2405
  }
2413
- if (typeof value === "number") {
2414
- 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"
2415
2457
  }
2416
- return value;
2458
+ ]
2459
+ };
2460
+ var jobsSchema = {
2461
+ name: {
2462
+ plural: "jobs",
2463
+ singular: "job"
2417
2464
  },
2418
- parseLiteral(ast) {
2419
- if (ast.kind === import_graphql.Kind.STRING) {
2420
- return new Date(ast.value);
2421
- }
2422
- if (ast.kind === import_graphql.Kind.INT) {
2423
- 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"
2424
2522
  }
2425
- return null;
2426
- }
2427
- });
2428
- var map = (field) => {
2429
- let type;
2430
- switch (field.type) {
2431
- case "text":
2432
- case "shortText":
2433
- case "longText":
2434
- case "code":
2435
- type = "String";
2436
- break;
2437
- case "number":
2438
- type = "Float";
2439
- break;
2440
- case "boolean":
2441
- type = "Boolean";
2442
- break;
2443
- case "json":
2444
- type = "JSON";
2445
- break;
2446
- case "date":
2447
- type = "Date";
2448
- break;
2449
- default:
2450
- type = "String";
2451
- }
2452
- return type;
2453
- };
2454
- function createTypeDefs(table) {
2455
- const fields = table.fields.map((field) => {
2456
- let type;
2457
- type = map(field);
2458
- const required = field.required ? "!" : "";
2459
- return ` ${field.name}: ${type}${required}`;
2460
- });
2461
- const typeDef = `
2462
- type ${table.name.singular} {
2463
- ${fields.join("\n")}
2464
- id: ID!
2465
- createdAt: Date!
2466
- updatedAt: Date!
2467
- }
2468
- `;
2469
- const inputDef = `
2470
- input ${table.name.singular}Input {
2471
- ${table.fields.map((f) => ` ${f.name}: ${map(f)}`).join("\n")}
2472
- }
2473
- `;
2474
- return typeDef + inputDef;
2475
- }
2476
- function createFilterTypeDefs(table) {
2477
- const fieldFilters = table.fields.map((field) => {
2478
- let type;
2479
- type = map(field);
2480
- return `
2481
- ${field.name}: FilterOperator${type}`;
2482
- });
2483
- const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2484
- const operatorTypes = `
2485
- input FilterOperatorString {
2486
- eq: String
2487
- ne: String
2488
- in: [String]
2489
- contains: String
2490
- }
2491
-
2492
- input FilterOperatorDate {
2493
- lte: Date
2494
- gte: Date
2495
- }
2496
-
2497
- input FilterOperatorFloat {
2498
- eq: Float
2499
- ne: Float
2500
- in: [Float]
2501
- }
2502
-
2503
- input FilterOperatorBoolean {
2504
- eq: Boolean
2505
- ne: Boolean
2506
- in: [Boolean]
2507
- }
2508
-
2509
- input FilterOperatorJSON {
2510
- eq: JSON
2511
- ne: JSON
2512
- in: [JSON]
2513
- }
2514
-
2515
- input SortBy {
2516
- field: String!
2517
- direction: SortDirection!
2518
- }
2519
-
2520
- enum SortDirection {
2521
- ASC
2522
- DESC
2523
- }
2524
-
2525
- input Filter${tableNameSingularUpperCaseFirst} {
2526
- ${fieldFilters.join("\n")}
2527
- }`;
2528
- return operatorTypes;
2529
- }
2530
- var getRequestedFields = (info) => {
2531
- const selections = info.operation.selectionSet.selections[0].selectionSet.selections;
2532
- const itemsSelection = selections.find((s) => s.name.value === "items");
2533
- const fields = itemsSelection ? Object.keys(itemsSelection.selectionSet.selections.reduce((acc, field) => {
2534
- acc[field.name.value] = true;
2535
- return acc;
2536
- }, {})) : Object.keys(selections.reduce((acc, field) => {
2537
- acc[field.name.value] = true;
2538
- return acc;
2539
- }, {}));
2540
- return fields.filter((field) => field !== "pageInfo" && field !== "items");
2523
+ ]
2541
2524
  };
2542
- function createMutations(table) {
2543
- const tableNamePlural = table.name.plural.toLowerCase();
2544
- const tableNameSingular = table.name.singular.toLowerCase();
2545
- return {
2546
- [`${tableNamePlural}CreateOne`]: async (_, args, context, info) => {
2547
- const { db: db2 } = context;
2548
- const requestedFields = getRequestedFields(info);
2549
- let { input } = args;
2550
- input = encryptSensitiveFields(input);
2551
- const results = await db2(tableNamePlural).insert({
2552
- ...input,
2553
- createdAt: /* @__PURE__ */ new Date(),
2554
- updatedAt: /* @__PURE__ */ new Date()
2555
- }).returning(requestedFields);
2556
- return results[0];
2557
- },
2558
- [`${tableNamePlural}UpdateOne`]: async (_, args, context, info) => {
2559
- const { db: db2 } = context;
2560
- let { where, input } = args;
2561
- input = encryptSensitiveFields(input);
2562
- await db2(tableNamePlural).where(where).update({
2563
- ...input,
2564
- updatedAt: /* @__PURE__ */ new Date()
2565
- });
2566
- const requestedFields = getRequestedFields(info);
2567
- const result = await db2.from(tableNamePlural).select(requestedFields).where(where).first();
2568
- return result;
2525
+ var agentsSchema = {
2526
+ name: {
2527
+ plural: "agents",
2528
+ singular: "agent"
2529
+ },
2530
+ fields: [
2531
+ {
2532
+ name: "name",
2533
+ type: "text"
2569
2534
  },
2570
- [`${tableNamePlural}UpdateOneById`]: async (_, args, context, info) => {
2571
- let { id, input } = args;
2572
- input = encryptSensitiveFields(input);
2573
- const { db: db2 } = context;
2574
- await db2(tableNamePlural).where({ id }).update({
2575
- ...input,
2576
- updatedAt: /* @__PURE__ */ new Date()
2577
- });
2578
- const requestedFields = getRequestedFields(info);
2579
- const result = await db2.from(tableNamePlural).select(requestedFields).where({ id }).first();
2580
- return result;
2535
+ {
2536
+ name: "description",
2537
+ type: "text"
2581
2538
  },
2582
- [`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
2583
- const { db: db2 } = context;
2584
- const { where } = args;
2585
- const requestedFields = getRequestedFields(info);
2586
- const result = await db2.from(tableNamePlural).select(requestedFields).where(where).first();
2587
- await db2(tableNamePlural).where(where).del();
2588
- return result;
2539
+ {
2540
+ name: "extensions",
2541
+ type: "json"
2589
2542
  },
2590
- [`${tableNamePlural}RemoveOneById`]: async (_, args, context, info) => {
2591
- const { id } = args;
2592
- const { db: db2 } = context;
2593
- const requestedFields = getRequestedFields(info);
2594
- const result = await db2.from(tableNamePlural).select(requestedFields).where({ id }).first();
2595
- await db2(tableNamePlural).where({ id }).del();
2596
- return result;
2597
- }
2598
- };
2599
- }
2600
- function createQueries(table) {
2601
- const tableNamePlural = table.name.plural.toLowerCase();
2602
- const tableNameSingular = table.name.singular.toLowerCase();
2603
- const applyFilters = (query, filters) => {
2604
- filters.forEach((filter) => {
2605
- Object.entries(filter).forEach(([fieldName, operators]) => {
2606
- if (operators) {
2607
- if (operators.eq !== void 0) {
2608
- query = query.where(fieldName, operators.eq);
2609
- }
2610
- if (operators.ne !== void 0) {
2611
- query = query.whereRaw(`?? IS DISTINCT FROM ?`, [fieldName, operators.ne]);
2612
- }
2613
- if (operators.in !== void 0) {
2614
- query = query.whereIn(fieldName, operators.in);
2615
- }
2616
- if (operators.contains !== void 0) {
2617
- query = query.where(fieldName, "like", `%${operators.contains}%`);
2618
- }
2619
- }
2620
- });
2621
- });
2622
- return query;
2623
- };
2624
- const applySorting = (query, sort) => {
2625
- if (sort) {
2626
- query = query.orderBy(sort.field, sort.direction.toLowerCase());
2627
- }
2628
- return query;
2629
- };
2630
- return {
2631
- [`${tableNameSingular}ById`]: async (_, args, context, info) => {
2632
- const { db: db2 } = context;
2633
- const requestedFields = getRequestedFields(info);
2634
- const result = await db2.from(tableNamePlural).select(requestedFields).where({ id: args.id }).first();
2635
- return result;
2543
+ {
2544
+ name: "backend",
2545
+ type: "text"
2636
2546
  },
2637
- [`${tableNameSingular}One`]: async (_, args, context, info) => {
2638
- const { filters = [], sort } = args;
2639
- const { db: db2 } = context;
2640
- const requestedFields = getRequestedFields(info);
2641
- let query = db2.from(tableNamePlural).select(requestedFields);
2642
- query = applyFilters(query, filters);
2643
- query = applySorting(query, sort);
2644
- const result = await query.first();
2645
- return result;
2547
+ {
2548
+ name: "type",
2549
+ type: "text"
2646
2550
  },
2647
- [`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
2648
- const { limit = 10, page = 0, filters = [], sort } = args;
2649
- const { db: db2 } = context;
2650
- let baseQuery = db2(tableNamePlural);
2651
- baseQuery = applyFilters(baseQuery, filters);
2652
- const [{ count }] = await baseQuery.clone().count("* as count");
2653
- const itemCount = Number(count);
2654
- const pageCount = Math.ceil(itemCount / limit);
2655
- const currentPage = page;
2656
- const hasPreviousPage = currentPage > 1;
2657
- const hasNextPage = currentPage < pageCount - 1;
2658
- let dataQuery = baseQuery.clone();
2659
- const requestedFields = getRequestedFields(info);
2660
- dataQuery = applySorting(dataQuery, sort);
2661
- if (page > 1) {
2662
- dataQuery = dataQuery.offset((page - 1) * limit);
2663
- }
2664
- const items = await dataQuery.select(requestedFields).limit(limit);
2665
- return {
2666
- pageInfo: {
2667
- pageCount,
2668
- itemCount,
2669
- currentPage,
2670
- hasPreviousPage,
2671
- hasNextPage
2672
- },
2673
- items
2674
- };
2551
+ {
2552
+ name: "active",
2553
+ type: "boolean",
2554
+ default: false
2675
2555
  },
2676
- // Add jobStatistics query for jobs table
2677
- ...tableNamePlural === "jobs" ? {
2678
- jobStatistics: async (_, args, context, info) => {
2679
- const { user, agent, from, to } = args;
2680
- const { db: db2 } = context;
2681
- let query = db2("jobs");
2682
- if (user) {
2683
- query = query.where("user", user);
2684
- }
2685
- if (agent) {
2686
- query = query.where("agent", agent);
2687
- }
2688
- if (from) {
2689
- query = query.where("createdAt", ">=", from);
2690
- }
2691
- if (to) {
2692
- query = query.where("createdAt", "<=", to);
2693
- }
2694
- const completedQuery = query.clone().where("status", "completed");
2695
- const [{ completedCount }] = await completedQuery.count("* as completedCount");
2696
- const failedQuery = query.clone().where("status", "failed");
2697
- const [{ failedCount }] = await failedQuery.count("* as failedCount");
2698
- const durationQuery = query.clone().where("status", "completed").whereNotNull("duration").select(db2.raw('AVG("duration") as averageDuration'));
2699
- const [{ averageDuration }] = await durationQuery;
2700
- return {
2701
- completedCount: Number(completedCount),
2702
- failedCount: Number(failedCount),
2703
- averageDuration: averageDuration ? Number(averageDuration) : 0
2704
- };
2705
- }
2706
- } : {}
2707
- };
2708
- }
2709
- function createSDL(tables) {
2710
- let typeDefs = `
2711
- scalar JSON
2712
- scalar Date
2713
-
2714
- type Query {
2715
- `;
2716
- let mutationDefs = `
2717
- type Mutation {
2718
- `;
2719
- let modelDefs = "";
2720
- const resolvers = { JSON: import_graphql_type_json.default, Date: GraphQLDate, Query: {}, Mutation: {} };
2721
- for (const table of tables) {
2722
- const tableNamePlural = table.name.plural.toLowerCase();
2723
- const tableNameSingular = table.name.singular.toLowerCase();
2724
- const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
2725
- typeDefs += `
2726
- ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
2727
- ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
2728
- ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
2729
- ${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
2730
- `;
2731
- mutationDefs += `
2732
- ${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
2733
- ${tableNamePlural}UpdateOne(where: JSON!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2734
- ${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!): ${tableNameSingular}
2735
- ${tableNamePlural}RemoveOne(where: JSON!): ${tableNameSingular}
2736
- ${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
2737
- `;
2738
- modelDefs += createTypeDefs(table);
2739
- modelDefs += createFilterTypeDefs(table);
2740
- modelDefs += `
2741
- type ${tableNameSingularUpperCaseFirst}PaginationResult {
2742
- pageInfo: PageInfo!
2743
- items: [${tableNameSingular}]!
2744
- }
2745
-
2746
- type PageInfo {
2747
- pageCount: Int!
2748
- itemCount: Int!
2749
- currentPage: Int!
2750
- hasPreviousPage: Boolean!
2751
- hasNextPage: Boolean!
2752
- }
2753
- `;
2754
- if (tableNamePlural === "jobs") {
2755
- modelDefs += `
2756
- type JobStatistics {
2757
- completedCount: Int!
2758
- failedCount: Int!
2759
- averageDuration: Float!
2760
- }
2761
- `;
2762
- }
2763
- Object.assign(resolvers.Query, createQueries(table));
2764
- Object.assign(resolvers.Mutation, createMutations(table));
2765
- }
2766
- typeDefs += "}\n";
2767
- mutationDefs += "}\n";
2768
- const fullSDL = typeDefs + mutationDefs + modelDefs;
2769
- const schema = (0, import_schema.makeExecutableSchema)({
2770
- typeDefs: fullSDL,
2771
- resolvers
2772
- });
2773
- console.log("\n\u{1F4CA} GraphQL Schema Overview\n");
2774
- const queriesTable = Object.keys(resolvers.Query).map((query) => ({
2775
- "Operation Type": "Query",
2776
- "Name": query,
2777
- "Description": "Retrieves data"
2778
- }));
2779
- const mutationsTable = Object.keys(resolvers.Mutation).map((mutation) => ({
2780
- "Operation Type": "Mutation",
2781
- "Name": mutation,
2782
- "Description": "Modifies data"
2783
- }));
2784
- const typesTable = tables.flatMap(
2785
- (table) => table.fields.map((field) => ({
2786
- "Type": table.name.singular,
2787
- "Field": field.name,
2788
- "Field Type": field.type,
2789
- "Required": field.required ? "Yes" : "No"
2790
- }))
2791
- );
2792
- console.log("\u{1F50D} Operations:");
2793
- console.table([...queriesTable, ...mutationsTable]);
2794
- console.log("\n\u{1F4DD} Types and Fields:");
2795
- console.table(typesTable);
2796
- console.log("\n");
2797
- return schema;
2798
- }
2799
- var sensitiveFields = ["anthropic_token"];
2800
- var encryptSensitiveFields = (input) => {
2801
- sensitiveFields.forEach((field) => {
2802
- if (input[field]) {
2803
- 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"
2804
2564
  }
2805
- });
2806
- return input;
2565
+ ]
2807
2566
  };
2808
2567
 
2809
- // src/registry/routes.ts
2810
- var import_express5 = require("@as-integrations/express5");
2811
-
2812
2568
  // src/registry/uppy.ts
2813
2569
  var import_express = require("express");
2814
2570
  var createUppyRoutes = async (app) => {
@@ -4947,6 +4703,259 @@ var getTicket = new ExuluTool({
4947
4703
  }
4948
4704
  });
4949
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
+
4950
4959
  // src/registry/index.ts
4951
4960
  var ExuluApp = class {
4952
4961
  _agents = [];
@@ -4961,6 +4970,7 @@ var ExuluApp = class {
4961
4970
  // Factory function so we can async
4962
4971
  // initialize the MCP server if needed.
4963
4972
  create = async ({ contexts, agents, workflows, config, tools }) => {
4973
+ await execute();
4964
4974
  this._workflows = workflows ?? [];
4965
4975
  this._contexts = contexts ?? {};
4966
4976
  this._agents = [
@@ -6276,14 +6286,6 @@ var ExuluChunkers = {
6276
6286
  rules: RecursiveRules
6277
6287
  }
6278
6288
  };
6279
- var ExuluDatabase = {
6280
- init: async () => {
6281
- await execute();
6282
- },
6283
- generateApiKey: async (name, email) => {
6284
- return await generateApiKey(name, email);
6285
- }
6286
- };
6287
6289
  // Annotate the CommonJS export names for ESM import in node:
6288
6290
  0 && (module.exports = {
6289
6291
  EXULU_JOB_STATUS_ENUM,
@@ -6293,7 +6295,6 @@ var ExuluDatabase = {
6293
6295
  ExuluAuthentication,
6294
6296
  ExuluChunkers,
6295
6297
  ExuluContext,
6296
- ExuluDatabase,
6297
6298
  ExuluEmbedder,
6298
6299
  ExuluEval,
6299
6300
  ExuluJobs,