@exulu/backend 1.42.2 → 1.43.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -3
- package/dist/index.cjs +328 -219
- package/dist/index.d.cts +4 -7
- package/dist/index.d.ts +4 -7
- package/dist/index.js +328 -218
- package/package.json +1 -1
- package/types/models/agent.ts +1 -0
package/dist/index.js
CHANGED
|
@@ -1435,6 +1435,10 @@ var addCoreFields = (schema) => {
|
|
|
1435
1435
|
name: "last_processed_at",
|
|
1436
1436
|
type: "date"
|
|
1437
1437
|
});
|
|
1438
|
+
schema.fields.push({
|
|
1439
|
+
name: "embeddings_updated_at",
|
|
1440
|
+
type: "date"
|
|
1441
|
+
});
|
|
1438
1442
|
if (schema.RBAC) {
|
|
1439
1443
|
if (!schema.fields.some((field) => field.name === "rights_mode")) {
|
|
1440
1444
|
schema.fields.push({
|
|
@@ -1648,7 +1652,8 @@ var checkRecordAccess = async (record, request, user) => {
|
|
|
1648
1652
|
const isPublic = record.rights_mode === "public";
|
|
1649
1653
|
const byUsers = record.rights_mode === "users";
|
|
1650
1654
|
const byRoles = record.rights_mode === "roles";
|
|
1651
|
-
const
|
|
1655
|
+
const createdBy = typeof record.created_by === "string" ? record.created_by : record.created_by?.toString();
|
|
1656
|
+
const isCreator = user ? createdBy === user.id.toString() : false;
|
|
1652
1657
|
const isAdmin = user ? user.super_admin : false;
|
|
1653
1658
|
const isApi = user ? user.type === "api" : false;
|
|
1654
1659
|
let hasAccess = "none";
|
|
@@ -1855,6 +1860,7 @@ ${enumValues}
|
|
|
1855
1860
|
fields.push(" maxContextLength: Int");
|
|
1856
1861
|
fields.push(" provider: String");
|
|
1857
1862
|
fields.push(" authenticationInformation: String");
|
|
1863
|
+
fields.push(" systemInstructions: String");
|
|
1858
1864
|
fields.push(" slug: String");
|
|
1859
1865
|
}
|
|
1860
1866
|
const rbacField = table.RBAC ? " RBAC: RBACData" : "";
|
|
@@ -1919,6 +1925,8 @@ input FilterOperatorDate {
|
|
|
1919
1925
|
input FilterOperatorFloat {
|
|
1920
1926
|
eq: Float
|
|
1921
1927
|
ne: Float
|
|
1928
|
+
lte: Float
|
|
1929
|
+
gte: Float
|
|
1922
1930
|
in: [Float]
|
|
1923
1931
|
and: [FilterOperatorFloat]
|
|
1924
1932
|
or: [FilterOperatorFloat]
|
|
@@ -1982,7 +1990,11 @@ var getRequestedFields = (info) => {
|
|
|
1982
1990
|
return fields.filter((field) => field !== "pageInfo" && field !== "items" && field !== "RBAC");
|
|
1983
1991
|
};
|
|
1984
1992
|
var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRbacRecords) => {
|
|
1985
|
-
const {
|
|
1993
|
+
const {
|
|
1994
|
+
users = [],
|
|
1995
|
+
roles = []
|
|
1996
|
+
/* projects = [] */
|
|
1997
|
+
} = rbacData;
|
|
1986
1998
|
if (!existingRbacRecords) {
|
|
1987
1999
|
existingRbacRecords = await db3.from("rbac").where({
|
|
1988
2000
|
entity: entityName,
|
|
@@ -1991,25 +2003,19 @@ var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRba
|
|
|
1991
2003
|
}
|
|
1992
2004
|
const newUserRecords = new Set(users.map((u) => `${u.id}:${u.rights}`));
|
|
1993
2005
|
const newRoleRecords = new Set(roles.map((r) => `${r.id}:${r.rights}`));
|
|
1994
|
-
const newProjectRecords = new Set(projects.map((p) => `${p.id}:${p.rights}`));
|
|
1995
2006
|
const existingUserRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "User").map((r) => `${r.user_id}:${r.rights}`));
|
|
1996
2007
|
const existingRoleRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "Role").map((r) => `${r.role_id}:${r.rights}`));
|
|
1997
2008
|
const existingProjectRecords = new Set(existingRbacRecords.filter((r) => r.access_type === "Project").map((r) => `${r.project_id}:${r.rights}`));
|
|
1998
2009
|
const usersToCreate = users.filter((u) => !existingUserRecords.has(`${u.id}:${u.rights}`));
|
|
1999
2010
|
const rolesToCreate = roles.filter((r) => !existingRoleRecords.has(`${r.id}:${r.rights}`));
|
|
2000
|
-
const projectsToCreate = projects.filter((p) => !existingProjectRecords.has(`${p.id}:${p.rights}`));
|
|
2001
2011
|
const usersToRemove = existingRbacRecords.filter((r) => r.access_type === "User" && !newUserRecords.has(`${r.user_id}:${r.rights}`));
|
|
2002
2012
|
const rolesToRemove = existingRbacRecords.filter((r) => r.access_type === "Role" && !newRoleRecords.has(`${r.role_id}:${r.rights}`));
|
|
2003
|
-
const projectsToRemove = existingRbacRecords.filter((r) => r.access_type === "Project" && !newProjectRecords.has(`${r.project_id}:${r.rights}`));
|
|
2004
2013
|
if (usersToRemove.length > 0) {
|
|
2005
2014
|
await db3.from("rbac").whereIn("id", usersToRemove.map((r) => r.id)).del();
|
|
2006
2015
|
}
|
|
2007
2016
|
if (rolesToRemove.length > 0) {
|
|
2008
2017
|
await db3.from("rbac").whereIn("id", rolesToRemove.map((r) => r.id)).del();
|
|
2009
2018
|
}
|
|
2010
|
-
if (projectsToRemove.length > 0) {
|
|
2011
|
-
await db3.from("rbac").whereIn("id", projectsToRemove.map((r) => r.id)).del();
|
|
2012
|
-
}
|
|
2013
2019
|
const recordsToInsert = [];
|
|
2014
2020
|
usersToCreate.forEach((user) => {
|
|
2015
2021
|
recordsToInsert.push({
|
|
@@ -2033,17 +2039,6 @@ var handleRBACUpdate = async (db3, entityName, resourceId, rbacData, existingRba
|
|
|
2033
2039
|
updatedAt: /* @__PURE__ */ new Date()
|
|
2034
2040
|
});
|
|
2035
2041
|
});
|
|
2036
|
-
projectsToCreate.forEach((project) => {
|
|
2037
|
-
recordsToInsert.push({
|
|
2038
|
-
entity: entityName,
|
|
2039
|
-
access_type: "Project",
|
|
2040
|
-
target_resource_id: resourceId,
|
|
2041
|
-
project_id: project.id,
|
|
2042
|
-
rights: project.rights,
|
|
2043
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
2044
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
2045
|
-
});
|
|
2046
|
-
});
|
|
2047
2042
|
if (recordsToInsert.length > 0) {
|
|
2048
2043
|
await db3.from("rbac").insert(recordsToInsert);
|
|
2049
2044
|
}
|
|
@@ -2057,7 +2052,7 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2057
2052
|
if (user.super_admin === true) {
|
|
2058
2053
|
return true;
|
|
2059
2054
|
}
|
|
2060
|
-
if (!user.super_admin && (!user.role || !(table.name.plural === "agents" && user.role.agents === "write") && !(table.name.plural === "workflow_templates" && user.role.workflows === "write") && !(table.name.plural === "variables" && user.role.variables === "write") && !(table.name.plural === "users" && user.role.users === "write") && !((table.name.plural === "test_cases" || table.name.plural === "eval_sets" || table.name.plural === "eval_runs") && user.role.evals === "write"))) {
|
|
2055
|
+
if (!user.super_admin && (table.name.plural === "agents" || table.name.plural === "workflow_templates" || table.name.plural === "variables" || table.name.plural === "users" || table.name.plural === "test_cases" || table.name.plural === "eval_sets" || table.name.plural === "eval_runs") && (!user.role || !(table.name.plural === "agents" && user.role.agents === "write") && !(table.name.plural === "workflow_templates" && user.role.workflows === "write") && !(table.name.plural === "variables" && user.role.variables === "write") && !(table.name.plural === "users" && user.role.users === "write") && !((table.name.plural === "test_cases" || table.name.plural === "eval_sets" || table.name.plural === "eval_runs") && user.role.evals === "write"))) {
|
|
2061
2056
|
console.error("Access control error: no role found for current user or no access to entity type.");
|
|
2062
2057
|
throw new Error("Access control error: no role found for current user or no access to entity type.");
|
|
2063
2058
|
}
|
|
@@ -2111,6 +2106,61 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2111
2106
|
}
|
|
2112
2107
|
};
|
|
2113
2108
|
const mutations = {
|
|
2109
|
+
[`${tableNamePlural}CopyOneById`]: async (_, args, context, info) => {
|
|
2110
|
+
const { db: db3 } = context;
|
|
2111
|
+
const requestedFields = getRequestedFields(info);
|
|
2112
|
+
let { id } = args;
|
|
2113
|
+
if (!id) {
|
|
2114
|
+
throw new Error("ID is required for copying a record.");
|
|
2115
|
+
}
|
|
2116
|
+
await validateWriteAccess(id, context);
|
|
2117
|
+
const item = await db3.from(tableNamePlural).select("*").where({ id }).first();
|
|
2118
|
+
if (!item) {
|
|
2119
|
+
throw new Error("Record not found");
|
|
2120
|
+
}
|
|
2121
|
+
if (item.rights_mode) {
|
|
2122
|
+
item.rights_mode = "private";
|
|
2123
|
+
}
|
|
2124
|
+
if (item.created_at) {
|
|
2125
|
+
item.created_at = /* @__PURE__ */ new Date();
|
|
2126
|
+
}
|
|
2127
|
+
if (item.createdAt) {
|
|
2128
|
+
item.createdAt = /* @__PURE__ */ new Date();
|
|
2129
|
+
}
|
|
2130
|
+
if (item.updated_at) {
|
|
2131
|
+
item.updated_at = /* @__PURE__ */ new Date();
|
|
2132
|
+
}
|
|
2133
|
+
if (item.updatedAt) {
|
|
2134
|
+
item.updatedAt = /* @__PURE__ */ new Date();
|
|
2135
|
+
}
|
|
2136
|
+
if (item.created_by) {
|
|
2137
|
+
item.created_by = context.user.id;
|
|
2138
|
+
}
|
|
2139
|
+
if (item.createdBy) {
|
|
2140
|
+
item.createdBy = context.user.id;
|
|
2141
|
+
}
|
|
2142
|
+
if (item.name) {
|
|
2143
|
+
item.name = item.name + " (Copy)";
|
|
2144
|
+
}
|
|
2145
|
+
Object.keys(item).forEach((key) => {
|
|
2146
|
+
if (table.fields.find((field) => field.name === key)?.type === "json") {
|
|
2147
|
+
if (typeof item[key] === "object" || Array.isArray(item[key])) {
|
|
2148
|
+
item[key] = JSON.stringify(item[key]);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
const insert = db3(tableNamePlural).insert({
|
|
2153
|
+
...item,
|
|
2154
|
+
id: db3.fn.uuid()
|
|
2155
|
+
}).returning("*");
|
|
2156
|
+
const result = await insert;
|
|
2157
|
+
if (!result[0]) {
|
|
2158
|
+
throw new Error("Failed to copy record.");
|
|
2159
|
+
}
|
|
2160
|
+
return {
|
|
2161
|
+
item: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result: result[0], user: context.user })
|
|
2162
|
+
};
|
|
2163
|
+
},
|
|
2114
2164
|
[`${tableNamePlural}CreateOne`]: async (_, args, context, info) => {
|
|
2115
2165
|
const { db: db3 } = context;
|
|
2116
2166
|
const requestedFields = getRequestedFields(info);
|
|
@@ -2368,7 +2418,7 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2368
2418
|
}
|
|
2369
2419
|
const { limit = 10, filters = [], sort } = args;
|
|
2370
2420
|
const { db: db3 } = context;
|
|
2371
|
-
const { items } = await
|
|
2421
|
+
const { items } = await itemsPaginationRequest({
|
|
2372
2422
|
db: db3,
|
|
2373
2423
|
limit,
|
|
2374
2424
|
page: 0,
|
|
@@ -2467,9 +2517,6 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2467
2517
|
};
|
|
2468
2518
|
};
|
|
2469
2519
|
mutations[`${tableNameSingular}GenerateChunks`] = async (_, args, context, info) => {
|
|
2470
|
-
if (!context.user?.super_admin) {
|
|
2471
|
-
throw new Error("You are not authorized to generate chunks via API, user must be super admin.");
|
|
2472
|
-
}
|
|
2473
2520
|
const { db: db3 } = await postgresClient();
|
|
2474
2521
|
const exists = contexts.find((context2) => context2.id === table.id);
|
|
2475
2522
|
if (!exists) {
|
|
@@ -2480,13 +2527,17 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2480
2527
|
const columns = await db3(mainTable).columnInfo();
|
|
2481
2528
|
let query = db3.from(mainTable).select(Object.keys(columns));
|
|
2482
2529
|
if (!args.where) {
|
|
2530
|
+
if (!context.user?.super_admin) {
|
|
2531
|
+
throw new Error("You are not authorized to generate all chunks via API, user must be super admin.");
|
|
2532
|
+
}
|
|
2483
2533
|
const {
|
|
2484
2534
|
jobs: jobs2,
|
|
2485
2535
|
items: items2
|
|
2486
2536
|
} = await embeddings.generate.all(
|
|
2487
2537
|
config,
|
|
2488
2538
|
context.user.id,
|
|
2489
|
-
context.user.role?.id
|
|
2539
|
+
context.user.role?.id,
|
|
2540
|
+
args.limit
|
|
2490
2541
|
);
|
|
2491
2542
|
return {
|
|
2492
2543
|
message: "Chunks generated successfully.",
|
|
@@ -2495,6 +2546,9 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2495
2546
|
};
|
|
2496
2547
|
}
|
|
2497
2548
|
query = applyFilters(query, args.where, table);
|
|
2549
|
+
if (args.limit) {
|
|
2550
|
+
query = query.limit(args.limit);
|
|
2551
|
+
}
|
|
2498
2552
|
const items = await query;
|
|
2499
2553
|
if (items.length === 0) {
|
|
2500
2554
|
throw new Error("No items found to generate chunks for.");
|
|
@@ -2519,9 +2573,6 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2519
2573
|
};
|
|
2520
2574
|
};
|
|
2521
2575
|
mutations[`${tableNameSingular}DeleteChunks`] = async (_, args, context, info) => {
|
|
2522
|
-
if (!context.user?.super_admin) {
|
|
2523
|
-
throw new Error("You are not authorized to delete chunks via API, user must be super admin.");
|
|
2524
|
-
}
|
|
2525
2576
|
const { db: db3 } = await postgresClient();
|
|
2526
2577
|
const id = contexts.find((context2) => context2.id === table.id)?.id;
|
|
2527
2578
|
if (!id) {
|
|
@@ -2530,6 +2581,10 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2530
2581
|
if (args.where) {
|
|
2531
2582
|
let query = db3.from(getTableName(id)).select("id");
|
|
2532
2583
|
query = applyFilters(query, args.where, table);
|
|
2584
|
+
query = applyAccessControl(table, query, context.user);
|
|
2585
|
+
if (args.limit) {
|
|
2586
|
+
query = query.limit(args.limit);
|
|
2587
|
+
}
|
|
2533
2588
|
const items = await query;
|
|
2534
2589
|
if (items.length === 0) {
|
|
2535
2590
|
throw new Error("No items found to delete chunks for.");
|
|
@@ -2543,11 +2598,20 @@ function createMutations(table, agents, contexts, tools, config) {
|
|
|
2543
2598
|
jobs: []
|
|
2544
2599
|
};
|
|
2545
2600
|
} else {
|
|
2546
|
-
|
|
2547
|
-
|
|
2601
|
+
if (!context.user?.super_admin) {
|
|
2602
|
+
throw new Error("You are not authorized to delete all chunks via API, user must be super admin.");
|
|
2603
|
+
}
|
|
2604
|
+
let count = 0;
|
|
2605
|
+
if (!args.limit) {
|
|
2606
|
+
const result = await db3.from(getChunksTableName(id)).count();
|
|
2607
|
+
count = parseInt(result[0].count);
|
|
2608
|
+
await db3.from(getChunksTableName(id)).truncate();
|
|
2609
|
+
} else {
|
|
2610
|
+
count = await db3.from(getChunksTableName(id)).limit(args.limit).delete();
|
|
2611
|
+
}
|
|
2548
2612
|
return {
|
|
2549
2613
|
message: "Chunks deleted successfully.",
|
|
2550
|
-
items:
|
|
2614
|
+
items: count,
|
|
2551
2615
|
jobs: []
|
|
2552
2616
|
};
|
|
2553
2617
|
}
|
|
@@ -2562,8 +2626,8 @@ var applyAccessControl = (table, query, user, field_prefix) => {
|
|
|
2562
2626
|
}
|
|
2563
2627
|
console.log("[EXULU] user.role", user?.role);
|
|
2564
2628
|
console.log("[EXULU] table.name.plural", table.name.plural);
|
|
2565
|
-
if (user && !user?.super_admin && (!user?.role || !(table.name.plural === "agents" && (user.role.agents === "read" || user.role.agents === "write")) && !(table.name.plural === "workflow_templates" && (user.role.workflows === "read" || user.role.workflows === "write")) && !(table.name.plural === "variables" && (user.role.variables === "read" || user.role.variables === "write")) && !(table.name.plural === "users" && (user.role.users === "read" || user.role.users === "write")) && !((table.name.plural === "test_cases" || table.name.plural === "eval_sets" || table.name.plural === "eval_runs") && (user.role.evals === "read" || user.role.evals === "write")))) {
|
|
2566
|
-
console.error("==== Access control error: no role found or no access to entity type. ====");
|
|
2629
|
+
if (user && !user?.super_admin && (table.name.plural === "agents" || table.name.plural === "workflow_templates" || table.name.plural === "variables" || table.name.plural === "users" || table.name.plural === "test_cases" || table.name.plural === "eval_sets" || table.name.plural === "eval_runs") && (!user?.role || !(table.name.plural === "agents" && (user.role.agents === "read" || user.role.agents === "write")) && !(table.name.plural === "workflow_templates" && (user.role.workflows === "read" || user.role.workflows === "write")) && !(table.name.plural === "variables" && (user.role.variables === "read" || user.role.variables === "write")) && !(table.name.plural === "users" && (user.role.users === "read" || user.role.users === "write")) && !((table.name.plural === "test_cases" || table.name.plural === "eval_sets" || table.name.plural === "eval_runs") && (user.role.evals === "read" || user.role.evals === "write")))) {
|
|
2630
|
+
console.error("==== Access control error: no role found or no access to entity type. ====", user, table.name.plural);
|
|
2567
2631
|
throw new Error("Access control error: no role found or no access to entity type.");
|
|
2568
2632
|
}
|
|
2569
2633
|
const hasRBAC = table.RBAC === true;
|
|
@@ -2606,6 +2670,7 @@ var converOperatorToQuery = (query, fieldName, operators, table, field_prefix) =
|
|
|
2606
2670
|
const isJsonField = field?.type === "json";
|
|
2607
2671
|
const prefix = field_prefix ? field_prefix + "." : "";
|
|
2608
2672
|
fieldName = prefix + fieldName;
|
|
2673
|
+
console.log("[EXULU] operators", operators);
|
|
2609
2674
|
if (operators.eq !== void 0) {
|
|
2610
2675
|
if (isJsonField) {
|
|
2611
2676
|
query = query.whereRaw(`?? = ?::jsonb`, [fieldName, JSON.stringify(operators.eq)]);
|
|
@@ -2637,7 +2702,13 @@ var converOperatorToQuery = (query, fieldName, operators, table, field_prefix) =
|
|
|
2637
2702
|
}
|
|
2638
2703
|
}
|
|
2639
2704
|
if (operators.lte !== void 0) {
|
|
2640
|
-
|
|
2705
|
+
console.log("[EXULU] operators.lte", operators.lte);
|
|
2706
|
+
console.log("[EXULU] fieldName", fieldName);
|
|
2707
|
+
if (operators.lte === 0 || operators.lte === "0") {
|
|
2708
|
+
query = query.whereNull(fieldName).orWhere(fieldName, "=", 0);
|
|
2709
|
+
} else {
|
|
2710
|
+
query = query.where(fieldName, "<=", operators.lte);
|
|
2711
|
+
}
|
|
2641
2712
|
}
|
|
2642
2713
|
if (operators.gte !== void 0) {
|
|
2643
2714
|
query = query.where(fieldName, ">=", operators.gte);
|
|
@@ -2653,7 +2724,8 @@ var backendAgentFields = [
|
|
|
2653
2724
|
"capabilities",
|
|
2654
2725
|
"maxContextLength",
|
|
2655
2726
|
"provider",
|
|
2656
|
-
"authenticationInformation"
|
|
2727
|
+
"authenticationInformation",
|
|
2728
|
+
"systemInstructions"
|
|
2657
2729
|
];
|
|
2658
2730
|
var removeAgentFields = (requestedFields) => {
|
|
2659
2731
|
const filtered = requestedFields.filter((field) => !backendAgentFields.includes(field));
|
|
@@ -2738,6 +2810,9 @@ var addAgentFields = async (args, requestedFields, agents, result, tools, user,
|
|
|
2738
2810
|
if (requestedFields.includes("provider")) {
|
|
2739
2811
|
result.provider = backend?.provider || "";
|
|
2740
2812
|
}
|
|
2813
|
+
if (requestedFields.includes("systemInstructions")) {
|
|
2814
|
+
result.systemInstructions = backend?.config?.instructions || void 0;
|
|
2815
|
+
}
|
|
2741
2816
|
if (!requestedFields.includes("backend")) {
|
|
2742
2817
|
delete result.backend;
|
|
2743
2818
|
}
|
|
@@ -2962,7 +3037,7 @@ var applySorting = (query, sort, field_prefix) => {
|
|
|
2962
3037
|
}
|
|
2963
3038
|
return query;
|
|
2964
3039
|
};
|
|
2965
|
-
var
|
|
3040
|
+
var itemsPaginationRequest = async ({
|
|
2966
3041
|
db: db3,
|
|
2967
3042
|
limit,
|
|
2968
3043
|
page,
|
|
@@ -2992,7 +3067,8 @@ var paginationRequest = async ({
|
|
|
2992
3067
|
if (page > 1) {
|
|
2993
3068
|
dataQuery = dataQuery.offset((page - 1) * limit);
|
|
2994
3069
|
}
|
|
2995
|
-
|
|
3070
|
+
dataQuery = dataQuery.select(fields ? fields : "*").limit(limit);
|
|
3071
|
+
let items = await dataQuery;
|
|
2996
3072
|
return {
|
|
2997
3073
|
items,
|
|
2998
3074
|
pageInfo: {
|
|
@@ -3043,7 +3119,7 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
3043
3119
|
const { limit = 10, page = 0, filters = [], sort } = args;
|
|
3044
3120
|
const requestedFields = getRequestedFields(info);
|
|
3045
3121
|
const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
|
|
3046
|
-
const { items, pageInfo } = await
|
|
3122
|
+
const { items, pageInfo } = await itemsPaginationRequest({
|
|
3047
3123
|
db: db3,
|
|
3048
3124
|
limit,
|
|
3049
3125
|
page,
|
|
@@ -3118,6 +3194,46 @@ function createQueries(table, agents, tools, contexts) {
|
|
|
3118
3194
|
expand: args.expand
|
|
3119
3195
|
});
|
|
3120
3196
|
};
|
|
3197
|
+
queries[`${tableNameSingular}ChunkById`] = async (_, args, context, info) => {
|
|
3198
|
+
const exists = contexts.find((ctx) => ctx.id === table.id);
|
|
3199
|
+
if (!exists) {
|
|
3200
|
+
throw new Error("Context " + table.id + " not found in registry.");
|
|
3201
|
+
}
|
|
3202
|
+
const { db: db3 } = context;
|
|
3203
|
+
const chunksTable = getChunksTableName(exists.id);
|
|
3204
|
+
const mainTable = getTableName(exists.id);
|
|
3205
|
+
const chunk = await db3(chunksTable + " as chunks").select([
|
|
3206
|
+
"chunks.id as chunk_id",
|
|
3207
|
+
"chunks.source as chunk_source",
|
|
3208
|
+
"chunks.content as chunk_content",
|
|
3209
|
+
"chunks.chunk_index",
|
|
3210
|
+
"chunks.metadata as chunk_metadata",
|
|
3211
|
+
db3.raw('chunks."createdAt" as chunk_created_at'),
|
|
3212
|
+
db3.raw('chunks."updatedAt" as chunk_updated_at'),
|
|
3213
|
+
"items.id as item_id",
|
|
3214
|
+
"items.name as item_name",
|
|
3215
|
+
"items.external_id as item_external_id",
|
|
3216
|
+
db3.raw('items."updatedAt" as item_updated_at'),
|
|
3217
|
+
db3.raw('items."createdAt" as item_created_at')
|
|
3218
|
+
]).leftJoin(mainTable + " as items", "chunks.source", "items.id").where("chunks.id", args.id).first();
|
|
3219
|
+
if (!chunk) {
|
|
3220
|
+
return null;
|
|
3221
|
+
}
|
|
3222
|
+
return {
|
|
3223
|
+
chunk_content: chunk.chunk_content,
|
|
3224
|
+
chunk_index: chunk.chunk_index,
|
|
3225
|
+
chunk_id: chunk.chunk_id,
|
|
3226
|
+
chunk_source: chunk.chunk_source,
|
|
3227
|
+
chunk_metadata: chunk.chunk_metadata,
|
|
3228
|
+
chunk_created_at: chunk.chunk_created_at,
|
|
3229
|
+
chunk_updated_at: chunk.chunk_updated_at,
|
|
3230
|
+
item_id: chunk.item_id,
|
|
3231
|
+
item_name: chunk.item_name,
|
|
3232
|
+
item_external_id: chunk.item_external_id,
|
|
3233
|
+
item_updated_at: chunk.item_updated_at,
|
|
3234
|
+
item_created_at: chunk.item_created_at
|
|
3235
|
+
};
|
|
3236
|
+
};
|
|
3121
3237
|
}
|
|
3122
3238
|
return queries;
|
|
3123
3239
|
}
|
|
@@ -3250,120 +3366,52 @@ var vectorSearch = async ({
|
|
|
3250
3366
|
const fullTextWeight = 2;
|
|
3251
3367
|
const semanticWeight = 1;
|
|
3252
3368
|
const rrfK = 50;
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
(1 - (chunks.embedding <=> ${vectorExpr})) AS cosine_distance,
|
|
3300
|
-
|
|
3301
|
-
/* Hybrid RRF score */
|
|
3302
|
-
(
|
|
3303
|
-
COALESCE(1.0 / (? + ft.rank_ix), 0.0) * ?
|
|
3304
|
-
+
|
|
3305
|
-
COALESCE(1.0 / (? + se.rank_ix), 0.0) * ?
|
|
3306
|
-
)::float AS hybrid_score
|
|
3307
|
-
|
|
3308
|
-
FROM full_text ft
|
|
3309
|
-
FULL OUTER JOIN semantic se
|
|
3310
|
-
ON ft.id = se.id
|
|
3311
|
-
JOIN ${chunksTable} as chunks
|
|
3312
|
-
ON COALESCE(ft.id, se.id) = chunks.id
|
|
3313
|
-
JOIN ${mainTable} as items
|
|
3314
|
-
ON items.id = chunks.source
|
|
3315
|
-
WHERE (
|
|
3316
|
-
COALESCE(1.0 / (? + ft.rank_ix), 0.0) * ?
|
|
3317
|
-
+
|
|
3318
|
-
COALESCE(1.0 / (? + se.rank_ix), 0.0) * ?
|
|
3319
|
-
) >= ?
|
|
3320
|
-
AND (chunks.fts IS NULL OR ts_rank(chunks.fts, plainto_tsquery(?, ?)) > ?)
|
|
3321
|
-
AND (chunks.embedding IS NULL OR (1 - (chunks.embedding <=> ${vectorExpr})) >= ?)
|
|
3322
|
-
ORDER BY hybrid_score DESC
|
|
3323
|
-
LIMIT LEAST(?, 250)
|
|
3324
|
-
OFFSET 0
|
|
3325
|
-
`;
|
|
3326
|
-
const bindings = [
|
|
3327
|
-
// full_text: plainto_tsquery(lang, query) in rank and where
|
|
3328
|
-
language,
|
|
3329
|
-
query,
|
|
3330
|
-
language,
|
|
3331
|
-
query,
|
|
3332
|
-
language,
|
|
3333
|
-
query,
|
|
3334
|
-
cutoffs?.tsvector || 0,
|
|
3335
|
-
// full_text tsvector cutoff
|
|
3336
|
-
matchCount,
|
|
3337
|
-
// full_text limit
|
|
3338
|
-
cutoffs?.cosineDistance || 0,
|
|
3339
|
-
// semantic cosine distance cutoff
|
|
3340
|
-
matchCount,
|
|
3341
|
-
// semantic limit
|
|
3342
|
-
// fts_rank (ts_rank) call
|
|
3343
|
-
language,
|
|
3344
|
-
query,
|
|
3345
|
-
// RRF fusion parameters
|
|
3346
|
-
rrfK,
|
|
3347
|
-
fullTextWeight,
|
|
3348
|
-
rrfK,
|
|
3349
|
-
semanticWeight,
|
|
3350
|
-
// WHERE clause hybrid_score filter
|
|
3351
|
-
rrfK,
|
|
3352
|
-
fullTextWeight,
|
|
3353
|
-
rrfK,
|
|
3354
|
-
semanticWeight,
|
|
3355
|
-
cutoffs?.hybrid || 0,
|
|
3356
|
-
// Additional cutoff filters in main WHERE clause
|
|
3357
|
-
language,
|
|
3358
|
-
query,
|
|
3359
|
-
cutoffs?.tsvector || 0,
|
|
3360
|
-
// tsvector cutoff for results from semantic CTE
|
|
3361
|
-
cutoffs?.cosineDistance || 0,
|
|
3362
|
-
// cosine distance cutoff for results from full_text CTE
|
|
3363
|
-
matchCount
|
|
3364
|
-
// final limit
|
|
3365
|
-
];
|
|
3366
|
-
resultChunks = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
|
|
3369
|
+
let fullTextQuery = db3(chunksTable + " as chunks").select([
|
|
3370
|
+
"chunks.id",
|
|
3371
|
+
"chunks.source",
|
|
3372
|
+
db3.raw(`row_number() OVER (ORDER BY ts_rank(chunks.fts, plainto_tsquery(?, ?)) DESC) AS rank_ix`, [language, query])
|
|
3373
|
+
]).leftJoin(mainTable + " as items", "items.id", "chunks.source").whereRaw(`chunks.fts @@ plainto_tsquery(?, ?)`, [language, query]).whereRaw(`ts_rank(chunks.fts, plainto_tsquery(?, ?)) > ?`, [language, query, cutoffs?.tsvector || 0]).whereRaw(`(items.archived IS FALSE OR items.archived IS NULL)`).limit(Math.min(matchCount * 2, 500));
|
|
3374
|
+
fullTextQuery = applyFilters(fullTextQuery, filters, table, "items");
|
|
3375
|
+
fullTextQuery = applyAccessControl(table, fullTextQuery, user, "items");
|
|
3376
|
+
let semanticQuery = db3(chunksTable + " as chunks").select([
|
|
3377
|
+
"chunks.id",
|
|
3378
|
+
"chunks.source",
|
|
3379
|
+
db3.raw(`row_number() OVER (ORDER BY chunks.embedding <=> ${vectorExpr} ASC) AS rank_ix`)
|
|
3380
|
+
]).leftJoin(mainTable + " as items", "items.id", "chunks.source").whereNotNull("chunks.embedding").whereRaw(`(1 - (chunks.embedding <=> ${vectorExpr})) >= ?`, [cutoffs?.cosineDistance || 0]).whereRaw(`(items.archived IS FALSE OR items.archived IS NULL)`).limit(Math.min(matchCount * 2, 500));
|
|
3381
|
+
semanticQuery = applyFilters(semanticQuery, filters, table, "items");
|
|
3382
|
+
semanticQuery = applyAccessControl(table, semanticQuery, user, "items");
|
|
3383
|
+
let hybridQuery = db3.with("full_text", fullTextQuery).with("semantic", semanticQuery).select([
|
|
3384
|
+
"items.id as item_id",
|
|
3385
|
+
"items.name as item_name",
|
|
3386
|
+
"items.external_id as item_external_id",
|
|
3387
|
+
"chunks.id as chunk_id",
|
|
3388
|
+
"chunks.source",
|
|
3389
|
+
"chunks.content",
|
|
3390
|
+
"chunks.chunk_index",
|
|
3391
|
+
"chunks.metadata",
|
|
3392
|
+
db3.raw('chunks."createdAt" as chunk_created_at'),
|
|
3393
|
+
db3.raw('chunks."updatedAt" as chunk_updated_at'),
|
|
3394
|
+
db3.raw('items."updatedAt" as item_updated_at'),
|
|
3395
|
+
db3.raw('items."createdAt" as item_created_at'),
|
|
3396
|
+
db3.raw(`ts_rank(chunks.fts, plainto_tsquery(?, ?)) AS fts_rank`, [language, query]),
|
|
3397
|
+
db3.raw(`(1 - (chunks.embedding <=> ${vectorExpr})) AS cosine_distance`),
|
|
3398
|
+
db3.raw(`
|
|
3399
|
+
(
|
|
3400
|
+
COALESCE(1.0 / (? + ft.rank_ix), 0.0) * ?
|
|
3401
|
+
+
|
|
3402
|
+
COALESCE(1.0 / (? + se.rank_ix), 0.0) * ?
|
|
3403
|
+
)::float AS hybrid_score
|
|
3404
|
+
`, [rrfK, fullTextWeight, rrfK, semanticWeight])
|
|
3405
|
+
]).from("full_text as ft").fullOuterJoin("semantic as se", "ft.id", "se.id").join(chunksTable + " as chunks", function() {
|
|
3406
|
+
this.on(db3.raw("COALESCE(ft.id, se.id)"), "=", "chunks.id");
|
|
3407
|
+
}).join(mainTable + " as items", "items.id", "chunks.source").whereRaw(`
|
|
3408
|
+
(
|
|
3409
|
+
COALESCE(1.0 / (? + ft.rank_ix), 0.0) * ?
|
|
3410
|
+
+
|
|
3411
|
+
COALESCE(1.0 / (? + se.rank_ix), 0.0) * ?
|
|
3412
|
+
) >= ?
|
|
3413
|
+
`, [rrfK, fullTextWeight, rrfK, semanticWeight, cutoffs?.hybrid || 0]).whereRaw(`(chunks.fts IS NULL OR ts_rank(chunks.fts, plainto_tsquery(?, ?)) > ?)`, [language, query, cutoffs?.tsvector || 0]).whereRaw(`(chunks.embedding IS NULL OR (1 - (chunks.embedding <=> ${vectorExpr})) >= ?)`, [cutoffs?.cosineDistance || 0]).orderByRaw("hybrid_score DESC").limit(Math.min(matchCount, 250));
|
|
3414
|
+
resultChunks = await hybridQuery;
|
|
3367
3415
|
}
|
|
3368
3416
|
console.log("[EXULU] Vector search chunk results:", resultChunks?.length);
|
|
3369
3417
|
let results = resultChunks.map((chunk) => ({
|
|
@@ -3599,8 +3647,8 @@ var contextToTableDefinition = (context) => {
|
|
|
3599
3647
|
type: "text"
|
|
3600
3648
|
});
|
|
3601
3649
|
definition.fields.push({
|
|
3602
|
-
name: "
|
|
3603
|
-
type: "
|
|
3650
|
+
name: "chunks_count",
|
|
3651
|
+
type: "number"
|
|
3604
3652
|
});
|
|
3605
3653
|
definition.fields.push({
|
|
3606
3654
|
name: "name",
|
|
@@ -3703,10 +3751,13 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
|
|
|
3703
3751
|
if (table.type === "items") {
|
|
3704
3752
|
typeDefs += `
|
|
3705
3753
|
${tableNamePlural}VectorSearch(query: String!, method: VectorMethodEnum!, filters: [Filter${tableNameSingularUpperCaseFirst}], cutoffs: SearchCutoffs, expand: SearchExpand): ${tableNameSingular}VectorSearchResult
|
|
3754
|
+
${tableNameSingular}ChunkById(id: ID!): ${tableNameSingular}VectorSearchChunk
|
|
3706
3755
|
`;
|
|
3707
3756
|
}
|
|
3708
3757
|
mutationDefs += `
|
|
3709
3758
|
${tableNamePlural}CreateOne(input: ${tableNameSingular}Input!, upsert: Boolean): ${tableNameSingular}MutationPayload
|
|
3759
|
+
${tableNamePlural}CopyOneById(id: ID!): ${tableNameSingular}MutationPayload
|
|
3760
|
+
|
|
3710
3761
|
${tableNamePlural}UpdateOne(where: [Filter${tableNameSingularUpperCaseFirst}], input: ${tableNameSingular}Input!): ${tableNameSingular}MutationPayload
|
|
3711
3762
|
${tableNamePlural}UpdateOneById(id: ID!, input: ${tableNameSingular}Input!): ${tableNameSingular}MutationPayload
|
|
3712
3763
|
${tableNamePlural}RemoveOneById(id: ID!): ${tableNameSingular}
|
|
@@ -3714,9 +3765,9 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
|
|
|
3714
3765
|
`;
|
|
3715
3766
|
if (table.type === "items") {
|
|
3716
3767
|
mutationDefs += `
|
|
3717
|
-
${tableNameSingular}GenerateChunks(where: [Filter${tableNameSingularUpperCaseFirst}]): ${tableNameSingular}GenerateChunksReturnPayload
|
|
3768
|
+
${tableNameSingular}GenerateChunks(where: [Filter${tableNameSingularUpperCaseFirst}], limit: Int): ${tableNameSingular}GenerateChunksReturnPayload
|
|
3718
3769
|
${tableNameSingular}ExecuteSource(source: ID!, inputs: JSON!): ${tableNameSingular}ExecuteSourceReturnPayload
|
|
3719
|
-
${tableNameSingular}DeleteChunks(where: [Filter${tableNameSingularUpperCaseFirst}]): ${tableNameSingular}DeleteChunksReturnPayload
|
|
3770
|
+
${tableNameSingular}DeleteChunks(where: [Filter${tableNameSingularUpperCaseFirst}], limit: Int): ${tableNameSingular}DeleteChunksReturnPayload
|
|
3720
3771
|
`;
|
|
3721
3772
|
if (table.processor) {
|
|
3722
3773
|
mutationDefs += `
|
|
@@ -3850,6 +3901,9 @@ type PageInfo {
|
|
|
3850
3901
|
typeDefs += `
|
|
3851
3902
|
contextById(id: ID!): Context
|
|
3852
3903
|
`;
|
|
3904
|
+
typeDefs += `
|
|
3905
|
+
getUniquePromptTags: [String!]!
|
|
3906
|
+
`;
|
|
3853
3907
|
mutationDefs += `
|
|
3854
3908
|
runEval(id: ID!, test_case_ids: [ID!]): RunEvalReturnPayload
|
|
3855
3909
|
`;
|
|
@@ -4307,6 +4361,38 @@ type PageInfo {
|
|
|
4307
4361
|
array.push("agents");
|
|
4308
4362
|
return [...new Set(array)].sort();
|
|
4309
4363
|
};
|
|
4364
|
+
resolvers.Query["getUniquePromptTags"] = async (_, args, context, info) => {
|
|
4365
|
+
const { db: db3 } = context;
|
|
4366
|
+
const user = context.user;
|
|
4367
|
+
const promptTable = tables.find((t) => t.name.plural === "prompt_library");
|
|
4368
|
+
if (!promptTable) {
|
|
4369
|
+
throw new Error("Prompt library table not found");
|
|
4370
|
+
}
|
|
4371
|
+
let query = db3.from("prompt_library").select("tags");
|
|
4372
|
+
query = applyAccessControl(promptTable, query, user);
|
|
4373
|
+
const results = await query;
|
|
4374
|
+
const allTags = [];
|
|
4375
|
+
for (const row of results) {
|
|
4376
|
+
if (row.tags) {
|
|
4377
|
+
let tags = [];
|
|
4378
|
+
if (typeof row.tags === "string") {
|
|
4379
|
+
try {
|
|
4380
|
+
tags = JSON.parse(row.tags);
|
|
4381
|
+
} catch (e) {
|
|
4382
|
+
tags = [row.tags];
|
|
4383
|
+
}
|
|
4384
|
+
} else if (Array.isArray(row.tags)) {
|
|
4385
|
+
tags = row.tags;
|
|
4386
|
+
}
|
|
4387
|
+
tags.forEach((tag) => {
|
|
4388
|
+
if (tag && typeof tag === "string" && tag.trim()) {
|
|
4389
|
+
allTags.push(tag.trim().toLowerCase());
|
|
4390
|
+
}
|
|
4391
|
+
});
|
|
4392
|
+
}
|
|
4393
|
+
}
|
|
4394
|
+
return [...new Set(allTags)].sort();
|
|
4395
|
+
};
|
|
4310
4396
|
modelDefs += `
|
|
4311
4397
|
type ProviderPaginationResult {
|
|
4312
4398
|
items: [Provider]!
|
|
@@ -4697,14 +4783,16 @@ var addBucketPrefixToKey = (key, bucket) => {
|
|
|
4697
4783
|
}
|
|
4698
4784
|
return `${bucket}/${key}`;
|
|
4699
4785
|
};
|
|
4700
|
-
var uploadFile = async (file, fileName, config, options = {}, user, customBucket) => {
|
|
4786
|
+
var uploadFile = async (file, fileName, config, options = {}, user, customBucket, global) => {
|
|
4701
4787
|
if (!config.fileUploads) {
|
|
4702
4788
|
throw new Error("File uploads are not configured (in the exported uploadFile function)");
|
|
4703
4789
|
}
|
|
4704
4790
|
const client2 = getS3Client(config);
|
|
4705
4791
|
let defaultBucket = config.fileUploads.s3Bucket;
|
|
4706
4792
|
let key = fileName;
|
|
4707
|
-
|
|
4793
|
+
if (!global) {
|
|
4794
|
+
key = addUserPrefixToKey(key, user || "api");
|
|
4795
|
+
}
|
|
4708
4796
|
key = addGeneralPrefixToKey(key, config);
|
|
4709
4797
|
const sanitizedMetadata = sanitizeMetadata(options.metadata);
|
|
4710
4798
|
const command = new PutObjectCommand({
|
|
@@ -4855,7 +4943,7 @@ var createUppyRoutes = async (app, contexts, config) => {
|
|
|
4855
4943
|
return;
|
|
4856
4944
|
}
|
|
4857
4945
|
let allowed = false;
|
|
4858
|
-
if (user.type === "api" || user.super_admin || key.includes(`user_${user.id}/`)) {
|
|
4946
|
+
if (user.type === "api" || user.super_admin || !key.includes(`user_`) || key.includes(`user_${user.id}/`)) {
|
|
4859
4947
|
allowed = true;
|
|
4860
4948
|
}
|
|
4861
4949
|
if (!allowed) {
|
|
@@ -4933,9 +5021,16 @@ var createUppyRoutes = async (app, contexts, config) => {
|
|
|
4933
5021
|
return;
|
|
4934
5022
|
}
|
|
4935
5023
|
const client2 = getS3Client(config);
|
|
5024
|
+
let prefix = `${config.fileUploads.s3prefix ? config.fileUploads.s3prefix.replace(/\/$/, "") + "/" : ""}`;
|
|
5025
|
+
if (!req.headers.global) {
|
|
5026
|
+
prefix += `user_${authenticationResult.user.id}`;
|
|
5027
|
+
} else {
|
|
5028
|
+
prefix += "global";
|
|
5029
|
+
}
|
|
5030
|
+
console.log("[EXULU] prefix", prefix);
|
|
4936
5031
|
const command = new ListObjectsV2Command({
|
|
4937
5032
|
Bucket: config.fileUploads.s3Bucket,
|
|
4938
|
-
Prefix:
|
|
5033
|
+
Prefix: prefix,
|
|
4939
5034
|
MaxKeys: 9,
|
|
4940
5035
|
...req.query.continuationToken && { ContinuationToken: req.query.continuationToken }
|
|
4941
5036
|
});
|
|
@@ -5018,7 +5113,13 @@ var createUppyRoutes = async (app, contexts, config) => {
|
|
|
5018
5113
|
const { filename, contentType } = extractFileParameters(req);
|
|
5019
5114
|
validateFileParameters(filename, contentType);
|
|
5020
5115
|
const key = generateS3Key2(filename);
|
|
5021
|
-
let fullKey =
|
|
5116
|
+
let fullKey = key;
|
|
5117
|
+
console.log("[EXULU] global", req.headers.global);
|
|
5118
|
+
if (!req.headers.global) {
|
|
5119
|
+
fullKey = addUserPrefixToKey(key, user.type === "api" ? "api" : user.id);
|
|
5120
|
+
} else {
|
|
5121
|
+
fullKey = "global/" + key;
|
|
5122
|
+
}
|
|
5022
5123
|
fullKey = addGeneralPrefixToKey(fullKey, config);
|
|
5023
5124
|
console.log("[EXULU] signing on server for user", user.id, "with key", fullKey);
|
|
5024
5125
|
getSignedUrl(
|
|
@@ -5074,7 +5175,13 @@ var createUppyRoutes = async (app, contexts, config) => {
|
|
|
5074
5175
|
return res.status(400).json({ error: "s3: content type must be a string" });
|
|
5075
5176
|
}
|
|
5076
5177
|
const key = `${randomUUID()}-_EXULU_${filename}`;
|
|
5077
|
-
let fullKey =
|
|
5178
|
+
let fullKey = key;
|
|
5179
|
+
console.log("[EXULU] global", req.headers.global);
|
|
5180
|
+
if (!req.headers.global) {
|
|
5181
|
+
fullKey = addUserPrefixToKey(key, user.type === "api" ? "api" : user.id);
|
|
5182
|
+
} else {
|
|
5183
|
+
fullKey = "global/" + key;
|
|
5184
|
+
}
|
|
5078
5185
|
fullKey = addGeneralPrefixToKey(fullKey, config);
|
|
5079
5186
|
console.log("[EXULU] signing on server for user", user.id, "with key", fullKey);
|
|
5080
5187
|
const params = {
|
|
@@ -5722,6 +5829,31 @@ var ExuluAgent2 = class {
|
|
|
5722
5829
|
they are talking with the current date in mind as a reference.`;
|
|
5723
5830
|
let system = instructions || "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.";
|
|
5724
5831
|
system += "\n\n" + genericContext;
|
|
5832
|
+
const includesContextSearchTool = currentTools?.some((tool2) => tool2.name.toLowerCase().includes("context_search") || tool2.id.includes("context_search"));
|
|
5833
|
+
console.log("[EXULU] Current tools: " + currentTools?.map((tool2) => tool2.name));
|
|
5834
|
+
console.log("[EXULU] Includes context search tool: " + includesContextSearchTool);
|
|
5835
|
+
if (includesContextSearchTool) {
|
|
5836
|
+
system += `
|
|
5837
|
+
|
|
5838
|
+
|
|
5839
|
+
|
|
5840
|
+
When you use a context search tool, you will include references to the items
|
|
5841
|
+
retrieved from the context search tool inline in the response using this exact JSON format
|
|
5842
|
+
(all on one line, no line breaks):
|
|
5843
|
+
{item_name: <item_name>, item_id: <item_id>, context: <context_id>, chunk_id: <chunk_id>, chunk_index: <chunk_index>}
|
|
5844
|
+
|
|
5845
|
+
IMPORTANT formatting rules:
|
|
5846
|
+
- Use the exact format shown above, all on ONE line
|
|
5847
|
+
- Do NOT use quotes around field names or values
|
|
5848
|
+
- Use the context ID (e.g., "dx-newlift-newton-knowledge-g3y6r1") from the tool result
|
|
5849
|
+
- Include the file/item name, not the full path
|
|
5850
|
+
- Separate multiple citations with spaces
|
|
5851
|
+
|
|
5852
|
+
Example: {item_name: document.pdf, item_id: abc123, context: my-context-id, chunk_id: chunk_456, chunk_index: 0}
|
|
5853
|
+
|
|
5854
|
+
The citations will be rendered as interactive badges in the UI.
|
|
5855
|
+
`;
|
|
5856
|
+
}
|
|
5725
5857
|
if (prompt) {
|
|
5726
5858
|
let result = { object: null, text: "" };
|
|
5727
5859
|
let inputTokens = 0;
|
|
@@ -5982,6 +6114,31 @@ ${extractedText}
|
|
|
5982
6114
|
const genericContext = "IMPORTANT: \n\n The current date is " + (/* @__PURE__ */ new Date()).toLocaleDateString() + " and the current time is " + (/* @__PURE__ */ new Date()).toLocaleTimeString() + ". If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume they are talking with the current date in mind as a reference.";
|
|
5983
6115
|
let system = instructions || "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.";
|
|
5984
6116
|
system += "\n\n" + genericContext;
|
|
6117
|
+
const includesContextSearchTool = currentTools?.some((tool2) => tool2.name.toLowerCase().includes("context_search") || tool2.id.includes("context_search"));
|
|
6118
|
+
console.log("[EXULU] Current tools: " + currentTools?.map((tool2) => tool2.name));
|
|
6119
|
+
console.log("[EXULU] Includes context search tool: " + includesContextSearchTool);
|
|
6120
|
+
if (includesContextSearchTool) {
|
|
6121
|
+
system += `
|
|
6122
|
+
|
|
6123
|
+
|
|
6124
|
+
|
|
6125
|
+
When you use a context search tool, you will include references to the items
|
|
6126
|
+
retrieved from the context search tool inline in the response using this exact JSON format
|
|
6127
|
+
(all on one line, no line breaks):
|
|
6128
|
+
{item_name: <item_name>, item_id: <item_id>, context: <context_id>, chunk_id: <chunk_id>, chunk_index: <chunk_index>}
|
|
6129
|
+
|
|
6130
|
+
IMPORTANT formatting rules:
|
|
6131
|
+
- Use the exact format shown above, all on ONE line
|
|
6132
|
+
- Do NOT use quotes around field names or values
|
|
6133
|
+
- Use the context ID (e.g., "dx-newlift-newton-knowledge-g3y6r1") from the tool result
|
|
6134
|
+
- Include the file/item name, not the full path
|
|
6135
|
+
- Separate multiple citations with spaces
|
|
6136
|
+
|
|
6137
|
+
Example: {item_name: document.pdf, item_id: abc123, context: my-context-id, chunk_id: chunk_456, chunk_index: 0}
|
|
6138
|
+
|
|
6139
|
+
The citations will be rendered as interactive badges in the UI.
|
|
6140
|
+
`;
|
|
6141
|
+
}
|
|
5985
6142
|
const result = streamText({
|
|
5986
6143
|
model,
|
|
5987
6144
|
// Should be a LanguageModelV1
|
|
@@ -6525,6 +6682,7 @@ var ExuluContext = class {
|
|
|
6525
6682
|
})));
|
|
6526
6683
|
}
|
|
6527
6684
|
await db3.from(getTableName(this.id)).where({ id: item.id }).update({
|
|
6685
|
+
chunks_count: chunks?.length || 0,
|
|
6528
6686
|
embeddings_updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
6529
6687
|
}).returning("id");
|
|
6530
6688
|
return {
|
|
@@ -6794,9 +6952,13 @@ var ExuluContext = class {
|
|
|
6794
6952
|
trigger: trigger || "agent"
|
|
6795
6953
|
}, role, void 0);
|
|
6796
6954
|
},
|
|
6797
|
-
all: async (config, userId, roleId) => {
|
|
6955
|
+
all: async (config, userId, roleId, limit) => {
|
|
6798
6956
|
const { db: db3 } = await postgresClient();
|
|
6799
|
-
|
|
6957
|
+
let query = db3.from(getTableName(this.id)).select("*");
|
|
6958
|
+
if (limit) {
|
|
6959
|
+
query = query.limit(limit);
|
|
6960
|
+
}
|
|
6961
|
+
const items = await query;
|
|
6800
6962
|
const jobs = [];
|
|
6801
6963
|
const queue = await this.embedder?.queue;
|
|
6802
6964
|
if (!queue?.queue.name && items.length > 2e3) {
|
|
@@ -6841,7 +7003,7 @@ var ExuluContext = class {
|
|
|
6841
7003
|
table.text("rights_mode").defaultTo(this.configuration?.defaultRightsMode ?? "private");
|
|
6842
7004
|
table.integer("textlength");
|
|
6843
7005
|
table.text("source");
|
|
6844
|
-
table.
|
|
7006
|
+
table.integer("chunks_count").defaultTo(0);
|
|
6845
7007
|
for (const field of this.fields) {
|
|
6846
7008
|
let { type, name, unique } = field;
|
|
6847
7009
|
if (!type || !name) {
|
|
@@ -7370,12 +7532,6 @@ Mood: friendly and intelligent.
|
|
|
7370
7532
|
return;
|
|
7371
7533
|
}
|
|
7372
7534
|
}
|
|
7373
|
-
if (user?.type !== "api" && !user?.super_admin && req.body.resourceId !== user?.id) {
|
|
7374
|
-
res.status(400).json({
|
|
7375
|
-
message: "The provided user id in the resourceId field is not the same as the authenticated user. Only super admins and API users can impersonate other users."
|
|
7376
|
-
});
|
|
7377
|
-
return;
|
|
7378
|
-
}
|
|
7379
7535
|
console.log("[EXULU] agent tools", agentInstance.tools?.map((x) => x.name + " (" + x.id + ")"));
|
|
7380
7536
|
const disabledTools = req.body.disabledTools ? req.body.disabledTools : [];
|
|
7381
7537
|
let enabledTools = await getEnabledTools(agentInstance, tools, disabledTools, agents, user);
|
|
@@ -9420,45 +9576,6 @@ var createLogger = ({
|
|
|
9420
9576
|
};
|
|
9421
9577
|
var logger_default = createLogger;
|
|
9422
9578
|
|
|
9423
|
-
// src/templates/contexts/code-standards.ts
|
|
9424
|
-
var codeStandardsContext = new ExuluContext({
|
|
9425
|
-
id: "code_standards",
|
|
9426
|
-
name: "Code Standards",
|
|
9427
|
-
description: "Code standards that can be used with the Exulu CLI.",
|
|
9428
|
-
configuration: {
|
|
9429
|
-
defaultRightsMode: "public"
|
|
9430
|
-
},
|
|
9431
|
-
fields: [{
|
|
9432
|
-
name: "Best practices",
|
|
9433
|
-
type: "longText"
|
|
9434
|
-
}, {
|
|
9435
|
-
name: "Code style",
|
|
9436
|
-
type: "longText"
|
|
9437
|
-
}, {
|
|
9438
|
-
name: "Tech stack",
|
|
9439
|
-
type: "longText"
|
|
9440
|
-
}],
|
|
9441
|
-
active: true
|
|
9442
|
-
});
|
|
9443
|
-
|
|
9444
|
-
// src/templates/contexts/outputs.ts
|
|
9445
|
-
var outputsContext = new ExuluContext({
|
|
9446
|
-
id: "outputs_default_context",
|
|
9447
|
-
name: "Outputs",
|
|
9448
|
-
description: "Outputs from agent sessions that you have saved for re-used later.",
|
|
9449
|
-
configuration: {
|
|
9450
|
-
defaultRightsMode: "private",
|
|
9451
|
-
calculateVectors: "manual"
|
|
9452
|
-
},
|
|
9453
|
-
fields: [
|
|
9454
|
-
{
|
|
9455
|
-
name: "content",
|
|
9456
|
-
type: "longText"
|
|
9457
|
-
}
|
|
9458
|
-
],
|
|
9459
|
-
active: true
|
|
9460
|
-
});
|
|
9461
|
-
|
|
9462
9579
|
// src/registry/index.ts
|
|
9463
9580
|
import winston2 from "winston";
|
|
9464
9581
|
import util from "util";
|
|
@@ -10585,9 +10702,7 @@ var ExuluApp = class {
|
|
|
10585
10702
|
...evals ?? []
|
|
10586
10703
|
] : [];
|
|
10587
10704
|
this._contexts = {
|
|
10588
|
-
...contexts
|
|
10589
|
-
codeStandardsContext,
|
|
10590
|
-
outputsContext
|
|
10705
|
+
...contexts
|
|
10591
10706
|
};
|
|
10592
10707
|
this._agents = [
|
|
10593
10708
|
claudeSonnet4Agent,
|
|
@@ -12323,10 +12438,6 @@ var ExuluJobs = {
|
|
|
12323
12438
|
validate: validateJob
|
|
12324
12439
|
}
|
|
12325
12440
|
};
|
|
12326
|
-
var ExuluDefaultContexts = {
|
|
12327
|
-
codeStandards: codeStandardsContext,
|
|
12328
|
-
outputs: outputsContext
|
|
12329
|
-
};
|
|
12330
12441
|
var ExuluDefaultAgents = {
|
|
12331
12442
|
anthropic: {
|
|
12332
12443
|
opus4: claudeOpus4Agent,
|
|
@@ -12484,7 +12595,6 @@ export {
|
|
|
12484
12595
|
ExuluChunkers,
|
|
12485
12596
|
ExuluContext,
|
|
12486
12597
|
ExuluDefaultAgents,
|
|
12487
|
-
ExuluDefaultContexts,
|
|
12488
12598
|
ExuluEmbedder,
|
|
12489
12599
|
ExuluEval2 as ExuluEval,
|
|
12490
12600
|
ExuluJobs,
|