@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.js CHANGED
@@ -167,11 +167,17 @@ async function postgresClient() {
167
167
  pool: {
168
168
  min: 2,
169
169
  max: 20,
170
+ // Increased from 20 to handle more concurrent operations
170
171
  acquireTimeoutMillis: 3e4,
171
172
  createTimeoutMillis: 3e4,
172
173
  idleTimeoutMillis: 3e4,
173
174
  reapIntervalMillis: 1e3,
174
- createRetryIntervalMillis: 200
175
+ createRetryIntervalMillis: 200,
176
+ // Log pool events to help debug connection issues
177
+ afterCreate: (conn, done) => {
178
+ console.log("[EXULU] New database connection created");
179
+ done(null, conn);
180
+ }
175
181
  }
176
182
  });
177
183
  try {
@@ -411,7 +417,9 @@ var getToken = async (authHeader) => {
411
417
  }
412
418
  try {
413
419
  const secret = process.env.NEXTAUTH_SECRET;
414
- const jwk = await importJWK({ k: secret, alg: "HS256", kty: "oct" });
420
+ const secretBuffer = Buffer.from(secret, "utf-8");
421
+ const base64Secret = secretBuffer.toString("base64url");
422
+ const jwk = await importJWK({ k: base64Secret, alg: "HS256", kty: "oct" });
415
423
  const { payload } = await jwtVerify(token, jwk);
416
424
  return payload;
417
425
  } catch (error) {
@@ -450,6 +458,8 @@ var authentication = async ({
450
458
  type: "api",
451
459
  id: 192837465,
452
460
  email: "internal@exulu.com",
461
+ firstname: "API",
462
+ lastname: "User",
453
463
  role: {
454
464
  id: "internal",
455
465
  name: "Internal",
@@ -1283,10 +1293,10 @@ var rbacSchema = {
1283
1293
  name: "user_id",
1284
1294
  type: "number"
1285
1295
  },
1286
- {
1287
- name: "project_id",
1288
- type: "uuid"
1289
- },
1296
+ /* {
1297
+ name: "project_id",
1298
+ type: "uuid"
1299
+ }, */
1290
1300
  {
1291
1301
  name: "rights",
1292
1302
  type: "text",
@@ -2196,7 +2206,7 @@ function createMutations(table, agents, contexts, tools, config) {
2196
2206
  });
2197
2207
  return {
2198
2208
  // Filter result to only include requested fields
2199
- item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: results[0], user: context.user }),
2209
+ item: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result: results[0], user: context.user }),
2200
2210
  job
2201
2211
  };
2202
2212
  },
@@ -2244,7 +2254,7 @@ function createMutations(table, agents, contexts, tools, config) {
2244
2254
  }
2245
2255
  const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id, config });
2246
2256
  return {
2247
- item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2257
+ item: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2248
2258
  job
2249
2259
  };
2250
2260
  },
@@ -2285,7 +2295,7 @@ function createMutations(table, agents, contexts, tools, config) {
2285
2295
  const result = await db3.from(tableNamePlural).select(Object.keys(columns)).where({ id }).first();
2286
2296
  const { job } = await postprocessUpdate({ table, requestedFields, agents, contexts, tools, result, user: context.user.id, role: context.user.role?.id, config });
2287
2297
  return {
2288
- item: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2298
+ item: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id }),
2289
2299
  job
2290
2300
  };
2291
2301
  },
@@ -2317,7 +2327,7 @@ function createMutations(table, agents, contexts, tools, config) {
2317
2327
  }).del();
2318
2328
  }
2319
2329
  await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
2320
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2330
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2321
2331
  },
2322
2332
  [`${tableNamePlural}RemoveOne`]: async (_, args, context, info) => {
2323
2333
  const { where } = args;
@@ -2341,7 +2351,7 @@ function createMutations(table, agents, contexts, tools, config) {
2341
2351
  }
2342
2352
  await db3(tableNamePlural).where(where).del();
2343
2353
  await postprocessDeletion({ table, requestedFields, agents, contexts, tools, result });
2344
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2354
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user.id });
2345
2355
  }
2346
2356
  };
