@exulu/backend 1.37.0 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -219,11 +219,17 @@ async function postgresClient() {
219
219
  pool: {
220
220
  min: 2,
221
221
  max: 20,
222
+ // Increased from 20 to handle more concurrent operations
222
223
  acquireTimeoutMillis: 3e4,
223
224
  createTimeoutMillis: 3e4,
224
225
  idleTimeoutMillis: 3e4,
225
226
  reapIntervalMillis: 1e3,
226
- createRetryIntervalMillis: 200
227
+ createRetryIntervalMillis: 200,
228
+ // Log pool events to help debug connection issues
229
+ afterCreate: (conn, done) => {
230
+ console.log("[EXULU] New database connection created");
231
+ done(null, conn);
232
+ }
227
233
  }
228
234
  });
229
235
  try {
@@ -463,7 +469,9 @@ var getToken = async (authHeader) => {
463
469
  }
464
470
  try {
465
471
  const secret = process.env.NEXTAUTH_SECRET;
466
- const jwk = await (0, import_jose.importJWK)({ k: secret, alg: "HS256", kty: "oct" });
472
+ const secretBuffer = Buffer.from(secret, "utf-8");
473
+ const base64Secret = secretBuffer.toString("base64url");
474
+ const jwk = await (0, import_jose.importJWK)({ k: base64Secret, alg: "HS256", kty: "oct" });
467
475
  const { payload } = await (0, import_jose.jwtVerify)(token, jwk);
468
476
  return payload;
469
477
  } catch (error) {
@@ -502,6 +510,8 @@ var authentication = async ({
502
510
  type: "api",
503
511
  id: 192837465,
504
512
  email: "internal@exulu.com",
513
+ firstname: "API",
514
+ lastname: "User",
505
515
  role: {
506
516
  id: "internal",
507
517
  name: "Internal",
@@ -1335,10 +1345,10 @@ var rbacSchema = {
1335
1345
  name: "user_id",
1336
1346
  type: "number"
1337
1347
  },
1338
- {
1339
- name: "project_id",
1340
- type: "uuid"
1341
- },
1348
+ /* {
1349
+ name: "project_id",
1350
+ type: "uuid"
1351
+ }, */
1342
1352
  {
1343
1353
  name: "rights",
1344
1354
  type: "text",
@@ -2248,7 +2258,7 @@ function createMutations(table, agents, contexts, tools, config) {
2248
2258
  });
2249
2259
  return {
2250
2260
  // Filter result to only include requested fields
2251
- item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user }),
2261
+ item: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result: results[0], user: context.user }),
2252
2262
  job
2253
2263
  };
2254
2264
  },
@@ -2296,7 +2306,7 @@ function createMutations(table, agents, contexts, tools, config) {
2296
2306
  }
2297
2307
  const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id, config });
2298
2308
  return {
2299
- item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2309
+ item: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2300
2310
  job
2301
2311
  };
2302
2312
  },
@@ -2337,7 +2347,7 @@ function createMutations(table, agents, contexts, tools, config) {
2337
2347
  const result = await db3.from(tableNamePlural).select(Object.keys(columns)).where({ id }).first();
2338
2348
  const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id, config });
2339
2349
  return {
2340
- item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2350
+ item: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2341
2351
  job
2342
2352
  };
2343
2353
  },
@@ -2369,7 +2379,7 @@ function createMutations(table, agents, contexts, tools, config) {
2369
2379
  }).del();
2370
2380
  }
2371
2381
  await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
2372
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2382
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2373
2383
  },
