@exulu/backend 1.52.0 → 1.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -805,7 +805,7 @@ var JOB_STATUS_ENUM = {
805
805
  };
806
806
 
807
807
  // ee/agentic-retrieval/index.ts
808
- var import_zod5 = require("zod");
808
+ var import_zod6 = require("zod");
809
809
  var import_ai2 = require("ai");
810
810
 
811
811
  // src/uppy/index.ts
@@ -3229,7 +3229,7 @@ var mapType = (t, type, name, defaultValue, unique) => {
3229
3229
 
3230
3230
  // src/exulu/tool.ts
3231
3231
  var import_ai = require("ai");
3232
- var import_zod3 = require("zod");
3232
+ var import_zod4 = require("zod");
3233
3233
  var import_crypto_js2 = __toESM(require("crypto-js"), 1);
3234
3234
 
3235
3235
  // src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts
@@ -3445,6 +3445,102 @@ function sanitizeToolName(name) {
3445
3445
 
3446
3446
  // src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts
3447
3447
  var import_node_crypto2 = require("crypto");
3448
+
3449
+ // src/templates/tools/memory-tool.ts
3450
+ var import_zod3 = require("zod");
3451
+ var import_fs = __toESM(require("fs"), 1);
3452
+ var createNewMemoryItemTool = (agent, context) => {
3453
+ const fields = {
3454
+ name: import_zod3.z.string().describe("The name of the item to create"),
3455
+ description: import_zod3.z.string().describe("The description of the item to create")
3456
+ };
3457
+ for (const field of context.fields) {
3458
+ switch (field.type) {
3459
+ case "text":
3460
+ case "longText":
3461
+ case "shortText":
3462
+ case "code":
3463
+ case "enum":
3464
+ fields[field.name] = import_zod3.z.string().describe("The " + field.name + " of the item to create");
3465
+ break;
3466
+ case "json":
3467
+ fields[field.name] = import_zod3.z.string({}).describe(
3468
+ "The " + field.name + " of the item to create, it should be a valid JSON string."
3469
+ );
3470
+ break;
3471
+ case "markdown":
3472
+ fields[field.name] = import_zod3.z.string().describe(
3473
+ "The " + field.name + " of the item to create, it should be a valid Markdown string."
3474
+ );
3475
+ break;
3476
+ case "number":
3477
+ fields[field.name] = import_zod3.z.number().describe("The " + field.name + " of the item to create");
3478
+ break;
3479
+ case "boolean":
3480
+ fields[field.name] = import_zod3.z.boolean().describe("The " + field.name + " of the item to create");
3481
+ break;
3482
+ case "file":
3483
+ case "uuid":
3484
+ case "date":
3485
+ break;
3486
+ default:
3487
+ fields[field.name] = import_zod3.z.string().describe("The " + field.name + " of the item to create");
3488
+ break;
3489
+ }
3490
+ }
3491
+ const toolName = "create_" + sanitizeName(context.name) + "_memory_item";
3492
+ return new ExuluTool({
3493
+ id: toolName,
3494
+ name: "Create " + context.name + " Memory Item",
3495
+ category: agent.name + "_memory",
3496
+ description: "Create a new memory item in the " + agent.name + " memory context",
3497
+ type: "function",
3498
+ inputSchema: import_zod3.z.object(fields),
3499
+ config: [],
3500
+ execute: async ({ name, description, mode, information, exuluConfig, user }) => {
3501
+ let result = { result: "" };
3502
+ import_fs.default.writeFileSync("memory-tool.json", JSON.stringify({ name, description, information }, null, 2));
3503
+ try {
3504
+ const newItem = {
3505
+ name,
3506
+ description,
3507
+ information,
3508
+ rights_mode: "public"
3509
+ };
3510
+ const { item: createdItem, job: createdJob } = await context.createItem(
3511
+ newItem,
3512
+ exuluConfig,
3513
+ user?.id,
3514
+ user?.role?.id,
3515
+ false
3516
+ );
3517
+ import_fs.default.writeFileSync("memory-tool-created.json", JSON.stringify({ createdItem, createdJob }, null, 2));
3518
+ if (createdJob) {
3519
+ result = {
3520
+ result: `Created a Job to create the memory item with the following ID: ${createdJob}`
3521
+ };
3522
+ } else if (createdItem) {
3523
+ result = {
3524
+ result: `Created memory item with the following ID: ${createdItem.id}`
3525
+ };
3526
+ } else {
3527
+ result = {
3528
+ result: `Failed to create memory item`
3529
+ };
3530
+ }
3531
+ } catch (error) {
3532
+ import_fs.default.writeFileSync("memory-tool-error.json", JSON.stringify({ name, description, information, error }, null, 2));
3533
+ console.error("[EXULU] Error creating memory item", error);
3534
+ result = {
3535
+ result: `Failed to create memory item: ${error instanceof Error ? error.message : String(error)}`
3536
+ };
3537
+ }
3538
+ return result;
3539
+ }
3540
+ });
3541
+ };
3542
+
3543
+ // src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts
3448
3544
  var generateS3Key = (filename) => `${(0, import_node_crypto2.randomUUID)()}-${filename}`;
3449
3545
  var s3Client2;
3450
3546
  var getMimeType = (type) => {
@@ -3548,6 +3644,21 @@ var convertExuluToolsToAiSdkTools = async (currentTools, approvedTools, allExulu
3548
3644
  currentTools.push(projectRetrievalTool);
3549
3645
  }
3550
3646
  }
3647
+ if (agent?.memory && contexts?.length) {
3648
+ const context = contexts.find((context2) => context2.id === agent?.memory);
3649
+ if (!context) {
3650
+ throw new Error(
3651
+ "Context was set for agent memory but not found in the contexts: " + agent?.memory + " please double check with a developer to see if the context was removed from code."
3652
+ );
3653
+ }
3654
+ const createNewMemoryTool = createNewMemoryItemTool(agent, context);
3655
+ if (createNewMemoryTool) {
3656
+ if (!currentTools) {
3657
+ currentTools = [];
3658
+ }
3659
+ currentTools.push(createNewMemoryTool);
3660
+ }
3661
+ }
3551
3662
  console.log("[EXULU] Convert tools array to object, session items", items);
3552
3663
  if (items) {
3553
3664
  const sessionItemsRetrievalTool = await createSessionItemsRetrievalTool({
@@ -3793,7 +3904,7 @@ var ExuluTool = class {
3793
3904
  this.type = type;
3794
3905
  this.tool = (0, import_ai.tool)({
3795
3906
  description,
3796
- inputSchema: inputSchema || import_zod3.z.object({}),
3907
+ inputSchema: inputSchema || import_zod4.z.object({}),
3797
3908
  execute: execute2
3798
3909
  });
3799
3910
  }
@@ -3886,7 +3997,7 @@ var ExuluTool = class {
3886
3997
  };
3887
3998
 
3888
3999
  // src/exulu/context.ts
3889
- var import_zod4 = require("zod");
4000
+ var import_zod5 = require("zod");
3890
4001
 
3891
4002
  // ee/queues/decorator.ts
3892
4003
  var import_bullmq2 = require("bullmq");
@@ -4044,6 +4155,7 @@ var ExuluContext2 = class {
4044
4155
  exuluConfig
4045
4156
  });
4046
4157
  if (!result) {
4158
+ console.log("[EXULU] Item filtered out by processor, skipping processing execution...");
4047
4159
  return {
4048
4160
  result: void 0,
4049
4161
  job: void 0
@@ -4244,10 +4356,12 @@ var ExuluContext2 = class {
4244
4356
  }
4245
4357
  }
4246
4358
  });
4359
+ console.log("[EXULU] Creating item", item);
4247
4360
  const mutation = db2.from(getTableName(this.id)).insert({
4248
4361
  ...item,
4249
4362
  tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
4250
4363
  }).returning("id");
4364
+ console.log("[EXULU] Upsert", upsert);
4251
4365
  if (upsert) {
4252
4366
  if (item.external_id) {
4253
4367
  mutation.onConflict("external_id").merge();
@@ -4266,7 +4380,6 @@ var ExuluContext2 = class {
4266
4380
  let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
4267
4381
  if (this.processor) {
4268
4382
  const processor = this.processor;
4269
- console.log("[EXULU] Processor found", processor);
4270
4383
  if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4271
4384
  const { job: processorJob, result: processorResult } = await this.processField(
4272
4385
  "api",
@@ -4281,7 +4394,7 @@ var ExuluContext2 = class {
4281
4394
  if (processorJob) {
4282
4395
  jobs.push(processorJob);
4283
4396
  }
4284
- if (!processorJob) {
4397
+ if (!processorJob && processorResult) {
4285
4398
  await db2.from(getTableName(this.id)).where({ id: results[0].id }).update({
4286
4399
  ...processorResult
4287
4400
  });
@@ -4352,7 +4465,7 @@ var ExuluContext2 = class {
4352
4465
  if (processorJob) {
4353
4466
  jobs.push(processorJob);
4354
4467
  }
4355
- if (!processorJob) {
4468
+ if (!processorJob && processorResult) {
4356
4469
  await db2.from(getTableName(this.id)).where({ id: record.id }).update({
4357
4470
  ...processorResult
4358
4471
  });
@@ -4606,12 +4719,12 @@ var ExuluContext2 = class {
4606
4719
  category: "contexts",
4607
4720
  needsApproval: true,
4608
4721
  // todo make configurable
4609
- inputSchema: import_zod4.z.object({
4610
- query: import_zod4.z.string().describe("The original question that the user asked"),
4611
- keywords: import_zod4.z.array(import_zod4.z.string()).describe(
4722
+ inputSchema: import_zod5.z.object({
4723
+ query: import_zod5.z.string().describe("The original question that the user asked"),
4724
+ keywords: import_zod5.z.array(import_zod5.z.string()).describe(
4612
4725
  "The keywords that are relevant to the user's question, for example names of specific products, systems or parts, IDs, etc."
4613
4726
  ),
4614
- method: import_zod4.z.enum(["keyword", "semantic", "hybrid"]).default("hybrid").describe(
4727
+ method: import_zod5.z.enum(["keyword", "semantic", "hybrid"]).default("hybrid").describe(
4615
4728
  "Search method: 'hybrid' (best for most queries - combines semantic understanding with exact term matching), 'keyword' (best for exact terms, technical names, IDs, or specific phrases), 'semantic' (best for conceptual queries where synonyms and paraphrasing matter)"
4616
4729
  )
4617
4730
  }),
@@ -4822,11 +4935,11 @@ function createCustomAgenticRetrievalToolLoopAgent({
4822
4935
  return await (0, import_ai2.generateText)({
4823
4936
  model,
4824
4937
  output: import_ai2.Output.object({
4825
- schema: import_zod5.z.object({
4826
- reasoning: import_zod5.z.string().describe(
4938
+ schema: import_zod6.z.object({
4939
+ reasoning: import_zod6.z.string().describe(
4827
4940
  "The reasoning for the next step and why the agent needs to take this step. It MUST start with 'I must call tool XYZ', and MUST include the inputs for that tool."
4828
4941
  ),
4829
- finished: import_zod5.z.boolean().describe(
4942
+ finished: import_zod6.z.boolean().describe(
4830
4943
  "Whether the agent has finished meaning no further steps are needed, this should only be true if the agent believes no further tool calls are needed to get the relevant information for the query."
4831
4944
  )
4832
4945
  })
@@ -4956,9 +5069,9 @@ function createCustomAgenticRetrievalToolLoopAgent({
4956
5069
  if (chunksCount > 1) {
4957
5070
  dynamicTools[getMoreToolName] = (0, import_ai2.tool)({
4958
5071
  description: `The item ${chunk.item_name} has a total of${chunksCount} chunks, this tool allows you to get more content from this item across all its pages / chunks.`,
4959
- inputSchema: import_zod5.z.object({
4960
- from_index: import_zod5.z.number().default(1).describe("The index of the chunk to start from."),
4961
- to_index: import_zod5.z.number().max(chunksCount).describe("The index of the chunk to end at, max is " + chunksCount)
5072
+ inputSchema: import_zod6.z.object({
5073
+ from_index: import_zod6.z.number().default(1).describe("The index of the chunk to start from."),
5074
+ to_index: import_zod6.z.number().max(chunksCount).describe("The index of the chunk to end at, max is " + chunksCount)
4962
5075
  }),
4963
5076
  execute: async ({ from_index, to_index }) => {
4964
5077
  const chunks2 = await db2(chunksTable).select("*").where("source", chunk.item_id).whereBetween("chunk_index", [from_index, to_index]).orderBy("chunk_index", "asc");
@@ -4986,8 +5099,8 @@ function createCustomAgenticRetrievalToolLoopAgent({
4986
5099
  );
4987
5100
  dynamicTools[getContentToolName] = (0, import_ai2.tool)({
4988
5101
  description: `Get the content of the page ${chunk.chunk_index} for the item ${chunk.item_name}`,
4989
- inputSchema: import_zod5.z.object({
4990
- reasoning: import_zod5.z.string().describe("The reasoning for why you need to get the content of the page.")
5102
+ inputSchema: import_zod6.z.object({
5103
+ reasoning: import_zod6.z.string().describe("The reasoning for why you need to get the content of the page.")
4991
5104
  }),
4992
5105
  execute: async ({ reasoning }) => {
4993
5106
  const { db: db3 } = await postgresClient();
@@ -5055,8 +5168,8 @@ var createAgenticRetrievalAgent = ({
5055
5168
  search_items_by_name: (0, import_ai2.tool)({
5056
5169
  description: `
5057
5170
  Search for relevant items by name across the available knowledge bases.`,
5058
- inputSchema: import_zod5.z.object({
5059
- knowledge_base_ids: import_zod5.z.array(import_zod5.z.enum(contexts.map((ctx) => ctx.id))).describe(`
5171
+ inputSchema: import_zod6.z.object({
5172
+ knowledge_base_ids: import_zod6.z.array(import_zod6.z.enum(contexts.map((ctx) => ctx.id))).describe(`
5060
5173
  The available knowledge bases are:
5061
5174
  ${contexts.map(
5062
5175
  (ctx) => `
@@ -5068,8 +5181,8 @@ var createAgenticRetrievalAgent = ({
5068
5181
  `
5069
5182
  ).join("\n")}
5070
5183
  `),
5071
- item_name: import_zod5.z.string().describe("The name of the item to search for."),
5072
- limit: import_zod5.z.number().default(100).describe(
5184
+ item_name: import_zod6.z.string().describe("The name of the item to search for."),
5185
+ limit: import_zod6.z.number().default(100).describe(
5073
5186
  "Maximum number of items to return (max 400), if searching through multiple knowledge bases, the limit is applied for each knowledge base individually."
5074
5187
  )
5075
5188
  }),
@@ -5175,11 +5288,11 @@ var createAgenticRetrievalAgent = ({
5175
5288
  - You can always fetch content later if needed
5176
5289
 
5177
5290
  `,
5178
- inputSchema: import_zod5.z.object({
5179
- query: import_zod5.z.string().describe(
5291
+ inputSchema: import_zod6.z.object({
5292
+ query: import_zod6.z.string().describe(
5180
5293
  "The search query to find relevant chunks, this must always be related to the content you are looking for, not something like 'Page 2'."
5181
5294
  ),
5182
- knowledge_base_ids: import_zod5.z.array(import_zod5.z.enum(contexts.map((ctx) => ctx.id))).describe(`
5295
+ knowledge_base_ids: import_zod6.z.array(import_zod6.z.enum(contexts.map((ctx) => ctx.id))).describe(`
5183
5296
  The available knowledge bases are:
5184
5297
  ${contexts.map(
5185
5298
  (ctx) => `
@@ -5191,25 +5304,25 @@ var createAgenticRetrievalAgent = ({
5191
5304
  `
5192
5305
  ).join("\n")}
5193
5306
  `),
5194
- keywords: import_zod5.z.array(import_zod5.z.string()).optional().describe(
5307
+ keywords: import_zod6.z.array(import_zod6.z.string()).optional().describe(
5195
5308
  "Keywords to search for. Usually extracted from the query, allowing for more precise search results."
5196
5309
  ),
5197
- searchMethod: import_zod5.z.enum(["keyword", "semantic", "hybrid"]).default("hybrid").describe(
5310
+ searchMethod: import_zod6.z.enum(["keyword", "semantic", "hybrid"]).default("hybrid").describe(
5198
5311
  "Search method: 'hybrid' (best for most queries - combines semantic understanding with exact term matching), 'keyword' (best for exact terms, technical names, IDs, or specific phrases), 'semantic' (best for conceptual queries where synonyms and paraphrasing matter)"
5199
5312
  ),
5200
- includeContent: import_zod5.z.boolean().default(true).describe(
5313
+ includeContent: import_zod6.z.boolean().default(true).describe(
5201
5314
  "Whether to include the full chunk content in results. Set to FALSE when you only need to know WHICH documents/items are relevant (lists, overviews, counts). Set to TRUE when you need the ACTUAL content to answer the question (information, details, explanations). You can always fetch content later, so prefer FALSE for efficiency when listing documents."
5202
5315
  ),
5203
- item_ids: import_zod5.z.array(import_zod5.z.string()).optional().describe(
5316
+ item_ids: import_zod6.z.array(import_zod6.z.string()).optional().describe(
5204
5317
  "Use if you wish to retrieve content from specific items (documents) based on the item ID."
5205
5318
  ),
5206
- item_names: import_zod5.z.array(import_zod5.z.string()).optional().describe(
5319
+ item_names: import_zod6.z.array(import_zod6.z.string()).optional().describe(
5207
5320
  "Use if you wish to retrieve content from specific items (documents) based on the item name. Can be a partial match."
5208
5321
  ),
5209
- item_external_ids: import_zod5.z.array(import_zod5.z.string()).optional().describe(
5322
+ item_external_ids: import_zod6.z.array(import_zod6.z.string()).optional().describe(
5210
5323
  "Use if you wish to retrieve content from specific items (documents) based on the item external ID. Can be a partial match."
5211
5324
  ),
5212
- limit: import_zod5.z.number().default(10).describe("Maximum number of chunks to return (max 10)")
5325
+ limit: import_zod6.z.number().default(10).describe("Maximum number of chunks to return (max 10)")
5213
5326
  }),
5214
5327
  execute: async ({
5215
5328
  query,
@@ -5400,9 +5513,9 @@ var createAgenticRetrievalTool = ({
5400
5513
  default: true
5401
5514
  }))
5402
5515
  ],
5403
- inputSchema: import_zod5.z.object({
5404
- query: import_zod5.z.string().describe("The question or query to answer using the knowledge bases"),
5405
- userInstructions: import_zod5.z.string().optional().describe("Instructions provided by the user to customize the retrieval process.")
5516
+ inputSchema: import_zod6.z.object({
5517
+ query: import_zod6.z.string().describe("The question or query to answer using the knowledge bases"),
5518
+ userInstructions: import_zod6.z.string().optional().describe("Instructions provided by the user to customize the retrieval process.")
5406
5519
  }),
5407
5520
  execute: async function* ({
5408
5521
  query,
@@ -7420,7 +7533,32 @@ var createWorkers = async (providers, queues2, config, contexts, rerankers, eval
7420
7533
  );
7421
7534
  }
7422
7535
  const exuluStorage = new ExuluStorage({ config });
7423
- console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
7536
+ if (context.processor.filter) {
7537
+ const result2 = await context.processor.filter({
7538
+ item: data.inputs,
7539
+ user: data.user,
7540
+ role: data.role,
7541
+ utils: {
7542
+ storage: exuluStorage
7543
+ },
7544
+ exuluConfig: config
7545
+ });
7546
+ if (!result2) {
7547
+ console.log("[EXULU] Item filtered out by processor, skipping processing execution...");
7548
+ return {
7549
+ result: "Item filtered out by processor, skipping processing execution...",
7550
+ // last message
7551
+ metadata: {
7552
+ item: {
7553
+ name: data.inputs?.name,
7554
+ id: data.inputs?.id,
7555
+ external_id: data.inputs?.external_id
7556
+ }
7557
+ }
7558
+ };
7559
+ }
7560
+ }
7561
+ console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD", data.inputs);
7424
7562
  let processorResult = await context.processor.execute({
7425
7563
  item: data.inputs,
7426
7564
  user: data.user,
@@ -9870,7 +10008,7 @@ var import_utils5 = require("@apollo/utils.keyvaluecache");
9870
10008
  var import_body_parser = __toESM(require("body-parser"), 1);
9871
10009
  var import_crypto_js6 = __toESM(require("crypto-js"), 1);
9872
10010
  var import_openai = __toESM(require("openai"), 1);
9873
- var import_fs = __toESM(require("fs"), 1);
10011
+ var import_fs2 = __toESM(require("fs"), 1);
9874
10012
  var import_node_crypto4 = require("crypto");
9875
10013
  var import_api2 = require("@opentelemetry/api");
9876
10014
  var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
@@ -9919,98 +10057,6 @@ function generateSlug(name) {
9919
10057
  // src/exulu/provider.ts
9920
10058
  var import_crypto_js5 = __toESM(require("crypto-js"), 1);
9921
10059
  var import_officeparser = require("officeparser");
9922
-
9923
- // src/templates/tools/memory-tool.ts
9924
- var import_zod6 = require("zod");
9925
- var createNewMemoryItemTool = (agent, context) => {
9926
- const fields = {
9927
- name: import_zod6.z.string().describe("The name of the item to create"),
9928
- description: import_zod6.z.string().describe("The description of the item to create")
9929
- };
9930
- for (const field of context.fields) {
9931
- switch (field.type) {
9932
- case "text":
9933
- case "longText":
9934
- case "shortText":
9935
- case "code":
9936
- case "enum":
9937
- fields[field.name] = import_zod6.z.string().describe("The " + field.name + " of the item to create");
9938
- break;
9939
- case "json":
9940
- fields[field.name] = import_zod6.z.string({}).describe(
9941
- "The " + field.name + " of the item to create, it should be a valid JSON string."
9942
- );
9943
- break;
9944
- case "markdown":
9945
- fields[field.name] = import_zod6.z.string().describe(
9946
- "The " + field.name + " of the item to create, it should be a valid Markdown string."
9947
- );
9948
- break;
9949
- case "number":
9950
- fields[field.name] = import_zod6.z.number().describe("The " + field.name + " of the item to create");
9951
- break;
9952
- case "boolean":
9953
- fields[field.name] = import_zod6.z.boolean().describe("The " + field.name + " of the item to create");
9954
- break;
9955
- case "file":
9956
- case "uuid":
9957
- case "date":
9958
- break;
9959
- default:
9960
- fields[field.name] = import_zod6.z.string().describe("The " + field.name + " of the item to create");
9961
- break;
9962
- }
9963
- }
9964
- return new ExuluTool({
9965
- id: "create_" + agent.name + "_memory_item",
9966
- name: "Create " + agent.name + " Memory Item",
9967
- category: agent.name + "_memory",
9968
- description: "Create a new memory item in the " + agent.name + " memory context",
9969
- type: "function",
9970
- inputSchema: import_zod6.z.object(fields),
9971
- config: [],
9972
- execute: async ({ name, description, mode, information, exuluConfig, user }) => {
9973
- let result = { result: "" };
9974
- switch (mode) {
9975
- case "learnings":
9976
- break;
9977
- case "knowledge":
9978
- const newItem = {
9979
- name,
9980
- description,
9981
- information,
9982
- rights_mode: "public"
9983
- };
9984
- const { item: createdItem, job: createdJob } = await context.createItem(
9985
- newItem,
9986
- exuluConfig,
9987
- user?.id,
9988
- user?.role?.id,
9989
- false
9990
- );
9991
- if (createdJob) {
9992
- result = {
9993
- result: `Created a Job to create the memory item with the following ID: ${createdJob}`
9994
- };
9995
- } else if (createdItem) {
9996
- result = {
9997
- result: `Created memory item with the following ID: ${createdItem.id}`
9998
- };
9999
- } else {
10000
- result = {
10001
- result: `Failed to create memory item`
10002
- };
10003
- }
10004
- break;
10005
- default:
10006
- throw new Error(`Invalid mode: ${mode}`);
10007
- }
10008
- return result;
10009
- }
10010
- });
10011
- };
10012
-
10013
- // src/exulu/provider.ts
10014
10060
  var ExuluProvider = class {
10015
10061
  // Must begin with a letter (a-z) or underscore (_). Subsequent characters in a name can be letters, digits (0-9), or
10016
10062
  // underscores and be a max length of 80 characters and at least 5 characters long.
@@ -10270,13 +10316,6 @@ var ExuluProvider = class {
10270
10316
 
10271
10317
  ${result.chunks.map((chunk) => chunk.chunk_content).join("\n\n")}`;
10272
10318
  }
10273
- const createNewMemoryTool = createNewMemoryItemTool(agent, context);
10274
- if (createNewMemoryTool) {
10275
- if (!currentTools) {
10276
- currentTools = [];
10277
- }
10278
- currentTools.push(createNewMemoryTool);
10279
- }
10280
10319
  }
10281
10320
  const personalizationInformation = exuluConfig?.privacy?.systemPromptPersonalization !== false ? `
10282
10321
  ${user?.firstname ? `The users first name is "${user.firstname}"` : ""}
@@ -10340,6 +10379,10 @@ var ExuluProvider = class {
10340
10379
  Example: {url: https://www.google.com, title: Google, snippet: The result of the web search.}
10341
10380
  `;
10342
10381
  }
10382
+ system += `
10383
+
10384
+ When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
10385
+ 'Inform the user that the action was not performed.`;
10343
10386
  if (prompt) {
10344
10387
  let result = { object: null, text: "" };
10345
10388
  let inputTokens = 0;
@@ -10654,13 +10697,6 @@ ${extractedText}
10654
10697
 
10655
10698
  ${result2.chunks.map((chunk) => chunk.chunk_content).join("\n\n")}`;
10656
10699
  }
10657
- const createNewMemoryTool = createNewMemoryItemTool(agent, context);
10658
- if (createNewMemoryTool) {
10659
- if (!currentTools) {
10660
- currentTools = [];
10661
- }
10662
- currentTools.push(createNewMemoryTool);
10663
- }
10664
10700
  }
10665
10701
  messages = messages.filter(
10666
10702
  (message2, index, self) => index === self.findLastIndex((t) => t.id === message2.id)
@@ -10719,6 +10755,10 @@ ${extractedText}
10719
10755
  Example: {url: https://www.google.com, title: Google, snippet: The result of the web search.}
10720
10756
  `;
10721
10757
  }
10758
+ system += `
10759
+
10760
+ When a tool execution is not approved by the user, do not retry it unless explicitly asked by the user. ' +
10761
+ 'Inform the user that the action was not performed.`;
10722
10762
  const result = (0, import_ai4.streamText)({
10723
10763
  model,
10724
10764
  // Should be a LanguageModelV1
@@ -10863,12 +10903,11 @@ var providerRateLimiter = async (key, windowSeconds, limit, points) => {
10863
10903
 
10864
10904
  // src/exulu/routes.ts
10865
10905
  var import_zod_from_json_schema = require("zod-from-json-schema");
10866
- var import_zod8 = require("zod");
10867
10906
  var REQUEST_SIZE_LIMIT = "50mb";
10868
10907
  var getExuluVersionNumber = async () => {
10869
10908
  try {
10870
10909
  const path2 = process.cwd();
10871
- const packageJson = import_fs.default.readFileSync(path2 + "/package.json", "utf8");
10910
+ const packageJson = import_fs2.default.readFileSync(path2 + "/package.json", "utf8");
10872
10911
  const packageData = JSON.parse(packageJson);
10873
10912
  const exuluVersion = packageData.dependencies["@exulu/backend"];
10874
10913
  console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
@@ -11778,7 +11817,7 @@ var import_types2 = require("@modelcontextprotocol/sdk/types.js");
11778
11817
  var import_express4 = require("express");
11779
11818
  var import_api3 = require("@opentelemetry/api");
11780
11819
  var import_crypto_js7 = __toESM(require("crypto-js"), 1);
11781
- var import_zod9 = require("zod");
11820
+ var import_zod8 = require("zod");
11782
11821
  var SESSION_ID_HEADER = "mcp-session-id";
11783
11822
  var ExuluMCP = class {
11784
11823
  server = {};
@@ -11861,7 +11900,7 @@ var ExuluMCP = class {
11861
11900
  title: tool3.name + " agent",
11862
11901
  description: tool3.description,
11863
11902
  inputSchema: {
11864
- inputs: tool3.inputSchema || import_zod9.z.object({})
11903
+ inputs: tool3.inputSchema || import_zod8.z.object({})
11865
11904
  }
11866
11905
  },
11867
11906
  async ({ inputs }, args) => {
@@ -11913,7 +11952,7 @@ var ExuluMCP = class {
11913
11952
  title: "Get List of Prompt Templates",
11914
11953
  description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
11915
11954
  inputSchema: {
11916
- inputs: import_zod9.z.object({})
11955
+ inputs: import_zod8.z.object({})
11917
11956
  }
11918
11957
  },
11919
11958
  async ({ inputs }, args) => {
@@ -11959,8 +11998,8 @@ var ExuluMCP = class {
11959
11998
  title: "Get Prompt Template Details",
11960
11999
  description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
11961
12000
  inputSchema: {
11962
- inputs: import_zod9.z.object({
11963
- id: import_zod9.z.string().describe("The ID of the prompt template to retrieve")
12001
+ inputs: import_zod8.z.object({
12002
+ id: import_zod8.z.string().describe("The ID of the prompt template to retrieve")
11964
12003
  })
11965
12004
  }
11966
12005
  },
@@ -12868,7 +12907,7 @@ var ExuluEval = class {
12868
12907
  };
12869
12908
 
12870
12909
  // src/templates/evals/index.ts
12871
- var import_zod10 = require("zod");
12910
+ var import_zod9 = require("zod");
12872
12911
  var llmAsJudgeEval = () => {
12873
12912
  if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
12874
12913
  return new ExuluEval({
@@ -12913,8 +12952,8 @@ var llmAsJudgeEval = () => {
12913
12952
  contexts: [],
12914
12953
  rerankers: [],
12915
12954
  prompt,
12916
- outputSchema: import_zod10.z.object({
12917
- score: import_zod10.z.number().min(0).max(100).describe("The score between 0 and 100.")
12955
+ outputSchema: import_zod9.z.object({
12956
+ score: import_zod9.z.number().min(0).max(100).describe("The score between 0 and 100.")
12918
12957
  }),
12919
12958
  providerapikey
12920
12959
  });
@@ -13142,12 +13181,12 @@ Usage:
13142
13181
  - If no todos exist yet, an empty list will be returned`;
13143
13182
 
13144
13183
  // src/templates/tools/todo/todo.ts
13145
- var import_zod11 = __toESM(require("zod"), 1);
13146
- var TodoSchema = import_zod11.default.object({
13147
- content: import_zod11.default.string().describe("Brief description of the task"),
13148
- status: import_zod11.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
13149
- priority: import_zod11.default.string().describe("Priority level of the task: high, medium, low"),
13150
- id: import_zod11.default.string().describe("Unique identifier for the todo item")
13184
+ var import_zod10 = __toESM(require("zod"), 1);
13185
+ var TodoSchema = import_zod10.default.object({
13186
+ content: import_zod10.default.string().describe("Brief description of the task"),
13187
+ status: import_zod10.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
13188
+ priority: import_zod10.default.string().describe("Priority level of the task: high, medium, low"),
13189
+ id: import_zod10.default.string().describe("Unique identifier for the todo item")
13151
13190
  });
13152
13191
  var TodoWriteTool = new ExuluTool({
13153
13192
  id: "todo_write",
@@ -13163,8 +13202,8 @@ var TodoWriteTool = new ExuluTool({
13163
13202
  default: todowrite_default
13164
13203
  }
13165
13204
  ],
13166
- inputSchema: import_zod11.default.object({
13167
- todos: import_zod11.default.array(TodoSchema).describe("The updated todo list")
13205
+ inputSchema: import_zod10.default.object({
13206
+ todos: import_zod10.default.array(TodoSchema).describe("The updated todo list")
13168
13207
  }),
13169
13208
  execute: async (inputs) => {
13170
13209
  const { sessionID, todos, user } = inputs;
@@ -13199,7 +13238,7 @@ var TodoReadTool = new ExuluTool({
13199
13238
  id: "todo_read",
13200
13239
  name: "Todo Read",
13201
13240
  description: "Use this tool to read your todo list",
13202
- inputSchema: import_zod11.default.object({}),
13241
+ inputSchema: import_zod10.default.object({}),
13203
13242
  type: "function",
13204
13243
  category: "todo",
13205
13244
  config: [
@@ -13237,6 +13276,222 @@ async function getTodos(sessionID) {
13237
13276
  }
13238
13277
  var todoTools = [TodoWriteTool, TodoReadTool];
13239
13278
 
13279
+ // src/templates/tools/question/questionask.txt
13280
+ var questionask_default = `Use this tool to ask the user a question with multiple choice answer options during your session. This helps you gather user input, clarify requirements, and make informed decisions based on user preferences.
13281
+
13282
+ ## When to Use This Tool
13283
+
13284
+ Use this tool when you need to:
13285
+
13286
+ 1. Get user input on implementation choices - When there are multiple valid approaches and you need the user to decide
13287
+ 2. Clarify ambiguous requirements - When the user's request could be interpreted in different ways
13288
+ 3. Gather preferences - When you need to know the user's preference for styling, naming, architecture, etc.
13289
+ 4. Validate assumptions - When you want to confirm your understanding before proceeding
13290
+ 5. Offer options - When presenting multiple solutions and letting the user choose
13291
+
13292
+ ## When NOT to Use This Tool
13293
+
13294
+ Skip using this tool when:
13295
+ 1. The answer is clear from context or previous messages
13296
+ 2. You're asking a yes/no question (use a simpler approach)
13297
+ 3. The question requires a free-form text answer (this tool is for multiple choice only)
13298
+ 4. You're asking for information that should be obvious from the codebase
13299
+
13300
+ ## How It Works
13301
+
13302
+ - Provide a question and an array of answer option strings
13303
+ - The tool automatically adds "None of the above..." as a final option
13304
+ - Each answer option is assigned a unique ID
13305
+ - The question is stored with a "pending" status until the user answers
13306
+ - Use the Question Read tool to check if the question has been answered
13307
+
13308
+ ## Examples
13309
+
13310
+ <example>
13311
+ User: Add authentication to my app
13312
+ Assistant: I need to ask the user which authentication method they prefer.
13313
+ *Uses Question Ask tool with:*
13314
+ question: "Which authentication method would you like to implement?"
13315
+ answerOptions: ["JWT tokens", "OAuth 2.0", "Session-based auth", "Firebase Auth"]
13316
+
13317
+ <reasoning>
13318
+ The assistant used the question tool because there are multiple valid authentication approaches and the user's preference will significantly impact the implementation.
13319
+ </reasoning>
13320
+ </example>
13321
+
13322
+ <example>
13323
+ User: Style this button
13324
+ Assistant: I need to know the user's styling preference.
13325
+ *Uses Question Ask tool with:*
13326
+ question: "What styling approach should I use for the button?"
13327
+ answerOptions: ["Tailwind CSS", "CSS Modules", "Styled Components", "Plain CSS"]
13328
+
13329
+ <reasoning>
13330
+ The assistant used the question tool because the codebase could support multiple styling approaches and the user's preference matters for consistency.
13331
+ </reasoning>
13332
+ </example>
13333
+
13334
+ <example>
13335
+ User: Add error handling
13336
+ Assistant: I'll implement comprehensive error handling with try-catch blocks and proper error messages.
13337
+ *Does not use the question tool*
13338
+
13339
+ <reasoning>
13340
+ The assistant did not use the question tool because error handling is a standard practice and there's a clear best approach that doesn't require user input.
13341
+ </reasoning>
13342
+ </example>
13343
+
13344
+ ## Answer Format
13345
+
13346
+ When you call this tool, answer options are automatically converted to this format:
13347
+ \`\`\`json
13348
+ [
13349
+ { "id": "randomId1", "text": "First option" },
13350
+ { "id": "randomId2", "text": "Second option" },
13351
+ { "id": "randomId3", "text": "Third option" },
13352
+ { "id": "randomId4", "text": "None of the above..." }
13353
+ ]
13354
+ \`\`\`
13355
+
13356
+ The "None of the above..." option is always added automatically, so you don't need to include it in your answerOptions array.
13357
+
13358
+ ## Reading Answers
13359
+
13360
+ After asking a question, use the Question Read tool to check if the user has answered. The question status will change from "pending" to "answered" and the selectedAnswerId field will contain the ID of the chosen answer.
13361
+ `;
13362
+
13363
+ // src/templates/tools/question/questionread.txt
13364
+ var questionread_default = 'Use this tool to read questions you\'ve asked and check if they\'ve been answered by the user. This tool helps you track the status of questions and retrieve the user\'s selected answers.\n\n## When to Use This Tool\n\nUse this tool proactively in these situations:\n- After asking a question to check if the user has responded\n- To retrieve the user\'s answer before proceeding with implementation\n- To review all questions and answers in the current session\n- When you need to reference a previous answer\n\n## How It Works\n\n- This tool takes no parameters (leave the input blank or empty)\n- Returns an array of all questions in the session\n- Each question includes:\n - `id`: Unique identifier for the question\n - `question`: The question text\n - `answerOptions`: Array of answer options with their IDs and text\n - `status`: Either "pending" (not answered) or "answered"\n - `selectedAnswerId`: The ID of the chosen answer (only present if answered)\n\n## Usage Pattern\n\nTypically you\'ll:\n1. Use Question Ask to pose a question\n2. Wait for the user to respond\n3. Use Question Read to check the answer\n4. Find the selected answer by matching the `selectedAnswerId` with an option in `answerOptions`\n5. Proceed with implementation based on the user\'s choice\n\n## Example Response\n\n```json\n[\n {\n "id": "question123",\n "question": "Which authentication method would you like to implement?",\n "answerOptions": [\n { "id": "ans1", "text": "JWT tokens" },\n { "id": "ans2", "text": "OAuth 2.0" },\n { "id": "ans3", "text": "Session-based auth" },\n { "id": "ans4", "text": "None of the above..." }\n ],\n "status": "answered",\n "selectedAnswerId": "ans1"\n }\n]\n```\n\nIn this example, the user selected "JWT tokens" (id: ans1).\n\n## Important Notes\n\n- If no questions exist in the session, an empty array will be returned\n- Questions remain in the session even after being answered for reference\n- Use the `selectedAnswerId` to find which answer option the user chose by matching it against the `id` field in `answerOptions`\n';
13365
+
13366
+ // src/templates/tools/question/question.ts
13367
+ var import_zod11 = __toESM(require("zod"), 1);
13368
+ var import_node_crypto6 = require("crypto");
13369
+ var AnswerOptionSchema = import_zod11.default.object({
13370
+ id: import_zod11.default.string().describe("Unique identifier for the answer option"),
13371
+ text: import_zod11.default.string().describe("The text of the answer option")
13372
+ });
13373
+ var _QuestionSchema = import_zod11.default.object({
13374
+ id: import_zod11.default.string().describe("Unique identifier for the question"),
13375
+ question: import_zod11.default.string().describe("The question to ask the user"),
13376
+ answerOptions: import_zod11.default.array(AnswerOptionSchema).describe("Array of possible answer options"),
13377
+ selectedAnswerId: import_zod11.default.string().optional().describe("The ID of the answer option selected by the user"),
13378
+ status: import_zod11.default.enum(["pending", "answered"]).describe("Status of the question: pending or answered")
13379
+ });
13380
+ var QuestionAskTool = new ExuluTool({
13381
+ id: "question_ask",
13382
+ name: "Question Ask",
13383
+ description: "Use this tool to ask a question to the user with multiple choice answers",
13384
+ type: "function",
13385
+ category: "question",
13386
+ config: [
13387
+ {
13388
+ name: "description",
13389
+ description: "The description of the question tool, if set overwrites the default description.",
13390
+ type: "string",
13391
+ default: questionask_default
13392
+ }
13393
+ ],
13394
+ inputSchema: import_zod11.default.object({
13395
+ question: import_zod11.default.string().describe("The question to ask the user"),
13396
+ answerOptions: import_zod11.default.array(import_zod11.default.string()).describe("Array of possible answer options (strings)")
13397
+ }),
13398
+ execute: async (inputs) => {
13399
+ const { sessionID, question, answerOptions, user } = inputs;
13400
+ if (!user) {
13401
+ throw new Error(
13402
+ "No authenticated user available, a user is required for the question ask tool, this likely means the tool was called outside a session like in an MCP or API call instead of as part of an authenticated session."
13403
+ );
13404
+ }
13405
+ if (!sessionID) {
13406
+ throw new Error(
13407
+ "Session ID is required for the question ask tool, this likely means the tool was called outside a session like in an MCP or API call instead of as part of a conversation."
13408
+ );
13409
+ }
13410
+ const session = await getSession({ sessionID });
13411
+ if (!session?.id) {
13412
+ throw new Error(
13413
+ "Session with ID " + sessionID + " not found in the question ask tool."
13414
+ );
13415
+ }
13416
+ const hasAccessToSession = await checkRecordAccess(session, "read", user);
13417
+ if (!hasAccessToSession) {
13418
+ throw new Error("You don't have access to this session " + session.id + ".");
13419
+ }
13420
+ const answerOptionsWithIds = answerOptions.map((text) => ({
13421
+ id: (0, import_node_crypto6.randomUUID)(),
13422
+ text
13423
+ }));
13424
+ answerOptionsWithIds.push({
13425
+ id: (0, import_node_crypto6.randomUUID)(),
13426
+ text: "None of the above..."
13427
+ });
13428
+ const newQuestion = {
13429
+ id: (0, import_node_crypto6.randomUUID)(),
13430
+ question,
13431
+ answerOptions: answerOptionsWithIds,
13432
+ status: "pending"
13433
+ };
13434
+ await addQuestion({
13435
+ session,
13436
+ question: newQuestion
13437
+ });
13438
+ return {
13439
+ result: JSON.stringify(
13440
+ {
13441
+ questionId: newQuestion.id,
13442
+ question: newQuestion.question,
13443
+ answerOptions: newQuestion.answerOptions,
13444
+ status: newQuestion.status
13445
+ },
13446
+ null,
13447
+ 2
13448
+ )
13449
+ };
13450
+ }
13451
+ });
13452
+ var QuestionReadTool = new ExuluTool({
13453
+ id: "question_read",
13454
+ name: "Question Read",
13455
+ description: "Use this tool to read questions and their answers",
13456
+ inputSchema: import_zod11.default.object({}),
13457
+ type: "function",
13458
+ category: "question",
13459
+ config: [
13460
+ {
13461
+ name: "description",
13462
+ description: "The description of the question read tool, if set overwrites the default description.",
13463
+ type: "string",
13464
+ default: questionread_default
13465
+ }
13466
+ ],
13467
+ execute: async (inputs) => {
13468
+ const { sessionID } = inputs;
13469
+ const questions = await getQuestions(sessionID);
13470
+ return {
13471
+ result: JSON.stringify(questions, null, 2)
13472
+ };
13473
+ }
13474
+ });
13475
+ async function addQuestion(input) {
13476
+ const metadata = input.session.metadata ?? {};
13477
+ metadata["questions"] ??= [];
13478
+ metadata["questions"].push(input.question);
13479
+ const { db: db2 } = await postgresClient();
13480
+ await db2.from("agent_sessions").where({ id: input.session.id }).update({
13481
+ metadata
13482
+ });
13483
+ return input.session;
13484
+ }
13485
+ async function getQuestions(sessionID) {
13486
+ const { db: db2 } = await postgresClient();
13487
+ const session = await db2.from("agent_sessions").where({ id: sessionID }).first();
13488
+ if (!session) {
13489
+ throw new Error("Session not found for session ID: " + sessionID);
13490
+ }
13491
+ return session.metadata?.questions ?? [];
13492
+ }
13493
+ var questionTools = [QuestionAskTool, QuestionReadTool];
13494
+
13240
13495
  // src/templates/tools/perplexity.ts
13241
13496
  var import_zod12 = __toESM(require("zod"), 1);
13242
13497
  var import_perplexity_ai = __toESM(require("@perplexity-ai/perplexity_ai"), 1);
@@ -13347,14 +13602,29 @@ var isValidPostgresName = (id) => {
13347
13602
 
13348
13603
  // src/exulu/app/index.ts
13349
13604
  var isDev = process.env.NODE_ENV !== "production";
13605
+ var lineLimitFormat = import_winston2.default.format((info) => {
13606
+ if (typeof info.message === "string") {
13607
+ const lines = info.message.split("\n");
13608
+ if (lines.length > 50) {
13609
+ const truncatedLines = lines.slice(0, 50);
13610
+ truncatedLines.push(`... (${lines.length - 50} more lines omitted)`);
13611
+ info.message = truncatedLines.join("\n");
13612
+ }
13613
+ }
13614
+ return info;
13615
+ });
13350
13616
  var consoleTransport = new import_winston2.default.transports.Console({
13351
13617
  format: isDev ? import_winston2.default.format.combine(
13618
+ lineLimitFormat(),
13352
13619
  import_winston2.default.format.colorize(),
13353
13620
  import_winston2.default.format.timestamp({ format: "HH:mm:ss" }),
13354
13621
  import_winston2.default.format.printf(({ timestamp, level, message }) => {
13355
13622
  return `${timestamp} [${level}] ${message}`;
13356
13623
  })
13357
- ) : import_winston2.default.format.json()
13624
+ ) : import_winston2.default.format.combine(
13625
+ lineLimitFormat(),
13626
+ import_winston2.default.format.json()
13627
+ )
13358
13628
  });
13359
13629
  var formatArg = (arg) => typeof arg === "object" ? import_util.default.inspect(arg, { depth: null, colors: isDev }) : String(arg);
13360
13630
  var createLogMethod = (logger, logLevel) => {
@@ -13424,6 +13694,7 @@ var ExuluApp2 = class {
13424
13694
  this._tools = [
13425
13695
  ...tools ?? [],
13426
13696
  ...todoTools,
13697
+ ...questionTools,
13427
13698
  ...perplexityTools,
13428
13699
  // Add contexts as tools
13429
13700
  ...Object.values(contexts || {}).map((context) => context.tool()).filter(Boolean)
@@ -16003,7 +16274,7 @@ var MarkdownChunker = class {
16003
16274
  var import_child_process = require("child_process");
16004
16275
  var import_util2 = require("util");
16005
16276
  var import_path = require("path");
16006
- var import_fs2 = require("fs");
16277
+ var import_fs3 = require("fs");
16007
16278
  var import_url = require("url");
16008
16279
  var execAsync = (0, import_util2.promisify)(import_child_process.exec);
16009
16280
  function getPackageRoot() {
@@ -16013,9 +16284,9 @@ function getPackageRoot() {
16013
16284
  const maxAttempts = 10;
16014
16285
  while (attempts < maxAttempts) {
16015
16286
  const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
16016
- if ((0, import_fs2.existsSync)(packageJsonPath)) {
16287
+ if ((0, import_fs3.existsSync)(packageJsonPath)) {
16017
16288
  try {
16018
- const packageJson = JSON.parse((0, import_fs2.readFileSync)(packageJsonPath, "utf-8"));
16289
+ const packageJson = JSON.parse((0, import_fs3.readFileSync)(packageJsonPath, "utf-8"));
16019
16290
  if (packageJson.name === "@exulu/backend") {
16020
16291
  return currentDir;
16021
16292
  }
@@ -16042,7 +16313,7 @@ function isPythonEnvironmentSetup(packageRoot) {
16042
16313
  const root = packageRoot ?? getPackageRoot();
16043
16314
  const venvPath = getVenvPath(root);
16044
16315
  const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
16045
- return (0, import_fs2.existsSync)(venvPath) && (0, import_fs2.existsSync)(pythonPath);
16316
+ return (0, import_fs3.existsSync)(venvPath) && (0, import_fs3.existsSync)(pythonPath);
16046
16317
  }
16047
16318
  async function setupPythonEnvironment(options = {}) {
16048
16319
  const {
@@ -16063,7 +16334,7 @@ async function setupPythonEnvironment(options = {}) {
16063
16334
  };
16064
16335
  }
16065
16336
  const setupScriptPath = getSetupScriptPath(packageRoot);
16066
- if (!(0, import_fs2.existsSync)(setupScriptPath)) {
16337
+ if (!(0, import_fs3.existsSync)(setupScriptPath)) {
16067
16338
  return {
16068
16339
  success: false,
16069
16340
  message: `Setup script not found at: ${setupScriptPath}`,
@@ -16145,13 +16416,13 @@ async function validatePythonEnvironment(packageRoot, checkPackages = true) {
16145
16416
  const root = packageRoot ?? getPackageRoot();
16146
16417
  const venvPath = getVenvPath(root);
16147
16418
  const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
16148
- if (!(0, import_fs2.existsSync)(venvPath)) {
16419
+ if (!(0, import_fs3.existsSync)(venvPath)) {
16149
16420
  return {
16150
16421
  valid: false,
16151
16422
  message: getPythonSetupInstructions()
16152
16423
  };
16153
16424
  }
16154
- if (!(0, import_fs2.existsSync)(pythonPath)) {
16425
+ if (!(0, import_fs3.existsSync)(pythonPath)) {
16155
16426
  return {
16156
16427
  valid: false,
16157
16428
  message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
@@ -16203,7 +16474,7 @@ Or manually run the setup script:
16203
16474
  }
16204
16475
 
16205
16476
  // ee/python/documents/processing/doc_processor.ts
16206
- var fs2 = __toESM(require("fs"), 1);
16477
+ var fs3 = __toESM(require("fs"), 1);
16207
16478
  var path = __toESM(require("path"), 1);
16208
16479
  var import_ai7 = require("ai");
16209
16480
  var import_zod13 = require("zod");
@@ -16218,7 +16489,7 @@ var import_officeparser2 = require("officeparser");
16218
16489
  var import_child_process2 = require("child_process");
16219
16490
  var import_util3 = require("util");
16220
16491
  var import_path2 = require("path");
16221
- var import_fs3 = require("fs");
16492
+ var import_fs4 = require("fs");
16222
16493
  var import_url2 = require("url");
16223
16494
  var execAsync2 = (0, import_util3.promisify)(import_child_process2.exec);
16224
16495
  function getPackageRoot2() {
@@ -16228,9 +16499,9 @@ function getPackageRoot2() {
16228
16499
  const maxAttempts = 10;
16229
16500
  while (attempts < maxAttempts) {
16230
16501
  const packageJsonPath = (0, import_path2.join)(currentDir, "package.json");
16231
- if ((0, import_fs3.existsSync)(packageJsonPath)) {
16502
+ if ((0, import_fs4.existsSync)(packageJsonPath)) {
16232
16503
  try {
16233
- const packageJson = JSON.parse((0, import_fs3.readFileSync)(packageJsonPath, "utf-8"));
16504
+ const packageJson = JSON.parse((0, import_fs4.readFileSync)(packageJsonPath, "utf-8"));
16234
16505
  if (packageJson.name === "@exulu/backend") {
16235
16506
  return currentDir;
16236
16507
  }
@@ -16292,7 +16563,7 @@ async function executePythonScript(config) {
16292
16563
  await validatePythonEnvironmentForExecution(packageRoot);
16293
16564
  }
16294
16565
  const resolvedScriptPath = (0, import_path2.resolve)(packageRoot, scriptPath);
16295
- if (!(0, import_fs3.existsSync)(resolvedScriptPath)) {
16566
+ if (!(0, import_fs4.existsSync)(resolvedScriptPath)) {
16296
16567
  throw new PythonExecutionError(
16297
16568
  `Python script not found: ${resolvedScriptPath}`,
16298
16569
  "",
@@ -16428,7 +16699,7 @@ function reconstructHeadings(correctedText, headingsHierarchy) {
16428
16699
  return result;
16429
16700
  }
16430
16701
  async function validatePageWithVLM(page, imagePath, model) {
16431
- const imageBuffer = await fs2.promises.readFile(imagePath);
16702
+ const imageBuffer = await fs3.promises.readFile(imagePath);
16432
16703
  const imageBase64 = imageBuffer.toString("base64");
16433
16704
  const mimeType = "image/png";
16434
16705
  const prompt = `You are a document validation assistant. Your task is to analyze a page image and correct the output of an OCR/parsing pipeline. The content may include tables, technical diagrams, schematics, and structured text.
@@ -16760,7 +17031,7 @@ ${setupResult.output || ""}`);
16760
17031
  if (!result.success) {
16761
17032
  throw new Error(`Document processing failed: ${result.stderr}`);
16762
17033
  }
16763
- const jsonContent = await fs2.promises.readFile(paths.json, "utf-8");
17034
+ const jsonContent = await fs3.promises.readFile(paths.json, "utf-8");
16764
17035
  json = JSON.parse(jsonContent);
16765
17036
  } else if (config?.processor.name === "officeparser") {
16766
17037
  const text = await (0, import_officeparser2.parseOfficeAsync)(buffer, {
@@ -16793,9 +17064,9 @@ ${setupResult.output || ""}`);
16793
17064
  }, 10);
16794
17065
  const parser = new import_liteparse.LiteParse();
16795
17066
  const screenshots = await parser.screenshot(paths.source, void 0);
16796
- await fs2.promises.mkdir(paths.images, { recursive: true });
17067
+ await fs3.promises.mkdir(paths.images, { recursive: true });
16797
17068
  for (const screenshot of screenshots) {
16798
- await fs2.promises.writeFile(
17069
+ await fs3.promises.writeFile(
16799
17070
  path.join(
16800
17071
  paths.images,
16801
17072
  `${screenshot.pageNum}.png`
@@ -16810,15 +17081,15 @@ ${setupResult.output || ""}`);
16810
17081
  image: screenshots.find((s) => s.pageNum === page.index + 1)?.imagePath,
16811
17082
  headings: []
16812
17083
  }));
16813
- fs2.writeFileSync(paths.json, JSON.stringify(json, null, 2));
17084
+ fs3.writeFileSync(paths.json, JSON.stringify(json, null, 2));
16814
17085
  } else if (config?.processor.name === "liteparse") {
16815
17086
  const parser = new import_liteparse.LiteParse();
16816
17087
  const result = await parser.parse(paths.source);
16817
17088
  const screenshots = await parser.screenshot(paths.source, void 0);
16818
17089
  console.log(`[EXULU] Liteparse screenshots: ${JSON.stringify(screenshots)}`);
16819
- await fs2.promises.mkdir(paths.images, { recursive: true });
17090
+ await fs3.promises.mkdir(paths.images, { recursive: true });
16820
17091
  for (const screenshot of screenshots) {
16821
- await fs2.promises.writeFile(path.join(paths.images, `${screenshot.pageNum}.png`), screenshot.imageBuffer);
17092
+ await fs3.promises.writeFile(path.join(paths.images, `${screenshot.pageNum}.png`), screenshot.imageBuffer);
16822
17093
  screenshot.imagePath = path.join(paths.images, `${screenshot.pageNum}.png`);
16823
17094
  }
16824
17095
  json = result.pages.map((page) => ({
@@ -16826,7 +17097,7 @@ ${setupResult.output || ""}`);
16826
17097
  content: page.text,
16827
17098
  image: screenshots.find((s) => s.pageNum === page.pageNum)?.imagePath
16828
17099
  }));
16829
- fs2.writeFileSync(paths.json, JSON.stringify(json, null, 2));
17100
+ fs3.writeFileSync(paths.json, JSON.stringify(json, null, 2));
16830
17101
  }
16831
17102
  console.log(`[EXULU]
16832
17103
  \u2713 Document processing completed successfully`);
@@ -16857,13 +17128,13 @@ ${setupResult.output || ""}`);
16857
17128
  console.log(`[EXULU] Corrected: ${page.vlm_corrected_text.substring(0, 150)}...`);
16858
17129
  });
16859
17130
  }
16860
- await fs2.promises.writeFile(
17131
+ await fs3.promises.writeFile(
16861
17132
  paths.json,
16862
17133
  JSON.stringify(json, null, 2),
16863
17134
  "utf-8"
16864
17135
  );
16865
17136
  }
16866
- const markdownStream = fs2.createWriteStream(paths.markdown, { encoding: "utf-8" });
17137
+ const markdownStream = fs3.createWriteStream(paths.markdown, { encoding: "utf-8" });
16867
17138
  for (let i = 0; i < json.length; i++) {
16868
17139
  const p = json[i];
16869
17140
  if (!p) continue;
@@ -16879,7 +17150,7 @@ ${setupResult.output || ""}`);
16879
17150
  });
16880
17151
  console.log(`[EXULU] Validated output saved to: ${paths.json}`);
16881
17152
  console.log(`[EXULU] Validated markdown saved to: ${paths.markdown}`);
16882
- const markdown = await fs2.promises.readFile(paths.markdown, "utf-8");
17153
+ const markdown = await fs3.promises.readFile(paths.markdown, "utf-8");
16883
17154
  const processedJson = json.map((e) => {
16884
17155
  const finalContent = e.vlm_corrected_text ?? e.content;
16885
17156
  return {
@@ -16910,7 +17181,7 @@ var loadFile = async (file, name, tempDir) => {
16910
17181
  let buffer;
16911
17182
  if (Buffer.isBuffer(file)) {
16912
17183
  filePath = path.join(tempDir, `${UUID}.${fileType}`);
16913
- await fs2.promises.writeFile(filePath, file);
17184
+ await fs3.promises.writeFile(filePath, file);
16914
17185
  buffer = file;
16915
17186
  } else {
16916
17187
  filePath = filePath.trim();
@@ -16918,11 +17189,11 @@ var loadFile = async (file, name, tempDir) => {
16918
17189
  const response = await fetch(filePath);
16919
17190
  const array = await response.arrayBuffer();
16920
17191
  const tempFilePath = path.join(tempDir, `${UUID}.${fileType}`);
16921
- await fs2.promises.writeFile(tempFilePath, Buffer.from(array));
17192
+ await fs3.promises.writeFile(tempFilePath, Buffer.from(array));
16922
17193
  buffer = Buffer.from(array);
16923
17194
  filePath = tempFilePath;
16924
17195
  } else {
16925
- buffer = await fs2.promises.readFile(file);
17196
+ buffer = await fs3.promises.readFile(file);
16926
17197
  }
16927
17198
  }
16928
17199
  return { filePath, fileType, buffer };
@@ -16940,9 +17211,9 @@ async function documentProcessor({
16940
17211
  const tempDir = path.join(process.cwd(), "temp", uuid);
16941
17212
  const localFilesAndFoldersToDelete = [tempDir];
16942
17213
  console.log(`[EXULU] Temporary directory for processing document ${name}: ${tempDir}`);
16943
- await fs2.promises.mkdir(tempDir, { recursive: true });
17214
+ await fs3.promises.mkdir(tempDir, { recursive: true });
16944
17215
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
16945
- await fs2.promises.writeFile(path.join(tempDir, "created_at.txt"), timestamp);
17216
+ await fs3.promises.writeFile(path.join(tempDir, "created_at.txt"), timestamp);
16946
17217
  try {
16947
17218
  const {
16948
17219
  filePath,
@@ -16983,7 +17254,7 @@ async function documentProcessor({
16983
17254
  if (config?.debugging?.deleteTempFiles !== false) {
16984
17255
  for (const file2 of localFilesAndFoldersToDelete) {
16985
17256
  try {
16986
- await fs2.promises.rm(file2, { recursive: true });
17257
+ await fs3.promises.rm(file2, { recursive: true });
16987
17258
  console.log(`[EXULU] Deleted file or folder: ${file2}`);
16988
17259
  } catch (error) {
16989
17260
  console.error(`[EXULU] Error deleting file or folder: ${file2}`, error);