@exulu/backend 0.3.12 → 1.2.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/.github/workflows/release.yml +38 -0
- package/.husky/commit-msg +1 -0
- package/CHANGELOG.md +45 -0
- package/README.md +152 -47
- package/commitlint.config.js +4 -0
- package/dist/index.cjs +805 -504
- package/dist/index.d.cts +46 -44
- package/dist/index.d.ts +46 -44
- package/dist/index.js +805 -503
- package/package.json +98 -88
- package/release.config.cjs +9 -0
- package/types/models/user.ts +1 -0
- package/git-conventional-commits.yaml +0 -43
- package/types/models/embedder-backend.ts +0 -15
package/dist/index.js
CHANGED
|
@@ -78,11 +78,6 @@ async function postgresClient() {
|
|
|
78
78
|
if (!db["exulu"]) {
|
|
79
79
|
try {
|
|
80
80
|
console.log("[EXULU] Initializing exulu database.");
|
|
81
|
-
console.log(process.env.POSTGRES_DB_HOST);
|
|
82
|
-
console.log(process.env.POSTGRES_DB_PORT);
|
|
83
|
-
console.log(process.env.POSTGRES_DB_USER);
|
|
84
|
-
console.log(process.env.POSTGRES_DB_PASSWORD);
|
|
85
|
-
console.log(process.env.POSTGRES_DB_SSL);
|
|
86
81
|
const knex = Knex({
|
|
87
82
|
client: "pg",
|
|
88
83
|
connection: {
|
|
@@ -107,6 +102,47 @@ async function postgresClient() {
|
|
|
107
102
|
}
|
|
108
103
|
|
|
109
104
|
// src/postgres/core-schema.ts
|
|
105
|
+
var agentMessagesSchema = {
|
|
106
|
+
name: {
|
|
107
|
+
plural: "agent_messages",
|
|
108
|
+
singular: "agent_message"
|
|
109
|
+
},
|
|
110
|
+
fields: [
|
|
111
|
+
{
|
|
112
|
+
name: "content",
|
|
113
|
+
type: "text"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "title",
|
|
117
|
+
type: "text"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "session",
|
|
121
|
+
type: "text"
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
var agentSessionsSchema = {
|
|
126
|
+
name: {
|
|
127
|
+
plural: "agent_sessions",
|
|
128
|
+
singular: "agent_session"
|
|
129
|
+
},
|
|
130
|
+
fields: [
|
|
131
|
+
{
|
|
132
|
+
name: "agent",
|
|
133
|
+
type: "uuid"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "user",
|
|
137
|
+
// next auth stores users with id type SERIAL, so we need to use number
|
|
138
|
+
type: "number"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "title",
|
|
142
|
+
type: "text"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
};
|
|
110
146
|
var usersSchema = {
|
|
111
147
|
name: {
|
|
112
148
|
plural: "users",
|
|
@@ -164,14 +200,13 @@ var usersSchema = {
|
|
|
164
200
|
name: "last_used",
|
|
165
201
|
type: "date"
|
|
166
202
|
},
|
|
203
|
+
{
|
|
204
|
+
name: "anthropic_token",
|
|
205
|
+
type: "text"
|
|
206
|
+
},
|
|
167
207
|
{
|
|
168
208
|
name: "role",
|
|
169
|
-
type: "
|
|
170
|
-
references: {
|
|
171
|
-
table: "roles",
|
|
172
|
-
field: "id",
|
|
173
|
-
onDelete: "CASCADE"
|
|
174
|
-
}
|
|
209
|
+
type: "uuid"
|
|
175
210
|
}
|
|
176
211
|
]
|
|
177
212
|
};
|
|
@@ -217,10 +252,6 @@ var statisticsSchema = {
|
|
|
217
252
|
{
|
|
218
253
|
name: "total",
|
|
219
254
|
type: "number"
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
name: "timeseries",
|
|
223
|
-
type: "json"
|
|
224
255
|
}
|
|
225
256
|
]
|
|
226
257
|
};
|
|
@@ -276,11 +307,11 @@ var evalResultsSchema = {
|
|
|
276
307
|
},
|
|
277
308
|
{
|
|
278
309
|
name: "agent_id",
|
|
279
|
-
type: "
|
|
310
|
+
type: "uuid"
|
|
280
311
|
},
|
|
281
312
|
{
|
|
282
313
|
name: "workflow_id",
|
|
283
|
-
type: "
|
|
314
|
+
type: "uuid"
|
|
284
315
|
},
|
|
285
316
|
{
|
|
286
317
|
name: "eval_type",
|
|
@@ -296,50 +327,6 @@ var evalResultsSchema = {
|
|
|
296
327
|
}
|
|
297
328
|
]
|
|
298
329
|
};
|
|
299
|
-
var threadsSchema = {
|
|
300
|
-
name: {
|
|
301
|
-
plural: "threads",
|
|
302
|
-
singular: "thread"
|
|
303
|
-
},
|
|
304
|
-
fields: [
|
|
305
|
-
{
|
|
306
|
-
name: "resourceId",
|
|
307
|
-
type: "text"
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
name: "title",
|
|
311
|
-
type: "text"
|
|
312
|
-
},
|
|
313
|
-
{
|
|
314
|
-
name: "metadata",
|
|
315
|
-
type: "text"
|
|
316
|
-
}
|
|
317
|
-
]
|
|
318
|
-
};
|
|
319
|
-
var messagesSchema = {
|
|
320
|
-
name: {
|
|
321
|
-
plural: "messages",
|
|
322
|
-
singular: "message"
|
|
323
|
-
},
|
|
324
|
-
fields: [
|
|
325
|
-
{
|
|
326
|
-
name: "thread_id",
|
|
327
|
-
type: "text"
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
name: "content",
|
|
331
|
-
type: "text"
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: "role",
|
|
335
|
-
type: "text"
|
|
336
|
-
},
|
|
337
|
-
{
|
|
338
|
-
name: "type",
|
|
339
|
-
type: "text"
|
|
340
|
-
}
|
|
341
|
-
]
|
|
342
|
-
};
|
|
343
330
|
var jobsSchema = {
|
|
344
331
|
name: {
|
|
345
332
|
plural: "jobs",
|
|
@@ -372,19 +359,20 @@ var jobsSchema = {
|
|
|
372
359
|
},
|
|
373
360
|
{
|
|
374
361
|
name: "agent",
|
|
375
|
-
type: "
|
|
362
|
+
type: "uuid"
|
|
376
363
|
},
|
|
377
364
|
{
|
|
378
365
|
name: "workflow",
|
|
379
|
-
type: "
|
|
366
|
+
type: "uuid"
|
|
380
367
|
},
|
|
381
368
|
{
|
|
382
369
|
name: "user",
|
|
383
|
-
type
|
|
370
|
+
// next auth stores users with id type SERIAL, so we need to use number
|
|
371
|
+
type: "number"
|
|
384
372
|
},
|
|
385
373
|
{
|
|
386
374
|
name: "item",
|
|
387
|
-
type: "
|
|
375
|
+
type: "uuid"
|
|
388
376
|
},
|
|
389
377
|
{
|
|
390
378
|
name: "steps",
|
|
@@ -478,7 +466,7 @@ var mapType = (t, type, name, defaultValue) => {
|
|
|
478
466
|
return;
|
|
479
467
|
}
|
|
480
468
|
if (type === "date") {
|
|
481
|
-
t.
|
|
469
|
+
t.timestamp(name);
|
|
482
470
|
return;
|
|
483
471
|
}
|
|
484
472
|
if (type === "uuid") {
|
|
@@ -548,23 +536,44 @@ var generateApiKey = async (name, email) => {
|
|
|
548
536
|
|
|
549
537
|
// src/postgres/init-db.ts
|
|
550
538
|
var up = async function(knex) {
|
|
539
|
+
if (!await knex.schema.hasTable("agent_sessions")) {
|
|
540
|
+
await knex.schema.createTable("agent_sessions", (table) => {
|
|
541
|
+
table.uuid("id").primary().defaultTo(knex.fn.uuid());
|
|
542
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
543
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
544
|
+
for (const field of agentSessionsSchema.fields) {
|
|
545
|
+
const { type, name, default: defaultValue } = field;
|
|
546
|
+
if (!type || !name) {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
mapType(table, type, sanitizeName(name), defaultValue);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
if (!await knex.schema.hasTable("agent_messages")) {
|
|
554
|
+
await knex.schema.createTable("agent_messages", (table) => {
|
|
555
|
+
table.uuid("id").primary().defaultTo(knex.fn.uuid());
|
|
556
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
557
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
558
|
+
for (const field of agentMessagesSchema.fields) {
|
|
559
|
+
const { type, name, default: defaultValue } = field;
|
|
560
|
+
if (!type || !name) {
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
mapType(table, type, sanitizeName(name), defaultValue);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
551
567
|
if (!await knex.schema.hasTable("roles")) {
|
|
552
568
|
await knex.schema.createTable("roles", (table) => {
|
|
553
569
|
table.uuid("id").primary().defaultTo(knex.fn.uuid());
|
|
554
|
-
table.
|
|
555
|
-
table.
|
|
570
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
571
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
556
572
|
for (const field of rolesSchema.fields) {
|
|
557
|
-
const { type, name,
|
|
573
|
+
const { type, name, default: defaultValue } = field;
|
|
558
574
|
if (!type || !name) {
|
|
559
575
|
continue;
|
|
560
576
|
}
|
|
561
|
-
if (type === "reference") {
|
|
562
|
-
if (!references) {
|
|
563
|
-
throw new Error("Field with type reference must have a reference definition.");
|
|
564
|
-
}
|
|
565
|
-
table.uuid(name).references(references.field).inTable(references.table);
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
577
|
mapType(table, type, sanitizeName(name), defaultValue);
|
|
569
578
|
}
|
|
570
579
|
});
|
|
@@ -572,20 +581,13 @@ var up = async function(knex) {
|
|
|
572
581
|
if (!await knex.schema.hasTable("eval_results")) {
|
|
573
582
|
await knex.schema.createTable("eval_results", (table) => {
|
|
574
583
|
table.uuid("id").primary().defaultTo(knex.fn.uuid());
|
|
575
|
-
table.
|
|
576
|
-
table.
|
|
584
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
585
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
577
586
|
for (const field of evalResultsSchema.fields) {
|
|
578
|
-
const { type, name,
|
|
587
|
+
const { type, name, default: defaultValue } = field;
|
|
579
588
|
if (!type || !name) {
|
|
580
589
|
continue;
|
|
581
590
|
}
|
|
582
|
-
if (type === "reference") {
|
|
583
|
-
if (!references) {
|
|
584
|
-
throw new Error("Field with type reference must have a reference definition.");
|
|
585
|
-
}
|
|
586
|
-
table.uuid(name).references(references.field).inTable(references.table);
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
591
|
mapType(table, type, sanitizeName(name), defaultValue);
|
|
590
592
|
}
|
|
591
593
|
});
|
|
@@ -593,62 +595,41 @@ var up = async function(knex) {
|
|
|
593
595
|
if (!await knex.schema.hasTable("statistics")) {
|
|
594
596
|
await knex.schema.createTable("statistics", (table) => {
|
|
595
597
|
table.uuid("id").primary().defaultTo(knex.fn.uuid());
|
|
596
|
-
table.
|
|
597
|
-
table.
|
|
598
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
599
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
598
600
|
for (const field of statisticsSchema.fields) {
|
|
599
|
-
const { type, name,
|
|
601
|
+
const { type, name, default: defaultValue } = field;
|
|
600
602
|
if (!type || !name) {
|
|
601
603
|
continue;
|
|
602
604
|
}
|
|
603
|
-
if (type === "reference") {
|
|
604
|
-
if (!references) {
|
|
605
|
-
throw new Error("Field with type reference must have a reference definition.");
|
|
606
|
-
}
|
|
607
|
-
table.uuid(name).references(references.field).inTable(references.table);
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
605
|
mapType(table, type, sanitizeName(name), defaultValue);
|
|
611
606
|
}
|
|
612
607
|
});
|
|
613
608
|
}
|
|
614
609
|
if (!await knex.schema.hasTable("jobs")) {
|
|
615
610
|
await knex.schema.createTable("jobs", (table) => {
|
|
616
|
-
table.
|
|
617
|
-
table.
|
|
618
|
-
table.
|
|
611
|
+
table.uuid("id").primary().defaultTo(knex.fn.uuid());
|
|
612
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
613
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
619
614
|
for (const field of jobsSchema.fields) {
|
|
620
|
-
const { type, name,
|
|
615
|
+
const { type, name, default: defaultValue } = field;
|
|
621
616
|
if (!type || !name) {
|
|
622
617
|
continue;
|
|
623
618
|
}
|
|
624
|
-
if (type === "reference") {
|
|
625
|
-
if (!references) {
|
|
626
|
-
throw new Error("Field with type reference must have a reference definition.");
|
|
627
|
-
}
|
|
628
|
-
table.uuid(name).references(references.field).inTable(references.table);
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
619
|
mapType(table, type, sanitizeName(name), defaultValue);
|
|
632
620
|
}
|
|
633
621
|
});
|
|
634
622
|
}
|
|
635
623
|
if (!await knex.schema.hasTable("agents")) {
|
|
636
624
|
await knex.schema.createTable("agents", (table) => {
|
|
637
|
-
table.
|
|
638
|
-
table.
|
|
639
|
-
table.
|
|
625
|
+
table.uuid("id").primary().defaultTo(knex.fn.uuid());
|
|
626
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
627
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
640
628
|
for (const field of agentsSchema.fields) {
|
|
641
|
-
const { type, name,
|
|
629
|
+
const { type, name, default: defaultValue } = field;
|
|
642
630
|
if (!type || !name) {
|
|
643
631
|
continue;
|
|
644
632
|
}
|
|
645
|
-
if (type === "reference") {
|
|
646
|
-
if (!references) {
|
|
647
|
-
throw new Error("Field with type reference must have a reference definition.");
|
|
648
|
-
}
|
|
649
|
-
table.uuid(name).references(references.field).inTable(references.table);
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
633
|
mapType(table, type, sanitizeName(name), defaultValue);
|
|
653
634
|
}
|
|
654
635
|
});
|
|
@@ -664,8 +645,8 @@ var up = async function(knex) {
|
|
|
664
645
|
if (!await knex.schema.hasTable("users")) {
|
|
665
646
|
await knex.schema.createTable("users", (table) => {
|
|
666
647
|
table.increments("id").primary();
|
|
667
|
-
table.
|
|
668
|
-
table.
|
|
648
|
+
table.timestamp("createdAt").defaultTo(knex.fn.now());
|
|
649
|
+
table.timestamp("updatedAt").defaultTo(knex.fn.now());
|
|
669
650
|
table.string("name", 255);
|
|
670
651
|
table.string("password", 255);
|
|
671
652
|
table.string("email", 255);
|
|
@@ -673,20 +654,13 @@ var up = async function(knex) {
|
|
|
673
654
|
table.text("image");
|
|
674
655
|
for (const field of usersSchema.fields) {
|
|
675
656
|
console.log("[EXULU] field", field);
|
|
676
|
-
const { type, name,
|
|
657
|
+
const { type, name, default: defaultValue } = field;
|
|
677
658
|
if (name === "id" || name === "name" || name === "email" || name === "emailVerified" || name === "image") {
|
|
678
659
|
continue;
|
|
679
660
|
}
|
|
680
661
|
if (!type || !name) {
|
|
681
662
|
continue;
|
|
682
663
|
}
|
|
683
|
-
if (type === "reference") {
|
|
684
|
-
if (!references) {
|
|
685
|
-
throw new Error("Field with type reference must have a reference definition.");
|
|
686
|
-
}
|
|
687
|
-
table.uuid(name).references(references.field).inTable(references.table);
|
|
688
|
-
return;
|
|
689
|
-
}
|
|
690
664
|
mapType(table, type, sanitizeName(name), defaultValue);
|
|
691
665
|
}
|
|
692
666
|
});
|
|
@@ -707,14 +681,6 @@ var up = async function(knex) {
|
|
|
707
681
|
table.text("token_type");
|
|
708
682
|
});
|
|
709
683
|
}
|
|
710
|
-
if (!await knex.schema.hasTable("sessions")) {
|
|
711
|
-
await knex.schema.createTable("sessions", (table) => {
|
|
712
|
-
table.increments("id").primary();
|
|
713
|
-
table.integer("userId").notNullable();
|
|
714
|
-
table.timestamp("expires", { useTz: true }).notNullable();
|
|
715
|
-
table.string("sessionToken", 255).notNullable();
|
|
716
|
-
});
|
|
717
|
-
}
|
|
718
684
|
};
|
|
719
685
|
var execute = async () => {
|
|
720
686
|
console.log("[EXULU] Initializing database.");
|
|
@@ -965,6 +931,35 @@ var ExuluEvalUtils = {
|
|
|
965
931
|
};
|
|
966
932
|
|
|
967
933
|
// src/registry/classes.ts
|
|
934
|
+
function sanitizeToolName(name) {
|
|
935
|
+
if (typeof name !== "string") return "";
|
|
936
|
+
let sanitized = name.replace(/[^a-zA-Z0-9_-]+/g, "_");
|
|
937
|
+
sanitized = sanitized.replace(/^_+|_+$/g, "");
|
|
938
|
+
if (sanitized.length > 128) {
|
|
939
|
+
sanitized = sanitized.substring(0, 128);
|
|
940
|
+
}
|
|
941
|
+
return sanitized;
|
|
942
|
+
}
|
|
943
|
+
var convertToolsArrayToObject = (tools) => {
|
|
944
|
+
if (!tools) return {};
|
|
945
|
+
const sanitizedTools = tools ? tools.map((tool2) => ({
|
|
946
|
+
...tool2,
|
|
947
|
+
name: sanitizeToolName(tool2.name)
|
|
948
|
+
})) : [];
|
|
949
|
+
const askForConfirmation = {
|
|
950
|
+
description: "Ask the user for confirmation.",
|
|
951
|
+
parameters: z.object({
|
|
952
|
+
message: z.string().describe("The message to ask for confirmation.")
|
|
953
|
+
})
|
|
954
|
+
};
|
|
955
|
+
return {
|
|
956
|
+
...sanitizedTools?.reduce(
|
|
957
|
+
(prev, cur) => ({ ...prev, [cur.name]: cur.tool }),
|
|
958
|
+
{}
|
|
959
|
+
),
|
|
960
|
+
askForConfirmation
|
|
961
|
+
};
|
|
962
|
+
};
|
|
968
963
|
function generateSlug(name) {
|
|
969
964
|
const normalized = name.normalize("NFKD").replace(/[\u0300-\u036f]/g, "");
|
|
970
965
|
const lowercase = normalized.toLowerCase();
|
|
@@ -991,54 +986,119 @@ var ExuluAgent = class {
|
|
|
991
986
|
name;
|
|
992
987
|
description = "";
|
|
993
988
|
slug = "";
|
|
989
|
+
type;
|
|
994
990
|
streaming = false;
|
|
995
991
|
rateLimit;
|
|
996
992
|
config;
|
|
997
993
|
// private memory: Memory | undefined; // TODO do own implementation
|
|
998
|
-
tools;
|
|
999
994
|
evals;
|
|
1000
995
|
model;
|
|
1001
996
|
capabilities;
|
|
1002
|
-
constructor({ id, name, description, config, rateLimit, capabilities,
|
|
997
|
+
constructor({ id, name, description, config, rateLimit, capabilities, type, evals }) {
|
|
1003
998
|
this.id = id;
|
|
1004
999
|
this.name = name;
|
|
1005
1000
|
this.evals = evals;
|
|
1006
1001
|
this.description = description;
|
|
1007
1002
|
this.rateLimit = rateLimit;
|
|
1008
|
-
this.tools = tools;
|
|
1009
1003
|
this.config = config;
|
|
1010
|
-
this.
|
|
1004
|
+
this.type = type;
|
|
1005
|
+
this.capabilities = capabilities || {
|
|
1006
|
+
images: [],
|
|
1007
|
+
files: [],
|
|
1008
|
+
audio: [],
|
|
1009
|
+
video: []
|
|
1010
|
+
};
|
|
1011
1011
|
this.slug = `/agents/${generateSlug(this.name)}/run`;
|
|
1012
|
-
this.model = this.config
|
|
1012
|
+
this.model = this.config?.model;
|
|
1013
1013
|
}
|
|
1014
|
-
|
|
1014
|
+
// Exports the agent as a tool that can be used by another agent
|
|
1015
|
+
// todo test this
|
|
1016
|
+
tool = () => {
|
|
1017
|
+
return new ExuluTool({
|
|
1018
|
+
id: this.id,
|
|
1019
|
+
name: `${this.name} agent`,
|
|
1020
|
+
type: "agent",
|
|
1021
|
+
inputSchema: z.object({
|
|
1022
|
+
prompt: z.string()
|
|
1023
|
+
}),
|
|
1024
|
+
description: `A function that calls an AI agent named: ${this.name}. The agent does the following: ${this.description}.`,
|
|
1025
|
+
execute: async ({ prompt }) => {
|
|
1026
|
+
return await this.generateSync({
|
|
1027
|
+
prompt,
|
|
1028
|
+
statistics: {
|
|
1029
|
+
label: "",
|
|
1030
|
+
trigger: "tool"
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
};
|
|
1036
|
+
generateSync = async ({ messages, prompt, tools, statistics }) => {
|
|
1015
1037
|
if (!this.model) {
|
|
1016
1038
|
throw new Error("Model is required for streaming.");
|
|
1017
1039
|
}
|
|
1018
|
-
if (this.config
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1021
|
-
const { object } = await generateObject({
|
|
1022
|
-
model: this.model,
|
|
1023
|
-
schema: this.config.outputSchema,
|
|
1024
|
-
prompt
|
|
1025
|
-
});
|
|
1026
|
-
return object;
|
|
1040
|
+
if (!this.config) {
|
|
1041
|
+
throw new Error("Config is required for generating.");
|
|
1027
1042
|
}
|
|
1028
|
-
if (
|
|
1029
|
-
|
|
1030
|
-
model: this.model,
|
|
1031
|
-
prompt
|
|
1032
|
-
});
|
|
1033
|
-
const text2 = await result.text;
|
|
1034
|
-
return text2;
|
|
1043
|
+
if (prompt && messages) {
|
|
1044
|
+
throw new Error("Prompt and messages cannot be provided at the same time.");
|
|
1035
1045
|
}
|
|
1036
1046
|
const { text } = await generateText({
|
|
1037
1047
|
model: this.model,
|
|
1038
|
-
|
|
1048
|
+
system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
|
|
1049
|
+
messages,
|
|
1050
|
+
prompt,
|
|
1051
|
+
maxRetries: 2,
|
|
1052
|
+
tools: convertToolsArrayToObject(tools),
|
|
1053
|
+
maxSteps: 5
|
|
1039
1054
|
});
|
|
1055
|
+
if (statistics) {
|
|
1056
|
+
await updateStatistic({
|
|
1057
|
+
name: "count",
|
|
1058
|
+
label: statistics.label,
|
|
1059
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
1060
|
+
trigger: statistics.trigger,
|
|
1061
|
+
count: 1
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1040
1064
|
return text;
|
|
1041
1065
|
};
|
|
1066
|
+
generateStream = ({ messages, prompt, tools, statistics }) => {
|
|
1067
|
+
if (!this.model) {
|
|
1068
|
+
throw new Error("Model is required for streaming.");
|
|
1069
|
+
}
|
|
1070
|
+
if (!this.config) {
|
|
1071
|
+
throw new Error("Config is required for generating.");
|
|
1072
|
+
}
|
|
1073
|
+
if (prompt && messages) {
|
|
1074
|
+
throw new Error("Prompt and messages cannot be provided at the same time.");
|
|
1075
|
+
}
|
|
1076
|
+
return streamText({
|
|
1077
|
+
model: this.model,
|
|
1078
|
+
messages,
|
|
1079
|
+
prompt,
|
|
1080
|
+
system: "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.",
|
|
1081
|
+
maxRetries: 2,
|
|
1082
|
+
tools: convertToolsArrayToObject(tools),
|
|
1083
|
+
maxSteps: 5,
|
|
1084
|
+
onError: (error) => console.error("[EXULU] chat stream error.", error),
|
|
1085
|
+
onFinish: async ({ response, usage }) => {
|
|
1086
|
+
console.info(
|
|
1087
|
+
"[EXULU] chat stream finished.",
|
|
1088
|
+
usage
|
|
1089
|
+
);
|
|
1090
|
+
if (statistics) {
|
|
1091
|
+
await updateStatistic({
|
|
1092
|
+
name: "count",
|
|
1093
|
+
label: statistics.label,
|
|
1094
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
1095
|
+
trigger: statistics.trigger,
|
|
1096
|
+
count: 1
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
};
|
|
1042
1102
|
};
|
|
1043
1103
|
var ExuluEmbedder = class {
|
|
1044
1104
|
id;
|
|
@@ -1061,6 +1121,13 @@ var ExuluEmbedder = class {
|
|
|
1061
1121
|
}
|
|
1062
1122
|
async generateFromQuery(query, statistics) {
|
|
1063
1123
|
if (statistics) {
|
|
1124
|
+
await updateStatistic({
|
|
1125
|
+
name: "count",
|
|
1126
|
+
label: statistics.label,
|
|
1127
|
+
type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
|
|
1128
|
+
trigger: statistics.trigger,
|
|
1129
|
+
count: 1
|
|
1130
|
+
});
|
|
1064
1131
|
}
|
|
1065
1132
|
return await this.generateEmbeddings({
|
|
1066
1133
|
item: {
|
|
@@ -1074,16 +1141,22 @@ var ExuluEmbedder = class {
|
|
|
1074
1141
|
}
|
|
1075
1142
|
async generateFromDocument(input, statistics) {
|
|
1076
1143
|
if (statistics) {
|
|
1144
|
+
await updateStatistic({
|
|
1145
|
+
name: "count",
|
|
1146
|
+
label: statistics.label,
|
|
1147
|
+
type: STATISTICS_TYPE_ENUM.EMBEDDER_GENERATE,
|
|
1148
|
+
trigger: statistics.trigger,
|
|
1149
|
+
count: 1
|
|
1150
|
+
});
|
|
1077
1151
|
}
|
|
1078
1152
|
if (!this.chunker) {
|
|
1079
1153
|
throw new Error("Chunker not found for embedder " + this.name);
|
|
1080
1154
|
}
|
|
1081
|
-
console.log("generating chunks");
|
|
1082
1155
|
if (!input.id) {
|
|
1083
1156
|
throw new Error("Item id is required for generating embeddings.");
|
|
1084
1157
|
}
|
|
1085
1158
|
const output = await this.chunker(input, this.maxChunkSize);
|
|
1086
|
-
console.log("
|
|
1159
|
+
console.log("[EXULU] Generating embeddings.");
|
|
1087
1160
|
return await this.generateEmbeddings(output);
|
|
1088
1161
|
}
|
|
1089
1162
|
};
|
|
@@ -1263,9 +1336,8 @@ var ExuluEval = class {
|
|
|
1263
1336
|
if (!data.prompt) {
|
|
1264
1337
|
throw new Error("Prompt is required for running an agent.");
|
|
1265
1338
|
}
|
|
1266
|
-
const result = await runner.agent.
|
|
1267
|
-
prompt: data.prompt
|
|
1268
|
-
stream: false
|
|
1339
|
+
const result = await runner.agent.generateSync({
|
|
1340
|
+
prompt: data.prompt
|
|
1269
1341
|
});
|
|
1270
1342
|
data.result = result;
|
|
1271
1343
|
}
|
|
@@ -1360,18 +1432,18 @@ var ExuluTool = class {
|
|
|
1360
1432
|
id;
|
|
1361
1433
|
name;
|
|
1362
1434
|
description;
|
|
1363
|
-
|
|
1435
|
+
inputSchema;
|
|
1364
1436
|
type;
|
|
1365
1437
|
tool;
|
|
1366
|
-
constructor({ id, name, description,
|
|
1438
|
+
constructor({ id, name, description, inputSchema, type, execute: execute2 }) {
|
|
1367
1439
|
this.id = id;
|
|
1368
1440
|
this.name = name;
|
|
1369
1441
|
this.description = description;
|
|
1370
|
-
this.
|
|
1442
|
+
this.inputSchema = inputSchema;
|
|
1371
1443
|
this.type = type;
|
|
1372
1444
|
this.tool = tool({
|
|
1373
1445
|
description,
|
|
1374
|
-
parameters,
|
|
1446
|
+
parameters: inputSchema || z.object({}),
|
|
1375
1447
|
execute: execute2
|
|
1376
1448
|
});
|
|
1377
1449
|
}
|
|
@@ -1427,10 +1499,9 @@ var ExuluContext = class {
|
|
|
1427
1499
|
}
|
|
1428
1500
|
const { db: db2 } = await postgresClient();
|
|
1429
1501
|
Object.keys(item).forEach((key) => {
|
|
1430
|
-
if (key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textLength" || key === "upsert") {
|
|
1502
|
+
if (key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textLength" || key === "upsert" || key === "archived") {
|
|
1431
1503
|
return;
|
|
1432
1504
|
}
|
|
1433
|
-
console.log("this.fields", this.fields);
|
|
1434
1505
|
const field = this.fields.find((field2) => field2.name === key);
|
|
1435
1506
|
if (!field) {
|
|
1436
1507
|
throw new Error("Trying to uppdate value for field '" + key + "' that does not exist on the context fields definition. Available fields: " + this.fields.map((field2) => sanitizeName(field2.name)).join(", ") + " ,name, description, external_id");
|
|
@@ -1475,6 +1546,9 @@ var ExuluContext = class {
|
|
|
1475
1546
|
chunk_index: chunk.index,
|
|
1476
1547
|
embedding: pgvector2.toSql(chunk.vector)
|
|
1477
1548
|
})));
|
|
1549
|
+
await db2.from(this.getTableName()).where({ id }).update({
|
|
1550
|
+
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1551
|
+
}).returning("id");
|
|
1478
1552
|
}
|
|
1479
1553
|
return {
|
|
1480
1554
|
id: result[0].id,
|
|
@@ -1504,10 +1578,9 @@ var ExuluContext = class {
|
|
|
1504
1578
|
}
|
|
1505
1579
|
}
|
|
1506
1580
|
Object.keys(item).forEach((key) => {
|
|
1507
|
-
if (key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textLength" || key === "upsert") {
|
|
1581
|
+
if (key === "name" || key === "description" || key === "external_id" || key === "tags" || key === "source" || key === "textLength" || key === "upsert" || key === "archived") {
|
|
1508
1582
|
return;
|
|
1509
1583
|
}
|
|
1510
|
-
console.log("this.fields", this.fields);
|
|
1511
1584
|
const field = this.fields.find((field2) => field2.name === key);
|
|
1512
1585
|
if (!field) {
|
|
1513
1586
|
throw new Error("Trying to insert value for field '" + key + "' that does not exist on the context fields definition. Available fields: " + this.fields.map((field2) => sanitizeName(field2.name)).join(", ") + " ,name, description, external_id");
|
|
@@ -1549,13 +1622,16 @@ var ExuluContext = class {
|
|
|
1549
1622
|
if (!exists) {
|
|
1550
1623
|
await this.createChunksTable();
|
|
1551
1624
|
}
|
|
1552
|
-
console.log("
|
|
1625
|
+
console.log("[EXULU] Inserting chunks.");
|
|
1553
1626
|
await db2.from(this.getChunksTableName()).insert(chunks.map((chunk) => ({
|
|
1554
1627
|
source,
|
|
1555
1628
|
content: chunk.content,
|
|
1556
1629
|
chunk_index: chunk.index,
|
|
1557
1630
|
embedding: pgvector2.toSql(chunk.vector)
|
|
1558
1631
|
})));
|
|
1632
|
+
await db2.from(this.getTableName()).where({ id: result[0].id }).update({
|
|
1633
|
+
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1634
|
+
}).returning("id");
|
|
1559
1635
|
}
|
|
1560
1636
|
return {
|
|
1561
1637
|
id: result[0].id,
|
|
@@ -1565,6 +1641,8 @@ var ExuluContext = class {
|
|
|
1565
1641
|
getItems = async ({
|
|
1566
1642
|
statistics,
|
|
1567
1643
|
limit,
|
|
1644
|
+
sort,
|
|
1645
|
+
order,
|
|
1568
1646
|
page,
|
|
1569
1647
|
name,
|
|
1570
1648
|
archived,
|
|
@@ -1585,6 +1663,9 @@ var ExuluContext = class {
|
|
|
1585
1663
|
const columns = await db2(mainTable).columnInfo();
|
|
1586
1664
|
const totalQuery = db2.count("* as count").from(mainTable).first();
|
|
1587
1665
|
const itemsQuery = db2.select(Object.keys(columns).map((column) => mainTable + "." + column)).from(mainTable).offset(offset).limit(limit);
|
|
1666
|
+
if (sort) {
|
|
1667
|
+
itemsQuery.orderBy(sort, order === "desc" ? "desc" : "asc");
|
|
1668
|
+
}
|
|
1588
1669
|
if (typeof name === "string") {
|
|
1589
1670
|
itemsQuery.whereILike("name", `%${name}%`);
|
|
1590
1671
|
totalQuery.whereILike("name", `%${name}%`);
|
|
@@ -1628,7 +1709,7 @@ var ExuluContext = class {
|
|
|
1628
1709
|
}
|
|
1629
1710
|
itemsQuery.limit(limit * 5);
|
|
1630
1711
|
if (statistics) {
|
|
1631
|
-
updateStatistic({
|
|
1712
|
+
await updateStatistic({
|
|
1632
1713
|
name: "count",
|
|
1633
1714
|
label: statistics.label,
|
|
1634
1715
|
type: STATISTICS_TYPE_ENUM.CONTEXT_RETRIEVE,
|
|
@@ -1761,6 +1842,7 @@ var ExuluContext = class {
|
|
|
1761
1842
|
table.text("external_id");
|
|
1762
1843
|
table.integer("textLength");
|
|
1763
1844
|
table.text("source");
|
|
1845
|
+
table.timestamp("embeddings_updated_at");
|
|
1764
1846
|
for (const field of this.fields) {
|
|
1765
1847
|
const { type, name } = field;
|
|
1766
1848
|
if (!type || !name) {
|
|
@@ -1790,15 +1872,15 @@ var ExuluContext = class {
|
|
|
1790
1872
|
id: this.id,
|
|
1791
1873
|
name: `${this.name} context`,
|
|
1792
1874
|
type: "context",
|
|
1793
|
-
|
|
1875
|
+
inputSchema: z.object({
|
|
1794
1876
|
query: z.string()
|
|
1795
1877
|
}),
|
|
1796
1878
|
description: `Gets information from the context called: ${this.name}. The context description is: ${this.description}.`,
|
|
1797
|
-
execute: async ({
|
|
1879
|
+
execute: async ({ query }) => {
|
|
1798
1880
|
return await this.getItems({
|
|
1799
1881
|
page: 1,
|
|
1800
1882
|
limit: 10,
|
|
1801
|
-
query
|
|
1883
|
+
query,
|
|
1802
1884
|
statistics: {
|
|
1803
1885
|
label: this.name,
|
|
1804
1886
|
trigger: "agent"
|
|
@@ -1847,7 +1929,32 @@ var ExuluSource = class {
|
|
|
1847
1929
|
}
|
|
1848
1930
|
};
|
|
1849
1931
|
var updateStatistic = async (statistic) => {
|
|
1850
|
-
|
|
1932
|
+
const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1933
|
+
const { db: db2 } = await postgresClient();
|
|
1934
|
+
const existing = await db2.from("statistics").where({
|
|
1935
|
+
name: statistic.name,
|
|
1936
|
+
label: statistic.label,
|
|
1937
|
+
type: statistic.type,
|
|
1938
|
+
createdAt: currentDate
|
|
1939
|
+
}).first();
|
|
1940
|
+
if (!existing) {
|
|
1941
|
+
await db2.from("statistics").insert({
|
|
1942
|
+
name: statistic.name,
|
|
1943
|
+
label: statistic.label,
|
|
1944
|
+
type: statistic.type,
|
|
1945
|
+
total: statistic.count ?? 1,
|
|
1946
|
+
createdAt: currentDate
|
|
1947
|
+
});
|
|
1948
|
+
} else {
|
|
1949
|
+
await db2.from("statistics").update({
|
|
1950
|
+
total: db2.raw("total + ?", [statistic.count ?? 1])
|
|
1951
|
+
}).where({
|
|
1952
|
+
name: statistic.name,
|
|
1953
|
+
label: statistic.label,
|
|
1954
|
+
type: statistic.type,
|
|
1955
|
+
createdAt: currentDate
|
|
1956
|
+
});
|
|
1957
|
+
}
|
|
1851
1958
|
};
|
|
1852
1959
|
|
|
1853
1960
|
// src/registry/index.ts
|
|
@@ -1920,7 +2027,6 @@ var authentication = async ({
|
|
|
1920
2027
|
internalkey,
|
|
1921
2028
|
db: db2
|
|
1922
2029
|
}) => {
|
|
1923
|
-
console.log("[EXULU] apikey", apikey);
|
|
1924
2030
|
if (internalkey) {
|
|
1925
2031
|
if (!process.env.INTERNAL_SECRET) {
|
|
1926
2032
|
return {
|
|
@@ -1948,7 +2054,7 @@ var authentication = async ({
|
|
|
1948
2054
|
}
|
|
1949
2055
|
if (authtoken) {
|
|
1950
2056
|
try {
|
|
1951
|
-
console.log("authtoken", authtoken);
|
|
2057
|
+
console.log("[EXULU] authtoken", authtoken);
|
|
1952
2058
|
if (!authtoken?.email) {
|
|
1953
2059
|
return {
|
|
1954
2060
|
error: true,
|
|
@@ -1957,7 +2063,6 @@ var authentication = async ({
|
|
|
1957
2063
|
};
|
|
1958
2064
|
}
|
|
1959
2065
|
const user = await db2.from("users").select("*").where("email", authtoken?.email).first();
|
|
1960
|
-
console.log("user", user);
|
|
1961
2066
|
if (!user) {
|
|
1962
2067
|
return {
|
|
1963
2068
|
error: true,
|
|
@@ -2006,15 +2111,11 @@ var authentication = async ({
|
|
|
2006
2111
|
code: 401
|
|
2007
2112
|
};
|
|
2008
2113
|
}
|
|
2009
|
-
console.log("[EXULU] request_key_name", request_key_name);
|
|
2010
|
-
console.log("[EXULU] request_key_compare_value", request_key_compare_value);
|
|
2011
2114
|
const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(request_key_name));
|
|
2012
2115
|
for (const user of filtered) {
|
|
2013
2116
|
const user_key_last_slash_index = user.apikey.lastIndexOf("/");
|
|
2014
2117
|
const user_key_compare_value = user.apikey.substring(0, user_key_last_slash_index);
|
|
2015
|
-
console.log("[EXULU] user_key_compare_value", user_key_compare_value);
|
|
2016
2118
|
const isMatch = await bcrypt2.compare(request_key_compare_value, user_key_compare_value);
|
|
2017
|
-
console.log("[EXULU] isMatch", isMatch);
|
|
2018
2119
|
if (isMatch) {
|
|
2019
2120
|
await db2.from("users").where({ id: user.id }).update({
|
|
2020
2121
|
last_used: /* @__PURE__ */ new Date()
|
|
@@ -2047,9 +2148,7 @@ var requestValidators = {
|
|
|
2047
2148
|
const { db: db2 } = await postgresClient();
|
|
2048
2149
|
let authtoken = null;
|
|
2049
2150
|
if (typeof apikey !== "string") {
|
|
2050
|
-
|
|
2051
|
-
authtoken = await getToken(req.headers["authorization"] ?? "");
|
|
2052
|
-
console.log("[EXULU] authtoken", authtoken);
|
|
2151
|
+
authtoken = await getToken((req.headers["authorization"] || req.headers["x-api-key"]) ?? "");
|
|
2053
2152
|
}
|
|
2054
2153
|
return await authentication({
|
|
2055
2154
|
authtoken,
|
|
@@ -2151,7 +2250,6 @@ var requestValidators = {
|
|
|
2151
2250
|
};
|
|
2152
2251
|
},
|
|
2153
2252
|
agents: (req) => {
|
|
2154
|
-
console.log("[EXULU] validating request body and headers.", req.body);
|
|
2155
2253
|
const contentType = req.headers["content-type"] || "";
|
|
2156
2254
|
if (!contentType.includes("application/json")) {
|
|
2157
2255
|
return {
|
|
@@ -2243,6 +2341,42 @@ import "reflect-metadata";
|
|
|
2243
2341
|
// src/registry/utils/graphql.ts
|
|
2244
2342
|
import { makeExecutableSchema } from "@graphql-tools/schema";
|
|
2245
2343
|
import GraphQLJSON from "graphql-type-json";
|
|
2344
|
+
import { GraphQLScalarType, Kind } from "graphql";
|
|
2345
|
+
import CryptoJS from "crypto-js";
|
|
2346
|
+
var GraphQLDate = new GraphQLScalarType({
|
|
2347
|
+
name: "Date",
|
|
2348
|
+
description: "Date custom scalar type",
|
|
2349
|
+
serialize(value) {
|
|
2350
|
+
if (value instanceof Date) {
|
|
2351
|
+
return value.toISOString();
|
|
2352
|
+
}
|
|
2353
|
+
if (typeof value === "number") {
|
|
2354
|
+
return new Date(value).toISOString();
|
|
2355
|
+
}
|
|
2356
|
+
if (typeof value === "string") {
|
|
2357
|
+
return new Date(value).toISOString();
|
|
2358
|
+
}
|
|
2359
|
+
return value;
|
|
2360
|
+
},
|
|
2361
|
+
parseValue(value) {
|
|
2362
|
+
if (typeof value === "string") {
|
|
2363
|
+
return new Date(value);
|
|
2364
|
+
}
|
|
2365
|
+
if (typeof value === "number") {
|
|
2366
|
+
return new Date(value);
|
|
2367
|
+
}
|
|
2368
|
+
return value;
|
|
2369
|
+
},
|
|
2370
|
+
parseLiteral(ast) {
|
|
2371
|
+
if (ast.kind === Kind.STRING) {
|
|
2372
|
+
return new Date(ast.value);
|
|
2373
|
+
}
|
|
2374
|
+
if (ast.kind === Kind.INT) {
|
|
2375
|
+
return new Date(parseInt(ast.value, 10));
|
|
2376
|
+
}
|
|
2377
|
+
return null;
|
|
2378
|
+
}
|
|
2379
|
+
});
|
|
2246
2380
|
var map = (field) => {
|
|
2247
2381
|
let type;
|
|
2248
2382
|
switch (field.type) {
|
|
@@ -2262,7 +2396,7 @@ var map = (field) => {
|
|
|
2262
2396
|
type = "JSON";
|
|
2263
2397
|
break;
|
|
2264
2398
|
case "date":
|
|
2265
|
-
type = "
|
|
2399
|
+
type = "Date";
|
|
2266
2400
|
break;
|
|
2267
2401
|
default:
|
|
2268
2402
|
type = "String";
|
|
@@ -2280,8 +2414,8 @@ function createTypeDefs(table) {
|
|
|
2280
2414
|
type ${table.name.singular} {
|
|
2281
2415
|
${fields.join("\n")}
|
|
2282
2416
|
id: ID!
|
|
2283
|
-
createdAt:
|
|
2284
|
-
updatedAt:
|
|
2417
|
+
createdAt: Date!
|
|
2418
|
+
updatedAt: Date!
|
|
2285
2419
|
}
|
|
2286
2420
|
`;
|
|
2287
2421
|
const inputDef = `
|
|
@@ -2307,6 +2441,11 @@ input FilterOperatorString {
|
|
|
2307
2441
|
contains: String
|
|
2308
2442
|
}
|
|
2309
2443
|
|
|
2444
|
+
input FilterOperatorDate {
|
|
2445
|
+
lte: Date
|
|
2446
|
+
gte: Date
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2310
2449
|
input FilterOperatorFloat {
|
|
2311
2450
|
eq: Float
|
|
2312
2451
|
ne: Float
|
|
@@ -2359,17 +2498,19 @@ function createMutations(table) {
|
|
|
2359
2498
|
[`${tableNamePlural}CreateOne`]: async (_, args, context, info) => {
|
|
2360
2499
|
const { db: db2 } = context;
|
|
2361
2500
|
const requestedFields = getRequestedFields(info);
|
|
2501
|
+
let { input } = args;
|
|
2502
|
+
input = encryptSensitiveFields(input);
|
|
2362
2503
|
const results = await db2(tableNamePlural).insert({
|
|
2363
|
-
...
|
|
2504
|
+
...input,
|
|
2364
2505
|
createdAt: /* @__PURE__ */ new Date(),
|
|
2365
2506
|
updatedAt: /* @__PURE__ */ new Date()
|
|
2366
2507
|
}).returning(requestedFields);
|
|
2367
|
-
console.log("requestedFields", requestedFields);
|
|
2368
2508
|
return results[0];
|
|
2369
2509
|
},
|
|
2370
2510
|
[`${tableNamePlural}UpdateOne`]: async (_, args, context, info) => {
|
|
2371
2511
|
const { db: db2 } = context;
|
|
2372
|
-
|
|
2512
|
+
let { where, input } = args;
|
|
2513
|
+
input = encryptSensitiveFields(input);
|
|
2373
2514
|
await db2(tableNamePlural).where(where).update({
|
|
2374
2515
|
...input,
|
|
2375
2516
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -2379,7 +2520,8 @@ function createMutations(table) {
|
|
|
2379
2520
|
return result;
|
|
2380
2521
|
},
|
|
2381
2522
|
[`${tableNamePlural}UpdateOneById`]: async (_, args, context, info) => {
|
|
2382
|
-
|
|
2523
|
+
let { id, input } = args;
|
|
2524
|
+
input = encryptSensitiveFields(input);
|
|
2383
2525
|
const { db: db2 } = context;
|
|
2384
2526
|
await db2(tableNamePlural).where({ id }).update({
|
|
2385
2527
|
...input,
|
|
@@ -2457,7 +2599,6 @@ function createQueries(table) {
|
|
|
2457
2599
|
[`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
|
|
2458
2600
|
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
2459
2601
|
const { db: db2 } = context;
|
|
2460
|
-
console.log("page", page);
|
|
2461
2602
|
let baseQuery = db2(tableNamePlural);
|
|
2462
2603
|
baseQuery = applyFilters(baseQuery, filters);
|
|
2463
2604
|
const [{ count }] = await baseQuery.clone().count("* as count");
|
|
@@ -2473,8 +2614,6 @@ function createQueries(table) {
|
|
|
2473
2614
|
dataQuery = dataQuery.offset((page - 1) * limit);
|
|
2474
2615
|
}
|
|
2475
2616
|
const items = await dataQuery.select(requestedFields).limit(limit);
|
|
2476
|
-
console.log("items", items);
|
|
2477
|
-
console.log("query", dataQuery.toQuery());
|
|
2478
2617
|
return {
|
|
2479
2618
|
pageInfo: {
|
|
2480
2619
|
pageCount,
|
|
@@ -2485,12 +2624,44 @@ function createQueries(table) {
|
|
|
2485
2624
|
},
|
|
2486
2625
|
items
|
|
2487
2626
|
};
|
|
2488
|
-
}
|
|
2627
|
+
},
|
|
2628
|
+
// Add jobStatistics query for jobs table
|
|
2629
|
+
...tableNamePlural === "jobs" ? {
|
|
2630
|
+
jobStatistics: async (_, args, context, info) => {
|
|
2631
|
+
const { user, agent, from, to } = args;
|
|
2632
|
+
const { db: db2 } = context;
|
|
2633
|
+
let query = db2("jobs");
|
|
2634
|
+
if (user) {
|
|
2635
|
+
query = query.where("user", user);
|
|
2636
|
+
}
|
|
2637
|
+
if (agent) {
|
|
2638
|
+
query = query.where("agent", agent);
|
|
2639
|
+
}
|
|
2640
|
+
if (from) {
|
|
2641
|
+
query = query.where("createdAt", ">=", from);
|
|
2642
|
+
}
|
|
2643
|
+
if (to) {
|
|
2644
|
+
query = query.where("createdAt", "<=", to);
|
|
2645
|
+
}
|
|
2646
|
+
const completedQuery = query.clone().where("status", "completed");
|
|
2647
|
+
const [{ completedCount }] = await completedQuery.count("* as completedCount");
|
|
2648
|
+
const failedQuery = query.clone().where("status", "failed");
|
|
2649
|
+
const [{ failedCount }] = await failedQuery.count("* as failedCount");
|
|
2650
|
+
const durationQuery = query.clone().where("status", "completed").whereNotNull("duration").select(db2.raw('AVG("duration") as averageDuration'));
|
|
2651
|
+
const [{ averageDuration }] = await durationQuery;
|
|
2652
|
+
return {
|
|
2653
|
+
completedCount: Number(completedCount),
|
|
2654
|
+
failedCount: Number(failedCount),
|
|
2655
|
+
averageDuration: averageDuration ? Number(averageDuration) : 0
|
|
2656
|
+
};
|
|
2657
|
+
}
|
|
2658
|
+
} : {}
|
|
2489
2659
|
};
|
|
2490
2660
|
}
|
|
2491
2661
|
function createSDL(tables) {
|
|
2492
2662
|
let typeDefs = `
|
|
2493
2663
|
scalar JSON
|
|
2664
|
+
scalar Date
|
|
2494
2665
|
|
|
2495
2666
|
type Query {
|
|
2496
2667
|
`;
|
|
@@ -2498,7 +2669,7 @@ function createSDL(tables) {
|
|
|
2498
2669
|
type Mutation {
|
|
2499
2670
|
`;
|
|
2500
2671
|
let modelDefs = "";
|
|
2501
|
-
const resolvers = { JSON: GraphQLJSON, Query: {}, Mutation: {} };
|
|
2672
|
+
const resolvers = { JSON: GraphQLJSON, Date: GraphQLDate, Query: {}, Mutation: {} };
|
|
2502
2673
|
for (const table of tables) {
|
|
2503
2674
|
const tableNamePlural = table.name.plural.toLowerCase();
|
|
2504
2675
|
const tableNameSingular = table.name.singular.toLowerCase();
|
|
@@ -2507,6 +2678,7 @@ function createSDL(tables) {
|
|
|
2507
2678
|
${tableNameSingular}ById(id: ID!): ${tableNameSingular}
|
|
2508
2679
|
${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
|
|
2509
2680
|
${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
|
|
2681
|
+
${tableNamePlural === "jobs" ? `jobStatistics(user: ID, agent: String, from: String, to: String): JobStatistics` : ""}
|
|
2510
2682
|
`;
|
|
2511
2683
|
mutationDefs += `
|
|
2512
2684
|
${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!): ${tableNameSingular}
|
|
@@ -2531,6 +2703,15 @@ type PageInfo {
|
|
|
2531
2703
|
hasNextPage: Boolean!
|
|
2532
2704
|
}
|
|
2533
2705
|
`;
|
|
2706
|
+
if (tableNamePlural === "jobs") {
|
|
2707
|
+
modelDefs += `
|
|
2708
|
+
type JobStatistics {
|
|
2709
|
+
completedCount: Int!
|
|
2710
|
+
failedCount: Int!
|
|
2711
|
+
averageDuration: Float!
|
|
2712
|
+
}
|
|
2713
|
+
`;
|
|
2714
|
+
}
|
|
2534
2715
|
Object.assign(resolvers.Query, createQueries(table));
|
|
2535
2716
|
Object.assign(resolvers.Mutation, createMutations(table));
|
|
2536
2717
|
}
|
|
@@ -2567,6 +2748,15 @@ type PageInfo {
|
|
|
2567
2748
|
console.log("\n");
|
|
2568
2749
|
return schema;
|
|
2569
2750
|
}
|
|
2751
|
+
var sensitiveFields = ["anthropic_token"];
|
|
2752
|
+
var encryptSensitiveFields = (input) => {
|
|
2753
|
+
sensitiveFields.forEach((field) => {
|
|
2754
|
+
if (input[field]) {
|
|
2755
|
+
input[field] = CryptoJS.AES.encrypt(input[field], process.env.NEXTAUTH_SECRET).toString();
|
|
2756
|
+
}
|
|
2757
|
+
});
|
|
2758
|
+
return input;
|
|
2759
|
+
};
|
|
2570
2760
|
|
|
2571
2761
|
// src/registry/routes.ts
|
|
2572
2762
|
import { expressMiddleware } from "@as-integrations/express5";
|
|
@@ -2965,6 +3155,34 @@ var createUppyRoutes = async (app) => {
|
|
|
2965
3155
|
// src/registry/routes.ts
|
|
2966
3156
|
import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
|
|
2967
3157
|
import bodyParser from "body-parser";
|
|
3158
|
+
import CryptoJS2 from "crypto-js";
|
|
3159
|
+
|
|
3160
|
+
// src/registry/utils/claude-messages.ts
|
|
3161
|
+
var CLAUDE_MESSAGES = {
|
|
3162
|
+
authentication_error: `
|
|
3163
|
+
\x1B[41m -- Authentication error please check your IMP token and try again. --
|
|
3164
|
+
\x1B[0m`,
|
|
3165
|
+
missing_body: `
|
|
3166
|
+
\x1B[41m -- Missing body Anthropic response. --
|
|
3167
|
+
\x1B[0m`,
|
|
3168
|
+
missing_nextauth_secret: `
|
|
3169
|
+
\x1B[41m -- Missing NEXTAUTH_SECRET in environment variables on the server. --
|
|
3170
|
+
\x1B[0m`,
|
|
3171
|
+
not_enabled: `
|
|
3172
|
+
\x1B[31m
|
|
3173
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
|
|
3174
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
3175
|
+
\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
3176
|
+
\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
3177
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
3178
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
3179
|
+
Intelligence Management Platform
|
|
3180
|
+
\x1B[0m
|
|
3181
|
+
\x1B[41m -- Your account has not been enabled to use Claude Code, please contact your admin or enable Claude Code in the user settings. --
|
|
3182
|
+
\x1B[0m`
|
|
3183
|
+
};
|
|
3184
|
+
|
|
3185
|
+
// src/registry/routes.ts
|
|
2968
3186
|
var REQUEST_SIZE_LIMIT = "50mb";
|
|
2969
3187
|
var global_queues = {
|
|
2970
3188
|
logs_cleaner: "logs-cleaner"
|
|
@@ -3001,7 +3219,7 @@ var createRecurringJobs = async () => {
|
|
|
3001
3219
|
console.table(recurringJobSchedulersLogs);
|
|
3002
3220
|
return queue;
|
|
3003
3221
|
};
|
|
3004
|
-
var createExpressRoutes = async (app, agents,
|
|
3222
|
+
var createExpressRoutes = async (app, agents, tools, workflows, contexts) => {
|
|
3005
3223
|
const routeLogs = [];
|
|
3006
3224
|
var corsOptions = {
|
|
3007
3225
|
origin: "*",
|
|
@@ -3021,6 +3239,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3021
3239
|
\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
|
|
3022
3240
|
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2554\u255D \u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
3023
3241
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
3242
|
+
Intelligence Management Platform
|
|
3024
3243
|
|
|
3025
3244
|
`);
|
|
3026
3245
|
console.log("Agents:");
|
|
@@ -3069,7 +3288,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3069
3288
|
} else {
|
|
3070
3289
|
console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
|
|
3071
3290
|
}
|
|
3072
|
-
const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema, workflowSchema, evalResultsSchema,
|
|
3291
|
+
const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema, workflowSchema, evalResultsSchema, agentSessionsSchema, agentMessagesSchema]);
|
|
3073
3292
|
console.log("[EXULU] graphql server");
|
|
3074
3293
|
const server = new ApolloServer({
|
|
3075
3294
|
cache: new InMemoryLRUCache(),
|
|
@@ -3097,10 +3316,52 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3097
3316
|
}
|
|
3098
3317
|
})
|
|
3099
3318
|
);
|
|
3100
|
-
app.get(`/
|
|
3319
|
+
app.get(`/providers`, async (req, res) => {
|
|
3320
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
3321
|
+
if (!authenticationResult.user?.id) {
|
|
3322
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3101
3325
|
res.status(200).json(agents);
|
|
3102
3326
|
});
|
|
3327
|
+
app.get(`/agents`, async (req, res) => {
|
|
3328
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
3329
|
+
if (!authenticationResult.user?.id) {
|
|
3330
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
const { db: db2 } = await postgresClient();
|
|
3334
|
+
const agentsFromDb = await db2.from("agents").select("*");
|
|
3335
|
+
res.status(200).json(agentsFromDb.map((agent) => {
|
|
3336
|
+
const backend = agents.find((a) => a.id === agent.backend);
|
|
3337
|
+
if (!backend) {
|
|
3338
|
+
return null;
|
|
3339
|
+
}
|
|
3340
|
+
return {
|
|
3341
|
+
name: agent.name,
|
|
3342
|
+
id: agent.id,
|
|
3343
|
+
description: agent.description,
|
|
3344
|
+
provider: backend?.model?.provider,
|
|
3345
|
+
model: backend?.model?.modelId,
|
|
3346
|
+
active: agent.active,
|
|
3347
|
+
public: agent.public,
|
|
3348
|
+
type: agent.type,
|
|
3349
|
+
slug: backend?.slug,
|
|
3350
|
+
rateLimit: backend?.rateLimit,
|
|
3351
|
+
streaming: backend?.streaming,
|
|
3352
|
+
capabilities: backend?.capabilities,
|
|
3353
|
+
// todo add contexts
|
|
3354
|
+
availableTools: tools,
|
|
3355
|
+
enabledTools: agent.tools
|
|
3356
|
+
};
|
|
3357
|
+
}).filter(Boolean));
|
|
3358
|
+
});
|
|
3103
3359
|
app.get(`/agents/:id`, async (req, res) => {
|
|
3360
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
3361
|
+
if (!authenticationResult.user?.id) {
|
|
3362
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3104
3365
|
const { db: db2 } = await postgresClient();
|
|
3105
3366
|
const id = req.params.id;
|
|
3106
3367
|
if (!id) {
|
|
@@ -3123,6 +3384,8 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3123
3384
|
name: agent.name,
|
|
3124
3385
|
id: agent.id,
|
|
3125
3386
|
description: agent.description,
|
|
3387
|
+
provider: backend?.model?.provider,
|
|
3388
|
+
model: backend?.model?.modelId,
|
|
3126
3389
|
active: agent.active,
|
|
3127
3390
|
public: agent.public,
|
|
3128
3391
|
type: agent.type,
|
|
@@ -3131,7 +3394,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3131
3394
|
streaming: backend?.streaming,
|
|
3132
3395
|
capabilities: backend?.capabilities,
|
|
3133
3396
|
// todo add contexts
|
|
3134
|
-
availableTools:
|
|
3397
|
+
availableTools: tools,
|
|
3135
3398
|
enabledTools: agent.tools
|
|
3136
3399
|
}
|
|
3137
3400
|
});
|
|
@@ -3142,8 +3405,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3142
3405
|
name: tool2.name,
|
|
3143
3406
|
description: tool2.description,
|
|
3144
3407
|
type: tool2.type || "tool",
|
|
3145
|
-
inputSchema: tool2.inputSchema ? zerialize(tool2.inputSchema) : null
|
|
3146
|
-
outputSchema: tool2.outputSchema ? zerialize(tool2.outputSchema) : null
|
|
3408
|
+
inputSchema: tool2.inputSchema ? zerialize(tool2.inputSchema) : null
|
|
3147
3409
|
})));
|
|
3148
3410
|
});
|
|
3149
3411
|
app.get("/tools/:id", async (req, res) => {
|
|
@@ -3261,6 +3523,14 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3261
3523
|
});
|
|
3262
3524
|
return;
|
|
3263
3525
|
}
|
|
3526
|
+
const itemsTableExists = await context.tableExists();
|
|
3527
|
+
if (!itemsTableExists) {
|
|
3528
|
+
await context.createItemsTable();
|
|
3529
|
+
}
|
|
3530
|
+
const chunksTableExists = await db2.schema.hasTable(context.getChunksTableName());
|
|
3531
|
+
if (!chunksTableExists) {
|
|
3532
|
+
await context.createChunksTable();
|
|
3533
|
+
}
|
|
3264
3534
|
const item = await db2.from(context.getTableName()).where({ id: req.params.id }).select("*").first();
|
|
3265
3535
|
if (!item) {
|
|
3266
3536
|
res.status(404).json({
|
|
@@ -3382,6 +3652,20 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3382
3652
|
}
|
|
3383
3653
|
let limit = req.query.limit ? parseInt(req.query.limit) : 10;
|
|
3384
3654
|
let page = req.query.page ? parseInt(req.query.page) : 1;
|
|
3655
|
+
let sort = req.query.sort ? req.query.sort : "created_at";
|
|
3656
|
+
let order = req.query.order ? req.query.order : "desc";
|
|
3657
|
+
if (sort && !["created_at", "embeddings_updated_at"].includes(sort)) {
|
|
3658
|
+
res.status(400).json({
|
|
3659
|
+
message: "Invalid sort field, must be one of: createdAt, embeddings_updated_at"
|
|
3660
|
+
});
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
if (order && !["desc", "asc"].includes(order)) {
|
|
3664
|
+
res.status(400).json({
|
|
3665
|
+
message: "Invalid order, must be one of: desc, asc"
|
|
3666
|
+
});
|
|
3667
|
+
return;
|
|
3668
|
+
}
|
|
3385
3669
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3386
3670
|
if (!authenticationResult.user?.id) {
|
|
3387
3671
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
@@ -3405,6 +3689,8 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3405
3689
|
return;
|
|
3406
3690
|
}
|
|
3407
3691
|
const result = await context.getItems({
|
|
3692
|
+
sort,
|
|
3693
|
+
order,
|
|
3408
3694
|
page,
|
|
3409
3695
|
limit,
|
|
3410
3696
|
archived: req.query.archived === "true",
|
|
@@ -3484,6 +3770,18 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3484
3770
|
message: "Embedding deleted."
|
|
3485
3771
|
});
|
|
3486
3772
|
});
|
|
3773
|
+
app.get("/ping", async (req, res) => {
|
|
3774
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
3775
|
+
if (!authenticationResult.user?.id) {
|
|
3776
|
+
res.status(200).json({
|
|
3777
|
+
authenticated: false
|
|
3778
|
+
});
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
3781
|
+
res.status(200).json({
|
|
3782
|
+
authenticated: true
|
|
3783
|
+
});
|
|
3784
|
+
});
|
|
3487
3785
|
console.log("[EXULU] statistics timeseries");
|
|
3488
3786
|
app.post("/statistics/timeseries", async (req, res) => {
|
|
3489
3787
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
@@ -3574,7 +3872,6 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3574
3872
|
const response = await db2("jobs").select(db2.raw(`to_char("createdAt", 'YYYY-MM-DD') as date`)).count("* as count").where("type", "embedder").groupByRaw(`to_char("createdAt", 'YYYY-MM-DD')`).then((rows) => ({
|
|
3575
3873
|
jobs: rows
|
|
3576
3874
|
}));
|
|
3577
|
-
console.log({ response });
|
|
3578
3875
|
let jobs = [];
|
|
3579
3876
|
if (response[0]) {
|
|
3580
3877
|
jobs = response[0].jobs.map((job) => ({
|
|
@@ -3624,6 +3921,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3624
3921
|
slug: "/contexts/" + context.id,
|
|
3625
3922
|
active: context.active,
|
|
3626
3923
|
fields: context.fields,
|
|
3924
|
+
configuration: context.configuration,
|
|
3627
3925
|
sources: context.sources.get().map((source) => ({
|
|
3628
3926
|
id: source.id,
|
|
3629
3927
|
name: source.name,
|
|
@@ -3671,13 +3969,11 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3671
3969
|
});
|
|
3672
3970
|
console.log("[EXULU] contexts get list");
|
|
3673
3971
|
app.get(`/contexts`, async (req, res) => {
|
|
3674
|
-
console.log("contexts!!");
|
|
3675
3972
|
const authenticationResult = await requestValidators.authenticate(req);
|
|
3676
3973
|
if (!authenticationResult.user?.id) {
|
|
3677
3974
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
3678
3975
|
return;
|
|
3679
3976
|
}
|
|
3680
|
-
console.log("contexts", contexts?.length);
|
|
3681
3977
|
res.status(200).json(contexts.map((context) => ({
|
|
3682
3978
|
id: context.id,
|
|
3683
3979
|
name: context.name,
|
|
@@ -3820,47 +4116,28 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3820
4116
|
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
3821
4117
|
return;
|
|
3822
4118
|
}
|
|
4119
|
+
console.log("[EXULU] agent tools", agentInstance.tools);
|
|
4120
|
+
const enabledTools = agentInstance.tools.map((tool2) => tools.find(({ id }) => id === tool2)).filter(Boolean);
|
|
4121
|
+
console.log("[EXULU] enabled tools", enabledTools);
|
|
3823
4122
|
if (!!stream) {
|
|
3824
|
-
const
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
const { textStream } = await chatClient.stream(req.body.messages, {
|
|
3832
|
-
threadId: `${req.body.threadId}`,
|
|
3833
|
-
// conversation id
|
|
3834
|
-
resourceId: `${req.body.resourceId}`,
|
|
3835
|
-
// user id
|
|
3836
|
-
...agent.outputSchema && { output: agent.outputSchema },
|
|
3837
|
-
maxRetries: 2,
|
|
3838
|
-
// todo make part of ExuluAgent class
|
|
3839
|
-
maxSteps: 5,
|
|
3840
|
-
// todo make part of ExuluAgent class
|
|
3841
|
-
onError: (error) => console.error("[EXULU] chat stream error.", error),
|
|
3842
|
-
onFinish: ({ response, usage }) => console.info(
|
|
3843
|
-
"[EXULU] chat stream finished.",
|
|
3844
|
-
usage
|
|
3845
|
-
)
|
|
4123
|
+
const result = agent.generateStream({
|
|
4124
|
+
messages: req.body.messages,
|
|
4125
|
+
tools: enabledTools,
|
|
4126
|
+
statistics: {
|
|
4127
|
+
label: agent.name,
|
|
4128
|
+
trigger: "agent"
|
|
4129
|
+
}
|
|
3846
4130
|
});
|
|
3847
|
-
|
|
3848
|
-
res.write(`data: ${delta}
|
|
3849
|
-
|
|
3850
|
-
`);
|
|
3851
|
-
}
|
|
3852
|
-
res.end();
|
|
4131
|
+
result.pipeDataStreamToResponse(res);
|
|
3853
4132
|
return;
|
|
3854
4133
|
} else {
|
|
3855
|
-
const response = await agent.
|
|
3856
|
-
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
maxSteps: 5
|
|
3863
|
-
// todo make part of ExuluAgent class
|
|
4134
|
+
const response = await agent.generateSync({
|
|
4135
|
+
messages: req.body.messages,
|
|
4136
|
+
tools: enabledTools.map(),
|
|
4137
|
+
statistics: {
|
|
4138
|
+
label: agent.name,
|
|
4139
|
+
trigger: "agent"
|
|
4140
|
+
}
|
|
3864
4141
|
});
|
|
3865
4142
|
res.status(200).json(response);
|
|
3866
4143
|
return;
|
|
@@ -3935,7 +4212,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3935
4212
|
console.log("Routes:");
|
|
3936
4213
|
console.table(routeLogs);
|
|
3937
4214
|
const TARGET_API = "https://api.anthropic.com";
|
|
3938
|
-
app.use("/gateway/anthropic", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
4215
|
+
app.use("/gateway/anthropic/:id", express.raw({ type: "*/*", limit: REQUEST_SIZE_LIMIT }), async (req, res) => {
|
|
3939
4216
|
const path3 = req.url;
|
|
3940
4217
|
const url = `${TARGET_API}${path3}`;
|
|
3941
4218
|
console.log("[PROXY] Manual proxy to:", url);
|
|
@@ -3944,60 +4221,102 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
|
|
|
3944
4221
|
console.log("[PROXY] Request body length:", req.body ? req.body.length : 0);
|
|
3945
4222
|
console.log("[PROXY] Request model name:", req.body.model);
|
|
3946
4223
|
console.log("[PROXY] Request stream:", req.body.stream);
|
|
3947
|
-
console.log("[PROXY] API key:", req.headers["x-api-key"]);
|
|
3948
4224
|
console.log("[PROXY] Request messages:", req.body.messages?.length);
|
|
3949
4225
|
try {
|
|
3950
|
-
const headers = {
|
|
3951
|
-
"x-api-key": process.env.ANTHROPIC_API_KEY,
|
|
3952
|
-
"anthropic-version": "2023-06-01",
|
|
3953
|
-
"content-type": req.headers["content-type"] || "application/json"
|
|
3954
|
-
};
|
|
3955
|
-
if (req.headers["accept"]) headers["accept"] = req.headers["accept"];
|
|
3956
|
-
if (req.headers["user-agent"]) headers["user-agent"] = req.headers["user-agent"];
|
|
3957
4226
|
console.log("[PROXY] Request body tools array length:", req.body.tools?.length);
|
|
3958
4227
|
if (!req.body.tools) {
|
|
3959
4228
|
req.body.tools = [];
|
|
3960
4229
|
}
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4230
|
+
const authenticationResult = await requestValidators.authenticate(req);
|
|
4231
|
+
if (!authenticationResult.user?.id) {
|
|
4232
|
+
res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
console.log("[EXULU] authentication result", authenticationResult);
|
|
4236
|
+
const { db: db2 } = await postgresClient();
|
|
4237
|
+
const agent = await db2.from("agents").where({
|
|
4238
|
+
id: req.params.id
|
|
4239
|
+
}).first();
|
|
4240
|
+
if (!agent) {
|
|
4241
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(`
|
|
4242
|
+
\x1B[41m -- Agent ${req.params.id} not found or you do not have access to it. --
|
|
4243
|
+
\x1B[0m`);
|
|
4244
|
+
res.setHeader("Content-Type", "application/json");
|
|
4245
|
+
res.end(Buffer.from(arrayBuffer));
|
|
4246
|
+
return;
|
|
4247
|
+
}
|
|
4248
|
+
console.log("[EXULU] agent", agent?.name);
|
|
4249
|
+
if (!process.env.NEXTAUTH_SECRET) {
|
|
4250
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_nextauth_secret);
|
|
4251
|
+
res.setHeader("Content-Type", "application/json");
|
|
4252
|
+
res.end(Buffer.from(arrayBuffer));
|
|
4253
|
+
return;
|
|
4254
|
+
}
|
|
4255
|
+
if (!authenticationResult.user?.anthropic_token) {
|
|
4256
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.not_enabled);
|
|
4257
|
+
res.setHeader("Content-Type", "application/json");
|
|
4258
|
+
res.end(Buffer.from(arrayBuffer));
|
|
3971
4259
|
return;
|
|
3972
4260
|
}
|
|
4261
|
+
const bytes = CryptoJS2.AES.decrypt(authenticationResult.user?.anthropic_token, process.env.NEXTAUTH_SECRET);
|
|
4262
|
+
const anthropicApiKey = bytes.toString(CryptoJS2.enc.Utf8);
|
|
4263
|
+
const headers = {
|
|
4264
|
+
"x-api-key": anthropicApiKey,
|
|
4265
|
+
"anthropic-version": "2023-06-01",
|
|
4266
|
+
"content-type": req.headers["content-type"] || "application/json"
|
|
4267
|
+
};
|
|
4268
|
+
if (req.headers["accept"]) headers["accept"] = req.headers["accept"];
|
|
4269
|
+
if (req.headers["user-agent"]) headers["user-agent"] = req.headers["user-agent"];
|
|
4270
|
+
console.log("[EXULU] anthropic api key", anthropicApiKey);
|
|
3973
4271
|
const response = await fetch(url, {
|
|
3974
4272
|
method: req.method,
|
|
3975
4273
|
headers,
|
|
3976
4274
|
body: req.method !== "GET" ? JSON.stringify(req.body) : void 0
|
|
3977
4275
|
});
|
|
3978
|
-
console.log("[PROXY] Response
|
|
4276
|
+
console.log("[PROXY] Response:", response);
|
|
4277
|
+
console.log("[PROXY] Response:", response.body);
|
|
4278
|
+
await updateStatistic({
|
|
4279
|
+
name: "count",
|
|
4280
|
+
label: "Claude Code",
|
|
4281
|
+
type: STATISTICS_TYPE_ENUM.AGENT_RUN,
|
|
4282
|
+
trigger: "claude-code",
|
|
4283
|
+
count: 1
|
|
4284
|
+
});
|
|
3979
4285
|
response.headers.forEach((value, key) => {
|
|
3980
4286
|
res.setHeader(key, value);
|
|
3981
4287
|
});
|
|
3982
4288
|
res.status(response.status);
|
|
3983
|
-
|
|
4289
|
+
const isStreaming = response.headers.get("content-type")?.includes("text/event-stream");
|
|
4290
|
+
if (isStreaming && !response?.body) {
|
|
4291
|
+
const arrayBuffer = createCustomAnthropicStreamingMessage(CLAUDE_MESSAGES.missing_body);
|
|
4292
|
+
res.setHeader("Content-Type", "application/json");
|
|
4293
|
+
res.end(Buffer.from(arrayBuffer));
|
|
4294
|
+
return;
|
|
4295
|
+
}
|
|
4296
|
+
if (isStreaming) {
|
|
3984
4297
|
const reader = response.body.getReader();
|
|
3985
4298
|
const decoder = new TextDecoder();
|
|
3986
4299
|
while (true) {
|
|
3987
4300
|
const { done, value } = await reader.read();
|
|
3988
4301
|
if (done) break;
|
|
3989
4302
|
const chunk = decoder.decode(value, { stream: true });
|
|
4303
|
+
console.log("[PROXY] Chunk:", chunk);
|
|
3990
4304
|
res.write(chunk);
|
|
3991
4305
|
}
|
|
3992
4306
|
res.end();
|
|
3993
|
-
|
|
3994
|
-
const data = await response.arrayBuffer();
|
|
3995
|
-
res.end(Buffer.from(data));
|
|
4307
|
+
return;
|
|
3996
4308
|
}
|
|
4309
|
+
const data = await response.arrayBuffer();
|
|
4310
|
+
console.log("[PROXY] Data:", data);
|
|
4311
|
+
res.end(Buffer.from(data));
|
|
3997
4312
|
} catch (error) {
|
|
3998
4313
|
console.error("[PROXY] Manual proxy error:", error);
|
|
3999
4314
|
if (!res.headersSent) {
|
|
4000
|
-
|
|
4315
|
+
if (error?.message === "Invalid token") {
|
|
4316
|
+
res.status(500).json({ error: "Authentication error, please check your IMP token and try again." });
|
|
4317
|
+
} else {
|
|
4318
|
+
res.status(500).json({ error: error.message });
|
|
4319
|
+
}
|
|
4001
4320
|
}
|
|
4002
4321
|
}
|
|
4003
4322
|
});
|
|
@@ -4045,6 +4364,20 @@ var getPresignedFileUrl = async (key) => {
|
|
|
4045
4364
|
console.log(`[EXULU] presigned url for file with key: ${key}, generated: ${json.url}`);
|
|
4046
4365
|
return json.url;
|
|
4047
4366
|
};
|
|
4367
|
+
var createCustomAnthropicStreamingMessage = (message) => {
|
|
4368
|
+
const responseData = {
|
|
4369
|
+
type: "message",
|
|
4370
|
+
content: [
|
|
4371
|
+
{
|
|
4372
|
+
type: "text",
|
|
4373
|
+
text: message
|
|
4374
|
+
}
|
|
4375
|
+
]
|
|
4376
|
+
};
|
|
4377
|
+
const jsonString = JSON.stringify(responseData);
|
|
4378
|
+
const arrayBuffer = new TextEncoder().encode(jsonString).buffer;
|
|
4379
|
+
return arrayBuffer;
|
|
4380
|
+
};
|
|
4048
4381
|
|
|
4049
4382
|
// src/registry/workers.ts
|
|
4050
4383
|
import IORedis from "ioredis";
|
|
@@ -4097,7 +4430,7 @@ import * as fs2 from "fs";
|
|
|
4097
4430
|
import path2 from "path";
|
|
4098
4431
|
var defaultLogsDir = path2.join(process.cwd(), "logs");
|
|
4099
4432
|
var redisConnection;
|
|
4100
|
-
var createWorkers = async (queues2, contexts,
|
|
4433
|
+
var createWorkers = async (queues2, contexts, workflows, _logsDir) => {
|
|
4101
4434
|
if (!redisServer.host || !redisServer.port) {
|
|
4102
4435
|
console.error("[EXULU] you are trying to start workers, but no redis server is configured in the environment.");
|
|
4103
4436
|
throw new Error("No redis server configured in the environment, so cannot start workers.");
|
|
@@ -4128,7 +4461,7 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
|
|
|
4128
4461
|
if (!bullmqJob.data.embedder) {
|
|
4129
4462
|
throw new Error(`No embedder set for embedder job.`);
|
|
4130
4463
|
}
|
|
4131
|
-
const embedder =
|
|
4464
|
+
const embedder = contexts.find((context2) => context2.embedder?.id === bullmqJob.data.embedder);
|
|
4132
4465
|
if (!embedder) {
|
|
4133
4466
|
throw new Error(`Embedder ${bullmqJob.data.embedder} not found in the registry.`);
|
|
4134
4467
|
}
|
|
@@ -4168,6 +4501,9 @@ var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) =>
|
|
|
4168
4501
|
duration,
|
|
4169
4502
|
result: JSON.stringify(result)
|
|
4170
4503
|
});
|
|
4504
|
+
await db2.from((void 0).getTableName()).where({ id: result[0].id }).update({
|
|
4505
|
+
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
4506
|
+
}).returning("id");
|
|
4171
4507
|
return result;
|
|
4172
4508
|
}
|
|
4173
4509
|
if (bullmqJob.data.type === "workflow") {
|
|
@@ -4265,7 +4601,7 @@ var ExuluMCP = class {
|
|
|
4265
4601
|
express;
|
|
4266
4602
|
constructor() {
|
|
4267
4603
|
}
|
|
4268
|
-
create = async ({ express: express3, contexts,
|
|
4604
|
+
create = async ({ express: express3, contexts, agents, workflows, config, tools }) => {
|
|
4269
4605
|
this.express = express3;
|
|
4270
4606
|
if (!this.server) {
|
|
4271
4607
|
console.log("[EXULU] Creating MCP server.");
|
|
@@ -4342,8 +4678,6 @@ ${code}`
|
|
|
4342
4678
|
throw new Error("MCP server not initialized.");
|
|
4343
4679
|
}
|
|
4344
4680
|
const sessionId = req.headers[SESSION_ID_HEADER];
|
|
4345
|
-
console.log("sessionId!!", sessionId);
|
|
4346
|
-
console.log("req.headers!!", req.headers);
|
|
4347
4681
|
let transport;
|
|
4348
4682
|
if (sessionId && this.transports[sessionId]) {
|
|
4349
4683
|
transport = this.transports[sessionId];
|
|
@@ -4373,10 +4707,9 @@ ${code}`
|
|
|
4373
4707
|
await transport.handleRequest(req, res, req.body);
|
|
4374
4708
|
});
|
|
4375
4709
|
const handleSessionRequest = async (req, res) => {
|
|
4376
|
-
console.log("handleSessionRequest", req.body);
|
|
4377
4710
|
const sessionId = req.headers[SESSION_ID_HEADER];
|
|
4378
4711
|
if (!sessionId || !this.transports[sessionId]) {
|
|
4379
|
-
console.log("
|
|
4712
|
+
console.log("[EXULU] MCP request invalid or missing session ID");
|
|
4380
4713
|
res.status(400).send("Invalid or missing session ID");
|
|
4381
4714
|
return;
|
|
4382
4715
|
}
|
|
@@ -4399,28 +4732,210 @@ ${code}`
|
|
|
4399
4732
|
|
|
4400
4733
|
// src/registry/index.ts
|
|
4401
4734
|
import express2 from "express";
|
|
4735
|
+
|
|
4736
|
+
// src/templates/agents/claude-code.ts
|
|
4737
|
+
var agentId = "0832-5178-1145-2194";
|
|
4738
|
+
var claudeCodeAgent = new ExuluAgent({
|
|
4739
|
+
id: `${agentId}-claude-code-agent`,
|
|
4740
|
+
name: `Claude Code Agent`,
|
|
4741
|
+
description: `Claude Code agent, enabling the creation of multiple Claude Code Agent instances with different configurations (rate limits, functions, etc).`,
|
|
4742
|
+
type: "custom"
|
|
4743
|
+
});
|
|
4744
|
+
|
|
4745
|
+
// src/templates/agents/claude-opus-4.ts
|
|
4746
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
4747
|
+
import { z as z3 } from "zod";
|
|
4748
|
+
var agentId2 = "5434-5678-9143-2590";
|
|
4749
|
+
var defaultAgent = new ExuluAgent({
|
|
4750
|
+
id: `${agentId2}-default-claude-4-opus-agent`,
|
|
4751
|
+
name: `Default Claude 4 Opus Agent`,
|
|
4752
|
+
description: `Basic agent without any defined tools, that can support MCP's.`,
|
|
4753
|
+
type: "agent",
|
|
4754
|
+
capabilities: {
|
|
4755
|
+
tools: false,
|
|
4756
|
+
images: [],
|
|
4757
|
+
files: [],
|
|
4758
|
+
audio: [],
|
|
4759
|
+
video: []
|
|
4760
|
+
},
|
|
4761
|
+
evals: [],
|
|
4762
|
+
config: {
|
|
4763
|
+
name: `Default agent`,
|
|
4764
|
+
instructions: "You are a helpful assistant.",
|
|
4765
|
+
model: anthropic("claude-4-opus-20250514"),
|
|
4766
|
+
// todo add a field of type string that adds a dropdown list from which the user can select the model
|
|
4767
|
+
// todo for each model, check which provider is used, and require the admin to add one or multiple
|
|
4768
|
+
// API keys for the provider (which we can then auto-rotate).
|
|
4769
|
+
// todo also add custom fields for rate limiting, so the admin can set custom rate limits for the agent
|
|
4770
|
+
// and allow him/her to decide if the rate limit is per user or per agent.
|
|
4771
|
+
// todo finally allow switching on or off immutable audit logs on the agent. Which then enables OTEL
|
|
4772
|
+
// and stores the logs into the pre-defined storage.
|
|
4773
|
+
custom: z3.object({
|
|
4774
|
+
apiKey: z3.string()
|
|
4775
|
+
})
|
|
4776
|
+
}
|
|
4777
|
+
});
|
|
4778
|
+
|
|
4779
|
+
// src/templates/tools/browserbase.ts
|
|
4780
|
+
import { z as z4 } from "zod";
|
|
4781
|
+
import { Stagehand } from "@browserbasehq/stagehand";
|
|
4782
|
+
import { Browserbase } from "@browserbasehq/sdk";
|
|
4783
|
+
var PROJECT_ID = "811444dd-6e6d-40b5-bd90-541c93e44be6";
|
|
4784
|
+
process.env.BROWSERBASE_PROJECT_ID = PROJECT_ID;
|
|
4785
|
+
var BB_API_KEY = "bb_live_LwMwNgZB5cIEKcBwMuAugrgNkFM";
|
|
4786
|
+
async function createContext() {
|
|
4787
|
+
const bb = new Browserbase({ apiKey: BB_API_KEY });
|
|
4788
|
+
const context = await bb.contexts.create({
|
|
4789
|
+
projectId: PROJECT_ID
|
|
4790
|
+
});
|
|
4791
|
+
return context;
|
|
4792
|
+
}
|
|
4793
|
+
async function createAuthSession(contextId) {
|
|
4794
|
+
const bb = new Browserbase({ apiKey: BB_API_KEY });
|
|
4795
|
+
const session = await bb.sessions.create({
|
|
4796
|
+
projectId: PROJECT_ID,
|
|
4797
|
+
browserSettings: {
|
|
4798
|
+
context: {
|
|
4799
|
+
id: contextId,
|
|
4800
|
+
persist: true
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
/* proxies: [{ // not included in the free tier
|
|
4804
|
+
type: "browserbase",
|
|
4805
|
+
geolocation: {
|
|
4806
|
+
city: CITY,
|
|
4807
|
+
country: COUNTRY
|
|
4808
|
+
}
|
|
4809
|
+
}] */
|
|
4810
|
+
});
|
|
4811
|
+
const liveViewLinks = await bb.sessions.debug(session.id);
|
|
4812
|
+
const liveViewLink = liveViewLinks.debuggerFullscreenUrl;
|
|
4813
|
+
console.log(`\u{1F50D} Live View Link: ${liveViewLink}`);
|
|
4814
|
+
console.log("Session URL: https://browserbase.com/sessions/" + session.id);
|
|
4815
|
+
return {
|
|
4816
|
+
url: liveViewLink,
|
|
4817
|
+
id: session.id
|
|
4818
|
+
};
|
|
4819
|
+
}
|
|
4820
|
+
var createSession = new ExuluTool({
|
|
4821
|
+
id: `1234-5178-9423-4267`,
|
|
4822
|
+
type: "function",
|
|
4823
|
+
name: "Create a browserbase session.",
|
|
4824
|
+
description: `
|
|
4825
|
+
Creates a browserbase session and returns the live view url as well as
|
|
4826
|
+
the session id as a JSON object. A browserbase session is a headless browser
|
|
4827
|
+
that can be used to to visit websites and perform actions.
|
|
4828
|
+
`,
|
|
4829
|
+
execute: async () => {
|
|
4830
|
+
const { id } = await createContext();
|
|
4831
|
+
return await createAuthSession(id);
|
|
4832
|
+
}
|
|
4833
|
+
});
|
|
4834
|
+
var askChatgpt = new ExuluTool({
|
|
4835
|
+
id: `1234-5178-9423-4268`,
|
|
4836
|
+
type: "function",
|
|
4837
|
+
name: "ChatGPT browserbase operation.",
|
|
4838
|
+
inputSchema: z4.object({
|
|
4839
|
+
session: z4.string().describe("The session id of the browserbase session."),
|
|
4840
|
+
question: z4.string().describe("The question to ask ChatGPT.")
|
|
4841
|
+
}),
|
|
4842
|
+
description: `Uses an existing, authenticated browserbase session to visit ChatGPT and perform actions such as asking questions.`,
|
|
4843
|
+
execute: async ({ session, question }) => {
|
|
4844
|
+
const stagehand = new Stagehand({
|
|
4845
|
+
// With npx create-browser-app, this config is found
|
|
4846
|
+
// in a separate stagehand.config.ts file
|
|
4847
|
+
env: "BROWSERBASE",
|
|
4848
|
+
// set to "LOCAL" for local development
|
|
4849
|
+
apiKey: BB_API_KEY,
|
|
4850
|
+
// todo make this a config variable the admin can set in the UI
|
|
4851
|
+
modelName: "openai/gpt-4.1-mini",
|
|
4852
|
+
// todo change to claude || optionally make configurable?
|
|
4853
|
+
browserbaseSessionID: session,
|
|
4854
|
+
modelClientOptions: {
|
|
4855
|
+
apiKey: process.env.OPENAI_API_KEY
|
|
4856
|
+
// todo make this a config variable the admin can set in the UI
|
|
4857
|
+
}
|
|
4858
|
+
});
|
|
4859
|
+
await stagehand.init();
|
|
4860
|
+
const page = stagehand.page;
|
|
4861
|
+
await page.goto("https://chatgpt.com");
|
|
4862
|
+
await page.act(`Type in '${question}' into the search bar`);
|
|
4863
|
+
const { answer } = await page.extract({
|
|
4864
|
+
instruction: "The answer to the question generated by ChatGPT.",
|
|
4865
|
+
schema: z4.object({
|
|
4866
|
+
answer: z4.string()
|
|
4867
|
+
})
|
|
4868
|
+
});
|
|
4869
|
+
console.log(answer);
|
|
4870
|
+
await stagehand.close();
|
|
4871
|
+
return {
|
|
4872
|
+
answer
|
|
4873
|
+
};
|
|
4874
|
+
}
|
|
4875
|
+
});
|
|
4876
|
+
|
|
4877
|
+
// src/templates/tools/jira.ts
|
|
4878
|
+
import { z as z5 } from "zod";
|
|
4879
|
+
var getTicket = new ExuluTool({
|
|
4880
|
+
id: `1414-5179-1423-1269`,
|
|
4881
|
+
name: "JIRA ticket retrieval.",
|
|
4882
|
+
type: "function",
|
|
4883
|
+
inputSchema: z5.object({
|
|
4884
|
+
ticketId: z5.string().describe("The id of the ticket to retrieve.")
|
|
4885
|
+
}),
|
|
4886
|
+
description: `Retrieves a ticket from Jira.`,
|
|
4887
|
+
execute: async ({ session, question }) => {
|
|
4888
|
+
return {
|
|
4889
|
+
name: "BYD-1234",
|
|
4890
|
+
id: "12345678",
|
|
4891
|
+
status: "Open",
|
|
4892
|
+
description: "This is a test ticket",
|
|
4893
|
+
assignee: "John Doe",
|
|
4894
|
+
createdAt: "2021-01-01",
|
|
4895
|
+
updatedAt: "2021-01-01",
|
|
4896
|
+
dueDate: "2021-01-01",
|
|
4897
|
+
priority: "High"
|
|
4898
|
+
};
|
|
4899
|
+
}
|
|
4900
|
+
});
|
|
4901
|
+
|
|
4902
|
+
// src/registry/index.ts
|
|
4402
4903
|
var ExuluApp = class {
|
|
4403
4904
|
_agents = [];
|
|
4404
4905
|
_workflows = [];
|
|
4405
4906
|
_config;
|
|
4406
|
-
_embedders = [];
|
|
4407
4907
|
_queues = [];
|
|
4408
4908
|
_contexts = {};
|
|
4409
4909
|
_tools = [];
|
|
4410
4910
|
_expressApp = null;
|
|
4411
4911
|
constructor() {
|
|
4412
4912
|
}
|
|
4413
|
-
// Factory function so we can async
|
|
4414
|
-
// MCP server if needed.
|
|
4415
|
-
create = async ({ contexts,
|
|
4416
|
-
this._embedders = embedders ?? [];
|
|
4913
|
+
// Factory function so we can async
|
|
4914
|
+
// initialize the MCP server if needed.
|
|
4915
|
+
create = async ({ contexts, agents, workflows, config, tools }) => {
|
|
4417
4916
|
this._workflows = workflows ?? [];
|
|
4418
4917
|
this._contexts = contexts ?? {};
|
|
4419
|
-
this._agents =
|
|
4918
|
+
this._agents = [
|
|
4919
|
+
claudeCodeAgent,
|
|
4920
|
+
defaultAgent,
|
|
4921
|
+
...agents ?? []
|
|
4922
|
+
];
|
|
4420
4923
|
this._config = config;
|
|
4421
|
-
this._tools =
|
|
4924
|
+
this._tools = [
|
|
4925
|
+
...tools ?? [],
|
|
4926
|
+
// Add contexts as tools
|
|
4927
|
+
...Object.values(contexts || {}).map((context) => context.tool()),
|
|
4928
|
+
// Add agents as tools
|
|
4929
|
+
...(agents || []).map((agent) => agent.tool()),
|
|
4930
|
+
...[
|
|
4931
|
+
createSession,
|
|
4932
|
+
askChatgpt,
|
|
4933
|
+
getTicket
|
|
4934
|
+
]
|
|
4935
|
+
];
|
|
4936
|
+
const contextsArray = Object.values(contexts || {});
|
|
4422
4937
|
const queues2 = [
|
|
4423
|
-
...
|
|
4938
|
+
...contextsArray?.length ? contextsArray.map((context) => context.embedder.queue?.name || null) : [],
|
|
4424
4939
|
...workflows?.length ? workflows.map((workflow) => workflow.queue?.name || null) : []
|
|
4425
4940
|
];
|
|
4426
4941
|
this._queues = [...new Set(queues2.filter((o) => !!o))];
|
|
@@ -4437,9 +4952,6 @@ var ExuluApp = class {
|
|
|
4437
4952
|
}
|
|
4438
4953
|
return this._expressApp;
|
|
4439
4954
|
}
|
|
4440
|
-
embedder(id) {
|
|
4441
|
-
return this._embedders.find((x) => x.id === id);
|
|
4442
|
-
}
|
|
4443
4955
|
tool(id) {
|
|
4444
4956
|
return this._tools.find((x) => x.id === id);
|
|
4445
4957
|
}
|
|
@@ -4455,9 +4967,6 @@ var ExuluApp = class {
|
|
|
4455
4967
|
workflow(id) {
|
|
4456
4968
|
return this._workflows.find((x) => x.id === id);
|
|
4457
4969
|
}
|
|
4458
|
-
get embedders() {
|
|
4459
|
-
return this._embedders;
|
|
4460
|
-
}
|
|
4461
4970
|
get contexts() {
|
|
4462
4971
|
return Object.values(this._contexts ?? {});
|
|
4463
4972
|
}
|
|
@@ -4473,7 +4982,6 @@ var ExuluApp = class {
|
|
|
4473
4982
|
return await createWorkers(
|
|
4474
4983
|
this._queues,
|
|
4475
4984
|
Object.values(this._contexts ?? {}),
|
|
4476
|
-
this._embedders,
|
|
4477
4985
|
this._workflows,
|
|
4478
4986
|
this._config?.workers?.logsDir
|
|
4479
4987
|
);
|
|
@@ -4490,7 +4998,6 @@ var ExuluApp = class {
|
|
|
4490
4998
|
await createExpressRoutes(
|
|
4491
4999
|
app,
|
|
4492
5000
|
this._agents,
|
|
4493
|
-
this._embedders,
|
|
4494
5001
|
this._tools,
|
|
4495
5002
|
this._workflows,
|
|
4496
5003
|
Object.values(this._contexts ?? {})
|
|
@@ -4500,7 +5007,6 @@ var ExuluApp = class {
|
|
|
4500
5007
|
await mcp.create({
|
|
4501
5008
|
express: app,
|
|
4502
5009
|
contexts: this._contexts,
|
|
4503
|
-
embedders: this._embedders,
|
|
4504
5010
|
agents: this._agents,
|
|
4505
5011
|
workflows: this._workflows,
|
|
4506
5012
|
config: this._config,
|
|
@@ -4829,15 +5335,12 @@ var ExuluTokenizer = class {
|
|
|
4829
5335
|
console.log("[EXULU] Loading tokenizer.", modelName);
|
|
4830
5336
|
const model = await load(registry[models[modelName]]);
|
|
4831
5337
|
console.log("[EXULU] Loaded tokenizer.", modelName, performance.now() - time);
|
|
4832
|
-
console.log("[EXULU] Model.", model.bpe_ranks);
|
|
4833
|
-
console.log("[EXULU] Model.", model.special_tokens);
|
|
4834
|
-
console.log("[EXULU] Model.", model.pat_str);
|
|
4835
5338
|
const encoder = new Tiktoken(
|
|
4836
5339
|
model.bpe_ranks,
|
|
4837
5340
|
model.special_tokens,
|
|
4838
5341
|
model.pat_str
|
|
4839
5342
|
);
|
|
4840
|
-
console.log("[EXULU]
|
|
5343
|
+
console.log("[EXULU] Set encoder.");
|
|
4841
5344
|
this.encoder = encoder;
|
|
4842
5345
|
return encoder;
|
|
4843
5346
|
}
|
|
@@ -4860,9 +5363,9 @@ var ExuluTokenizer = class {
|
|
|
4860
5363
|
throw new Error("Tokenizer not initialized");
|
|
4861
5364
|
}
|
|
4862
5365
|
const time = performance.now();
|
|
4863
|
-
console.log("[EXULU] Encoding text
|
|
5366
|
+
console.log("[EXULU] Encoding text length: " + (text?.length || 0));
|
|
4864
5367
|
const tokens = this.encoder.encode(text);
|
|
4865
|
-
console.log("[EXULU]
|
|
5368
|
+
console.log("[EXULU] Finished encoding text.", performance.now() - time);
|
|
4866
5369
|
return tokens;
|
|
4867
5370
|
}
|
|
4868
5371
|
async countTokensBatch(texts) {
|
|
@@ -4876,7 +5379,6 @@ var ExuluTokenizer = class {
|
|
|
4876
5379
|
if (!this.encoder) {
|
|
4877
5380
|
throw new Error("Tokenizer not initialized");
|
|
4878
5381
|
}
|
|
4879
|
-
console.log("[EXULU] Counting tokens.", text);
|
|
4880
5382
|
const tokens = this.encoder.encode(text);
|
|
4881
5383
|
const count = tokens.length;
|
|
4882
5384
|
console.log("[EXULU] Token count.", count);
|
|
@@ -5046,8 +5548,6 @@ var RecursiveChunker = class _RecursiveChunker extends BaseChunker {
|
|
|
5046
5548
|
*
|
|
5047
5549
|
* @example <caption>Accessing properties and methods</caption>
|
|
5048
5550
|
* const chunker = await RecursiveChunker.create();
|
|
5049
|
-
* console.log(chunker.chunkSize); // 512
|
|
5050
|
-
* console.log(chunker.rules); // RecursiveRules instance
|
|
5051
5551
|
* const chunks = await chunker.chunk("Some text"); // Use as object method
|
|
5052
5552
|
*
|
|
5053
5553
|
* @note
|
|
@@ -5266,8 +5766,6 @@ var RecursiveChunker = class _RecursiveChunker extends BaseChunker {
|
|
|
5266
5766
|
if (!text) {
|
|
5267
5767
|
return [];
|
|
5268
5768
|
}
|
|
5269
|
-
console.log("[EXULU] Rule.", this.rules.length);
|
|
5270
|
-
console.log("[EXULU] Level.", level);
|
|
5271
5769
|
if (level >= this.rules.length) {
|
|
5272
5770
|
const tokenCount = await this._estimateTokenCount(text);
|
|
5273
5771
|
return [
|
|
@@ -5331,7 +5829,6 @@ var RecursiveChunker = class _RecursiveChunker extends BaseChunker {
|
|
|
5331
5829
|
* @returns {Promise<RecursiveChunk[]>} A promise that resolves to an array of RecursiveChunk objects
|
|
5332
5830
|
*/
|
|
5333
5831
|
async chunk(text) {
|
|
5334
|
-
console.log("[EXULU] Chunking text.", text);
|
|
5335
5832
|
const result = await this._recursiveChunk(text, 0, 0);
|
|
5336
5833
|
await this.tokenizer.free();
|
|
5337
5834
|
return result;
|
|
@@ -5717,200 +6214,6 @@ var SentenceChunker = class _SentenceChunker extends BaseChunker {
|
|
|
5717
6214
|
}
|
|
5718
6215
|
};
|
|
5719
6216
|
|
|
5720
|
-
// src/cli/index.tsx
|
|
5721
|
-
import { useState as useState2 } from "react";
|
|
5722
|
-
import { Box as Box2, Text as Text4, render as render2 } from "ink";
|
|
5723
|
-
import { UnorderedList as UnorderedList3 } from "@inkjs/ui";
|
|
5724
|
-
import patchConsole from "patch-console";
|
|
5725
|
-
|
|
5726
|
-
// src/cli/components/nav.tsx
|
|
5727
|
-
import { Select } from "@inkjs/ui";
|
|
5728
|
-
import { useApp } from "ink";
|
|
5729
|
-
import { jsx } from "react/jsx-runtime";
|
|
5730
|
-
var nav = [
|
|
5731
|
-
{
|
|
5732
|
-
label: "Agents",
|
|
5733
|
-
value: "agents"
|
|
5734
|
-
},
|
|
5735
|
-
{
|
|
5736
|
-
label: "Start Claude Code",
|
|
5737
|
-
value: "claude-code"
|
|
5738
|
-
},
|
|
5739
|
-
{
|
|
5740
|
-
label: "Exit",
|
|
5741
|
-
value: "exit"
|
|
5742
|
-
}
|
|
5743
|
-
];
|
|
5744
|
-
var Nav = ({ setView }) => {
|
|
5745
|
-
const { exit } = useApp();
|
|
5746
|
-
return /* @__PURE__ */ jsx(Select, { options: nav, onChange: (value) => {
|
|
5747
|
-
if (value === "exit") {
|
|
5748
|
-
exit();
|
|
5749
|
-
}
|
|
5750
|
-
setView(value);
|
|
5751
|
-
} });
|
|
5752
|
-
};
|
|
5753
|
-
var nav_default = Nav;
|
|
5754
|
-
|
|
5755
|
-
// src/cli/components/agent-selector.tsx
|
|
5756
|
-
import { Text as Text2 } from "ink";
|
|
5757
|
-
import { Select as Select2 } from "@inkjs/ui";
|
|
5758
|
-
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
5759
|
-
var AgentSelector = ({ exulu, setAgent, setEvaluations }) => {
|
|
5760
|
-
const agents = exulu.agents.map((agent) => ({
|
|
5761
|
-
label: agent.name,
|
|
5762
|
-
value: agent.id
|
|
5763
|
-
}));
|
|
5764
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5765
|
-
/* @__PURE__ */ jsx2(Text2, { children: "Please select an agent:" }),
|
|
5766
|
-
/* @__PURE__ */ jsx2(Select2, { options: agents, onChange: (value) => {
|
|
5767
|
-
console.log("selected agent", value);
|
|
5768
|
-
const agent = exulu.agent(value);
|
|
5769
|
-
if (!agent) {
|
|
5770
|
-
console.error("Agent not found", value);
|
|
5771
|
-
return;
|
|
5772
|
-
}
|
|
5773
|
-
setAgent(agent);
|
|
5774
|
-
if (agent) {
|
|
5775
|
-
setEvaluations(agent.evals || []);
|
|
5776
|
-
}
|
|
5777
|
-
} })
|
|
5778
|
-
] });
|
|
5779
|
-
};
|
|
5780
|
-
var agent_selector_default = AgentSelector;
|
|
5781
|
-
|
|
5782
|
-
// src/cli/components/eval-selector.tsx
|
|
5783
|
-
import { Select as Select3 } from "@inkjs/ui";
|
|
5784
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
5785
|
-
var EvalSelector = ({ evaluations, setEvaluation }) => {
|
|
5786
|
-
return /* @__PURE__ */ jsx3(Select3, { options: evaluations.map((evaluation) => ({
|
|
5787
|
-
label: evaluation.runner.name,
|
|
5788
|
-
value: evaluation.runner.name
|
|
5789
|
-
})), onChange: (value) => {
|
|
5790
|
-
console.log("selected eval", value);
|
|
5791
|
-
const evaluation = evaluations?.find((evaluation2) => evaluation2.runner.name === value);
|
|
5792
|
-
if (evaluation) {
|
|
5793
|
-
setEvaluation(evaluation);
|
|
5794
|
-
}
|
|
5795
|
-
} });
|
|
5796
|
-
};
|
|
5797
|
-
var eval_selector_default = EvalSelector;
|
|
5798
|
-
|
|
5799
|
-
// src/cli/components/eval-actions.tsx
|
|
5800
|
-
import { useState } from "react";
|
|
5801
|
-
import { ProgressBar as ProgressBar2, Select as Select4, UnorderedList as UnorderedList2 } from "@inkjs/ui";
|
|
5802
|
-
import { Text as Text3 } from "ink";
|
|
5803
|
-
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
5804
|
-
var EvalActions = ({ agent, evaluation, setEvaluation }) => {
|
|
5805
|
-
const [progress, setProgress] = useState(0);
|
|
5806
|
-
const [results, setResults] = useState([]);
|
|
5807
|
-
const [running, setRunning] = useState();
|
|
5808
|
-
const run = async (evaluation2) => {
|
|
5809
|
-
setRunning({
|
|
5810
|
-
label: evaluation2.runner.name
|
|
5811
|
-
});
|
|
5812
|
-
const testCases = evaluation2.runner.testcases;
|
|
5813
|
-
const total = testCases.length;
|
|
5814
|
-
if (!testCases) {
|
|
5815
|
-
throw new Error("No test cases found");
|
|
5816
|
-
}
|
|
5817
|
-
let i = 0;
|
|
5818
|
-
for (const testCase of testCases) {
|
|
5819
|
-
i++;
|
|
5820
|
-
const result = await evaluation2.runner.run({
|
|
5821
|
-
data: testCase,
|
|
5822
|
-
runner: {
|
|
5823
|
-
agent
|
|
5824
|
-
}
|
|
5825
|
-
});
|
|
5826
|
-
setProgress(Math.round(i / total * 100));
|
|
5827
|
-
setResults([...results, {
|
|
5828
|
-
name: evaluation2.runner.name,
|
|
5829
|
-
prompt: testCase.prompt?.slice(0, 100) + "...",
|
|
5830
|
-
score: result.score,
|
|
5831
|
-
comment: result.comment
|
|
5832
|
-
}]);
|
|
5833
|
-
}
|
|
5834
|
-
setRunning(void 0);
|
|
5835
|
-
};
|
|
5836
|
-
if (progress === 100) {
|
|
5837
|
-
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
5838
|
-
/* @__PURE__ */ jsx4(Text3, { children: "Evaluations completed." }),
|
|
5839
|
-
/* @__PURE__ */ jsx4(UnorderedList2, { children: results.map((result) => /* @__PURE__ */ jsx4(UnorderedList2.Item, { children: /* @__PURE__ */ jsxs2(Text3, { children: [
|
|
5840
|
-
result.name,
|
|
5841
|
-
": ",
|
|
5842
|
-
result.score,
|
|
5843
|
-
" - ",
|
|
5844
|
-
result.comment
|
|
5845
|
-
] }) })) })
|
|
5846
|
-
] });
|
|
5847
|
-
}
|
|
5848
|
-
if (running) {
|
|
5849
|
-
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
5850
|
-
/* @__PURE__ */ jsxs2(Text3, { children: [
|
|
5851
|
-
"Running ",
|
|
5852
|
-
running.label,
|
|
5853
|
-
"..."
|
|
5854
|
-
] }),
|
|
5855
|
-
/* @__PURE__ */ jsx4(ProgressBar2, { value: progress })
|
|
5856
|
-
] });
|
|
5857
|
-
}
|
|
5858
|
-
return /* @__PURE__ */ jsx4(Select4, { options: [{
|
|
5859
|
-
label: "Run evaluation",
|
|
5860
|
-
value: "run"
|
|
5861
|
-
}, {
|
|
5862
|
-
label: "Go back",
|
|
5863
|
-
value: "back"
|
|
5864
|
-
}], onChange: (value) => {
|
|
5865
|
-
if (value === "back") {
|
|
5866
|
-
setEvaluation(void 0);
|
|
5867
|
-
}
|
|
5868
|
-
if (value === "run") {
|
|
5869
|
-
run(evaluation);
|
|
5870
|
-
}
|
|
5871
|
-
} });
|
|
5872
|
-
};
|
|
5873
|
-
var eval_actions_default = EvalActions;
|
|
5874
|
-
|
|
5875
|
-
// src/cli/index.tsx
|
|
5876
|
-
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
5877
|
-
var Main = ({ exulu }) => {
|
|
5878
|
-
patchConsole((stream, data) => {
|
|
5879
|
-
setLogs([...logs, data]);
|
|
5880
|
-
});
|
|
5881
|
-
const [logs, setLogs] = useState2([]);
|
|
5882
|
-
const [view, setView] = useState2();
|
|
5883
|
-
const [agent, setAgent] = useState2();
|
|
5884
|
-
const [evaluations, setEvaluations] = useState2([]);
|
|
5885
|
-
const [evaluation, setEvaluation] = useState2();
|
|
5886
|
-
return /* @__PURE__ */ jsxs3(Box2, { borderStyle: "round", borderColor: "cyan", padding: 1, flexDirection: "column", width: "70%", children: [
|
|
5887
|
-
/* @__PURE__ */ jsx5(Text4, { children: "Logs:" }),
|
|
5888
|
-
/* @__PURE__ */ jsx5(UnorderedList3, { children: logs.map((log, index) => /* @__PURE__ */ jsx5(UnorderedList3.Item, { children: /* @__PURE__ */ jsx5(Text4, { children: log }) })) }),
|
|
5889
|
-
!view && /* @__PURE__ */ jsx5(nav_default, { setView }),
|
|
5890
|
-
view === "agents" && !agent && /* @__PURE__ */ jsx5(agent_selector_default, { exulu, setAgent, setEvaluations }),
|
|
5891
|
-
view === "agents" && agent && !evaluation && /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
5892
|
-
/* @__PURE__ */ jsxs3(Text4, { children: [
|
|
5893
|
-
'Selected agent "',
|
|
5894
|
-
agent.name,
|
|
5895
|
-
'". Please select an evaluation:'
|
|
5896
|
-
] }),
|
|
5897
|
-
/* @__PURE__ */ jsx5(eval_selector_default, { evaluations, setEvaluation })
|
|
5898
|
-
] }),
|
|
5899
|
-
view === "agents" && agent && evaluation && /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
5900
|
-
/* @__PURE__ */ jsxs3(Text4, { children: [
|
|
5901
|
-
"Selected evaluation: ",
|
|
5902
|
-
evaluation.runner.name
|
|
5903
|
-
] }),
|
|
5904
|
-
/* @__PURE__ */ jsx5(eval_actions_default, { agent, evaluation, setEvaluation })
|
|
5905
|
-
] })
|
|
5906
|
-
] });
|
|
5907
|
-
};
|
|
5908
|
-
var cli_default = {
|
|
5909
|
-
run: (exulu) => {
|
|
5910
|
-
render2(/* @__PURE__ */ jsx5(Main, { exulu }));
|
|
5911
|
-
}
|
|
5912
|
-
};
|
|
5913
|
-
|
|
5914
6217
|
// src/index.ts
|
|
5915
6218
|
var ExuluJobs = {
|
|
5916
6219
|
redis: redisClient,
|
|
@@ -5940,7 +6243,6 @@ export {
|
|
|
5940
6243
|
ExuluApp,
|
|
5941
6244
|
authentication as ExuluAuthentication,
|
|
5942
6245
|
ExuluChunkers,
|
|
5943
|
-
cli_default as ExuluCli,
|
|
5944
6246
|
ExuluContext,
|
|
5945
6247
|
ExuluDatabase,
|
|
5946
6248
|
ExuluEmbedder,
|