2374
2384
  [`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
2375
2385
  const { where } = args;
@@ -2393,7 +2403,7 @@ function createMutations(table, agents, contexts, tools, config) {
2393
2403
  }
2394
2404
  await db3(tableNamePlural).where(where).del();
2395
2405
  await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
2396
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2406
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2397
2407
  }
2398
2408
  };
2399
2409
  if (table.type === "items") {
@@ -2426,20 +2436,20 @@ function createMutations(table, agents, contexts, tools, config) {
2426
2436
  }
2427
2437
  const { db: db3 } = context;
2428
2438
  let query = db3.from(tableNamePlural).select("*").where({ id: args.item });
2429
- query = applyAccessControl(table, context.user, query);
2439
+ query = applyAccessControl(table, query, context.user);
2430
2440
  const item = await query.first();
2431
2441
  if (!item) {
2432
2442
  throw new Error("Item not found, or your user does not have access to it.");
2433
2443
  }
2434
2444
  const { job, result } = await exists.processField(
2435
2445
  "api",
2436
- context.user.id,
2437
- context.user.role?.id,
2438
2446
  {
2439
2447
  ...item,
2440
2448
  field: args.field
2441
2449
  },
2442
- config
2450
+ config,
2451
+ context.user.id,
2452
+ context.user.role?.id
2443
2453
  );
2444
2454
  return {
2445
2455
  message: job ? "Processing job scheduled." : "Item processed successfully.",
@@ -2486,7 +2496,10 @@ function createMutations(table, agents, contexts, tools, config) {
2486
2496
  };
2487
2497
  }
2488
2498
  console.log("[EXULU] Executing source function directly");
2489
- const result = await source.execute(args.inputs);
2499
+ const result = await source.execute({
2500
+ ...args.inputs,
2501
+ exuluConfig: config
2502
+ });
2490
2503
  let jobs = [];
2491
2504
  let items = [];
2492
2505
  for (const item of result) {
@@ -2610,14 +2623,14 @@ function createMutations(table, agents, contexts, tools, config) {
2610
2623
  }
2611
2624
  return mutations;
2612
2625
  }
2613
- var applyAccessControl = (table, user, query) => {
2626
+ var applyAccessControl = (table, query, user) => {
2614
2627
  const tableNamePlural = table.name.plural.toLowerCase();
2615
- if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2628
+ if (table.name.plural !== "agent_sessions" && user?.super_admin === true) {
2616
2629
  return query;
2617
2630
  }
2618
- console.log("[EXULU] user.role", user.role);
2631
+ console.log("[EXULU] user.role", user?.role);
2619
2632
  console.log("[EXULU] table.name.plural", table.name.plural);
2620
- if (!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")))) {
2633
+ if (!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")))) {
2621
2634
  console.error("==== Access control error: no role found or no access to entity type. ====");
2622
2635
  throw new Error("Access control error: no role found or no access to entity type.");
2623
2636
  }
@@ -2706,7 +2719,7 @@ var removeAgentFields = (requestedFields) => {
2706
2719
  filtered.push("backend");
2707
2720
  return filtered;
2708
2721
  };
2709
- var addAgentFields = async (requestedFields, agents, result, tools, user) => {
2722
+ var addAgentFields = async (args, requestedFields, agents, result, tools, user, contexts) => {
2710
2723
  let backend = agents.find((a) => a.id === result?.backend);
2711
2724
  if (requestedFields.includes("providerName")) {
2712
2725
  result.providerName = backend?.providerName || "";
@@ -2753,6 +2766,17 @@ var addAgentFields = async (requestedFields, agents, result, tools, user) => {
2753
2766
  console.log("[EXULU] hydratedTool", hydratedTool);
2754
2767
  return hydratedTool;
2755
2768
  }));
2769
+ if (args.project) {
2770
+ const projectTool = await createProjectRetrievalTool({
2771
+ projectId: args.project,
2772
+ user,
2773
+ role: user.role?.id,
2774
+ contexts
2775
+ });
2776
+ if (projectTool) {
2777
+ result.tools.unshift(projectTool);
2778
+ }
2779
+ }
2756
2780
  result.tools = result.tools.filter((tool2) => tool2 !== null);
2757
2781
  } else {
2758
2782
  result.tools = [];
@@ -2891,6 +2915,7 @@ var postprocessDeletion = async ({
2891
2915
  return result;
2892
2916
  };
2893
2917
  var finalizeRequestedFields = async ({
2918
+ args,
2894
2919
  table,
2895
2920
  requestedFields,
2896
2921
  agents,
@@ -2907,11 +2932,28 @@ var finalizeRequestedFields = async ({
2907
2932
  }
2908
2933
  if (Array.isArray(result)) {
2909
2934
  result = result.map((item) => {
2910
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item, user });
2935
+ return finalizeRequestedFields({
2936
+ args,
2937
+ table,
2938
+ requestedFields,
2939
+ agents,
2940
+ contexts,
2941
+ tools,
2942
+ result: item,
2943
+ user
2944
+ });
2911
2945
  });
2912
2946
  } else {
2913
2947
  if (table.name.singular === "agent") {
2914
- result = await addAgentFields(requestedFields, agents, result, tools, user);
2948
+ result = await addAgentFields(
2949
+ args,
2950
+ requestedFields,
2951
+ agents,
2952
+ result,
2953
+ tools,
2954
+ user,
2955
+ contexts
2956
+ );
2915
2957
  if (!requestedFields.includes("backend")) {
2916
2958
  delete result.backend;
2917
2959
  }
@@ -2988,18 +3030,18 @@ function createQueries(table, agents, tools, contexts) {
2988
3030
  const requestedFields = getRequestedFields(info);
2989
3031
  const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2990
3032
  let query = db3.from(tableNamePlural).select(sanitizedFields).where({ id: args.id });
2991
- query = applyAccessControl(table, context.user, query);
3033
+ query = applyAccessControl(table, query, context.user);
2992
3034
  let result = await query.first();
2993
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
3035
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user });
2994
3036
  },
2995
3037
  [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2996
3038
  const { db: db3 } = context;
2997
3039
  const requestedFields = getRequestedFields(info);
2998
3040
  const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2999
3041
  let query = db3.from(tableNamePlural).select(sanitizedFields).whereIn("id", args.ids);
3000
- query = applyAccessControl(table, context.user, query);
3042
+ query = applyAccessControl(table, query, context.user);
3001
3043
  let result = await query;
3002
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
3044
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user });
3003
3045
  },
3004
3046
  [`${tableNameSingular}One`]: async (_, args, context, info) => {
3005
3047
  const { filters = [], sort } = args;
@@ -3008,10 +3050,10 @@ function createQueries(table, agents, tools, contexts) {
3008
3050
  const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
3009
3051
  let query = db3.from(tableNamePlural).select(sanitizedFields);
3010
3052
  query = applyFilters(query, filters, table);
3011
- query = applyAccessControl(table, context.user, query);
3053
+ query = applyAccessControl(table, query, context.user);
3012
3054
  query = applySorting(query, sort);
3013
3055
  let result = await query.first();
3014
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
3056
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user });
3015
3057
  },
3016
3058
  [`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
3017
3059
  const { limit = 10, page = 0, filters = [], sort } = args;
@@ -3021,7 +3063,7 @@ function createQueries(table, agents, tools, contexts) {
3021
3063
  }
3022
3064
  let countQuery = db3(tableNamePlural);
3023
3065
  countQuery = applyFilters(countQuery, filters, table);
3024
- countQuery = applyAccessControl(table, context.user, countQuery);
3066
+ countQuery = applyAccessControl(table, countQuery, context.user);
3025
3067
  const countResult = await countQuery.count("* as count");
3026
3068
  const itemCount = Number(countResult[0]?.count || 0);
3027
3069
  const pageCount = Math.ceil(itemCount / limit);
@@ -3030,7 +3072,7 @@ function createQueries(table, agents, tools, contexts) {
3030
3072
  const hasNextPage = currentPage < pageCount - 1;
3031
3073
  let dataQuery = db3(tableNamePlural);
3032
3074
  dataQuery = applyFilters(dataQuery, filters, table);
3033
- dataQuery = applyAccessControl(table, context.user, dataQuery);
3075
+ dataQuery = applyAccessControl(table, dataQuery, context.user);
3034
3076
  const requestedFields = getRequestedFields(info);
3035
3077
  dataQuery = applySorting(dataQuery, sort);
3036
3078
  if (page > 1) {
@@ -3046,7 +3088,7 @@ function createQueries(table, agents, tools, contexts) {
3046
3088
  hasPreviousPage,
3047
3089
  hasNextPage
3048
3090
  },
3049
- items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items, user: context.user })
3091
+ items: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result: items, user: context.user })
3050
3092
  };
3051
3093
  },
3052
3094
  // Add generic statistics query for all tables
