@exulu/backend 1.36.1 → 1.38.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
@@ -411,7 +411,9 @@ var getToken = async (authHeader) => {
411
411
  }
412
412
  try {
413
413
  const secret = process.env.NEXTAUTH_SECRET;
414
- const jwk = await importJWK({ k: secret, alg: "HS256", kty: "oct" });
414
+ const secretBuffer = Buffer.from(secret, "utf-8");
415
+ const base64Secret = secretBuffer.toString("base64url");
416
+ const jwk = await importJWK({ k: base64Secret, alg: "HS256", kty: "oct" });
415
417
  const { payload } = await jwtVerify(token, jwk);
416
418
  return payload;
417
419
  } catch (error) {
@@ -687,25 +689,11 @@ var requestValidators = {
687
689
  message: "Missing body."
688
690
  };
689
691
  }
690
- if (!req.headers["user"]) {
692
+ if (!req.body.message && !req.body.messages) {
691
693
  return {
692
694
  error: true,
693
695
  code: 400,
694
- message: 'Missing "user" property in headers.'
695
- };
696
- }
697
- if (!req.headers["session"]) {
698
- return {
699
- error: true,
700
- code: 400,
701
- message: 'Missing "session" property in headers.'
702
- };
703
- }
704
- if (!req.body.message) {
705
- return {
706
- error: true,
707
- code: 400,
708
- message: 'Missing "message" property in body.'
696
+ message: 'Missing "message" or "messages" property in body.'
709
697
  };
710
698
  }
711
699
  return {
@@ -737,6 +725,12 @@ var agentMessagesSchema = {
737
725
  name: "user",
738
726
  type: "number"
739
727
  },
728
+ {
729
+ name: "message_id",
730
+ type: "text",
731
+ index: true,
732
+ unique: true
733
+ },
740
734
  {
741
735
  name: "session",
742
736
  type: "text"
@@ -2371,9 +2365,11 @@ function createMutations(table, agents, contexts, tools, config) {
2371
2365
  const name = args.field?.replace("_s3key", "");
2372
2366
  console.log("[EXULU] name", name);
2373
2367
  console.log("[EXULU] fields", exists.fields.map((field2) => field2.name));
2374
- const field = exists.fields.find((field2) => field2.name === name);
2368
+ const field = exists.fields.find((field2) => {
2369
+ return field2.name.replace("_s3key", "") === name;
2370
+ });
2375
2371
  if (!field) {
2376
- throw new Error(`Field ${name} not found in context ${exists.id}.`);
2372
+ throw new Error(`Field ${name} not found in context ${exists.id}].`);
2377
2373
  }
2378
2374
  if (!field.processor) {
2379
2375
  throw new Error(`Processor not set for field ${args.field} in context ${exists.id}.`);
@@ -2448,7 +2444,8 @@ function createMutations(table, agents, contexts, tools, config) {
2448
2444
  item,
2449
2445
  config,
2450
2446
  context.user.id,
2451
- context.user.role?.id
2447
+ context.user.role?.id,
2448
+ item.external_id || item.id ? true : false
2452
2449
  );
2453
2450
  if (job) {
2454
2451
  jobs.push(job);
@@ -2535,22 +2532,30 @@ function createMutations(table, agents, contexts, tools, config) {
2535
2532
  if (!id) {
2536
2533
  throw new Error(`Context ${table.id} not found.`);
2537
2534
  }
2538
- let query = db3.from(getTableName(id)).select("id");
2539
2535
  if (args.where) {
2536
+ let query = db3.from(getTableName(id)).select("id");
2540
2537
  query = applyFilters(query, args.where, table);
2538
+ const items = await query;
2539
+ if (items.length === 0) {
2540
+ throw new Error("No items found to delete chunks for.");
2541
+ }
2542
+ for (const item of items) {
2543
+ await db3.from(getChunksTableName(id)).where({ source: item.id }).delete();
2544
+ }
2545
+ return {
2546
+ message: "Chunks deleted successfully.",
2547
+ items: items.length,
2548
+ jobs: []
2549
+ };
2550
+ } else {
2551
+ const count = await db3.from(getChunksTableName(id)).count();
2552
+ await db3.from(getChunksTableName(id)).truncate();
2553
+ return {
2554
+ message: "Chunks deleted successfully.",
2555
+ items: parseInt(count[0].count),
2556
+ jobs: []
2557
+ };
2541
2558
  }
2542
- const items = await query;
2543
- if (items.length === 0) {
2544
- throw new Error("No items found to delete chunks for.");
2545
- }
2546
- for (const item of items) {
2547
- await db3.from(getChunksTableName(id)).where({ source: item.id }).delete();
2548
- }
2549
- return {
2550
- message: "Chunks deleted successfully.",
2551
- items: items.length,
2552
- jobs: []
2553
- };
2554
2559
  };
2555
2560
  }
2556
2561
  return mutations;
@@ -2825,6 +2830,13 @@ var postprocessDeletion = async ({
2825
2830
  }
2826
2831
  return result;
2827
2832
  }
2833
+ if (table.type === "agent_sessions") {
2834
+ if (!result.id) {
2835
+ return result;
2836
+ }
2837
+ const { db: db3 } = await postgresClient();
2838
+ await db3.from("agent_messages").where({ session: result.id }).where({ session: result.id }).delete();
2839
+ }
2828
2840
  }
2829
2841
  return result;
2830
2842
  };
@@ -3148,7 +3160,7 @@ var vectorSearch = async ({
3148
3160
  items = await itemsQuery;
3149
3161
  break;
3150
3162
  case "hybridSearch":
3151
- const matchCount = Math.min(limit * 5, 30);
3163
+ const matchCount = Math.min(limit * 5, 100);
3152
3164
  const fullTextWeight = 1;
3153
3165
  const semanticWeight = 1;
3154
3166
  const rrfK = 50;
@@ -3175,7 +3187,7 @@ var vectorSearch = async ({
3175
3187
  FROM ${chunksTable} c
3176
3188
  WHERE c.embedding IS NOT NULL
3177
3189
  ORDER BY rank_ix
3178
- LIMIT LEAST(?, 15) * 2
3190
+ LIMIT LEAST(?, 50) * 2
3179
3191
  )
3180
3192
  SELECT
3181
3193
  m.*,
@@ -3183,6 +3195,7 @@ var vectorSearch = async ({
3183
3195
  c.source,
3184
3196
  c.content,
3185
3197
  c.chunk_index,
3198
+ c.metadata,
3186
3199
  c."createdAt" AS chunk_created_at,
3187
3200
  c."updatedAt" AS chunk_updated_at,
3188
3201
  vector_dims(c.embedding) as embedding_size,
@@ -3206,7 +3219,7 @@ var vectorSearch = async ({
3206
3219
  JOIN ${mainTable} m
3207
3220
  ON m.id = c.source
3208
3221
  ORDER BY hybrid_score DESC
3209
- LIMIT LEAST(?, 10)
3222
+ LIMIT LEAST(?, 50)
3210
3223
  OFFSET 0
3211
3224
  `;
3212
3225
  const bindings = [
@@ -3232,6 +3245,7 @@ var vectorSearch = async ({
3232
3245
  ];
3233
3246
  items = await db3.raw(hybridSQL, bindings).then((r) => r.rows ?? r);
3234
3247
  }
3248
+ console.log("[EXULU] Vector search results:", items?.length);
3235
3249
  const seenSources = /* @__PURE__ */ new Map();
3236
3250
  items = items.reduce((acc, item) => {
3237
3251
  if (!seenSources.has(item.source)) {
@@ -3249,6 +3263,7 @@ var vectorSearch = async ({
3249
3263
  chunk_index: item.chunk_index,
3250
3264
  chunk_id: item.chunk_id,
3251
3265
  source: item.source,
3266
+ metadata: item.metadata,
3252
3267
  chunk_created_at: item.chunk_created_at,
3253
3268
  chunk_updated_at: item.chunk_updated_at,
3254
3269
  embedding_size: item.embedding_size,
@@ -3265,6 +3280,7 @@ var vectorSearch = async ({
3265
3280
  chunk_id: item.chunk_id,
3266
3281
  chunk_created_at: item.chunk_created_at,
3267
3282
  embedding_size: item.embedding_size,
3283
+ metadata: item.metadata,
3268
3284
  source: item.source,
3269
3285
  chunk_updated_at: item.chunk_updated_at,
3270
3286
  ...method === "cosineDistance" && { cosine_distance: item.cosine_distance },
@@ -3274,6 +3290,7 @@ var vectorSearch = async ({
3274
3290
  }
3275
3291
  return acc;
3276
3292
  }, []);
3293
+ console.log("[EXULU] Vector search results after deduplication:", items?.length);
3277
3294
  items.forEach((item) => {
3278
3295
  if (!item.chunks?.length) {
3279
3296
  return;
@@ -3305,7 +3322,7 @@ var vectorSearch = async ({
3305
3322
  if (resultReranker && query) {
3306
3323
  items = await resultReranker(items);
3307
3324
  }
3308
- items = items.slice(0, limit);
3325
+ console.log("[EXULU] Vector search results after slicing:", items?.length);
3309
3326
  await updateStatistic({
3310
3327
  name: "count",
3311
3328
  label: table.name.singular,
@@ -3967,6 +3984,10 @@ type PageInfo {
3967
3984
  }
3968
3985
  };
3969
3986
  }));
3987
+ let embedderQueue = void 0;
3988
+ if (data.embedder?.queue) {
3989
+ embedderQueue = await data.embedder.queue;
3990
+ }
3970
3991
  const clean = {
3971
3992
  id: data.id,
3972
3993
  name: data.name,
@@ -3974,7 +3995,8 @@ type PageInfo {
3974
3995
  embedder: data.embedder ? {
3975
3996
  name: data.embedder.name,
3976
3997
  id: data.embedder.id,
3977
- config: data.embedder?.config || void 0
3998
+ config: data.embedder?.config || void 0,
3999
+ queue: embedderQueue?.queue.name || void 0
3978
4000
  } : void 0,
3979
4001
  slug: "/contexts/" + data.id,
3980
4002
  active: data.active,
@@ -4161,6 +4183,7 @@ type ItemChunks {
4161
4183
  chunk_created_at: Date
4162
4184
  chunk_updated_at: Date
4163
4185
  embedding_size: Float
4186
+ metadata: JSON
4164
4187
  }
4165
4188
 
4166
4189
  type Provider {
@@ -4201,6 +4224,7 @@ type Embedder {
4201
4224
  name: String!
4202
4225
  id: ID!
4203
4226
  config: [EmbedderConfig!]
4227
+ queue: String
4204
4228
  }
4205
4229
  type EmbedderConfig {
4206
4230
  name: String!
@@ -4392,10 +4416,21 @@ var getPresignedUrl = async (key, config) => {
4392
4416
  if (!config.fileUploads) {
4393
4417
  throw new Error("File uploads are not configured");
4394
4418
  }
4419
+ let bucket = config.fileUploads.s3Bucket;
4420
+ if (key.includes("[bucket:")) {
4421
+ console.log("[EXULU] key includes [bucket:name]", key);
4422
+ bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4423
+ if (!bucket?.length) {
4424
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4425
+ }
4426
+ key = key.split("]")[1] || "";
4427
+ console.log("[EXULU] bucket", bucket);
4428
+ console.log("[EXULU] key", key);
4429
+ }
4395
4430
  const url = await getSignedUrl(
4396
4431
  getS3Client(config),
4397
4432
  new GetObjectCommand({
4398
- Bucket: config.fileUploads.s3Bucket,
4433
+ Bucket: bucket,
4399
4434
  Key: key
4400
4435
  }),
4401
4436
  { expiresIn }
@@ -4432,7 +4467,7 @@ var uploadFile = async (user, file, key, config, options = {}) => {
4432
4467
  ContentLength: file.byteLength
4433
4468
  });
4434
4469
  await client2.send(command);
4435
- return key;
4470
+ return fullKey;
4436
4471
  };
4437
4472
  var createUppyRoutes = async (app, config) => {
4438
4473
  if (!config.fileUploads) {
@@ -4503,29 +4538,24 @@ var createUppyRoutes = async (app, config) => {
4503
4538
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4504
4539
  return;
4505
4540
  }
4506
- const { key } = req.query;
4541
+ let { key } = req.query;
4507
4542
  if (typeof key !== "string" || key.trim() === "") {
4508
4543
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4509
4544
  return;
4510
4545
  }
4511
- const userPrefix = extractUserPrefix(key);
4512
- console.log("userPrefix", userPrefix);
4513
- console.log("authenticationResult.user.id", authenticationResult.user.id);
4514
- if (!userPrefix) {
4515
- res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
4516
- return;
4517
- }
4518
- if (userPrefix !== authenticationResult.user.id.toString()) {
4519
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4520
- return;
4521
- }
4522
- if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
4523
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4524
- return;
4546
+ let bucket = config.fileUploads.s3Bucket;
4547
+ if (key.includes("[bucket:")) {
4548
+ console.log("[EXULU] key includes [bucket:name]", key);
4549
+ bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4550
+ if (!bucket?.length) {
4551
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4552
+ }
4553
+ key = key.split("]")[1] || "";
4554
+ console.log("[EXULU] bucket", bucket);
4525
4555
  }
4526
4556
  const client2 = getS3Client(config);
4527
4557
  const command = new DeleteObjectCommand({
4528
- Bucket: config.fileUploads.s3Bucket,
4558
+ Bucket: bucket,
4529
4559
  Key: key
4530
4560
  });
4531
4561
  await client2.send(command);
@@ -4554,19 +4584,6 @@ var createUppyRoutes = async (app, config) => {
4554
4584
  res.status(400).json({ error: "Missing or invalid `key` query parameter." });
4555
4585
  return;
4556
4586
  }
4557
- const userPrefix = extractUserPrefix(key);
4558
- if (!userPrefix) {
4559
- res.status(405).json({ error: 'Invalid key, does not contain a user prefix like "<user_id>/<key>.' });
4560
- return;
4561
- }
4562
- if (userPrefix !== authenticationResult.user.id.toString()) {
4563
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4564
- return;
4565
- }
4566
- if (authenticationResult.user.type !== "api" && !key.includes(authenticationResult.user.id.toString())) {
4567
- res.status(405).json({ error: "Not allowed to access the files in the folder based on authenticated user." });
4568
- return;
4569
- }
4570
4587
  try {
4571
4588
  const url = await getPresignedUrl(key, config);
4572
4589
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -4596,11 +4613,24 @@ var createUppyRoutes = async (app, config) => {
4596
4613
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
4597
4614
  return;
4598
4615
  }
4599
- const { key } = req.body;
4616
+ let { key } = req.body;
4617
+ let bucket = config.fileUploads.s3Bucket;
4618
+ if (key.includes("[bucket:")) {
4619
+ console.log("[EXULU] key includes [bucket:name]", key);
4620
+ bucket = key.split("[bucket:")[1]?.split("]")[0] || "";
4621
+ console.log("[EXULU] bucket", bucket);
4622
+ if (!bucket?.length) {
4623
+ throw new Error("Invalid key, does not contain a bucket name like '[bucket:name]'.");
4624
+ }
4625
+ key = key.split("]")[1] || "";
4626
+ console.log("[EXULU] key", key);
4627
+ }
4600
4628
  console.log("[EXULU] Getting object metadata from s3", key);
4629
+ console.log("[EXULU] bucket", bucket);
4630
+ console.log("[EXULU] key", key);
4601
4631
  const client2 = getS3Client(config);
4602
4632
  const command = new HeadObjectCommand({
4603
- Bucket: config.fileUploads.s3Bucket,
4633
+ Bucket: bucket,
4604
4634
  Key: key
4605
4635
  });
4606
4636
  const response = await client2.send(command);
@@ -4908,6 +4938,7 @@ var createUppyRoutes = async (app, config) => {
4908
4938
  };
4909
4939
 
4910
4940
  // src/registry/classes.ts
4941
+ import { parseOfficeAsync } from "officeparser";
4911
4942
  var s3Client2;
4912
4943
  function sanitizeToolName(name) {
4913
4944
  if (typeof name !== "string") return "";
@@ -4918,7 +4949,7 @@ function sanitizeToolName(name) {
4918
4949
  }
4919
4950
  return sanitized;
4920
4951
  }
4921
- var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID) => {
4952
+ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providerapikey, contexts, user, exuluConfig, sessionID, req) => {
4922
4953
  if (!currentTools) return {};
4923
4954
  if (!allExuluTools) return {};
4924
4955
  if (!contexts) {
@@ -5005,6 +5036,7 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5005
5036
  const response = await cur.tool.execute({
5006
5037
  ...inputs,
5007
5038
  sessionID,
5039
+ req,
5008
5040
  // Convert config to object format if a config object
5009
5041
  // is available, after we added the .value property
5010
5042
  // by hydrating it from the variables table.
@@ -5014,6 +5046,7 @@ var convertToolsArrayToObject = (currentTools, allExuluTools, configs, providera
5014
5046
  user,
5015
5047
  contexts: contextsMap,
5016
5048
  upload,
5049
+ exuluConfig,
5017
5050
  config: config ? config.config.reduce((acc, curr) => {
5018
5051
  acc[curr.name] = curr.value;
5019
5052
  return acc;
@@ -5235,6 +5268,7 @@ var ExuluAgent2 = class {
5235
5268
  };
5236
5269
  generateSync = async ({
5237
5270
  prompt,
5271
+ req,
5238
5272
  user,
5239
5273
  session,
5240
5274
  inputMessages,
@@ -5316,7 +5350,8 @@ var ExuluAgent2 = class {
5316
5350
  contexts,
5317
5351
  user,
5318
5352
  exuluConfig,
5319
- session
5353
+ session,
5354
+ req
5320
5355
  ),
5321
5356
  stopWhen: [stepCountIs(2)]
5322
5357
  });
@@ -5375,7 +5410,8 @@ var ExuluAgent2 = class {
5375
5410
  contexts,
5376
5411
  user,
5377
5412
  exuluConfig,
5378
- session
5413
+ session,
5414
+ req
5379
5415
  ),
5380
5416
  stopWhen: [stepCountIs(2)]
5381
5417
  });
@@ -5416,6 +5452,71 @@ var ExuluAgent2 = class {
5416
5452
  }
5417
5453
  return "";
5418
5454
  };
5455
+ /**
5456
+ * Convert file parts in messages to OpenAI Responses API compatible format.
5457
+ * The OpenAI Responses API doesn't support inline file parts with type 'file'.
5458
+ * This function converts:
5459
+ * - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
5460
+ * - Image files -> image parts (which ARE supported by Responses API)
5461
+ */
5462
+ async processFilePartsInMessages(messages) {
5463
+ const processedMessages = await Promise.all(messages.map(async (message) => {
5464
+ console.log("[EXULU] Processing file parts in messages: " + JSON.stringify(message, null, 2));
5465
+ if (message.role !== "user" || !Array.isArray(message.parts)) {
5466
+ return message;
5467
+ }
5468
+ const processedParts = await Promise.all(message.parts.map(async (part) => {
5469
+ if (part.type !== "file") {
5470
+ return part;
5471
+ }
5472
+ const { mediaType, url, filename } = part;
5473
+ const imageTypes = ["image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp"];
5474
+ if (imageTypes.includes(mediaType)) {
5475
+ console.log(`[EXULU] Converting file part to image part: ${filename}`);
5476
+ return {
5477
+ type: "image",
5478
+ image: url,
5479
+ mimeType: mediaType
5480
+ };
5481
+ }
5482
+ console.log(`[EXULU] Converting file part to text using officeparser: ${filename}`);
5483
+ try {
5484
+ const response = await fetch(url);
5485
+ if (!response.ok) {
5486
+ console.error(`[EXULU] Failed to fetch file: ${filename}, status: ${response.status}`);
5487
+ return {
5488
+ type: "text",
5489
+ text: `[Error: Could not load file ${filename}]`
5490
+ };
5491
+ }
5492
+ const arrayBuffer = await response.arrayBuffer();
5493
+ const extractedText = await parseOfficeAsync(arrayBuffer, {
5494
+ outputErrorToConsole: false,
5495
+ newlineDelimiter: "\n"
5496
+ });
5497
+ return {
5498
+ type: "text",
5499
+ text: `<file name="${filename}">
5500
+ ${extractedText}
5501
+ </file>`
5502
+ };
5503
+ } catch (error) {
5504
+ console.error(`[EXULU] Error processing file ${filename}:`, error);
5505
+ return {
5506
+ type: "text",
5507
+ text: `[Error extracting text from file ${filename}: ${error instanceof Error ? error.message : "Unknown error"}]`
5508
+ };
5509
+ }
5510
+ }));
5511
+ const result = {
5512
+ ...message,
5513
+ parts: processedParts
5514
+ };
5515
+ console.log("[EXULU] Result: " + JSON.stringify(result, null, 2));
5516
+ return result;
5517
+ }));
5518
+ return processedMessages;
5519
+ }
5419
5520
  generateStream = async ({
5420
5521
  user,
5421
5522
  session,
@@ -5427,7 +5528,8 @@ var ExuluAgent2 = class {
5427
5528
  providerapikey,
5428
5529
  contexts,
5429
5530
  exuluConfig,
5430
- instructions
5531
+ instructions,
5532
+ req
5431
5533
  }) => {
5432
5534
  if (!this.model) {
5433
5535
  console.error("[EXULU] Model is required for streaming.");
@@ -5450,7 +5552,7 @@ var ExuluAgent2 = class {
5450
5552
  console.log("[EXULU] loading previous messages from session: " + session);
5451
5553
  const previousMessages2 = await getAgentMessages({
5452
5554
  session,
5453
- user: user.id,
5555
+ user: user?.id,
5454
5556
  limit: 50,
5455
5557
  page: 1
5456
5558
  });
@@ -5462,6 +5564,11 @@ var ExuluAgent2 = class {
5462
5564
  // append the new message to the previous messages:
5463
5565
  messages: [...previousMessagesContent, message]
5464
5566
  });
5567
+ messages = messages.filter(
5568
+ (message2, index, self) => index === self.findIndex((t) => t.id === message2.id)
5569
+ );
5570
+ console.log("[EXULU] Processing file parts in messages for OpenAI Responses API compatibility");
5571
+ messages = await this.processFilePartsInMessages(messages);
5465
5572
  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.";
5466
5573
  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.";
5467
5574
  system += "\n\n" + genericContext;
@@ -5489,7 +5596,8 @@ var ExuluAgent2 = class {
5489
5596
  contexts,
5490
5597
  user,
5491
5598
  exuluConfig,
5492
- session
5599
+ session,
5600
+ req
5493
5601
  ),
5494
5602
  onError: (error) => {
5495
5603
  console.error("[EXULU] chat stream error.", error);
@@ -5507,7 +5615,7 @@ var ExuluAgent2 = class {
5507
5615
  var getAgentMessages = async ({ session, user, limit, page }) => {
5508
5616
  const { db: db3 } = await postgresClient();
5509
5617
  console.log("[EXULU] getting agent messages for session: " + session + " and user: " + user + " and page: " + page);
5510
- const query = db3.from("agent_messages").where({ session, user }).limit(limit);
5618
+ const query = db3.from("agent_messages").where({ session, user: user || null }).limit(limit);
5511
5619
  if (page > 0) {
5512
5620
  query.offset((page - 1) * limit);
5513
5621
  }
@@ -5525,15 +5633,17 @@ var getSession = async ({ sessionID }) => {
5525
5633
  };
5526
5634
  var saveChat = async ({ session, user, messages }) => {
5527
5635
  const { db: db3 } = await postgresClient();
5528
- const promises = messages.map((message) => {
5529
- return db3.from("agent_messages").insert({
5636
+ for (const message of messages) {
5637
+ const mutation = db3.from("agent_messages").insert({
5530
5638
  session,
5531
5639
  user,
5532
5640
  content: JSON.stringify(message),
5641
+ message_id: message.id,
5533
5642
  title: message.role === "user" ? "User" : "Assistant"
5534
- });
5535
- });
5536
- await Promise.all(promises);
5643
+ }).returning("id");
5644
+ mutation.onConflict("message_id").merge();
5645
+ await mutation;
5646
+ }
5537
5647
  };
5538
5648
  var ExuluEmbedder = class {
5539
5649
  id;
@@ -5574,19 +5684,36 @@ var ExuluEmbedder = class {
5574
5684
  });
5575
5685
  for (const config of this.config || []) {
5576
5686
  const name = config.name;
5687
+ const setting = variables.find((v) => v.name === name);
5688
+ if (!setting) {
5689
+ throw new Error("Setting value not found for embedder setting: " + name + ", for context: " + context + " and embedder: " + this.id + ". Make sure to set the value for this setting in the embedder settings.");
5690
+ }
5577
5691
  const {
5578
5692
  value: variableName,
5579
5693
  id
5580
- } = variables.find((v) => v.name === name);
5694
+ } = setting;
5581
5695
  let value = "";
5582
5696
  console.log("[EXULU] variable name", variableName);
5583
5697
  const variable = await db3.from("variables").where({ name: variableName }).first();
5584
5698
  if (!variable) {
5585
5699
  throw new Error("Variable not found for embedder setting: " + name + " in context: " + context + " and embedder: " + this.id);
5586
5700
  }
5701
+ console.log("[EXULU] variable", variable);
5587
5702
  if (variable.encrypted) {
5588
- const bytes = CryptoJS2.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5589
- value = bytes.toString(CryptoJS2.enc.Utf8);
5703
+ if (!process.env.NEXTAUTH_SECRET) {
5704
+ throw new Error("NEXTAUTH_SECRET environment variable is not set, cannot decrypt variable: " + name);
5705
+ }
5706
+ try {
5707
+ const bytes = CryptoJS2.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
5708
+ const decrypted = bytes.toString(CryptoJS2.enc.Utf8);
5709
+ if (!decrypted) {
5710
+ throw new Error("Decryption returned empty string - invalid key or corrupted data");
5711
+ }
5712
+ value = decrypted;
5713
+ console.log("[EXULU] successfully decrypted value for", name);
5714
+ } catch (error) {
5715
+ 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.`);
5716
+ }
5590
5717
  } else {
5591
5718
  value = variable.value;
5592
5719
  }
@@ -5745,7 +5872,9 @@ var ExuluContext = class {
5745
5872
  processField = async (trigger, user, role, item, config) => {
5746
5873
  console.log("[EXULU] processing field", item.field, " in context", this.id);
5747
5874
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
5748
- const field = this.fields.find((field2) => field2.name === item.field?.replace("_s3key", ""));
5875
+ const field = this.fields.find((field2) => {
5876
+ return field2.name.replace("_s3key", "") === item.field.replace("_s3key", "");
5877
+ });
5749
5878
  if (!field || !field.processor) {
5750
5879
  console.error("[EXULU] field not found or processor not set for field", item.field, " in context", this.id);
5751
5880
  throw new Error("Field not found or processor not set for field " + item.field + " in context " + this.id);
@@ -5755,7 +5884,7 @@ var ExuluContext = class {
5755
5884
  if (queue?.queue.name) {
5756
5885
  console.log("[EXULU] processor is in queue mode, scheduling job.");
5757
5886
  const job = await bullmqDecorator({
5758
- timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 180,
5887
+ timeoutInSeconds: field.processor?.config?.timeoutInSeconds || 600,
5759
5888
  label: `${this.name} ${field.name} data processor`,
5760
5889
  processor: `${this.id}-${field.name}`,
5761
5890
  context: this.id,
@@ -5846,6 +5975,7 @@ var ExuluContext = class {
5846
5975
  if (chunks?.length) {
5847
5976
  await db3.from(getChunksTableName(this.id)).insert(chunks.map((chunk) => ({
5848
5977
  source,
5978
+ metadata: chunk.metadata,
5849
5979
  content: chunk.content,
5850
5980
  chunk_index: chunk.index,
5851
5981
  embedding: pgvector2.toSql(chunk.vector)
@@ -5886,9 +6016,15 @@ var ExuluContext = class {
5886
6016
  if (!results[0]) {
5887
6017
  throw new Error("Failed to create item.");
5888
6018
  }
6019
+ console.log("[EXULU] context configuration", this.configuration);
5889
6020
  if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
6021
+ console.log("[EXULU] generating embeddings for item", results[0].id);
5890
6022
  const { job } = await this.embeddings.generate.one({
5891
- item: results[0],
6023
+ item: {
6024
+ ...item,
6025
+ // important we need to full record here with all fields for the embedder
6026
+ id: results[0].id
6027
+ },
5892
6028
  user,
5893
6029
  role,
5894
6030
  trigger: "api",
@@ -5931,7 +6067,7 @@ var ExuluContext = class {
5931
6067
  if (this.embedder && (this.configuration.calculateVectors === "onUpdate" || this.configuration.calculateVectors === "always")) {
5932
6068
  const { job } = await this.embeddings.generate.one({
5933
6069
  item: record,
5934
- // important we need to full record here with all fields
6070
+ // important we need to full record here with all fields for the embedder
5935
6071
  user,
5936
6072
  role,
5937
6073
  trigger: "api",
@@ -5948,10 +6084,13 @@ var ExuluContext = class {
5948
6084
  };
5949
6085
  };
5950
6086
  deleteItem = async (item, user, role) => {
5951
- if (!item.id) {
5952
- throw new Error("Item id is required for deleting item.");
5953
- }
5954
6087
  const { db: db3 } = await postgresClient();
6088
+ if (!item.id?.length && item?.external_id) {
6089
+ item = await db3.from(getTableName(this.id)).where({ external_id: item.external_id }).first();
6090
+ if (!item || !item.id) {
6091
+ throw new Error(`Item not found for external id ${item?.external_id || "undefined"}.`);
6092
+ }
6093
+ }
5955
6094
  await db3.from(getTableName(this.id)).where({ id: item.id }).delete();
5956
6095
  if (!this.embedder) {
5957
6096
  return {
@@ -6125,7 +6264,7 @@ var ExuluContext = class {
6125
6264
  const { db: db3 } = await postgresClient();
6126
6265
  const result = await vectorSearch({
6127
6266
  page: 1,
6128
- limit: 10,
6267
+ limit: 50,
6129
6268
  query,
6130
6269
  filters: [],
6131
6270
  user,
@@ -6153,7 +6292,6 @@ var ExuluContext = class {
6153
6292
  };
6154
6293
  };
6155
6294
  var updateStatistic = async (statistic) => {
6156
- console.log("[EXULU] updating statistic", statistic);
6157
6295
  const currentDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
6158
6296
  const { db: db3 } = await postgresClient();
6159
6297
  const existing = await db3.from("tracking").where({
@@ -6278,6 +6416,7 @@ var CLAUDE_MESSAGES = {
6278
6416
 
6279
6417
  // src/registry/routes.ts
6280
6418
  import { createIdGenerator } from "ai";
6419
+ import cookieParser from "cookie-parser";
6281
6420
  var REQUEST_SIZE_LIMIT = "50mb";
6282
6421
  var global_queues = {
6283
6422
  eval_runs: "eval_runs"
@@ -6314,6 +6453,7 @@ var createExpressRoutes = async (app, agents, tools, contexts, config, evals, tr
6314
6453
  app.use(cors(corsOptions));
6315
6454
  app.use(bodyParser.urlencoded({ extended: true, limit: REQUEST_SIZE_LIMIT }));
6316
6455
  app.use(bodyParser.json({ limit: REQUEST_SIZE_LIMIT }));
6456
+ app.use(cookieParser());
6317
6457
  console.log(`
6318
6458
  \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557
6319
6459
  \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551
@@ -6535,8 +6675,9 @@ Mood: friendly and intelligent.
6535
6675
  res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
6536
6676
  return;
6537
6677
  }
6678
+ console.log("[EXULU] agentInstance.rights_mode", agentInstance.rights_mode);
6538
6679
  const authenticationResult = await requestValidators.authenticate(req);
6539
- if (!authenticationResult.user?.id) {
6680
+ if (!authenticationResult.user?.id && agentInstance.rights_mode !== "public") {
6540
6681
  res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
6541
6682
  return;
6542
6683
  }
@@ -6560,7 +6701,7 @@ Mood: friendly and intelligent.
6560
6701
  return;
6561
6702
  }
6562
6703
  }
6563
- if (user.type !== "api" && !user.super_admin && req.body.resourceId !== user.id) {
6704
+ if (user?.type !== "api" && !user?.super_admin && req.body.resourceId !== user?.id) {
6564
6705
  res.status(400).json({
6565
6706
  message: "The provided user id in the resourceId field is not the same as the authenticated user. Only super admins and API users can impersonate other users."
6566
6707
  });
@@ -6581,7 +6722,6 @@ Mood: friendly and intelligent.
6581
6722
  return;
6582
6723
  }
6583
6724
  providerapikey = variable.value;
6584
- console.log("[EXULU] encrypted value", providerapikey);
6585
6725
  if (!variable.encrypted) {
6586
6726
  res.status(400).json({
6587
6727
  message: "Provider API key variable not encrypted, for security reasons you are only allowed to use encrypted variables for provider API keys."
@@ -6591,7 +6731,6 @@ Mood: friendly and intelligent.
6591
6731
  if (variable.encrypted) {
6592
6732
  const bytes = CryptoJS3.AES.decrypt(variable.value, process.env.NEXTAUTH_SECRET);
6593
6733
  providerapikey = bytes.toString(CryptoJS3.enc.Utf8);
6594
- console.log("[EXULU] decrypted value", providerapikey);
6595
6734
  }
6596
6735
  }
6597
6736
  if (!!headers.stream) {
@@ -6599,17 +6738,27 @@ Mood: friendly and intelligent.
6599
6738
  label: agent.name,
6600
6739
  trigger: "agent"
6601
6740
  };
6741
+ let previousMessages = [];
6742
+ let message;
6743
+ if (!req.body.message && !headers.session && req.body.messages) {
6744
+ message = req.body.messages[req.body.messages.length - 1];
6745
+ previousMessages = req.body.messages.slice(0, -1);
6746
+ } else {
6747
+ message = req.body.message;
6748
+ }
6602
6749
  const result = await agent.generateStream({
6603
6750
  contexts,
6604
6751
  user,
6605
6752
  instructions: agentInstance.instructions,
6606
6753
  session: headers.session,
6607
- message: req.body.message,
6754
+ message,
6755
+ previousMessages,
6608
6756
  currentTools: enabledTools,
6609
6757
  allExuluTools: tools,
6610
6758
  providerapikey,
6611
6759
  toolConfigs: agentInstance.tools,
6612
- exuluConfig: config
6760
+ exuluConfig: config,
6761
+ req
6613
6762
  });
6614
6763
  result.stream.consumeStream();
6615
6764
  result.stream.pipeUIMessageStreamToResponse(res, {
@@ -6636,11 +6785,12 @@ Mood: friendly and intelligent.
6636
6785
  size: 16
6637
6786
  }),
6638
6787
  onFinish: async ({ messages, isContinuation, isAborted, responseMessage }) => {
6639
- if (headers.session) {
6788
+ console.log("[EXULU] onFinish", messages?.map((msg) => msg.parts?.map((part) => part.type === "text" ? part.text : null)).join("\n"));
6789
+ if (headers.session && user?.id) {
6640
6790
  await saveChat({
6641
6791
  session: headers.session,
6642
6792
  user: user.id,
6643
- messages: messages.filter((x) => !result.previousMessages.find((y) => y.id === x.id))
6793
+ messages
6644
6794
  });
6645
6795
  }
6646
6796
  const metadata = messages[messages.length - 1]?.metadata;
@@ -6657,7 +6807,7 @@ Mood: friendly and intelligent.
6657
6807
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
6658
6808
  trigger: statistics.trigger,
6659
6809
  count: 1,
6660
- user: user.id,
6810
+ user: user?.id,
6661
6811
  role: user?.role?.id
6662
6812
  }),
6663
6813
  ...metadata?.inputTokens ? [
@@ -6667,7 +6817,7 @@ Mood: friendly and intelligent.
6667
6817
  type: STATISTICS_TYPE_ENUM.AGENT_RUN,
6668
6818
  trigger: statistics.trigger,
6669
6819
  count: metadata?.inputTokens,
6670
- user: user.id,
6820
+ user: user?.id,
6671
6821
  role: user?.role?.id
6672
6822
  })
6673
6823
  ] : [],
@@ -6688,6 +6838,7 @@ Mood: friendly and intelligent.
6688
6838
  } else {
6689
6839
  const response = await agent.generateSync({
6690
6840
  user,
6841
+ req,
6691
6842
  instructions: agentInstance.instructions,
6692
6843
  session: headers.session,
6693
6844
  inputMessages: [req.body.message],
@@ -7026,7 +7177,7 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7026
7177
  }));
7027
7178
  const { db: db3 } = await postgresClient();
7028
7179
  const data = bullmqJob.data;
7029
- const timeoutInSeconds = data.timeoutInSeconds || 300;
7180
+ const timeoutInSeconds = data.timeoutInSeconds || 600;
7030
7181
  const timeoutMs = timeoutInSeconds * 1e3;
7031
7182
  const timeoutPromise = new Promise((_, reject) => {
7032
7183
  setTimeout(() => {
@@ -7080,7 +7231,9 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7080
7231
  if (!context) {
7081
7232
  throw new Error(`Context ${data.context} not found in the registry.`);
7082
7233
  }
7083
- const field = context.fields.find((field2) => field2.name === data.inputs.field);
7234
+ const field = context.fields.find((field2) => {
7235
+ return field2.name.replace("_s3key", "") === data.inputs.field.replace("_s3key", "");
7236
+ });
7084
7237
  if (!field) {
7085
7238
  throw new Error(`Field ${data.inputs.field} not found in the context ${data.context}.`);
7086
7239
  }
@@ -7355,7 +7508,13 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7355
7508
  let jobs = [];
7356
7509
  let items = [];
7357
7510
  for (const item of result) {
7358
- const { item: createdItem, job } = await context.createItem(item, config, data.user, data.role);
7511
+ const { item: createdItem, job } = await context.createItem(
7512
+ item,
7513
+ config,
7514
+ data.user,
7515
+ data.role,
7516
+ item.external_id || item.id ? true : false
7517
+ );
7359
7518
  if (job) {
7360
7519
  jobs.push(job);
7361
7520
  console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`, logMetadata2(bullmqJob.name, {
@@ -7409,14 +7568,12 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
7409
7568
  }
7410
7569
  );
7411
7570
  worker.on("completed", async (job, returnvalue) => {
7412
- console.log(`[EXULU] completed job ${job.id}.`, logMetadata2(job.name, {
7413
- result: returnvalue
7414
- }));
7571
+ console.log(`[EXULU] completed job ${job.id}.`, returnvalue);
7415
7572
  const { db: db3 } = await postgresClient();
7416
7573
  await db3.from("job_results").where({ job_id: job.id }).update({
7417
7574
  state: JOB_STATUS_ENUM.completed,
7418
- result: returnvalue.result,
7419
- metadata: returnvalue.metadata
7575
+ result: returnvalue.result ? JSON.stringify(returnvalue.result) : null,
7576
+ metadata: returnvalue.metadata ? JSON.stringify(returnvalue.metadata) : null
7420
7577
  });
7421
7578
  });
7422
7579
  worker.on("failed", async (job, error, prev) => {
@@ -8100,7 +8257,6 @@ var claudeSonnet45Agent = new ExuluAgent2({
8100
8257
 
8101
8258
  // src/templates/agents/google/vertex/index.ts
8102
8259
  import { createVertex } from "@ai-sdk/google-vertex";
8103
- import "@ai-sdk/google-vertex";
8104
8260
  var vertexAuthenticationInformation = `
8105
8261
  ### Vertex Authentication Setup (Google Auth)
8106
8262
 
@@ -8155,7 +8311,6 @@ var vertexGemini25FlashAgent = new ExuluAgent2({
8155
8311
  instructions: "",
8156
8312
  model: {
8157
8313
  create: ({ apiKey }) => {
8158
- console.log("[EXULU] apiKey", apiKey);
8159
8314
  if (!apiKey) {
8160
8315
  throw new Error("Auth credentials not found for Google Vertex agent, make sure you have set the provider api key to a valid google authentication json.");
8161
8316
  }
@@ -8166,8 +8321,45 @@ var vertexGemini25FlashAgent = new ExuluAgent2({
8166
8321
  if (!googleAuthPayload.location) {
8167
8322
  throw new Error("Location not set in authentication json for Google Vertex Gemini 2.5 Flash agent, should be for example 'europe-west1'");
8168
8323
  }
8169
- const vertex2 = createVertex(googleAuthPayload);
8170
- const model = vertex2("gemini-2.5-flash");
8324
+ const vertex = createVertex(googleAuthPayload);
8325
+ const model = vertex("gemini-2.5-flash");
8326
+ return model;
8327
+ }
8328
+ }
8329
+ }
8330
+ });
8331
+ var vertexGemini20FlashAgent = new ExuluAgent2({
8332
+ id: `default_vertex_gemini_2_0_flash_agent`,
8333
+ name: `GEMINI-2.0-FLASH`,
8334
+ provider: "vertex",
8335
+ description: `Google Vertex Gemini 2.0 Flash model. High intelligence and capability. Moderately Fast.`,
8336
+ type: "agent",
8337
+ capabilities: {
8338
+ text: true,
8339
+ images: [".png", ".jpg", ".jpeg", ".webp"],
8340
+ files: [".pdf", ".txt"],
8341
+ audio: [".mpeg", ".mp3", ".m4a", ".wav", ".mp4"],
8342
+ video: [".mp4", ".mpeg"]
8343
+ },
8344
+ authenticationInformation: vertexAuthenticationInformation,
8345
+ maxContextLength: 1048576,
8346
+ config: {
8347
+ name: `GEMINI-2.0-FLASH`,
8348
+ instructions: "",
8349
+ model: {
8350
+ create: ({ apiKey }) => {
8351
+ if (!apiKey) {
8352
+ throw new Error("Auth credentials not found for Google Vertex agent, make sure you have set the provider api key to a valid google authentication json.");
8353
+ }
8354
+ const googleAuthPayload = JSON.parse(apiKey || "{}");
8355
+ if (!googleAuthPayload) {
8356
+ throw new Error("API key not found for Google Vertex Gemini 2.0 Flash agent.");
8357
+ }
8358
+ if (!googleAuthPayload.location) {
8359
+ throw new Error("Location not set in authentication json for Google Vertex Gemini 2.0 Flash agent, should be for example 'europe-west1'");
8360
+ }
8361
+ const vertex = createVertex(googleAuthPayload);
8362
+ const model = vertex("gemini-2.0-flash");
8171
8363
  return model;
8172
8364
  }
8173
8365
  }
@@ -8193,7 +8385,6 @@ var vertexGemini3ProAgent = new ExuluAgent2({
8193
8385
  instructions: "",
8194
8386
  model: {
8195
8387
  create: ({ apiKey }) => {
8196
- console.log("[EXULU] apiKey", apiKey);
8197
8388
  if (!apiKey) {
8198
8389
  throw new Error("Auth credentials not found for Google Vertex agent, make sure you have set the provider api key to a valid google authentication json.");
8199
8390
  }
@@ -8204,8 +8395,8 @@ var vertexGemini3ProAgent = new ExuluAgent2({
8204
8395
  if (!googleAuthPayload.location) {
8205
8396
  throw new Error("Location not set in authentication json for Google Vertex Gemini 3 Pro agent, should be for example 'europe-west1'");
8206
8397
  }
8207
- const vertex2 = createVertex(googleAuthPayload);
8208
- const model = vertex2("gemini-3-pro-preview");
8398
+ const vertex = createVertex(googleAuthPayload);
8399
+ const model = vertex("gemini-3-pro-preview");
8209
8400
  return model;
8210
8401
  }
8211
8402
  }
@@ -9295,6 +9486,32 @@ var mathTools = [
9295
9486
  degreesToRadiansTool
9296
9487
  ];
9297
9488
 
9489
+ // src/templates/tools/preview-pdf.ts
9490
+ import { z as z5 } from "zod";
9491
+ var previewPdfTool = new ExuluTool2({
9492
+ id: "preview_pdf",
9493
+ name: "Preview PDF",
9494
+ description: "Used to display a PDF file in an iframe web view",
9495
+ type: "function",
9496
+ config: [],
9497
+ inputSchema: z5.object({
9498
+ s3key: z5.string().describe("The S3 key of the PDF file to preview, can also optionally include a [bucket:name] to specify the bucket."),
9499
+ page: z5.number().describe("The page number to preview, defaults to 1.").optional()
9500
+ }),
9501
+ execute: async ({ s3key, page, exuluConfig }) => {
9502
+ const url = await getPresignedUrl(s3key, exuluConfig);
9503
+ if (!url) {
9504
+ throw new Error("No URL provided for PDF preview");
9505
+ }
9506
+ return {
9507
+ result: JSON.stringify({
9508
+ url,
9509
+ page: page ?? 1
9510
+ })
9511
+ };
9512
+ }
9513
+ });
9514
+
9298
9515
  // src/templates/tools/todo/todowrite.txt
9299
9516
  var todowrite_default = `Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
9300
9517
  It also helps the user understand the progress of the task and overall progress of their requests.
@@ -9481,12 +9698,12 @@ Usage:
9481
9698
  - If no todos exist yet, an empty list will be returned`;
9482
9699
 
9483
9700
  // src/templates/tools/todo/todo.ts
9484
- import z5 from "zod";
9485
- var TodoSchema = z5.object({
9486
- content: z5.string().describe("Brief description of the task"),
9487
- status: z5.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
9488
- priority: z5.string().describe("Priority level of the task: high, medium, low"),
9489
- id: z5.string().describe("Unique identifier for the todo item")
9701
+ import z6 from "zod";
9702
+ var TodoSchema = z6.object({
9703
+ content: z6.string().describe("Brief description of the task"),
9704
+ status: z6.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
9705
+ priority: z6.string().describe("Priority level of the task: high, medium, low"),
9706
+ id: z6.string().describe("Unique identifier for the todo item")
9490
9707
  });
9491
9708
  var TodoWriteTool = new ExuluTool2({
9492
9709
  id: "todo_write",
@@ -9499,8 +9716,8 @@ var TodoWriteTool = new ExuluTool2({
9499
9716
  description: "The description of the todo list, if set overwrites the default description.",
9500
9717
  default: todowrite_default
9501
9718
  }],
9502
- inputSchema: z5.object({
9503
- todos: z5.array(TodoSchema).describe("The updated todo list")
9719
+ inputSchema: z6.object({
9720
+ todos: z6.array(TodoSchema).describe("The updated todo list")
9504
9721
  }),
9505
9722
  execute: async (inputs) => {
9506
9723
  const { sessionID, todos, user } = inputs;
@@ -9531,7 +9748,7 @@ var TodoReadTool = new ExuluTool2({
9531
9748
  id: "todo_read",
9532
9749
  name: "Todo Read",
9533
9750
  description: "Use this tool to read your todo list",
9534
- inputSchema: z5.object({}),
9751
+ inputSchema: z6.object({}),
9535
9752
  type: "function",
9536
9753
  category: "todo",
9537
9754
  config: [{
@@ -9643,6 +9860,7 @@ var ExuluApp = class {
9643
9860
  this._tools = [
9644
9861
  ...tools ?? [],
9645
9862
  ...mathTools,
9863
+ ...[previewPdfTool],
9646
9864
  ...todoTools,
9647
9865
  // Add contexts as tools
9648
9866
  ...Object.values(contexts || {}).map((context) => context.tool())