2347
2357
  if (table.type === "items") {
@@ -2374,20 +2384,20 @@ function createMutations(table, agents, contexts, tools, config) {
2374
2384
  }
2375
2385
  const { db: db3 } = context;
2376
2386
  let query = db3.from(tableNamePlural).select("*").where({ id: args.item });
2377
- query = applyAccessControl(table, context.user, query);
2387
+ query = applyAccessControl(table, query, context.user);
2378
2388
  const item = await query.first();
2379
2389
  if (!item) {
2380
2390
  throw new Error("Item not found, or your user does not have access to it.");
2381
2391
  }
2382
2392
  const { job, result } = await exists.processField(
2383
2393
  "api",
2384
- context.user.id,
2385
- context.user.role?.id,
2386
2394
  {
2387
2395
  ...item,
2388
2396
  field: args.field
2389
2397
  },
2390
- config
2398
+ config,
2399
+ context.user.id,
2400
+ context.user.role?.id
2391
2401
  );
2392
2402
  return {
2393
2403
  message: job ? "Processing job scheduled." : "Item processed successfully.",
@@ -2434,7 +2444,10 @@ function createMutations(table, agents, contexts, tools, config) {
2434
2444
  };
2435
2445
  }
2436
2446
  console.log("[EXULU] Executing source function directly");
2437
- const result = await source.execute(args.inputs);
2447
+ const result = await source.execute({
2448
+ ...args.inputs,
2449
+ exuluConfig: config
2450
+ });
2438
2451
  let jobs = [];
2439
2452
  let items = [];
2440
2453
  for (const item of result) {
@@ -2558,14 +2571,14 @@ function createMutations(table, agents, contexts, tools, config) {
2558
2571
  }
2559
2572
  return mutations;
2560
2573
  }
2561
- var applyAccessControl = (table, user, query) => {
2574
+ var applyAccessControl = (table, query, user) => {
2562
2575
  const tableNamePlural = table.name.plural.toLowerCase();
2563
- if (table.name.plural !== "agent_sessions" && user.super_admin === true) {
2576
+ if (table.name.plural !== "agent_sessions" && user?.super_admin === true) {
2564
2577
  return query;
2565
2578
  }
2566
- console.log("[EXULU] user.role", user.role);
2579
+ console.log("[EXULU] user.role", user?.role);
2567
2580
  console.log("[EXULU] table.name.plural", table.name.plural);
2568
- 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")))) {
2581
+ 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")))) {
2569
2582
  console.error("==== Access control error: no role found or no access to entity type. ====");
2570
2583
  throw new Error("Access control error: no role found or no access to entity type.");
2571
2584
  }
@@ -2654,7 +2667,7 @@ var removeAgentFields = (requestedFields) => {
2654
2667
  filtered.push("backend");
2655
2668
  return filtered;
2656
2669
  };
2657
- var addAgentFields = async (requestedFields, agents, result, tools, user) => {
2670
+ var addAgentFields = async (args, requestedFields, agents, result, tools, user, contexts) => {
2658
2671
  let backend = agents.find((a) => a.id === result?.backend);
2659
2672
  if (requestedFields.includes("providerName")) {
2660
2673
  result.providerName = backend?.providerName || "";
@@ -2701,6 +2714,17 @@ var addAgentFields = async (requestedFields, agents, result, tools, user) => {
2701
2714
  console.log("[EXULU] hydratedTool", hydratedTool);
2702
2715
  return hydratedTool;
2703
2716
  }));
2717
+ if (args.project) {
2718
+ const projectTool = await createProjectRetrievalTool({
2719
+ projectId: args.project,
2720
+ user,
2721
+ role: user.role?.id,
2722
+ contexts
2723
+ });
2724
+ if (projectTool) {
2725
+ result.tools.unshift(projectTool);
2726
+ }
2727
+ }
2704
2728
  result.tools = result.tools.filter((tool2) => tool2 !== null);
2705
2729
  } else {
2706
2730
  result.tools = [];
@@ -2839,6 +2863,7 @@ var postprocessDeletion = async ({
2839
2863
  return result;
2840
2864
  };
2841
2865
  var finalizeRequestedFields = async ({
2866
+ args,
2842
2867
  table,
2843
2868
  requestedFields,
2844
2869
  agents,
@@ -2855,11 +2880,28 @@ var finalizeRequestedFields = async ({
2855
2880
  }
2856
2881
  if (Array.isArray(result)) {
2857
2882
  result = result.map((item) => {
2858
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: item, user });
2883
+ return finalizeRequestedFields({
2884
+ args,
2885
+ table,
2886
+ requestedFields,
2887
+ agents,
2888
+ contexts,
2889
+ tools,
2890
+ result: item,
2891
+ user
2892
+ });
2859
2893
  });
2860
2894
  } else {
2861
2895
  if (table.name.singular === "agent") {
2862
- result = await addAgentFields(requestedFields, agents, result, tools, user);
2896
+ result = await addAgentFields(
2897
+ args,
2898
+ requestedFields,
2899
+ agents,
2900
+ result,
2901
+ tools,
2902
+ user,
2903
+ contexts
2904
+ );
2863
2905
  if (!requestedFields.includes("backend")) {
2864
2906
  delete result.backend;
2865
2907
  }
@@ -2936,18 +2978,18 @@ function createQueries(table, agents, tools, contexts) {
2936
2978
  const requestedFields = getRequestedFields(info);
2937
2979
  const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2938
2980
  let query = db3.from(tableNamePlural).select(sanitizedFields).where({ id: args.id });
2939
- query = applyAccessControl(table, context.user, query);
2981
+ query = applyAccessControl(table, query, context.user);
2940
2982
  let result = await query.first();
2941
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
2983
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user });
2942
2984
  },
2943
2985
  [`${tableNameSingular}ByIds`]: async (_, args, context, info) => {
2944
2986
  const { db: db3 } = context;
2945
2987
  const requestedFields = getRequestedFields(info);
2946
2988
  const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2947
2989
  let query = db3.from(tableNamePlural).select(sanitizedFields).whereIn("id", args.ids);
2948
- query = applyAccessControl(table, context.user, query);
2990
+ query = applyAccessControl(table, query, context.user);
2949
2991
  let result = await query;
2950
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
2992
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user });
2951
2993
  },
2952
2994
  [`${tableNameSingular}One`]: async (_, args, context, info) => {
2953
2995
  const { filters = [], sort } = args;
@@ -2956,10 +2998,10 @@ function createQueries(table, agents, tools, contexts) {
2956
2998
  const sanitizedFields = sanitizeRequestedFields(table, requestedFields);
2957
2999
  let query = db3.from(tableNamePlural).select(sanitizedFields);
2958
3000
  query = applyFilters(query, filters, table);
2959
- query = applyAccessControl(table, context.user, query);
3001
+ query = applyAccessControl(table, query, context.user);
2960
3002
  query = applySorting(query, sort);
2961
3003
  let result = await query.first();
2962
- return finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result, user: context.user });
3004
+ return finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result, user: context.user });
2963
3005
  },
2964
3006
  [`${tableNamePlural}Pagination`]: async (_, args, context, info) => {
2965
3007
  const { limit = 10, page = 0, filters = [], sort } = args;
@@ -2969,7 +3011,7 @@ function createQueries(table, agents, tools, contexts) {
2969
3011
  }
2970
3012
  let countQuery = db3(tableNamePlural);
2971
3013
  countQuery = applyFilters(countQuery, filters, table);
2972
- countQuery = applyAccessControl(table, context.user, countQuery);
3014
+ countQuery = applyAccessControl(table, countQuery, context.user);
2973
3015
  const countResult = await countQuery.count("* as count");
2974
3016
  const itemCount = Number(countResult[0]?.count || 0);
2975
3017
  const pageCount = Math.ceil(itemCount / limit);
@@ -2978,7 +3020,7 @@ function createQueries(table, agents, tools, contexts) {
2978
3020
  const hasNextPage = currentPage < pageCount - 1;
2979
3021
  let dataQuery = db3(tableNamePlural);
2980
3022
  dataQuery = applyFilters(dataQuery, filters, table);
2981
- dataQuery = applyAccessControl(table, context.user, dataQuery);
3023
+ dataQuery = applyAccessControl(table, dataQuery, context.user);
2982
3024
  const requestedFields = getRequestedFields(info);
2983
3025
  dataQuery = applySorting(dataQuery, sort);
2984
3026
  if (page > 1) {
@@ -2994,7 +3036,7 @@ function createQueries(table, agents, tools, contexts) {
2994
3036
  hasPreviousPage,
2995
3037
  hasNextPage
2996
3038
  },
2997
- items: finalizeRequestedFields({ table, requestedFields, agents, contexts, tools, result: items, user: context.user })
3039
+ items: finalizeRequestedFields({ args, table, requestedFields, agents, contexts, tools, result: items, user: context.user })
2998
3040
  };
2999
3041
  },
3000
3042
  // Add generic statistics query for all tables
@@ -3003,7 +3045,7 @@ function createQueries(table, agents, tools, contexts) {
3003
3045
  const { db: db3 } = context;
3004
3046
  let query = db3(tableNamePlural);
3005
3047
  query = applyFilters(query, filters, table);
3006
- query = applyAccessControl(table, context.user, query);
3048
+ query = applyAccessControl(table, query, context.user);
3007
3049
  if (groupBy) {
3008
3050
  query = query.select(groupBy).groupBy(groupBy);
3009
3051
  if (tableNamePlural === "tracking") {
@@ -3103,11 +3145,11 @@ var vectorSearch = async ({
3103
3145
  const chunksTable = getChunksTableName(id);
3104
3146
  let countQuery = db3(mainTable);
3105
3147
  countQuery = applyFilters(countQuery, filters, table);
3106
- countQuery = applyAccessControl(table, user, countQuery);
3148
+ countQuery = applyAccessControl(table, countQuery, user);
3107
3149
  const columns = await db3(mainTable).columnInfo();
3108
3150
  let itemsQuery = db3(mainTable).select(Object.keys(columns).map((column) => mainTable + "." + column));
3109
3151
  itemsQuery = applyFilters(itemsQuery, filters, table);
3110
- itemsQuery = applyAccessControl(table, user, itemsQuery);
3152
+ itemsQuery = applyAccessControl(table, itemsQuery, user);
3111
3153
  itemsQuery = applySorting(itemsQuery, sort);
3112
3154
  if (queryRewriter) {
3113
3155
  query = await queryRewriter(query);
@@ -3126,7 +3168,7 @@ var vectorSearch = async ({
3126
3168
  const { chunks } = await embedder.generateFromQuery(context.id, query, {
3127
3169
  label: table.name.singular,
3128
3170
  trigger
3129
- }, user.id, role);
3171
+ }, user?.id, role);
3130
3172
  if (!chunks?.[0]?.vector) {
3131
3173
  throw new Error("No vector generated for query.");
3132
3174
  }
@@ -3516,7 +3558,8 @@ function createSDL(tables, contexts, agents, tools, config, evals, queues2) {
3516
3558
  const tableNameSingularUpperCaseFirst = table.name.singular.charAt(0).toUpperCase() + table.name.singular.slice(1);
3517
3559
  const processorFields = table.fields.filter((field) => field.processor?.execute);
3518
3560
  typeDefs += `
3519
- ${tableNameSingular}ById(id: ID!): ${tableNameSingular}
3561
+ ${tableNameSingular === "agent" ? `${tableNameSingular}ById(id: ID!, project: ID): ${tableNameSingular}` : `${tableNameSingular}ById(id: ID!): ${tableNameSingular}`}
3562
+
3520
3563
  ${tableNameSingular}ByIds(ids: [ID!]!): [${tableNameSingular}]!
3521
3564
  ${tableNamePlural}Pagination(limit: Int, page: Int, filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingularUpperCaseFirst}PaginationResult
3522
3565
  ${tableNameSingular}One(filters: [Filter${tableNameSingularUpperCaseFirst}], sort: SortBy): ${tableNameSingular}
@@ -4448,16 +4491,27 @@ var addPrefixToKey = (keyPath, config) => {
4448
4491
  }
4449
4492
  return `${prefix}/${keyPath}`;
4450
4493
  };
4451
- var uploadFile = async (user, file, key, config, options = {}) => {
4452
- console.log("[EXULU] Uploading file to S3", key);
4494
+ var uploadFile = async (file, key, config, options = {}, user) => {
4453
4495
  if (!config.fileUploads) {
4454
- throw new Error("File uploads are not configured");
4496
+ throw new Error("File uploads are not configured (in the exported uploadFile function)");
4455
4497
  }
4456
4498
  const client2 = getS3Client(config);
4457
- let folder = `${user}/`;
4499
+ let defaultBucket = config.fileUploads.s3Bucket;
4500
+ let customBucket = false;
4501
+ if (key.includes("[bucket:")) {
4502
+ console.log("[EXULU] key includes [bucket:name]", key);
4503
+ customBucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4504
+ if (!customBucket?.length) {
4505
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4506
+ }
4507
+ key = key.split("]")[1] || "";
4508
+ console.log("[EXULU] custom bucket", customBucket);
4509
+ }
4510
+ let folder = user ? `${user}/` : "";
4458
4511
  const fullKey = addPrefixToKey(!key.includes(folder) ? folder + key : key, config);
4512
+ console.log("[EXULU] uploading file to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4459
4513
  const command = new PutObjectCommand({
4460
- Bucket: config.fileUploads.s3Bucket,
4514
+ Bucket: customBucket || defaultBucket,
4461
4515
  Key: fullKey,
4462
4516
  Body: file,
4463
4517
  ContentType: options.contentType,
@@ -4465,6 +4519,10 @@ var uploadFile = async (user, file, key, config, options = {}) => {
4465
4519
  ContentLength: file.byteLength
4466
4520
  });
4467
4521
  await client2.send(command);
4522
+ console.log("[EXULU] file uploaded to s3 into bucket", customBucket || defaultBucket, "with key", fullKey);
4523
+ if (customBucket) {
4524
+ return "[bucket:" + customBucket + "]" + fullKey;
4525
+ }
4468
4526
  return fullKey;
4469
4527
  };
4470
4528
  var createUppyRoutes = async (app, config) => {
@@ -4623,16 +4681,12 @@ var createUppyRoutes = async (app, config) => {
4623
4681
  key = key.split("]")[1] || "";
4624
4682
  console.log("[EXULU] key", key);
4625
4683
  }
4626
- console.log("[EXULU] Getting object metadata from s3", key);
4627
- console.log("[EXULU] bucket", bucket);
4628
- console.log("[EXULU] key", key);
4629
4684
  const client2 = getS3Client(config);
4630
4685
  const command = new HeadObjectCommand({
4631
4686
  Bucket: bucket,
4632
4687
  Key: key
4633
4688
  });
4634
4689
  const response = await client2.send(command);
4635
- console.log("[EXULU] Object metadata from s3", response);
4636
4690
  res.json(response);
4637
4691
  res.end();
4638
4692
  });
@@ -4936,6 +4990,7 @@ var createUppyRoutes = async (app, config) => {
4936
4990
  };
4937
4991
 
4938
4992
  // src/registry/classes.ts
4993
+ import { parseOfficeAsync } from "officeparser";
4939
4994
  var s3Client2;
4940
4995
  function sanitizeToolName(name) {
4941
4996
  if (typeof name !== "string") return "";
@@ -4946,12 +5001,117 @@ function sanitizeToolName(name) {
4946
5001
  }
4947
5002
  return sanitized;
4948
5003
  }
4949
- var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req) => {
5004
+ var projectsCache = /* @__PURE__ */ new Map();
5005
+ var createProjectRetrievalTool = async ({
5006
+ user,
5007
+ role,
5008
+ contexts,
5009
+ projectId
5010
+ }) => {
5011
+ let project;
5012
+ const cachedProject = projectsCache.get(projectId);
5013
+ const OneMinuteAgo = new Date(Date.now() - 1e3 * 60);
5014
+ if (cachedProject && cachedProject.age > OneMinuteAgo) {
5015
+ project = cachedProject.project;
5016
+ } else {
5017
+ const { db: db3 } = await postgresClient();
5018
+ project = await db3.from("projects").where("id", projectId).first();
5019
+ if (project) {
5020
+ projectsCache.set(projectId, {
5021
+ age: /* @__PURE__ */ new Date(),
5022
+ project
5023
+ });
5024
+ } else {
5025
+ return;
5026
+ }
5027
+ }
5028
+ console.log("[EXULU] Project search tool created for project", project);
5029
+ if (!project.project_items?.length) {
5030
+ return;
5031
+ }
5032
+ const projectRetrievalTool = new ExuluTool2({
5033
+ id: "project_information_retrieval_tool_" + projectId,
5034
+ name: "Project information retrieval tool for project " + project.name,
5035
+ description: "This tool retrieves information about a project from conversations and items that were added to the project " + project.name + ".",
5036
+ inputSchema: z.object({
5037
+ query: z.string().describe("The query to retrieve information about the project " + project.name + "."),
5038
+ keywords: z.array(z.string()).describe("The most relevant keywords in the query, such as names of people, companies, products, etc. in the project " + project.name + ".")
5039
+ }),
5040
+ type: "function",
5041
+ category: "project",
5042
+ config: [],
5043
+ execute: async ({ query, keywords }) => {
5044
+ console.log("[EXULU] Project search tool searching for project", project);
5045
+ const items = project.project_items;
5046
+ const set = {};
5047
+ for (const item of items) {
5048
+ const context = item.split("/")[0];
5049
+ if (!context) {
5050
+ 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.");
5051
+ }
5052
+ const id = item.split("/")[1];
5053
+ if (set[context]) {
5054
+ set[context].push(id);
5055
+ } else {
5056
+ set[context] = [id];
5057
+ }
5058
+ }
5059
+ console.log("[EXULU] Project search tool searching through contexts", Object.keys(set));
5060
+ const results = await Promise.all(Object.keys(set).map(async (contextName, index) => {
5061
+ const context = contexts.find((context2) => context2.id === contextName);
5062
+ if (!context) {
5063
+ console.error("[EXULU] Context not found for project information retrieval tool.", contextName);
5064
+ return [];
5065
+ }
5066
+ const itemIds = set[contextName];
5067
+ console.log("[EXULU] Project search tool searching through items", itemIds);
5068
+ return await context.search({
5069
+ // todo check if it is more performant to use a concatenation of
5070
+ // the query and keywords, or just the keywords, instead of the
5071
+ // query itself.
5072
+ query,
5073
+ filters: [{
5074
+ id: {
5075
+ in: itemIds
5076
+ }
5077
+ }],
5078
+ user,
5079
+ role,
5080
+ method: "hybridSearch",
5081
+ sort: {
5082
+ field: "updatedAt",
5083
+ direction: "desc"
5084
+ },
5085
+ trigger: "tool",
5086
+ limit: 10,
5087
+ page: 1
5088
+ });
5089
+ }));
5090
+ console.log("[EXULU] Project search tool results", results);
5091
+ return {
5092
+ result: JSON.stringify(results.flat())
5093
+ };
5094
+ }
5095
+ });
5096
+ return projectRetrievalTool;
5097
+ };
5098
+ var convertToolsArrayToObject = async (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req, project) => {
4950
5099
  if (!currentTools) return {};
4951
5100
  if (!allExuluTools) return {};
4952
5101
  if (!contexts) {
4953
5102
  contexts = [];
4954
5103
  }
5104
+ if (project) {
5105
+ const projectRetrievalTool = await createProjectRetrievalTool({
5106
+ user,
5107
+ role: user?.role?.id,
5108
+ contexts,
5109
+ projectId: project
5110
+ });
5111
+ if (projectRetrievalTool) {
5112
+ currentTools.push(projectRetrievalTool);
5113
+ }
5114
+ }
4955
5115
  const sanitizedTools = currentTools ? currentTools.map((tool2) => ({
4956
5116
  ...tool2,
4957
5117
  name: sanitizeToolName(tool2.name)
@@ -4960,9 +5120,9 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
4960
5120
  return {
4961
5121
  ...sanitizedTools?.reduce(
4962
5122
  (prev, cur) => {
4963
- let config = configs?.find((config2) => config2.id === cur.id);
4964
- const userDefinedConfigDescription = config?.config.find((config2) => config2.name === "description")?.value;
4965
- const defaultConfigDescription = config?.config.find((config2) => config2.name === "description")?.default;
5123
+ let toolVariableConfig = configs?.find((config) => config.id === cur.id);
5124
+ const userDefinedConfigDescription = toolVariableConfig?.config.find((config) => config.name === "description")?.value;
5125
+ const defaultConfigDescription = toolVariableConfig?.config.find((config) => config.name === "description")?.default;
4966
5126
  const toolDescription = cur.description;
4967
5127
  const description = userDefinedConfigDescription || defaultConfigDescription || toolDescription;
4968
5128
  return {
@@ -4975,8 +5135,8 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
4975
5135
  console.error("[EXULU] Tool execute function is undefined.", cur.tool);
4976
5136
  throw new Error("Tool execute function is undefined.");
4977
5137
  }
4978
- if (config) {
4979
- config = await hydrateVariables(config || []);
5138
+ if (toolVariableConfig) {
5139
+ toolVariableConfig = await hydrateVariables(toolVariableConfig || []);
4980
5140
  }
4981
5141
  let upload = void 0;
4982
5142
  if (exuluConfig?.fileUploads?.s3endpoint && exuluConfig?.fileUploads?.s3key && exuluConfig?.fileUploads?.s3secret && exuluConfig?.fileUploads?.s3Bucket) {
@@ -5029,7 +5189,6 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5029
5189
  acc[curr.id] = curr;
5030
5190
  return acc;
5031
5191
  }, {});
5032
- console.log("[EXULU] Config", config);
5033
5192
  const response = await cur.tool.execute({
5034
5193
  ...inputs,
5035
5194
  sessionID,
@@ -5044,7 +5203,7 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5044
5203
  contexts: contextsMap,
5045
5204
  upload,
5046
5205
  exuluConfig,
5047
- config: config ? config.config.reduce((acc, curr) => {
5206
+ toolVariablesConfig: toolVariableConfig ? toolVariableConfig.config.reduce((acc, curr) => {
5048
5207
  acc[curr.name] = curr.value;
5049
5208
  return acc;
5050
5209
  }, {}) : {}
@@ -5078,7 +5237,6 @@ var hydrateVariables = async (tool2) => {
5078
5237
  const variableName = toolConfig.variable;
5079
5238
  const variable = await db3.from("variables").where({ name: variableName }).first();
5080
5239
  if (!variable) {
5081
- console.error("[EXULU] Variable " + variableName + " not found.");
5082
5240
  throw new Error("Variable " + variableName + " not found.");
5083
5241
  }
5084
5242
  let value = variable.value;
@@ -5090,7 +5248,6 @@ var hydrateVariables = async (tool2) => {
5090
5248
  return toolConfig;
5091
5249
  });
5092
5250
  await Promise.all(promises);
5093
- console.log("[EXULU] Variable values retrieved and added to tool config.");
5094
5251
  return tool2;
5095
5252
  };
5096
5253
  function generateSlug(name) {
@@ -5314,7 +5471,21 @@ var ExuluAgent2 = class {
5314
5471
  });
5315
5472
  }
5316
5473
  console.log("[EXULU] Message count for agent: " + this.name, "loaded for generating sync.", messages.length);
5317
- 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.";
5474
+ let project;
5475
+ if (session) {
5476
+ const sessionData = await getSession({ sessionID: session });
5477
+ project = sessionData.project;
5478
+ }
5479
+ const personalizationInformation = exuluConfig?.privacy?.systemPromptPersonalization !== false ? `
5480
+ ${user?.firstname ? `The users first name is "${user.firstname}"` : ""}
5481
+ ${user?.lastname ? `The users last name is "${user.lastname}"` : ""}
5482
+ ${user?.email ? `The users email is "${user.email}"` : ""}
5483
+ ` : "";
5484
+ const genericContext = `IMPORTANT general information:
5485
+ ${personalizationInformation}
5486
+ The current date is "${(/* @__PURE__ */ new Date()).toLocaleDateString()}" and the current time is "${(/* @__PURE__ */ new Date()).toLocaleTimeString()}".
5487
+ If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume
5488
+ they are talking with the current date in mind as a reference.`;
5318
5489
  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.";
5319
5490
  system += "\n\n" + genericContext;
5320
5491
  if (prompt) {
@@ -5339,7 +5510,7 @@ var ExuluAgent2 = class {
5339
5510
  system,
5340
5511
  prompt,
5341
5512
  maxRetries: 2,
5342
- tools: convertToolsArrayToObject(
5513
+ tools: await convertToolsArrayToObject(
5343
5514
  currentTools,
5344
5515
  allExuluTools,
5345
5516
  toolConfigs,
@@ -5348,7 +5519,8 @@ var ExuluAgent2 = class {
5348
5519
  user,
5349
5520
  exuluConfig,
5350
5521
  session,
5351
- req
5522
+ req,
5523
+ project
5352
5524
  ),
5353
5525
  stopWhen: [stepCountIs(2)]
5354
5526
  });
@@ -5399,7 +5571,7 @@ var ExuluAgent2 = class {
5399
5571
  ignoreIncompleteToolCalls: true
5400
5572
  }),
5401
5573
  maxRetries: 2,
5402
- tools: convertToolsArrayToObject(
5574
+ tools: await convertToolsArrayToObject(
5403
5575
  currentTools,
5404
5576
  allExuluTools,
5405
5577
  toolConfigs,
@@ -5408,7 +5580,8 @@ var ExuluAgent2 = class {
5408
5580
  user,
5409
5581
  exuluConfig,
5410
5582
  session,
5411
- req
5583
+ req,
5584
+ project
5412
5585
  ),
5413
5586
  stopWhen: [stepCountIs(2)]
5414
5587
  });
@@ -5449,6 +5622,70 @@ var ExuluAgent2 = class {
5449
5622
  }
5450
5623
  return "";
5451
5624
  };
5625
+ /**
5626
+ * Convert file parts in messages to OpenAI Responses API compatible format.
5627
+ * The OpenAI Responses API doesn't support inline file parts with type 'file'.
5628
+ * This function converts:
5629
+ * - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
5630
+ * - Image files -> image parts (which ARE supported by Responses API)
5631
+ */
5632
+ async processFilePartsInMessages(messages) {
5633
+ const processedMessages = await Promise.all(messages.map(async (message) => {
5634
+ if (message.role !== "user" || !Array.isArray(message.parts)) {
5635
+ return message;
5636
+ }
5637
+ const processedParts = await Promise.all(message.parts.map(async (part) => {
5638
+ if (part.type !== "file") {
5639
+ return part;
5640
+ }
5641
+ const { mediaType, url, filename } = part;
5642
+ const imageTypes = ["image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp"];
5643
+ if (imageTypes.includes(mediaType)) {
5644
+ console.log(`[EXULU] Converting file part to image part: ${filename} `);
5645
+ return {
5646
+ type: "image",
5647
+ image: url,
5648
+ mimeType: mediaType
5649
+ };
5650
+ }
5651
+ console.log(`[EXULU] Converting file part to text using officeparser: ${filename}`);
5652
+ try {
5653
+ const response = await fetch(url);
5654
+ if (!response.ok) {
5655
+ console.error(`[EXULU] Failed to fetch file: ${filename}, status: ${response.status} `);
5656
+ return {
5657
+ type: "text",
5658
+ text: `[Error: Could not load file ${filename}]`
5659
+ };
5660
+ }
5661
+ const arrayBuffer = await response.arrayBuffer();
5662
+ const extractedText = await parseOfficeAsync(arrayBuffer, {
5663
+ outputErrorToConsole: false,
5664
+ newlineDelimiter: "\n"
5665
+ });
5666
+ return {
5667
+ type: "text",
5668
+ text: `<file file name = "${filename}" >
5669
+ ${extractedText}
5670
+ </file>`
5671
+ };
5672
+ } catch (error) {
5673
+ console.error(`[EXULU] Error processing file ${filename}:`, error);
5674
+ return {
5675
+ type: "text",
5676
+ text: `[Error extracting text from file ${filename}: ${error instanceof Error ? error.message : "Unknown error"}]`
5677
+ };
5678
+ }
5679
+ }));
5680
+ const result = {
5681
+ ...message,
5682
+ parts: processedParts
5683
+ };
5684
+ console.log("[EXULU] Result: " + JSON.stringify(result, null, 2));
5685
+ return result;
5686
+ }));
5687
+ return processedMessages;
5688
+ }
5452
5689
  generateStream = async ({
5453
5690
  user,
5454
5691
  session,
@@ -5480,7 +5717,10 @@ var ExuluAgent2 = class {
5480
5717
  });
5481
5718
  let messages = [];
5482
5719
  let previousMessagesContent = previousMessages || [];
5720
+ let project;
5483
5721
  if (session) {
5722
+ const sessionData = await getSession({ sessionID: session });
5723
+ project = sessionData.project;
5484
5724
  console.log("[EXULU] loading previous messages from session: " + session);
5485
5725
  const previousMessages2 = await getAgentMessages({
5486
5726
  session,
@@ -5496,18 +5736,21 @@ var ExuluAgent2 = class {
5496
5736
  // append the new message to the previous messages:
5497
5737
  messages: [...previousMessagesContent, message]
5498
5738
  });
5739
+ messages = messages.filter(
5740
+ (message2, index, self) => index === self.findIndex((t) => t.id === message2.id)
5741
+ );
5742
+ messages = await this.processFilePartsInMessages(messages);
5499
5743
  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.";
5500
5744
  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.";
5501
5745
  system += "\n\n" + genericContext;
5502
- console.log("[EXULU] tools for agent: " + this.name, currentTools?.map((x) => x.name + " (" + x.id + ")"));
5503
- console.log("[EXULU] system", system.slice(0, 100) + "...");
5504
5746
  const result = streamText({
5505
5747
  model,
5506
5748
  // Should be a LanguageModelV1
5507
5749
  messages: convertToModelMessages(messages, {
5508
5750
  ignoreIncompleteToolCalls: true
5509
5751
  }),
5510
- // prepareStep could be used here to set the model for the first step or change other params
5752
+ // PrepareStep could be used here to set the model
5753
+ // for the first step or change other parameters.
5511
5754
  system,
5512
5755
  maxRetries: 2,
5513
5756
  providerOptions: {
@@ -5515,7 +5758,7 @@ var ExuluAgent2 = class {
5515
5758
  reasoningSummary: "auto"
5516
5759
  }
5517
5760
  },
5518
- tools: convertToolsArrayToObject(
5761
+ tools: await convertToolsArrayToObject(
5519
5762
  currentTools,
5520
5763
  allExuluTools,
5521
5764
  toolConfigs,
@@ -5524,7 +5767,8 @@ var ExuluAgent2 = class {
5524
5767
  user,
5525
5768
  exuluConfig,
5526
5769
  session,
5527
- req
5770
+ req,
5771
+ project
5528
5772
  ),
5529
5773
  onError: (error) => {
5530
5774
  console.error("[EXULU] chat stream error.", error);
@@ -5560,7 +5804,7 @@ var getSession = async ({ sessionID }) => {
5560
5804
  };
5561
5805
  var saveChat = async ({ session, user, messages }) => {
5562
5806
  const { db: db3 } = await postgresClient();
5563
- const promises = messages.map((message) => {
5807
+ for (const message of messages) {
5564
5808
  const mutation = db3.from("agent_messages").insert({
5565
5809
  session,
5566
5810
  user,
@@ -5569,9 +5813,8 @@ var saveChat = async ({ session, user, messages }) => {
5569
5813
  title: message.role === "user" ? "User" : "Assistant"
5570
5814
  }).returning("id");
5571
5815
  mutation.onConflict("message_id").merge();
5572
- return mutation;
5573
- });
5574
- await Promise.all(promises);
5816
+ await mutation;
5817
+ }
5575
5818
  };
5576
5819
  var ExuluEmbedder = class {
5577
5820
  id;
@@ -5621,12 +5864,10 @@ var ExuluEmbedder = class {
5621
5864
  id
5622
5865
  } = setting;
5623
5866
  let value = "";
5624
- console.log("[EXULU] variable name", variableName);
5625
5867
  const variable = await db3.from("variables").where({ name: variableName }).first();
5626
5868
  if (!variable) {
5627
5869
  throw new Error("Variable not found for embedder setting: " + name + " in context: " + context + " and embedder: " + this.id);
5628
5870
  }
5629
- console.log("[EXULU] variable", variable);
5630
5871
  if (variable.encrypted) {
5631
5872
  if (!process.env.NEXTAUTH_SECRET) {
5632
5873
  throw new Error("NEXTAUTH_SECRET environment variable is not set, cannot decrypt variable: " + name);
@@ -5638,14 +5879,12 @@ var ExuluEmbedder = class {
5638
5879
  throw new Error("Decryption returned empty string - invalid key or corrupted data");
5639
5880
  }
5640
5881
  value = decrypted;
5641
- console.log("[EXULU] successfully decrypted value for", name);
5642
5882
  } catch (error) {
5643
5883
  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.`);
5644
5884
  }
5645
5885
  } else {
5646
5886
  value = variable.value;
5647
5887
  }
5648
- console.log("[EXULU] variable value", value);
5649
5888
  hydrated.push({
5650
5889
  id: id || "",
5651
5890
  name,
@@ -5753,14 +5992,20 @@ var ExuluStorage = class {
5753
5992
  getPresignedUrl = async (key) => {
5754
5993
  return await getPresignedUrl(key, this.config);
5755
5994
  };
5756
- uploadFile = async (user, file, key, type, metadata) => {
5757
- return await uploadFile(user, file, key, this.config, {
5758
- contentType: type,
5759
- metadata: {
5760
- ...metadata,
5761
- type
5762
- }
5763
- });
5995
+ uploadFile = async (file, key, type, user, metadata) => {
5996
+ return await uploadFile(
5997
+ file,
5998
+ key,
5999
+ this.config,
6000
+ {
6001
+ contentType: type,
6002
+ metadata: {
6003
+ ...metadata,
6004
+ type
6005
+ }
6006
+ },
6007
+ user
6008
+ );
5764
6009
  };
5765
6010
  // todo add upload and delete methods
5766
6011
  };
@@ -5780,7 +6025,19 @@ var ExuluContext = class {
5780
6025
  // todo typings
5781
6026
  configuration;
5782
6027
  sources = [];
5783
- constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration, sources }) {
6028
+ constructor({
6029
+ id,
6030
+ name,
6031
+ description,
6032
+ embedder,
6033
+ active,
6034
+ rateLimit,
6035
+ fields,
6036
+ queryRewriter,
6037
+ resultReranker,
6038
+ configuration,
6039
+ sources
6040
+ }) {
5784
6041
  this.id = id;
5785
6042
  this.name = name;
5786
6043
  this.fields = fields || [];
@@ -5797,7 +6054,7 @@ var ExuluContext = class {
5797
6054
  this.queryRewriter = queryRewriter;
5798
6055
  this.resultReranker = resultReranker;
5799
6056
  }
5800
- processField = async (trigger, user, role, item, config) => {
6057
+ processField = async (trigger, item, exuluConfig, user, role) => {
5801
6058
  console.log("[EXULU] processing field", item.field, " in context", this.id);
5802
6059
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
5803
6060
  const field = this.fields.find((field2) => {
@@ -5807,7 +6064,7 @@ var ExuluContext = class {
5807
6064
  console.error("[EXULU] field not found or processor not set for field", item.field, " in context", this.id);
5808
6065
  throw new Error("Field not found or processor not set for field " + item.field + " in context " + this.id);
5809
6066
  }
5810
- const exuluStorage = new ExuluStorage({ config });
6067
+ const exuluStorage = new ExuluStorage({ config: exuluConfig });
5811
6068
  const queue = await field.processor.config?.queue;
5812
6069
  if (queue?.queue.name) {
5813
6070
  console.log("[EXULU] processor is in queue mode, scheduling job.");
@@ -5833,6 +6090,7 @@ var ExuluContext = class {
5833
6090
  job: job.id
5834
6091
  };
5835
6092
  }
6093
+ console.log("[EXULU] POS 1 -- EXULU CONTEXT PROCESS FIELD");
5836
6094
  const result = await field.processor.execute({
5837
6095
  item,
5838
6096
  user,
@@ -5845,13 +6103,24 @@ var ExuluContext = class {
5845
6103
  delete: this.deleteItem
5846
6104
  }
5847
6105
  },
5848
- config
6106
+ exuluConfig
5849
6107
  });
5850
6108
  return {
5851
6109
  result,
5852
6110
  job: void 0
5853
6111
  };
5854
6112
  };
6113
+ search = async (options) => {
6114
+ const { db: db3 } = await postgresClient();
6115
+ const result = await vectorSearch({
6116
+ ...options,
6117
+ user: options.user,
6118
+ role: options.role,
6119
+ context: this,
6120
+ db: db3
6121
+ });
6122
+ return result;
6123
+ };
5855
6124
  deleteAll = async () => {
5856
6125
  const { db: db3 } = await postgresClient();
5857
6126
  await db3.from(getTableName(this.id)).delete();
@@ -5861,8 +6130,11 @@ var ExuluContext = class {
5861
6130
  results: []
5862
6131
  };
5863
6132
  };
5864
- executeSource = async (source, inputs) => {
5865
- return await source.execute(inputs);
6133
+ executeSource = async (source, inputs, exuluConfig) => {
6134
+ return await source.execute({
6135
+ ...inputs,
6136
+ exuluConfig
6137
+ });
5866
6138
  };
5867
6139
  tableExists = async () => {
5868
6140
  const { db: db3 } = await postgresClient();
@@ -5918,7 +6190,7 @@ var ExuluContext = class {
5918
6190
  job
5919
6191
  };
5920
6192
  };
5921
- createItem = async (item, config, user, role, upsert) => {
6193
+ createItem = async (item, config, user, role, upsert, generateEmbeddingsOverwrite) => {
5922
6194
  if (upsert && (!item.id && !item.external_id)) {
5923
6195
  throw new Error("Item id or external id is required for upsert.");
5924
6196
  }
@@ -5945,12 +6217,40 @@ var ExuluContext = class {
5945
6217
  throw new Error("Failed to create item.");
5946
6218
  }
5947
6219
  console.log("[EXULU] context configuration", this.configuration);
5948
- if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
6220
+ let jobs = [];
6221
+ let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
6222
+ for (const [key, value] of Object.entries(item)) {
6223
+ console.log("[EXULU] Checking for processors for field", key);
6224
+ const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
6225
+ console.log("[EXULU] Processor found", processor);
6226
+ if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
6227
+ const {
6228
+ job: processorJob,
6229
+ result: processorResult
6230
+ } = await this.processField(
6231
+ "api",
6232
+ {
6233
+ ...item,
6234
+ id: results[0].id,
6235
+ field: key
6236
+ },
6237
+ config,
6238
+ user,
6239
+ role
6240
+ );
6241
+ if (processorJob) {
6242
+ jobs.push(processorJob);
6243
+ }
6244
+ if (!processorJob && processor.config?.generateEmbeddings) {
6245
+ shouldGenerateEmbeddings = true;
6246
+ }
6247
+ }
6248
+ }
6249
+ if (shouldGenerateEmbeddings) {
5949
6250
  console.log("[EXULU] generating embeddings for item", results[0].id);
5950
- const { job } = await this.embeddings.generate.one({
6251
+ const { job: embeddingsJob } = await this.embeddings.generate.one({
5951
6252
  item: {
5952
6253
  ...item,
5953
- // important we need to full record here with all fields for the embedder
5954
6254
  id: results[0].id
5955
6255
  },
5956
6256
  user,
@@ -5958,17 +6258,16 @@ var ExuluContext = class {
5958
6258
  trigger: "api",
5959
6259
  config
5960
6260
  });
5961
- return {
5962
- item: results[0],
5963
- job
5964
- };
6261
+ if (embeddingsJob) {
6262
+ jobs.push(embeddingsJob);
6263
+ }
5965
6264
  }
5966
6265
  return {
5967
6266
  item: results[0],
5968
- job: void 0
6267
+ job: jobs.length > 0 ? jobs.join(",") : void 0
5969
6268
  };
5970
6269
  };
5971
- updateItem = async (item, config, user, role) => {
6270
+ updateItem = async (item, config, user, role, generateEmbeddingsOverwrite) => {
5972
6271
  const { db: db3 } = await postgresClient();
5973
6272
  if (item.field) {
5974
6273
  delete item.field;
@@ -5992,8 +6291,35 @@ var ExuluContext = class {
5992
6291
  }
5993
6292
  ).returning("id");
5994
6293
  await mutation;
5995
- if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
5996
- const { job } = await this.embeddings.generate.one({
6294
+ let jobs = [];
6295
+ let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always");
6296
+ for (const [key, value] of Object.entries(item)) {
6297
+ const processor = this.fields.find((field) => field.name === key.replace("_s3key", ""))?.processor;
6298
+ if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
6299
+ const {
6300
+ job: processorJob,
6301
+ result: processorResult
6302
+ } = await this.processField(
6303
+ "api",
6304
+ {
6305
+ ...item,
6306
+ id: record.id,
6307
+ field: key
6308
+ },
6309
+ config,
6310
+ user,
6311
+ role
6312
+ );
6313
+ if (processorJob) {
6314
+ jobs.push(processorJob);
6315
+ }
6316
+ if (!processorJob && processor.config?.generateEmbeddings) {
6317
+ shouldGenerateEmbeddings = true;
6318
+ }
6319
+ }
6320
+ }
6321
+ if (shouldGenerateEmbeddings) {
6322
+ const { job: embeddingsJob } = await this.embeddings.generate.one({
5997
6323
  item: record,
5998
6324
  // important we need to full record here with all fields for the embedder
5999
6325
  user,
@@ -6001,14 +6327,13 @@ var ExuluContext = class {
6001
6327
  trigger: "api",
6002
6328
  config
6003
6329
  });
6004
- return {
6005
- item: record,
6006
- job
6007
- };
6330
+ if (embeddingsJob) {
6331
+ jobs.push(embeddingsJob);
6332
+ }
6008
6333
  }
6009
6334
  return {
6010
6335
  item: record,
6011
- job: void 0
6336
+ job: jobs.length > 0 ? jobs.join(",") : void 0
6012
6337
  };
6013
6338
  };
6014
6339
  deleteItem = async (item, user, role) => {
@@ -6035,6 +6360,34 @@ var ExuluContext = class {
6035
6360
  job: void 0
6036
6361
  };
6037
6362
  };
6363
+ getItem = async ({ item }) => {
6364
+ const { db: db3 } = await postgresClient();
6365
+ if (!item.id && !item.external_id) {
6366
+ throw new Error("Item id or external id is required to get an item.");
6367
+ }
6368
+ const result = await db3.from(getTableName(this.id)).where({
6369
+ ...item.id ? { id: item.id } : {},
6370
+ ...item.external_id ? { external_id: item.external_id } : {}
6371
+ }).first();
6372
+ if (result) {
6373
+ const chunksCount = await db3.from(getChunksTableName(this.id)).where(
6374
+ { source: result.id }
6375
+ ).count("id");
6376
+ result.chunksCount = Number(chunksCount[0].count) || 0;
6377
+ }
6378
+ return result;
6379
+ };
6380
+ getItems = async ({
6381
+ filters,
6382
+ fields
6383
+ }) => {
6384
+ const { db: db3 } = await postgresClient();
6385
+ let query = db3.from(getTableName(this.id)).select(fields || "*");
6386
+ const tableDefinition = contextToTableDefinition(this);
6387
+ query = applyFilters(query, filters || [], tableDefinition);
6388
+ const items = await query;
6389
+ return items;
6390
+ };
6038
6391
  embeddings = {
6039
6392
  generate: {
6040
6393
  one: async ({
@@ -6051,6 +6404,12 @@ var ExuluContext = class {
6051
6404
  if (!item.id) {
6052
6405
  throw new Error("Item id is required for generating embeddings.");
6053
6406
  }
6407
+ const { db: db3 } = await postgresClient();
6408
+ const record = await db3.from(getTableName(this.id)).where({ id: item.id }).first();
6409
+ if (!record) {
6410
+ throw new Error("Item not found.");
6411
+ }
6412
+ item = record;
6054
6413
  const queue = await this.embedder.queue;
6055
6414
  if (queue?.queue.name) {
6056
6415
  console.log("[EXULU] embedder is in queue mode, scheduling job.");
@@ -6816,7 +7175,7 @@ Mood: friendly and intelligent.
6816
7175
  const user = authenticationResult.user;
6817
7176
  let agentQuery = db3("agents");
6818
7177
  agentQuery.select("*");
6819
- agentQuery = applyAccessControl(agentsSchema2(), authenticationResult.user, agentQuery);
7178
+ agentQuery = applyAccessControl(agentsSchema2(), agentQuery, authenticationResult.user);
6820
7179
  agentQuery.where({ id: req.params.agent });
6821
7180
  const agent = await agentQuery.first();
6822
7181
  if (!agent) {
@@ -6833,7 +7192,7 @@ Mood: friendly and intelligent.
6833
7192
  } else {
6834
7193
  let projectQuery = db3("projects");
6835
7194
  projectQuery.select("*");
6836
- projectQuery = applyAccessControl(projectsSchema2(), authenticationResult.user, projectQuery);
7195
+ projectQuery = applyAccessControl(projectsSchema2(), projectQuery, authenticationResult.user);
6837
7196
  projectQuery.where({ id: req.params.project });
6838
7197
  project = await projectQuery.first();
6839
7198
  if (!project) {
@@ -7175,6 +7534,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7175
7534
  if (!data.role) {
7176
7535
  throw new Error(`Role not set for processor job.`);
7177
7536
  }
7537
+ console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
7178
7538
  const result = await field.processor.execute({
7179
7539
  item: data.inputs,
7180
7540
  user: data.user,
@@ -7189,9 +7549,24 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7189
7549
  },
7190
7550
  config
7191
7551
  });
7552
+ let jobs = [];
7553
+ if (field.processor.config?.generateEmbeddings) {
7554
+ const { job: embeddingsJob } = await context.embeddings.generate.one({
7555
+ item: data.inputs,
7556
+ user: data.user,
7557
+ role: data.role,
7558
+ trigger: "api",
7559
+ config
7560
+ });
7561
+ if (embeddingsJob) {
7562
+ jobs.push(embeddingsJob);
7563
+ }
7564
+ }
7192
7565
  return {
7193
7566
  result,
7194
- metadata: {}
7567
+ metadata: {
7568
+ jobs: jobs.length > 0 ? jobs.join(",") : void 0
7569
+ }
7195
7570
  };
7196
7571
  }
7197
7572
  if (data.type === "eval_run") {
@@ -7877,7 +8252,7 @@ var ExuluMCP = class {
7877
8252
  console.log("[EXULU] MCP tool inputs", inputs);
7878
8253
  console.log("[EXULU] MCP tool args", args);
7879
8254
  const configValues = agentInstance.tools;
7880
- const tools = convertToolsArrayToObject(
8255
+ const tools = await convertToolsArrayToObject(
7881
8256
  [tool2],
7882
8257
  allTools,
7883
8258
  configValues,