@@ -3055,7 +3097,7 @@ function createQueries(table, agents, tools, contexts) {
3055
3097
  const { db: db3 } = context;
3056
3098
  let query = db3(tableNamePlural);
3057
3099
  query = applyFilters(query, filters, table);
3058
- query = applyAccessControl(table, context.user, query);
3100
+ query = applyAccessControl(table, query, context.user);
3059
3101
  if (groupBy) {
3060
3102
  query = query.select(groupBy).groupBy(groupBy);
3061
3103
  if (tableNamePlural === "tracking") {
@@ -3155,11 +3197,11 @@ var vectorSearch = async ({
3155
3197
  const chunksTable = getChunksTableName(id);
3156
3198
  let countQuery = db3(mainTable);
3157
3199
  countQuery = applyFilters(countQuery, filters, table);
3158
- countQuery = applyAccessControl(table, user, countQuery);
3200
+ countQuery = applyAccessControl(table, countQuery, user);
3159
3201
  const columns = await db3(mainTable).columnInfo();
3160
3202
  let itemsQuery = db3(mainTable).select(Object.keys(columns).map((column) => mainTable + "." + column));
3161
3203
  itemsQuery = applyFilters(itemsQuery, filters, table);
3162
- itemsQuery = applyAccessControl(table, user, itemsQuery);
3204
+ itemsQuery = applyAccessControl(table, itemsQuery, user);
3163
3205
  itemsQuery = applySorting(itemsQuery, sort);
3164
3206
  if (queryRewriter) {
3165
3207
  query = await queryRewriter(query);
@@ -3178,7 +3220,7 @@ var vectorSearch = async ({
3178
3220
  const { chunks } = await embedder.generateFromQuery(context.id, query, {
3179
3221
  label: table.name.singular,
3180
3222
  trigger
3181
- }, user.id, role);
3223
+ }, user?.id, role);
3182
3224
  if (!chunks?.[0]?.vector) {
3183
3225
  throw new Error("No vector generated for query.");
3184
3226
  }
@@ -3568,7 +3610,8 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
3568
3610
  const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
3569
3611
  const processorFields = table.fields.filter((field) => field.processor?.execute);
3570
3612
  typeDefs += `
3571
- ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
3613
+ ${tableNameSingular === "agent" ? `${tableNameSingular}ById(id: ID!, project: ID): ${tableNameSingular}` : `${tableNameSingular}ById(id: ID!): ${tableNameSingular}`}
3614
+
3572
3615
  ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
3573
3616
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
3574
3617
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
@@ -4481,16 +4524,27 @@ var addPrefixToKey = (keyPath, config) => {
4481
4524
  }
4482
4525
  return `${prefix}/${keyPath}`;
4483
4526
  };
4484
- var uploadFile = async (user, file, key, config, options = {}) => {
4485
- console.log("[EXULU] Uploading file to S3", key);
4527
+ var uploadFile = async (file, key, config, options = {}, user) => {
4486
4528
  if (!config.fileUploads) {
4487
- throw new Error("File uploads are not configured");
4529
+ throw new Error("File uploads are not configured (in the exported uploadFile function)");
4488
4530
  }
4489
4531
  const client2 = getS3Client(config);
4490
- let folder = `${user}/`;
4532
+ let defaultBucket = config.fileUploads.s3Bucket;
4533
+ let customBucket = false;
4534
+ if (key.includes("[bucket:")) {
4535
+ console.log("[EXULU] key includes [bucket:name]", key);
4536
+ customBucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4537
+ if (!customBucket?.length) {
4538
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4539
+ }
4540
+ key = key.split("]")[1] || "";
4541
+ console.log("[EXULU] custom bucket", customBucket);
4542
+ }
4543
+ let folder = user ? `${user}/` : "";
4491
4544
  const fullKey = addPrefixToKey(!key.includes(folder) ? folder + key : key, config);
4545
+ console.log("[EXULU] uploading file to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4492
4546
  const command = new import_client_s3.PutObjectCommand({
4493
- Bucket: config.fileUploads.s3Bucket,
4547
+ Bucket: customBucket || defaultBucket,
4494
4548
  Key: fullKey,
4495
4549
  Body: file,
4496
4550
  ContentType: options.contentType,
@@ -4498,6 +4552,10 @@ var uploadFile = async (user, file, key, config, options = {}) => {
4498
4552
  ContentLength: file.byteLength
4499
4553
  });
4500
4554
  await client2.send(command);
4555
+ console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4556
+ if (customBucket) {
4557
+ return "[bucket:" + customBucket + "]" + fullKey;
4558
+ }
4501
4559
  return fullKey;
4502
4560
  };
4503
4561
  var createUppyRoutes = async (app, config) => {
@@ -4656,16 +4714,12 @@ var createUppyRoutes = async (app, config) => {
4656
4714
  key = key.split("]")[1] || "";
4657
4715
  console.log("[EXULU] key", key);
4658
4716
  }
4659
- console.log("[EXULU] Getting object metadata from s3", key);
4660
- console.log("[EXULU] bucket", bucket);
4661
- console.log("[EXULU] key", key);
4662
4717
  const client2 = getS3Client(config);
4663
4718
  const command = new import_client_s3.HeadObjectCommand({
4664
4719
  Bucket: bucket,
4665
4720
  Key: key
4666
4721
  });
4667
4722
  const response = await client2.send(command);
4668
- console.log("[EXULU] Object metadata from s3", response);
4669
4723
  res.json(response);
4670
4724
  res.end();
4671
4725
  });
@@ -4969,6 +5023,7 @@ var createUppyRoutes = async (app, config) => {
4969
5023
  };
4970
5024
 
4971
5025
  // src/registry/classes.ts
5026
+ var import_officeparser = require("officeparser");
4972
5027
  var s3Client2;
4973
5028
  function sanitizeToolName(name) {
4974
5029
  if (typeof name !== "string") return "";
@@ -4979,12 +5034,117 @@ function sanitizeToolName(name) {
4979
5034
  }
4980
5035
  return sanitized;
4981
5036
  }
4982
- var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req) => {
5037
+ var projectsCache = /* @__PURE__ */ new Map();
5038
+ var createProjectRetrievalTool = async ({
5039
+ user,
5040
+ role,
5041
+ contexts,
5042
+ projectId
5043
+ }) => {
5044
+ let project;
5045
+ const cachedProject = projectsCache.get(projectId);
5046
+ const OneMinuteAgo = new Date(Date.now() - 1e3 * 60);
5047
+ if (cachedProject && cachedProject.age > OneMinuteAgo) {
5048
+ project = cachedProject.project;
5049
+ } else {
5050
+ const { db: db3 } = await postgresClient();
5051
+ project = await db3.from("projects").where("id", projectId).first();
5052
+ if (project) {
5053
+ projectsCache.set(projectId, {
5054
+ age: /* @__PURE__ */ new Date(),
5055
+ project
5056
+ });
5057
+ } else {
5058
+ return;
5059
+ }
5060
+ }
5061
+ console.log("[EXULU] Project search tool created for project", project);
5062
+ if (!project.project_items?.length) {
5063
+ return;
5064
+ }
5065
+ const projectRetrievalTool = new ExuluTool2({
5066
+ id: "project_information_retrieval_tool_" + projectId,
5067
+ name: "Project information retrieval tool for project " + project.name,
5068
+ description: "This tool retrieves information about a project from conversations and items that were added to the project " + project.name + ".",
5069
+ inputSchema: import_zod.z.object({
5070
+ query: import_zod.z.string().describe("The query to retrieve information about the project " + project.name + "."),
5071
+ keywords: import_zod.z.array(import_zod.z.string()).describe("The most relevant keywords in the query, such as names of people, companies, products, etc. in the project " + project.name + ".")
5072
+ }),
5073
+ type: "function",
5074
+ category: "project",
5075
+ config: [],
5076
+ execute: async ({ query, keywords }) => {
5077
+ console.log("[EXULU] Project search tool searching for project", project);
5078
+ const items = project.project_items;
5079
+ const set = {};
5080
+ for (const item of items) {
5081
+ const context = item.split("/")[0];
5082
+ if (!context) {
5083
+ throw new Error("The item added to the project does not have a valid gid with the context id as the prefix before the first slash.");
5084
+ }
5085
+ const id = item.split("/")[1];
5086
+ if (set[context]) {
5087
+ set[context].push(id);
5088
+ } else {
5089
+ set[context] = [id];
5090
+ }
5091
+ }
5092
+ console.log("[EXULU] Project search tool searching through contexts", Object.keys(set));
5093
+ const results = await Promise.all(Object.keys(set).map(async (contextName, index) => {
5094
+ const context = contexts.find((context2) => context2.id === contextName);
5095
+ if (!context) {
5096
+ console.error("[EXULU] Context not found for project information retrieval tool.", contextName);
5097
+ return [];
5098
+ }
5099
+ const itemIds = set[contextName];
5100
+ console.log("[EXULU] Project search tool searching through items", itemIds);
5101
+ return await context.search({
5102
+ // todo check if it is more performant to use a concatenation of
5103
+ // the query and keywords, or just the keywords, instead of the
5104
+ // query itself.
5105
+ query,
5106
+ filters: [{
5107
+ id: {
5108
+ in: itemIds
5109
+ }
5110
+ }],
5111
+ user,
5112
+ role,
5113
+ method: "hybridSearch",
5114
+ sort: {
5115
+ field: "updatedAt",
5116
+ direction: "desc"
5117
+ },
5118
+ trigger: "tool",
5119
+ limit: 10,
5120
+ page: 1
5121
+ });
5122
+ }));
5123
+ console.log("[EXULU] Project search tool results", results);
5124
+ return {
5125
+ result: JSON.stringify(results.flat())
5126
+ };
5127
+ }
5128
+ });
5129
+ return projectRetrievalTool;
5130
+ };
5131
+ var convertToolsArrayToObject = async (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req, project) => {
4983
5132
  if (!currentTools) return {};
4984
5133
  if (!allExuluTools) return {};
4985
5134
  if (!contexts) {
4986
5135
  contexts = [];
4987
5136
  }
5137
+ if (project) {
5138
+ const projectRetrievalTool = await createProjectRetrievalTool({
5139
+ user,
5140
+ role: user?.role?.id,
5141
+ contexts,
5142
+ projectId: project
5143
+ });
5144
+ if (projectRetrievalTool) {
5145
+ currentTools.push(projectRetrievalTool);
5146
+ }
5147
+ }
4988
5148
  const sanitizedTools = currentTools ? currentTools.map((tool2) => ({
4989
5149
  ...tool2,
4990
5150
  name: sanitizeToolName(tool2.name)
@@ -4993,9 +5153,9 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
4993
5153
  return {
4994
5154
  ...sanitizedTools?.reduce(
4995
5155
  (prev, cur) => {
4996
- let config = configs?.find((config2) => config2.id === cur.id);
4997
- const userDefinedConfigDescription = config?.config.find((config2) => config2.name === "description")?.value;
4998
- const defaultConfigDescription = config?.config.find((config2) => config2.name === "description")?.default;
5156
+ let toolVariableConfig = configs?.find((config) => config.id === cur.id);
5157
+ const userDefinedConfigDescription = toolVariableConfig?.config.find((config) => config.name === "description")?.value;
5158
+ const defaultConfigDescription = toolVariableConfig?.config.find((config) => config.name === "description")?.default;
4999
5159
  const toolDescription = cur.description;
5000
5160
  const description = userDefinedConfigDescription || defaultConfigDescription || toolDescription;
5001
5161
  return {
@@ -5008,8 +5168,8 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5008
5168
  console.error("[EXULU] Tool execute function is undefined.", cur.tool);
5009
5169
  throw new Error("Tool execute function is undefined.");
5010
5170
  }
5011
- if (config) {
5012
- config = await hydrateVariables(config || []);
5171
+ if (toolVariableConfig) {
5172
+ toolVariableConfig = await hydrateVariables(toolVariableConfig || []);
5013
5173
  }
5014
5174
  let upload = void 0;
5015
5175
  if (exuluConfig?.fileUploads?.s3endpoint && exuluConfig?.fileUploads?.s3key && exuluConfig?.fileUploads?.s3secret && exuluConfig?.fileUploads?.s3Bucket) {
@@ -5062,7 +5222,6 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5062
5222
  acc[curr.id] = curr;
5063
5223
  return acc;
5064
5224
  }, {});
5065
- console.log("[EXULU] Config", config);
5066
5225
  const response = await cur.tool.execute({
5067
5226
  ...inputs,
5068
5227
  sessionID,
@@ -5077,7 +5236,7 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5077
5236
  contexts: contextsMap,
5078
5237
  upload,
5079
5238
  exuluConfig,
5080
- config: config ? config.config.reduce((acc, curr) => {
5239
+ toolVariablesConfig: toolVariableConfig ? toolVariableConfig.config.reduce((acc, curr) => {
5081
5240
  acc[curr.name] = curr.value;
5082
5241
  return acc;
5083
5242
  }, {}) : {}
@@ -5111,7 +5270,6 @@ var hydrateVariables = async (tool2) => {
5111
5270
  const variableName = toolConfig.variable;
5112
5271
  const variable = await db3.from("variables").where({ name: variableName }).first();
5113
5272
  if (!variable) {
5114
- console.error("[EXULU] Variable " + variableName + " not found.");
5115
5273
  throw new Error("Variable " + variableName + " not found.");
5116
5274
  }
5117
5275
  let value = variable.value;
@@ -5123,7 +5281,6 @@ var hydrateVariables = async (tool2) => {
5123
5281
  return toolConfig;
5124
5282
  });
5125
5283
  await Promise.all(promises);
5126
- console.log("[EXULU] Variable values retrieved and added to tool config.");
5127
5284
  return tool2;
5128
5285
  };
5129
5286
  function generateSlug(name) {
@@ -5347,7 +5504,21 @@ var ExuluAgent2 = class {
5347
5504
  });
5348
5505
  }
5349
5506
  console.log("[EXULU] Message count for agent: " + this.name, "loaded for generating sync.", messages.length);
5350
- 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.";
5507
+ let project;
5508
+ if (session) {
5509
+ const sessionData = await getSession({ sessionID: session });
5510
+ project = sessionData.project;
5511
+ }
5512
+ const personalizationInformation = exuluConfig?.privacy?.systemPromptPersonalization !== false ? `
5513
+ ${user?.firstname ? `The users first name is "${user.firstname}"` : ""}
5514
+ ${user?.lastname ? `The users last name is "${user.lastname}"` : ""}
5515
+ ${user?.email ? `The users email is "${user.email}"` : ""}
5516
+ ` : "";
5517
+ const genericContext = `IMPORTANT general information:
5518
+ ${personalizationInformation}
5519
+ The current date is "${(/* @__PURE__ */ new Date()).toLocaleDateString()}" and the current time is "${(/* @__PURE__ */ new Date()).toLocaleTimeString()}".
5520
+ If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume
5521
+ they are talking with the current date in mind as a reference.`;
5351
5522
  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.";
5352
5523
  system += "\n\n" + genericContext;
5353
5524
  if (prompt) {
@@ -5372,7 +5543,7 @@ var ExuluAgent2 = class {
5372
5543
  system,
5373
5544
  prompt,
5374
5545
  maxRetries: 2,
5375
- tools: convertToolsArrayToObject(
5546
+ tools: await convertToolsArrayToObject(
5376
5547
  currentTools,
5377
5548
  allExuluTools,
5378
5549
  toolConfigs,
@@ -5381,7 +5552,8 @@ var ExuluAgent2 = class {
5381
5552
  user,
5382
5553
  exuluConfig,
5383
5554
  session,
5384
- req
5555
+ req,
5556
+ project
5385
5557
  ),
5386
5558
  stopWhen: [(0, import_ai.stepCountIs)(2)]
5387
5559
  });
@@ -5432,7 +5604,7 @@ var ExuluAgent2 = class {
5432
5604
  ignoreIncompleteToolCalls: true
5433
5605
  }),
5434
5606
  maxRetries: 2,
5435
- tools: convertToolsArrayToObject(
5607
+ tools: await convertToolsArrayToObject(
5436
5608
  currentTools,
5437
5609
  allExuluTools,
5438
5610
  toolConfigs,
@@ -5441,7 +5613,8 @@ var ExuluAgent2 = class {
5441
5613
  user,
5442
5614
  exuluConfig,
5443
5615
  session,
5444
- req
5616
+ req,
5617
+ project
5445
5618
  ),
5446
5619
  stopWhen: [(0, import_ai.stepCountIs)(2)]
5447
5620
  });
@@ -5482,6 +5655,70 @@ var ExuluAgent2 = class {
5482
5655
  }
5483
5656
  return "";
5484
5657
  };
5658
+ /**
5659
+ * Convert file parts in messages to OpenAI Responses API compatible format.
5660
+ * The OpenAI Responses API doesn't support inline file parts with type 'file'.
5661
+ * This function converts:
5662
+ * - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
5663
+ * - Image files -> image parts (which ARE supported by Responses API)
5664
+ */
5665
+ async processFilePartsInMessages(messages) {
5666
+ const processedMessages = await Promise.all(messages.map(async (message) => {
5667
+ if (message.role !== "user" || !Array.isArray(message.parts)) {
5668
+ return message;
5669
+ }
5670
+ const processedParts = await Promise.all(message.parts.map(async (part) => {
5671
+ if (part.type !== "file") {
5672
+ return part;
5673
+ }
5674
+ const { mediaType, url, filename } = part;
5675
+ const imageTypes = ["image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp"];
5676
+ if (imageTypes.includes(mediaType)) {
5677
+ console.log(`[EXULU] Converting file part to image part: ${filename} `);
5678
+ return {
5679
+ type: "image",
5680
+ image: url,
5681
+ mimeType: mediaType
5682
+ };
5683
+ }
5684
+ console.log(`[EXULU] Converting file part to text using officeparser: ${filename}`);
5685
+ try {
5686
+ const response = await fetch(url);
5687
+ if (!response.ok) {
5688
+ console.error(`[EXULU] Failed to fetch file: ${filename}, status: ${response.status} `);
5689
+ return {
5690
+ type: "text",
5691
+ text: `[Error: Could not load file ${filename}]`
5692
+ };
5693
+ }
5694
+ const arrayBuffer = await response.arrayBuffer();
5695
+ const extractedText = await (0, import_officeparser.parseOfficeAsync)(arrayBuffer, {
5696
+ outputErrorToConsole: false,
5697
+ newlineDelimiter: "\n"
5698
+ });
5699
+ return {
5700
+ type: "text",
5701
+ text: `<file file name = "${filename}" >
5702
+ ${extractedText}
5703
+ </file>`
5704
+ };
5705
+ } catch (error) {
5706
+ console.error(`[EXULU] Error processing file ${filename}:`, error);
5707
+ return {
5708
+ type: "text",
5709
+ text: `[Error extracting text from file ${filename}: ${error instanceof Error ? error.message : "Unknown error"}]`
5710
+ };
5711
+ }
5712
+ }));
5713
+ const result = {
5714
+ ...message,
5715
+ parts: processedParts
5716
+ };
5717
+ console.log("[EXULU] Result: " + JSON.stringify(result, null, 2));
5718
+ return result;
5719
+ }));
5720
+ return processedMessages;
5721
+ }
5485
5722
  generateStream = async ({
5486
5723
  user,
5487
5724
  session,
@@ -5513,7 +5750,10 @@ var ExuluAgent2 = class {
5513
5750
  });
5514
5751
  let messages = [];
5515
5752
  let previousMessagesContent = previousMessages || [];
5753
+ let project;
5516
5754
  if (session) {
5755
+ const sessionData = await getSession({ sessionID: session });
5756
+ project = sessionData.project;
5517
5757
  console.log("[EXULU] loading previous messages from session: " + session);
5518
5758
  const previousMessages2 = await getAgentMessages({
5519
5759
  session,
@@ -5529,18 +5769,21 @@ var ExuluAgent2 = class {
5529
5769
  // append the new message to the previous messages:
5530
5770
  messages: [...previousMessagesContent, message]
5531
5771
  });
5772
+ messages = messages.filter(
5773
+ (message2, index, self) => index === self.findIndex((t) => t.id === message2.id)
5774
+ );
5775
+ messages = await this.processFilePartsInMessages(messages);
5532
5776
  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.";
5533
5777
  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.";
5534
5778
  system += "\n\n" + genericContext;
5535
- console.log("[EXULU] tools for agent: " + this.name, currentTools?.map((x) => x.name + " (" + x.id + ")"));
5536
- console.log("[EXULU] system", system.slice(0, 100) + "...");
5537
5779
  const result = (0, import_ai.streamText)({
5538
5780
  model,
5539
5781
  // Should be a LanguageModelV1
5540
5782
  messages: (0, import_ai.convertToModelMessages)(messages, {
5541
5783
  ignoreIncompleteToolCalls: true
5542
5784
  }),
5543
- // prepareStep could be used here to set the model for the first step or change other params
5785
+ // PrepareStep could be used here to set the model
5786
+ // for the first step or change other parameters.
5544
5787
  system,
5545
5788
  maxRetries: 2,
5546
5789
  providerOptions: {
@@ -5548,7 +5791,7 @@ var ExuluAgent2 = class {
5548
5791
  reasoningSummary: "auto"
5549
5792
  }
5550
5793
  },
5551
- tools: convertToolsArrayToObject(
5794
+ tools: await convertToolsArrayToObject(
5552
5795
  currentTools,
5553
5796
  allExuluTools,
5554
5797
  toolConfigs,
@@ -5557,7 +5800,8 @@ var ExuluAgent2 = class {
5557
5800
  user,
5558
5801
  exuluConfig,
5559
5802
  session,
5560
- req
5803
+ req,
5804
+ project
5561
5805
  ),
5562
5806
  onError: (error) => {
5563
5807
  console.error("[EXULU] chat stream error.", error);
@@ -5593,7 +5837,7 @@ var getSession = async ({ sessionID }) => {
5593
5837
  };
5594
5838
  var saveChat = async ({ session, user, messages }) => {
5595
5839
  const { db: db3 } = await postgresClient();
5596
- const promises = messages.map((message) => {
5840
+ for (const message of messages) {
5597
5841
  const mutation = db3.from("agent_messages").insert({
5598
5842
  session,
5599
5843
  user,
@@ -5602,9 +5846,8 @@ var saveChat = async ({ session, user, messages }) => {
5602
5846
  title: message.role === "user" ? "User" : "Assistant"
5603
5847
  }).returning("id");
5604
5848
  mutation.onConflict("message_id").merge();
5605
- return mutation;
5606
- });
5607
- await Promise.all(promises);
5849
+ await mutation;
5850
+ }
5608
5851
  };
5609
5852
  var ExuluEmbedder = class {
5610
5853
  id;
@@ -5654,12 +5897,10 @@ var ExuluEmbedder = class {
5654
5897
  id
5655
5898
  } = setting;
5656
5899
  let value = "";
5657
- console.log("[EXULU] variable name", variableName);
5658
5900
  const variable = await db3.from("variables").where({ name: variableName }).first();
5659
5901
  if (!variable) {
5660
5902
  throw new Error("Variable not found for embedder setting: " + name + " in context: " + context + " and embedder: " + this.id);
5661
5903
  }
5662
- console.log("[EXULU] variable", variable);
5663
5904
  if (variable.encrypted) {
5664
5905
  if (!process.env.NEXTAUTH_SECRET) {
5665
5906
  throw new Error("NEXTAUTH_SECRET environment variable is not set, cannot decrypt variable: " + name);
@@ -5671,14 +5912,12 @@ var ExuluEmbedder = class {
5671
5912
  throw new Error("Decryption returned empty string - invalid key or corrupted data");
5672
5913
  }
5673
5914
  value = decrypted;
5674
- console.log("[EXULU] successfully decrypted value for", name);
5675
5915
  } catch (error) {
5676
5916
  throw new Error(`Failed to decrypt variable "${name}" for embedder setting in context "${context}": ${error instanceof Error ? error.message : "Unknown error"}. Verify that NEXTAUTH_SECRET matches the key used during encryption.`);
5677
5917
  }
5678
5918
  } else {
5679
5919
  value = variable.value;
5680
5920
  }
5681
- console.log("[EXULU] variable value", value);
5682
5921
  hydrated.push({
5683
5922
  id: id || "",
5684
5923
  name,
@@ -5786,14 +6025,20 @@ var ExuluStorage = class {
5786
6025
  getPresignedUrl = async (key) => {
5787
6026
  return await getPresignedUrl(key, this.config);
5788
6027
  };
5789
- uploadFile = async (user, file, key, type, metadata) => {
5790
- return await uploadFile(user, file, key, this.config, {
5791
- contentType: type,
5792
- metadata: {
5793
- ...metadata,
5794
- type
5795
- }
5796
- });
6028
+ uploadFile = async (file, key, type, user, metadata) => {
6029
+ return await uploadFile(
6030
+ file,
6031
+ key,
6032
+ this.config,
6033
+ {
6034
+ contentType: type,
6035
+ metadata: {
6036
+ ...metadata,
6037
+ type
6038
+ }
6039
+ },
6040
+ user
6041
+ );
5797
6042
  };
5798
6043
  // todo add upload and delete methods
5799
6044
  };
@@ -5813,7 +6058,19 @@ var ExuluContext = class {
5813
6058
  // todo typings
5814
6059
  configuration;
5815
6060
  sources = [];
5816
- constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration, sources }) {
6061
+ constructor({
6062
+ id,
6063
+ name,
6064
+ description,
6065
+ embedder,
6066
+ active,
6067
+ rateLimit,
6068
+ fields,
6069
+ queryRewriter,
6070
+ resultReranker,
6071
+ configuration,
6072
+ sources
6073
+ }) {
5817
6074
  this.id = id;
5818
6075
  this.name = name;
5819
6076
  this.fields = fields || [];
@@ -5830,7 +6087,7 @@ var ExuluContext = class {
5830
6087
  this.queryRewriter = queryRewriter;
5831
6088
  this.resultReranker = resultReranker;
5832
6089
  }
5833
- processField = async (trigger, user, role, item, config) => {
6090
+ processField = async (trigger, item, exuluConfig, user, role) => {
5834
6091
  console.log("[EXULU] processing field", item.field, " in context", this.id);
5835
6092
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
5836
6093
  const field = this.fields.find((field2) => {
@@ -5840,7 +6097,7 @@ var ExuluContext = class {
5840
6097
  console.error("[EXULU] field not found or processor not set for field", item.field, " in context", this.id);
5841
6098
  throw new Error("Field not found or processor not set for field " + item.field + " in context " + this.id);
5842
6099
  }
5843
- const exuluStorage = new ExuluStorage({ config });
6100
+ const exuluStorage = new ExuluStorage({ config: exuluConfig });
5844
6101
  const queue = await field.processor.config?.queue;
5845
6102
  if (queue?.queue.name) {
5846
6103
  console.log("[EXULU] processor is in queue mode, scheduling job.");
@@ -5866,6 +6123,7 @@ var ExuluContext = class {
5866
6123
  job: job.id
5867
6124
  };
5868
6125
  }
6126
+ console.log("[EXULU] POS 1 -- EXULU CONTEXT PROCESS FIELD");
5869
6127
  const result = await field.processor.execute({
5870
6128
  item,
5871
6129
  user,
@@ -5878,13 +6136,24 @@ var ExuluContext = class {
5878
6136
  delete: this.deleteItem
5879
6137
  }
5880
6138
  },
5881
- config
6139
+ exuluConfig
5882
6140
  });
5883
6141
  return {
5884
6142
  result,
5885
6143
  job: void 0
5886
6144
  };
5887
6145
  };
6146
+ search = async (options) => {
6147
+ const { db: db3 } = await postgresClient();
6148
+ const result = await vectorSearch({
6149
+ ...options,
6150
+ user: options.user,
6151
+ role: options.role,
6152
+ context: this,
6153
+ db: db3
6154
+ });
6155
+ return result;
6156
+ };
5888
6157
  deleteAll = async () => {
5889
6158
  const { db: db3 } = await postgresClient();
5890
6159
  await db3.from(getTableName(this.id)).delete();
@@ -5894,8 +6163,11 @@ var ExuluContext = class {
5894
6163
  results: []
5895
6164
  };
5896
6165
  };
5897
- executeSource = async (source, inputs) => {
5898
- return await source.execute(inputs);
6166
+ executeSource = async (source, inputs, exuluConfig) => {
6167
+ return await source.execute({
6168
+ ...inputs,
6169
+ exuluConfig
6170
+ });
5899
6171
  };
5900
6172
  tableExists = async () => {
5901
6173
  const { db: db3 } = await postgresClient();
@@ -5951,7 +6223,7 @@ var ExuluContext = class {
5951
6223
  job
5952
6224
  };
5953
6225
  };
5954
- createItem = async (item, config, user, role, upsert) => {
6226
+ createItem = async (item, config, user, role, upsert, generateEmbeddingsOverwrite) => {
5955
6227
  if (upsert && (!item.id && !item.external_id)) {
5956
6228
  throw new Error("Item id or external id is required for upsert.");
5957
6229
  }
@@ -5978,12 +6250,40 @@ var ExuluContext = class {
5978
6250
  throw new Error("Failed to create item.");
5979
6251
  }
5980
6252
  console.log("[EXULU] context configuration", this.configuration);
5981
- if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
6253
+ let jobs = [];
6254
+ let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
6255
+ for (const [key, value] of Object.entries(item)) {
6256
+ console.log("[EXULU] Checking for processors for field", key);
6257
+ const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
6258
+ console.log("[EXULU] Processor found", processor);
6259
+ if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
6260
+ const {
6261
+ job: processorJob,
6262
+ result: processorResult
6263
+ } = await this.processField(
6264
+ "api",
6265
+ {
6266
+ ...item,
6267
+ id: results[0].id,
6268
+ field: key
6269
+ },
6270
+ config,
6271
+ user,
6272
+ role
6273
+ );
6274
+ if (processorJob) {
6275
+ jobs.push(processorJob);
6276
+ }
6277
+ if (!processorJob && processor.config?.generateEmbeddings) {
6278
+ shouldGenerateEmbeddings = true;
6279
+ }
6280
+ }
6281
+ }
6282
+ if (shouldGenerateEmbeddings) {
5982
6283
  console.log("[EXULU] generating embeddings for item", results[0].id);
5983
- const { job } = await this.embeddings.generate.one({
6284
+ const { job: embeddingsJob } = await this.embeddings.generate.one({
5984
6285
  item: {
5985
6286
  ...item,
5986
- // important we need to full record here with all fields for the embedder
5987
6287
  id: results[0].id
5988
6288
  },
5989
6289
  user,
@@ -5991,17 +6291,16 @@ var ExuluContext = class {
5991
6291
  trigger: "api",
5992
6292
  config
5993
6293
  });
5994
- return {
5995
- item: results[0],
5996
- job
5997
- };
6294
+ if (embeddingsJob) {
6295
+ jobs.push(embeddingsJob);
6296
+ }
5998
6297
  }
5999
6298
  return {
6000
6299
  item: results[0],
6001
- job: void 0
6300
+ job: jobs.length > 0 ? jobs.join(",") : void 0
6002
6301
  };
6003
6302
  };
6004
- updateItem = async (item, config, user, role) => {
6303
+ updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
6005
6304
  const { db: db3 } = await postgresClient();
6006
6305
  if (item.field) {
6007
6306
  delete item.field;
@@ -6025,8 +6324,35 @@ var ExuluContext = class {
6025
6324
  }
6026
6325
  ).returning("id");
6027
6326
  await mutation;
6028
- if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
6029
- const { job } = await this.embeddings.generate.one({
6327
+ let jobs = [];
6328
+ let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
6329
+ for (const [key, value] of Object.entries(item)) {
6330
+ const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
6331
+ if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
6332
+ const {
6333
+ job: processorJob,
6334
+ result: processorResult
6335
+ } = await this.processField(
6336
+ "api",
6337
+ {
6338
+ ...item,
6339
+ id: record.id,
6340
+ field: key
6341
+ },
6342
+ config,
6343
+ user,
6344
+ role
6345
+ );
6346
+ if (processorJob) {
6347
+ jobs.push(processorJob);
6348
+ }
6349
+ if (!processorJob && processor.config?.generateEmbeddings) {
6350
+ shouldGenerateEmbeddings = true;
6351
+ }
6352
+ }
6353
+ }
6354
+ if (shouldGenerateEmbeddings) {
6355
+ const { job: embeddingsJob } = await this.embeddings.generate.one({
6030
6356
  item: record,
6031
6357
  // important we need to full record here with all fields for the embedder
6032
6358
  user,
@@ -6034,14 +6360,13 @@ var ExuluContext = class {
6034
6360
  trigger: "api",
6035
6361
  config
6036
6362
  });
6037
- return {
6038
- item: record,
6039
- job
6040
- };
6363
+ if (embeddingsJob) {
6364
+ jobs.push(embeddingsJob);
6365
+ }
6041
6366
  }
6042
6367
  return {
6043
6368
  item: record,
6044
- job: void 0
6369
+ job: jobs.length > 0 ? jobs.join(",") : void 0
6045
6370
  };
6046
6371
  };
6047
6372
  deleteItem = async (item, user, role) => {
@@ -6068,6 +6393,34 @@ var ExuluContext = class {
6068
6393
  job: void 0
6069
6394
  };
6070
6395
  };
6396
+ getItem = async ({ item }) => {
6397
+ const { db: db3 } = await postgresClient();
6398
+ if (!item.id && !item.external_id) {
6399
+ throw new Error("Item id or external id is required to get an item.");
6400
+ }
6401
+ const result = await db3.from(getTableName(this.id)).where({
6402
+ ...item.id ? { id: item.id } : {},
6403
+ ...item.external_id ? { external_id: item.external_id } : {}
6404
+ }).first();
6405
+ if (result) {
6406
+ const chunksCount = await db3.from(getChunksTableName(this.id)).where(
6407
+ { source: result.id }
6408
+ ).count("id");
6409
+ result.chunksCount = Number(chunksCount[0].count) || 0;
6410
+ }
6411
+ return result;
6412
+ };
6413
+ getItems = async ({
6414
+ filters,
6415
+ fields
6416
+ }) => {
6417
+ const { db: db3 } = await postgresClient();
6418
+ let query = db3.from(getTableName(this.id)).select(fields || "*");
6419
+ const tableDefinition = contextToTableDefinition(this);
6420
+ query = applyFilters(query, filters || [], tableDefinition);
6421
+ const items = await query;
6422
+ return items;
6423
+ };
6071
6424
  embeddings = {
6072
6425
  generate: {
6073
6426
  one: async ({
@@ -6084,6 +6437,12 @@ var ExuluContext = class {
6084
6437
  if (!item.id) {
6085
6438
  throw new Error("Item id is required for generating embeddings.");
6086
6439
  }
6440
+ const { db: db3 } = await postgresClient();
6441
+ const record = await db3.from(getTableName(this.id)).where({ id: item.id }).first();
6442
+ if (!record) {
6443
+ throw new Error("Item not found.");
6444
+ }
6445
+ item = record;
6087
6446
  const queue = await this.embedder.queue;
6088
6447
  if (queue?.queue.name) {
6089
6448
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
@@ -6849,7 +7208,7 @@ Mood: friendly and intelligent.
6849
7208
  const user = authenticationResult.user;
6850
7209
  let agentQuery = db3("agents");
6851
7210
  agentQuery.select("*");
6852
- agentQuery = applyAccessControl(agentsSchema2(), authenticationResult.user, agentQuery);
7211
+ agentQuery = applyAccessControl(agentsSchema2(), agentQuery, authenticationResult.user);
6853
7212
  agentQuery.where({ id: req.params.agent });
6854
7213
  const agent = await agentQuery.first();
6855
7214
  if (!agent) {
@@ -6866,7 +7225,7 @@ Mood: friendly and intelligent.
6866
7225
  } else {
6867
7226
  let projectQuery = db3("projects");
6868
7227
  projectQuery.select("*");
6869
- projectQuery = applyAccessControl(projectsSchema2(), authenticationResult.user, projectQuery);
7228
+ projectQuery = applyAccessControl(projectsSchema2(), projectQuery, authenticationResult.user);
6870
7229
  projectQuery.where({ id: req.params.project });
6871
7230
  project = await projectQuery.first();
6872
7231
  if (!project) {
@@ -7208,6 +7567,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7208
7567
  if (!data.role) {
7209
7568
  throw new Error(`Role not set for processor job.`);
7210
7569
  }
7570
+ console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
7211
7571
  const result = await field.processor.execute({
7212
7572
  item: data.inputs,
7213
7573
  user: data.user,
@@ -7222,9 +7582,24 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7222
7582
  },
7223
7583
  config
7224
7584
  });
7585
+ let jobs = [];
7586
+ if (field.processor.config?.generateEmbeddings) {
7587
+ const { job: embeddingsJob } = await context.embeddings.generate.one({
7588
+ item: data.inputs,
7589
+ user: data.user,
7590
+ role: data.role,
7591
+ trigger: "api",
7592
+ config
7593
+ });
7594
+ if (embeddingsJob) {
7595
+ jobs.push(embeddingsJob);
7596
+ }
7597
+ }
7225
7598
  return {
7226
7599
  result,
7227
- metadata: {}
7600
+ metadata: {
7601
+ jobs: jobs.length > 0 ? jobs.join(",") : void 0
7602
+ }
7228
7603
  };
7229
7604
  }
7230
7605
  if (data.type === "eval_run") {
@@ -7910,7 +8285,7 @@ var ExuluMCP = class {
7910
8285
  console.log("[EXULU] MCP tool inputs", inputs);
7911
8286
  console.log("[EXULU] MCP tool args", args);
7912
8287
  const configValues = agentInstance.tools;
7913
- const tools = convertToolsArrayToObject(
8288
+ const tools = await convertToolsArrayToObject(
7914
8289
  [tool2],
7915
8290
  allTools,
7916
8291
  configValues,