@exulu/backend 1.51.1 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4044,6 +4044,7 @@ var ExuluContext2 = class {
4044
4044
  exuluConfig
4045
4045
  });
4046
4046
  if (!result) {
4047
+ console.log("[EXULU] Item filtered out by processor, skipping processing execution...");
4047
4048
  return {
4048
4049
  result: void 0,
4049
4050
  job: void 0
@@ -4244,10 +4245,12 @@ var ExuluContext2 = class {
4244
4245
  }
4245
4246
  }
4246
4247
  });
4248
+ console.log("[EXULU] Creating item", item);
4247
4249
  const mutation = db2.from(getTableName(this.id)).insert({
4248
4250
  ...item,
4249
4251
  tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
4250
4252
  }).returning("id");
4253
+ console.log("[EXULU] Upsert", upsert);
4251
4254
  if (upsert) {
4252
4255
  if (item.external_id) {
4253
4256
  mutation.onConflict("external_id").merge();
@@ -4266,7 +4269,6 @@ var ExuluContext2 = class {
4266
4269
  let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
4267
4270
  if (this.processor) {
4268
4271
  const processor = this.processor;
4269
- console.log("[EXULU] Processor found", processor);
4270
4272
  if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4271
4273
  const { job: processorJob, result: processorResult } = await this.processField(
4272
4274
  "api",
@@ -4281,7 +4283,7 @@ var ExuluContext2 = class {
4281
4283
  if (processorJob) {
4282
4284
  jobs.push(processorJob);
4283
4285
  }
4284
- if (!processorJob) {
4286
+ if (!processorJob && processorResult) {
4285
4287
  await db2.from(getTableName(this.id)).where({ id: results[0].id }).update({
4286
4288
  ...processorResult
4287
4289
  });
@@ -4352,7 +4354,7 @@ var ExuluContext2 = class {
4352
4354
  if (processorJob) {
4353
4355
  jobs.push(processorJob);
4354
4356
  }
4355
- if (!processorJob) {
4357
+ if (!processorJob && processorResult) {
4356
4358
  await db2.from(getTableName(this.id)).where({ id: record.id }).update({
4357
4359
  ...processorResult
4358
4360
  });
@@ -7420,7 +7422,32 @@ var createWorkers = async (providers, queues2, config, contexts, rerankers, eval
7420
7422
  );
7421
7423
  }
7422
7424
  const exuluStorage = new ExuluStorage({ config });
7423
- console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
7425
+ if (context.processor.filter) {
7426
+ const result2 = await context.processor.filter({
7427
+ item: data.inputs,
7428
+ user: data.user,
7429
+ role: data.role,
7430
+ utils: {
7431
+ storage: exuluStorage
7432
+ },
7433
+ exuluConfig: config
7434
+ });
7435
+ if (!result2) {
7436
+ console.log("[EXULU] Item filtered out by processor, skipping processing execution...");
7437
+ return {
7438
+ result: "Item filtered out by processor, skipping processing execution...",
7439
+ // last message
7440
+ metadata: {
7441
+ item: {
7442
+ name: data.inputs?.name,
7443
+ id: data.inputs?.id,
7444
+ external_id: data.inputs?.external_id
7445
+ }
7446
+ }
7447
+ };
7448
+ }
7449
+ }
7450
+ console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD", data.inputs);
7424
7451
  let processorResult = await context.processor.execute({
7425
7452
  item: data.inputs,
7426
7453
  user: data.user,
@@ -10863,7 +10890,6 @@ var providerRateLimiter = async (key, windowSeconds, limit, points) => {
10863
10890
 
10864
10891
  // src/exulu/routes.ts
10865
10892
  var import_zod_from_json_schema = require("zod-from-json-schema");
10866
- var import_zod8 = require("zod");
10867
10893
  var REQUEST_SIZE_LIMIT = "50mb";
10868
10894
  var getExuluVersionNumber = async () => {
10869
10895
  try {
@@ -11331,12 +11357,16 @@ Mood: friendly and intelligent.
11331
11357
  message = req.body.message;
11332
11358
  }
11333
11359
  const approvedTools = req.body.approvedTools ? typeof req.body.approvedTools === "string" ? JSON.parse(req.body.approvedTools) : req.body.approvedTools : [];
11360
+ const customInstructions = req.body.customInstructions ? typeof req.body.customInstructions === "string" ? req.body.customInstructions : JSON.stringify(req.body.customInstructions) : "";
11361
+ const instructions = customInstructions ? `${agent.instructions}
11362
+
11363
+ ${customInstructions}` : agent.instructions;
11334
11364
  const result = await provider.generateStream({
11335
11365
  contexts,
11336
11366
  rerankers: rerankers || [],
11337
11367
  agent,
11338
11368
  user,
11339
- instructions: agent.instructions,
11369
+ instructions,
11340
11370
  session: headers.session,
11341
11371
  message,
11342
11372
  previousMessages,
@@ -11437,6 +11467,10 @@ Mood: friendly and intelligent.
11437
11467
  });
11438
11468
  return;
11439
11469
  } else {
11470
+ const customInstructions = req.body.customInstructions ? typeof req.body.customInstructions === "string" ? req.body.customInstructions : JSON.stringify(req.body.customInstructions) : "";
11471
+ const instructions = customInstructions ? `${agent.instructions}
11472
+
11473
+ ${customInstructions}` : agent.instructions;
11440
11474
  const response = await provider.generateSync({
11441
11475
  contexts,
11442
11476
  rerankers: rerankers || [],
@@ -11444,7 +11478,7 @@ Mood: friendly and intelligent.
11444
11478
  agent,
11445
11479
  user,
11446
11480
  req,
11447
- instructions: agent.instructions,
11481
+ instructions,
11448
11482
  session: headers.session,
11449
11483
  inputMessages: [req.body.message],
11450
11484
  currentTools: enabledTools,
@@ -11589,6 +11623,10 @@ Mood: friendly and intelligent.
11589
11623
  providers,
11590
11624
  user
11591
11625
  );
11626
+ const customInstructions = req.body.customInstructions ? typeof req.body.customInstructions === "string" ? req.body.customInstructions : JSON.stringify(req.body.customInstructions) : "";
11627
+ const agentInstructions = customInstructions ? `${agent?.instructions}
11628
+
11629
+ ${customInstructions}` : agent?.instructions;
11592
11630
  let system = req.body.system;
11593
11631
  if (Array.isArray(req.body.system)) {
11594
11632
  system = [
@@ -11598,7 +11636,7 @@ Mood: friendly and intelligent.
11598
11636
  type: "text",
11599
11637
  text: `
11600
11638
  You are an agent named: ${agent?.name}
11601
- Here are some additional instructions for you: ${agent?.instructions}`
11639
+ Here are some additional instructions for you: ${agentInstructions}`
11602
11640
  }
11603
11641
  ] : [],
11604
11642
  ...project ? [
@@ -11616,7 +11654,7 @@ Mood: friendly and intelligent.
11616
11654
 
11617
11655
 
11618
11656
  ${agent ? `You are an agent named: ${agent?.name}
11619
- Here are some additional instructions for you: ${agent?.instructions}` : ""}
11657
+ Here are some additional instructions for you: ${agentInstructions}` : ""}
11620
11658
 
11621
11659
  ${project?.id ? `Additional information:
11622
11660
 
@@ -11766,7 +11804,7 @@ var import_types2 = require("@modelcontextprotocol/sdk/types.js");
11766
11804
  var import_express4 = require("express");
11767
11805
  var import_api3 = require("@opentelemetry/api");
11768
11806
  var import_crypto_js7 = __toESM(require("crypto-js"), 1);
11769
- var import_zod9 = require("zod");
11807
+ var import_zod8 = require("zod");
11770
11808
  var SESSION_ID_HEADER = "mcp-session-id";
11771
11809
  var ExuluMCP = class {
11772
11810
  server = {};
@@ -11849,7 +11887,7 @@ var ExuluMCP = class {
11849
11887
  title: tool3.name + " agent",
11850
11888
  description: tool3.description,
11851
11889
  inputSchema: {
11852
- inputs: tool3.inputSchema || import_zod9.z.object({})
11890
+ inputs: tool3.inputSchema || import_zod8.z.object({})
11853
11891
  }
11854
11892
  },
11855
11893
  async ({ inputs }, args) => {
@@ -11901,7 +11939,7 @@ var ExuluMCP = class {
11901
11939
  title: "Get List of Prompt Templates",
11902
11940
  description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
11903
11941
  inputSchema: {
11904
- inputs: import_zod9.z.object({})
11942
+ inputs: import_zod8.z.object({})
11905
11943
  }
11906
11944
  },
11907
11945
  async ({ inputs }, args) => {
@@ -11947,8 +11985,8 @@ var ExuluMCP = class {
11947
11985
  title: "Get Prompt Template Details",
11948
11986
  description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
11949
11987
  inputSchema: {
11950
- inputs: import_zod9.z.object({
11951
- id: import_zod9.z.string().describe("The ID of the prompt template to retrieve")
11988
+ inputs: import_zod8.z.object({
11989
+ id: import_zod8.z.string().describe("The ID of the prompt template to retrieve")
11952
11990
  })
11953
11991
  }
11954
11992
  },
@@ -12856,7 +12894,7 @@ var ExuluEval = class {
12856
12894
  };
12857
12895
 
12858
12896
  // src/templates/evals/index.ts
12859
- var import_zod10 = require("zod");
12897
+ var import_zod9 = require("zod");
12860
12898
  var llmAsJudgeEval = () => {
12861
12899
  if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
12862
12900
  return new ExuluEval({
@@ -12901,8 +12939,8 @@ var llmAsJudgeEval = () => {
12901
12939
  contexts: [],
12902
12940
  rerankers: [],
12903
12941
  prompt,
12904
- outputSchema: import_zod10.z.object({
12905
- score: import_zod10.z.number().min(0).max(100).describe("The score between 0 and 100.")
12942
+ outputSchema: import_zod9.z.object({
12943
+ score: import_zod9.z.number().min(0).max(100).describe("The score between 0 and 100.")
12906
12944
  }),
12907
12945
  providerapikey
12908
12946
  });
@@ -13130,12 +13168,12 @@ Usage:
13130
13168
  - If no todos exist yet, an empty list will be returned`;
13131
13169
 
13132
13170
  // src/templates/tools/todo/todo.ts
13133
- var import_zod11 = __toESM(require("zod"), 1);
13134
- var TodoSchema = import_zod11.default.object({
13135
- content: import_zod11.default.string().describe("Brief description of the task"),
13136
- status: import_zod11.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
13137
- priority: import_zod11.default.string().describe("Priority level of the task: high, medium, low"),
13138
- id: import_zod11.default.string().describe("Unique identifier for the todo item")
13171
+ var import_zod10 = __toESM(require("zod"), 1);
13172
+ var TodoSchema = import_zod10.default.object({
13173
+ content: import_zod10.default.string().describe("Brief description of the task"),
13174
+ status: import_zod10.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
13175
+ priority: import_zod10.default.string().describe("Priority level of the task: high, medium, low"),
13176
+ id: import_zod10.default.string().describe("Unique identifier for the todo item")
13139
13177
  });
13140
13178
  var TodoWriteTool = new ExuluTool({
13141
13179
  id: "todo_write",
@@ -13151,8 +13189,8 @@ var TodoWriteTool = new ExuluTool({
13151
13189
  default: todowrite_default
13152
13190
  }
13153
13191
  ],
13154
- inputSchema: import_zod11.default.object({
13155
- todos: import_zod11.default.array(TodoSchema).describe("The updated todo list")
13192
+ inputSchema: import_zod10.default.object({
13193
+ todos: import_zod10.default.array(TodoSchema).describe("The updated todo list")
13156
13194
  }),
13157
13195
  execute: async (inputs) => {
13158
13196
  const { sessionID, todos, user } = inputs;
@@ -13187,7 +13225,7 @@ var TodoReadTool = new ExuluTool({
13187
13225
  id: "todo_read",
13188
13226
  name: "Todo Read",
13189
13227
  description: "Use this tool to read your todo list",
13190
- inputSchema: import_zod11.default.object({}),
13228
+ inputSchema: import_zod10.default.object({}),
13191
13229
  type: "function",
13192
13230
  category: "todo",
13193
13231
  config: [
@@ -13225,6 +13263,222 @@ async function getTodos(sessionID) {
13225
13263
  }
13226
13264
  var todoTools = [TodoWriteTool, TodoReadTool];
13227
13265
 
13266
+ // src/templates/tools/question/questionask.txt
13267
+ 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.
13268
+
13269
+ ## When to Use This Tool
13270
+
13271
+ Use this tool when you need to:
13272
+
13273
+ 1. Get user input on implementation choices - When there are multiple valid approaches and you need the user to decide
13274
+ 2. Clarify ambiguous requirements - When the user's request could be interpreted in different ways
13275
+ 3. Gather preferences - When you need to know the user's preference for styling, naming, architecture, etc.
13276
+ 4. Validate assumptions - When you want to confirm your understanding before proceeding
13277
+ 5. Offer options - When presenting multiple solutions and letting the user choose
13278
+
13279
+ ## When NOT to Use This Tool
13280
+
13281
+ Skip using this tool when:
13282
+ 1. The answer is clear from context or previous messages
13283
+ 2. You're asking a yes/no question (use a simpler approach)
13284
+ 3. The question requires a free-form text answer (this tool is for multiple choice only)
13285
+ 4. You're asking for information that should be obvious from the codebase
13286
+
13287
+ ## How It Works
13288
+
13289
+ - Provide a question and an array of answer option strings
13290
+ - The tool automatically adds "None of the above..." as a final option
13291
+ - Each answer option is assigned a unique ID
13292
+ - The question is stored with a "pending" status until the user answers
13293
+ - Use the Question Read tool to check if the question has been answered
13294
+
13295
+ ## Examples
13296
+
13297
+ <example>
13298
+ User: Add authentication to my app
13299
+ Assistant: I need to ask the user which authentication method they prefer.
13300
+ *Uses Question Ask tool with:*
13301
+ question: "Which authentication method would you like to implement?"
13302
+ answerOptions: ["JWT tokens", "OAuth 2.0", "Session-based auth", "Firebase Auth"]
13303
+
13304
+ <reasoning>
13305
+ The assistant used the question tool because there are multiple valid authentication approaches and the user's preference will significantly impact the implementation.
13306
+ </reasoning>
13307
+ </example>
13308
+
13309
+ <example>
13310
+ User: Style this button
13311
+ Assistant: I need to know the user's styling preference.
13312
+ *Uses Question Ask tool with:*
13313
+ question: "What styling approach should I use for the button?"
13314
+ answerOptions: ["Tailwind CSS", "CSS Modules", "Styled Components", "Plain CSS"]
13315
+
13316
+ <reasoning>
13317
+ The assistant used the question tool because the codebase could support multiple styling approaches and the user's preference matters for consistency.
13318
+ </reasoning>
13319
+ </example>
13320
+
13321
+ <example>
13322
+ User: Add error handling
13323
+ Assistant: I'll implement comprehensive error handling with try-catch blocks and proper error messages.
13324
+ *Does not use the question tool*
13325
+
13326
+ <reasoning>
13327
+ 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.
13328
+ </reasoning>
13329
+ </example>
13330
+
13331
+ ## Answer Format
13332
+
13333
+ When you call this tool, answer options are automatically converted to this format:
13334
+ \`\`\`json
13335
+ [
13336
+ { "id": "randomId1", "text": "First option" },
13337
+ { "id": "randomId2", "text": "Second option" },
13338
+ { "id": "randomId3", "text": "Third option" },
13339
+ { "id": "randomId4", "text": "None of the above..." }
13340
+ ]
13341
+ \`\`\`
13342
+
13343
+ The "None of the above..." option is always added automatically, so you don't need to include it in your answerOptions array.
13344
+
13345
+ ## Reading Answers
13346
+
13347
+ 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.
13348
+ `;
13349
+
13350
+ // src/templates/tools/question/questionread.txt
13351
+ 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';
13352
+
13353
+ // src/templates/tools/question/question.ts
13354
+ var import_zod11 = __toESM(require("zod"), 1);
13355
+ var import_node_crypto6 = require("crypto");
13356
+ var AnswerOptionSchema = import_zod11.default.object({
13357
+ id: import_zod11.default.string().describe("Unique identifier for the answer option"),
13358
+ text: import_zod11.default.string().describe("The text of the answer option")
13359
+ });
13360
+ var _QuestionSchema = import_zod11.default.object({
13361
+ id: import_zod11.default.string().describe("Unique identifier for the question"),
13362
+ question: import_zod11.default.string().describe("The question to ask the user"),
13363
+ answerOptions: import_zod11.default.array(AnswerOptionSchema).describe("Array of possible answer options"),
13364
+ selectedAnswerId: import_zod11.default.string().optional().describe("The ID of the answer option selected by the user"),
13365
+ status: import_zod11.default.enum(["pending", "answered"]).describe("Status of the question: pending or answered")
13366
+ });
13367
+ var QuestionAskTool = new ExuluTool({
13368
+ id: "question_ask",
13369
+ name: "Question Ask",
13370
+ description: "Use this tool to ask a question to the user with multiple choice answers",
13371
+ type: "function",
13372
+ category: "question",
13373
+ config: [
13374
+ {
13375
+ name: "description",
13376
+ description: "The description of the question tool, if set overwrites the default description.",
13377
+ type: "string",
13378
+ default: questionask_default
13379
+ }
13380
+ ],
13381
+ inputSchema: import_zod11.default.object({
13382
+ question: import_zod11.default.string().describe("The question to ask the user"),
13383
+ answerOptions: import_zod11.default.array(import_zod11.default.string()).describe("Array of possible answer options (strings)")
13384
+ }),
13385
+ execute: async (inputs) => {
13386
+ const { sessionID, question, answerOptions, user } = inputs;
13387
+ if (!user) {
13388
+ throw new Error(
13389
+ "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."
13390
+ );
13391
+ }
13392
+ if (!sessionID) {
13393
+ throw new Error(
13394
+ "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."
13395
+ );
13396
+ }
13397
+ const session = await getSession({ sessionID });
13398
+ if (!session?.id) {
13399
+ throw new Error(
13400
+ "Session with ID " + sessionID + " not found in the question ask tool."
13401
+ );
13402
+ }
13403
+ const hasAccessToSession = await checkRecordAccess(session, "read", user);
13404
+ if (!hasAccessToSession) {
13405
+ throw new Error("You don't have access to this session " + session.id + ".");
13406
+ }
13407
+ const answerOptionsWithIds = answerOptions.map((text) => ({
13408
+ id: (0, import_node_crypto6.randomUUID)(),
13409
+ text
13410
+ }));
13411
+ answerOptionsWithIds.push({
13412
+ id: (0, import_node_crypto6.randomUUID)(),
13413
+ text: "None of the above..."
13414
+ });
13415
+ const newQuestion = {
13416
+ id: (0, import_node_crypto6.randomUUID)(),
13417
+ question,
13418
+ answerOptions: answerOptionsWithIds,
13419
+ status: "pending"
13420
+ };
13421
+ await addQuestion({
13422
+ session,
13423
+ question: newQuestion
13424
+ });
13425
+ return {
13426
+ result: JSON.stringify(
13427
+ {
13428
+ questionId: newQuestion.id,
13429
+ question: newQuestion.question,
13430
+ answerOptions: newQuestion.answerOptions,
13431
+ status: newQuestion.status
13432
+ },
13433
+ null,
13434
+ 2
13435
+ )
13436
+ };
13437
+ }
13438
+ });
13439
+ var QuestionReadTool = new ExuluTool({
13440
+ id: "question_read",
13441
+ name: "Question Read",
13442
+ description: "Use this tool to read questions and their answers",
13443
+ inputSchema: import_zod11.default.object({}),
13444
+ type: "function",
13445
+ category: "question",
13446
+ config: [
13447
+ {
13448
+ name: "description",
13449
+ description: "The description of the question read tool, if set overwrites the default description.",
13450
+ type: "string",
13451
+ default: questionread_default
13452
+ }
13453
+ ],
13454
+ execute: async (inputs) => {
13455
+ const { sessionID } = inputs;
13456
+ const questions = await getQuestions(sessionID);
13457
+ return {
13458
+ result: JSON.stringify(questions, null, 2)
13459
+ };
13460
+ }
13461
+ });
13462
+ async function addQuestion(input) {
13463
+ const metadata = input.session.metadata ?? {};
13464
+ metadata["questions"] ??= [];
13465
+ metadata["questions"].push(input.question);
13466
+ const { db: db2 } = await postgresClient();
13467
+ await db2.from("agent_sessions").where({ id: input.session.id }).update({
13468
+ metadata
13469
+ });
13470
+ return input.session;
13471
+ }
13472
+ async function getQuestions(sessionID) {
13473
+ const { db: db2 } = await postgresClient();
13474
+ const session = await db2.from("agent_sessions").where({ id: sessionID }).first();
13475
+ if (!session) {
13476
+ throw new Error("Session not found for session ID: " + sessionID);
13477
+ }
13478
+ return session.metadata?.questions ?? [];
13479
+ }
13480
+ var questionTools = [QuestionAskTool, QuestionReadTool];
13481
+
13228
13482
  // src/templates/tools/perplexity.ts
13229
13483
  var import_zod12 = __toESM(require("zod"), 1);
13230
13484
  var import_perplexity_ai = __toESM(require("@perplexity-ai/perplexity_ai"), 1);
@@ -13335,14 +13589,29 @@ var isValidPostgresName = (id) => {
13335
13589
 
13336
13590
  // src/exulu/app/index.ts
13337
13591
  var isDev = process.env.NODE_ENV !== "production";
13592
+ var lineLimitFormat = import_winston2.default.format((info) => {
13593
+ if (typeof info.message === "string") {
13594
+ const lines = info.message.split("\n");
13595
+ if (lines.length > 50) {
13596
+ const truncatedLines = lines.slice(0, 50);
13597
+ truncatedLines.push(`... (${lines.length - 50} more lines omitted)`);
13598
+ info.message = truncatedLines.join("\n");
13599
+ }
13600
+ }
13601
+ return info;
13602
+ });
13338
13603
  var consoleTransport = new import_winston2.default.transports.Console({
13339
13604
  format: isDev ? import_winston2.default.format.combine(
13605
+ lineLimitFormat(),
13340
13606
  import_winston2.default.format.colorize(),
13341
13607
  import_winston2.default.format.timestamp({ format: "HH:mm:ss" }),
13342
13608
  import_winston2.default.format.printf(({ timestamp, level, message }) => {
13343
13609
  return `${timestamp} [${level}] ${message}`;
13344
13610
  })
13345
- ) : import_winston2.default.format.json()
13611
+ ) : import_winston2.default.format.combine(
13612
+ lineLimitFormat(),
13613
+ import_winston2.default.format.json()
13614
+ )
13346
13615
  });
13347
13616
  var formatArg = (arg) => typeof arg === "object" ? import_util.default.inspect(arg, { depth: null, colors: isDev }) : String(arg);
13348
13617
  var createLogMethod = (logger, logLevel) => {
@@ -13412,6 +13681,7 @@ var ExuluApp2 = class {
13412
13681
  this._tools = [
13413
13682
  ...tools ?? [],
13414
13683
  ...todoTools,
13684
+ ...questionTools,
13415
13685
  ...perplexityTools,
13416
13686
  // Add contexts as tools
13417
13687
  ...Object.values(contexts || {}).map((context) => context.tool()).filter(Boolean)
@@ -15954,6 +16224,7 @@ var MarkdownChunker = class {
15954
16224
  if (currentSlice.length === 0) {
15955
16225
  currentPosition++;
15956
16226
  targetPosition = currentPosition + chunkSize * this._CHARS_PER_TOKEN;
16227
+ contentLeft = text.length - currentPosition;
15957
16228
  }
15958
16229
  }
15959
16230
  const mergedChunks = [];
@@ -16701,7 +16972,8 @@ var getMistralApiKey = async () => {
16701
16972
  if (process.env.MISTRAL_API_KEY) {
16702
16973
  return process.env.MISTRAL_API_KEY;
16703
16974
  } else {
16704
- return await ExuluVariables.get("MISTRAL_API_KEY");
16975
+ const variable = await ExuluVariables.get("MISTRAL_API_KEY");
16976
+ return variable;
16705
16977
  }
16706
16978
  };
16707
16979
  async function processPdf(buffer, paths, config, verbose = false) {
@@ -16760,7 +17032,7 @@ ${setupResult.output || ""}`);
16760
17032
  }];
16761
17033
  } else if (config?.processor.name === "mistral") {
16762
17034
  const MISTRAL_API_KEY = await getMistralApiKey();
16763
- if (MISTRAL_API_KEY) {
17035
+ if (!MISTRAL_API_KEY) {
16764
17036
  throw new Error('[EXULU] MISTRAL_API_KEY is not set, please set it in the environment variable via process.env or via an Exulu variable named "MISTRAL_API_KEY".');
16765
17037
  }
16766
17038
  await new Promise((resolve3) => setTimeout(resolve3, Math.floor(Math.random() * 4e3) + 1e3));
package/dist/index.d.cts CHANGED
@@ -241,7 +241,7 @@ declare class ExuluStorage {
241
241
  type ExuluContextProcessor = {
242
242
  name: string;
243
243
  description: string;
244
- filter: ({ item, user, role, utils, exuluConfig, }: {
244
+ filter?: ({ item, user, role, utils, exuluConfig, }: {
245
245
  item: Item;
246
246
  user?: number;
247
247
  role?: string;
package/dist/index.d.ts CHANGED
@@ -241,7 +241,7 @@ declare class ExuluStorage {
241
241
  type ExuluContextProcessor = {
242
242
  name: string;
243
243
  description: string;
244
- filter: ({ item, user, role, utils, exuluConfig, }: {
244
+ filter?: ({ item, user, role, utils, exuluConfig, }: {
245
245
  item: Item;
246
246
  user?: number;
247
247
  role?: string;
package/dist/index.js CHANGED
@@ -4003,6 +4003,7 @@ var ExuluContext2 = class {
4003
4003
  exuluConfig
4004
4004
  });
4005
4005
  if (!result) {
4006
+ console.log("[EXULU] Item filtered out by processor, skipping processing execution...");
4006
4007
  return {
4007
4008
  result: void 0,
4008
4009
  job: void 0
@@ -4203,10 +4204,12 @@ var ExuluContext2 = class {
4203
4204
  }
4204
4205
  }
4205
4206
  });
4207
+ console.log("[EXULU] Creating item", item);
4206
4208
  const mutation = db2.from(getTableName(this.id)).insert({
4207
4209
  ...item,
4208
4210
  tags: item.tags ? Array.isArray(item.tags) ? item.tags.join(",") : item.tags : void 0
4209
4211
  }).returning("id");
4212
+ console.log("[EXULU] Upsert", upsert);
4210
4213
  if (upsert) {
4211
4214
  if (item.external_id) {
4212
4215
  mutation.onConflict("external_id").merge();
@@ -4225,7 +4228,6 @@ var ExuluContext2 = class {
4225
4228
  let shouldGenerateEmbeddings = this.embedder && generateEmbeddingsOverwrite !== false && (generateEmbeddingsOverwrite || this.configuration.calculateVectors === "onInsert" || this.configuration.calculateVectors === "always");
4226
4229
  if (this.processor) {
4227
4230
  const processor = this.processor;
4228
- console.log("[EXULU] Processor found", processor);
4229
4231
  if (processor && (processor?.config?.trigger === "onInsert" || processor?.config?.trigger === "onUpdate" || processor?.config?.trigger === "always")) {
4230
4232
  const { job: processorJob, result: processorResult } = await this.processField(
4231
4233
  "api",
@@ -4240,7 +4242,7 @@ var ExuluContext2 = class {
4240
4242
  if (processorJob) {
4241
4243
  jobs.push(processorJob);
4242
4244
  }
4243
- if (!processorJob) {
4245
+ if (!processorJob && processorResult) {
4244
4246
  await db2.from(getTableName(this.id)).where({ id: results[0].id }).update({
4245
4247
  ...processorResult
4246
4248
  });
@@ -4311,7 +4313,7 @@ var ExuluContext2 = class {
4311
4313
  if (processorJob) {
4312
4314
  jobs.push(processorJob);
4313
4315
  }
4314
- if (!processorJob) {
4316
+ if (!processorJob && processorResult) {
4315
4317
  await db2.from(getTableName(this.id)).where({ id: record.id }).update({
4316
4318
  ...processorResult
4317
4319
  });
@@ -7379,7 +7381,32 @@ var createWorkers = async (providers, queues2, config, contexts, rerankers, eval
7379
7381
  );
7380
7382
  }
7381
7383
  const exuluStorage = new ExuluStorage({ config });
7382
- console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
7384
+ if (context.processor.filter) {
7385
+ const result2 = await context.processor.filter({
7386
+ item: data.inputs,
7387
+ user: data.user,
7388
+ role: data.role,
7389
+ utils: {
7390
+ storage: exuluStorage
7391
+ },
7392
+ exuluConfig: config
7393
+ });
7394
+ if (!result2) {
7395
+ console.log("[EXULU] Item filtered out by processor, skipping processing execution...");
7396
+ return {
7397
+ result: "Item filtered out by processor, skipping processing execution...",
7398
+ // last message
7399
+ metadata: {
7400
+ item: {
7401
+ name: data.inputs?.name,
7402
+ id: data.inputs?.id,
7403
+ external_id: data.inputs?.external_id
7404
+ }
7405
+ }
7406
+ };
7407
+ }
7408
+ }
7409
+ console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD", data.inputs);
7383
7410
  let processorResult = await context.processor.execute({
7384
7411
  item: data.inputs,
7385
7412
  user: data.user,
@@ -10829,7 +10856,6 @@ var providerRateLimiter = async (key, windowSeconds, limit, points) => {
10829
10856
 
10830
10857
  // src/exulu/routes.ts
10831
10858
  import { convertJsonSchemaToZod } from "zod-from-json-schema";
10832
- import "zod";
10833
10859
  var REQUEST_SIZE_LIMIT = "50mb";
10834
10860
  var getExuluVersionNumber = async () => {
10835
10861
  try {
@@ -11297,12 +11323,16 @@ Mood: friendly and intelligent.
11297
11323
  message = req.body.message;
11298
11324
  }
11299
11325
  const approvedTools = req.body.approvedTools ? typeof req.body.approvedTools === "string" ? JSON.parse(req.body.approvedTools) : req.body.approvedTools : [];
11326
+ const customInstructions = req.body.customInstructions ? typeof req.body.customInstructions === "string" ? req.body.customInstructions : JSON.stringify(req.body.customInstructions) : "";
11327
+ const instructions = customInstructions ? `${agent.instructions}
11328
+
11329
+ ${customInstructions}` : agent.instructions;
11300
11330
  const result = await provider.generateStream({
11301
11331
  contexts,
11302
11332
  rerankers: rerankers || [],
11303
11333
  agent,
11304
11334
  user,
11305
- instructions: agent.instructions,
11335
+ instructions,
11306
11336
  session: headers.session,
11307
11337
  message,
11308
11338
  previousMessages,
@@ -11403,6 +11433,10 @@ Mood: friendly and intelligent.
11403
11433
  });
11404
11434
  return;
11405
11435
  } else {
11436
+ const customInstructions = req.body.customInstructions ? typeof req.body.customInstructions === "string" ? req.body.customInstructions : JSON.stringify(req.body.customInstructions) : "";
11437
+ const instructions = customInstructions ? `${agent.instructions}
11438
+
11439
+ ${customInstructions}` : agent.instructions;
11406
11440
  const response = await provider.generateSync({
11407
11441
  contexts,
11408
11442
  rerankers: rerankers || [],
@@ -11410,7 +11444,7 @@ Mood: friendly and intelligent.
11410
11444
  agent,
11411
11445
  user,
11412
11446
  req,
11413
- instructions: agent.instructions,
11447
+ instructions,
11414
11448
  session: headers.session,
11415
11449
  inputMessages: [req.body.message],
11416
11450
  currentTools: enabledTools,
@@ -11555,6 +11589,10 @@ Mood: friendly and intelligent.
11555
11589
  providers,
11556
11590
  user
11557
11591
  );
11592
+ const customInstructions = req.body.customInstructions ? typeof req.body.customInstructions === "string" ? req.body.customInstructions : JSON.stringify(req.body.customInstructions) : "";
11593
+ const agentInstructions = customInstructions ? `${agent?.instructions}
11594
+
11595
+ ${customInstructions}` : agent?.instructions;
11558
11596
  let system = req.body.system;
11559
11597
  if (Array.isArray(req.body.system)) {
11560
11598
  system = [
@@ -11564,7 +11602,7 @@ Mood: friendly and intelligent.
11564
11602
  type: "text",
11565
11603
  text: `
11566
11604
  You are an agent named: ${agent?.name}
11567
- Here are some additional instructions for you: ${agent?.instructions}`
11605
+ Here are some additional instructions for you: ${agentInstructions}`
11568
11606
  }
11569
11607
  ] : [],
11570
11608
  ...project ? [
@@ -11582,7 +11620,7 @@ Mood: friendly and intelligent.
11582
11620
 
11583
11621
 
11584
11622
  ${agent ? `You are an agent named: ${agent?.name}
11585
- Here are some additional instructions for you: ${agent?.instructions}` : ""}
11623
+ Here are some additional instructions for you: ${agentInstructions}` : ""}
11586
11624
 
11587
11625
  ${project?.id ? `Additional information:
11588
11626
 
@@ -11732,7 +11770,7 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
11732
11770
  import "express";
11733
11771
  import "@opentelemetry/api";
11734
11772
  import CryptoJS7 from "crypto-js";
11735
- import { z as z9 } from "zod";
11773
+ import { z as z8 } from "zod";
11736
11774
  var SESSION_ID_HEADER = "mcp-session-id";
11737
11775
  var ExuluMCP = class {
11738
11776
  server = {};
@@ -11815,7 +11853,7 @@ var ExuluMCP = class {
11815
11853
  title: tool3.name + " agent",
11816
11854
  description: tool3.description,
11817
11855
  inputSchema: {
11818
- inputs: tool3.inputSchema || z9.object({})
11856
+ inputs: tool3.inputSchema || z8.object({})
11819
11857
  }
11820
11858
  },
11821
11859
  async ({ inputs }, args) => {
@@ -11867,7 +11905,7 @@ var ExuluMCP = class {
11867
11905
  title: "Get List of Prompt Templates",
11868
11906
  description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
11869
11907
  inputSchema: {
11870
- inputs: z9.object({})
11908
+ inputs: z8.object({})
11871
11909
  }
11872
11910
  },
11873
11911
  async ({ inputs }, args) => {
@@ -11913,8 +11951,8 @@ var ExuluMCP = class {
11913
11951
  title: "Get Prompt Template Details",
11914
11952
  description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
11915
11953
  inputSchema: {
11916
- inputs: z9.object({
11917
- id: z9.string().describe("The ID of the prompt template to retrieve")
11954
+ inputs: z8.object({
11955
+ id: z8.string().describe("The ID of the prompt template to retrieve")
11918
11956
  })
11919
11957
  }
11920
11958
  },
@@ -12822,7 +12860,7 @@ var ExuluEval = class {
12822
12860
  };
12823
12861
 
12824
12862
  // src/templates/evals/index.ts
12825
- import { z as z10 } from "zod";
12863
+ import { z as z9 } from "zod";
12826
12864
  var llmAsJudgeEval = () => {
12827
12865
  if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
12828
12866
  return new ExuluEval({
@@ -12867,8 +12905,8 @@ var llmAsJudgeEval = () => {
12867
12905
  contexts: [],
12868
12906
  rerankers: [],
12869
12907
  prompt,
12870
- outputSchema: z10.object({
12871
- score: z10.number().min(0).max(100).describe("The score between 0 and 100.")
12908
+ outputSchema: z9.object({
12909
+ score: z9.number().min(0).max(100).describe("The score between 0 and 100.")
12872
12910
  }),
12873
12911
  providerapikey
12874
12912
  });
@@ -13096,12 +13134,12 @@ Usage:
13096
13134
  - If no todos exist yet, an empty list will be returned`;
13097
13135
 
13098
13136
  // src/templates/tools/todo/todo.ts
13099
- import z11 from "zod";
13100
- var TodoSchema = z11.object({
13101
- content: z11.string().describe("Brief description of the task"),
13102
- status: z11.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
13103
- priority: z11.string().describe("Priority level of the task: high, medium, low"),
13104
- id: z11.string().describe("Unique identifier for the todo item")
13137
+ import z10 from "zod";
13138
+ var TodoSchema = z10.object({
13139
+ content: z10.string().describe("Brief description of the task"),
13140
+ status: z10.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
13141
+ priority: z10.string().describe("Priority level of the task: high, medium, low"),
13142
+ id: z10.string().describe("Unique identifier for the todo item")
13105
13143
  });
13106
13144
  var TodoWriteTool = new ExuluTool({
13107
13145
  id: "todo_write",
@@ -13117,8 +13155,8 @@ var TodoWriteTool = new ExuluTool({
13117
13155
  default: todowrite_default
13118
13156
  }
13119
13157
  ],
13120
- inputSchema: z11.object({
13121
- todos: z11.array(TodoSchema).describe("The updated todo list")
13158
+ inputSchema: z10.object({
13159
+ todos: z10.array(TodoSchema).describe("The updated todo list")
13122
13160
  }),
13123
13161
  execute: async (inputs) => {
13124
13162
  const { sessionID, todos, user } = inputs;
@@ -13153,7 +13191,7 @@ var TodoReadTool = new ExuluTool({
13153
13191
  id: "todo_read",
13154
13192
  name: "Todo Read",
13155
13193
  description: "Use this tool to read your todo list",
13156
- inputSchema: z11.object({}),
13194
+ inputSchema: z10.object({}),
13157
13195
  type: "function",
13158
13196
  category: "todo",
13159
13197
  config: [
@@ -13191,6 +13229,222 @@ async function getTodos(sessionID) {
13191
13229
  }
13192
13230
  var todoTools = [TodoWriteTool, TodoReadTool];
13193
13231
 
13232
+ // src/templates/tools/question/questionask.txt
13233
+ 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.
13234
+
13235
+ ## When to Use This Tool
13236
+
13237
+ Use this tool when you need to:
13238
+
13239
+ 1. Get user input on implementation choices - When there are multiple valid approaches and you need the user to decide
13240
+ 2. Clarify ambiguous requirements - When the user's request could be interpreted in different ways
13241
+ 3. Gather preferences - When you need to know the user's preference for styling, naming, architecture, etc.
13242
+ 4. Validate assumptions - When you want to confirm your understanding before proceeding
13243
+ 5. Offer options - When presenting multiple solutions and letting the user choose
13244
+
13245
+ ## When NOT to Use This Tool
13246
+
13247
+ Skip using this tool when:
13248
+ 1. The answer is clear from context or previous messages
13249
+ 2. You're asking a yes/no question (use a simpler approach)
13250
+ 3. The question requires a free-form text answer (this tool is for multiple choice only)
13251
+ 4. You're asking for information that should be obvious from the codebase
13252
+
13253
+ ## How It Works
13254
+
13255
+ - Provide a question and an array of answer option strings
13256
+ - The tool automatically adds "None of the above..." as a final option
13257
+ - Each answer option is assigned a unique ID
13258
+ - The question is stored with a "pending" status until the user answers
13259
+ - Use the Question Read tool to check if the question has been answered
13260
+
13261
+ ## Examples
13262
+
13263
+ <example>
13264
+ User: Add authentication to my app
13265
+ Assistant: I need to ask the user which authentication method they prefer.
13266
+ *Uses Question Ask tool with:*
13267
+ question: "Which authentication method would you like to implement?"
13268
+ answerOptions: ["JWT tokens", "OAuth 2.0", "Session-based auth", "Firebase Auth"]
13269
+
13270
+ <reasoning>
13271
+ The assistant used the question tool because there are multiple valid authentication approaches and the user's preference will significantly impact the implementation.
13272
+ </reasoning>
13273
+ </example>
13274
+
13275
+ <example>
13276
+ User: Style this button
13277
+ Assistant: I need to know the user's styling preference.
13278
+ *Uses Question Ask tool with:*
13279
+ question: "What styling approach should I use for the button?"
13280
+ answerOptions: ["Tailwind CSS", "CSS Modules", "Styled Components", "Plain CSS"]
13281
+
13282
+ <reasoning>
13283
+ The assistant used the question tool because the codebase could support multiple styling approaches and the user's preference matters for consistency.
13284
+ </reasoning>
13285
+ </example>
13286
+
13287
+ <example>
13288
+ User: Add error handling
13289
+ Assistant: I'll implement comprehensive error handling with try-catch blocks and proper error messages.
13290
+ *Does not use the question tool*
13291
+
13292
+ <reasoning>
13293
+ 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.
13294
+ </reasoning>
13295
+ </example>
13296
+
13297
+ ## Answer Format
13298
+
13299
+ When you call this tool, answer options are automatically converted to this format:
13300
+ \`\`\`json
13301
+ [
13302
+ { "id": "randomId1", "text": "First option" },
13303
+ { "id": "randomId2", "text": "Second option" },
13304
+ { "id": "randomId3", "text": "Third option" },
13305
+ { "id": "randomId4", "text": "None of the above..." }
13306
+ ]
13307
+ \`\`\`
13308
+
13309
+ The "None of the above..." option is always added automatically, so you don't need to include it in your answerOptions array.
13310
+
13311
+ ## Reading Answers
13312
+
13313
+ 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.
13314
+ `;
13315
+
13316
+ // src/templates/tools/question/questionread.txt
13317
+ 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';
13318
+
13319
+ // src/templates/tools/question/question.ts
13320
+ import z11 from "zod";
13321
+ import { randomUUID as randomUUID6 } from "crypto";
13322
+ var AnswerOptionSchema = z11.object({
13323
+ id: z11.string().describe("Unique identifier for the answer option"),
13324
+ text: z11.string().describe("The text of the answer option")
13325
+ });
13326
+ var _QuestionSchema = z11.object({
13327
+ id: z11.string().describe("Unique identifier for the question"),
13328
+ question: z11.string().describe("The question to ask the user"),
13329
+ answerOptions: z11.array(AnswerOptionSchema).describe("Array of possible answer options"),
13330
+ selectedAnswerId: z11.string().optional().describe("The ID of the answer option selected by the user"),
13331
+ status: z11.enum(["pending", "answered"]).describe("Status of the question: pending or answered")
13332
+ });
13333
+ var QuestionAskTool = new ExuluTool({
13334
+ id: "question_ask",
13335
+ name: "Question Ask",
13336
+ description: "Use this tool to ask a question to the user with multiple choice answers",
13337
+ type: "function",
13338
+ category: "question",
13339
+ config: [
13340
+ {
13341
+ name: "description",
13342
+ description: "The description of the question tool, if set overwrites the default description.",
13343
+ type: "string",
13344
+ default: questionask_default
13345
+ }
13346
+ ],
13347
+ inputSchema: z11.object({
13348
+ question: z11.string().describe("The question to ask the user"),
13349
+ answerOptions: z11.array(z11.string()).describe("Array of possible answer options (strings)")
13350
+ }),
13351
+ execute: async (inputs) => {
13352
+ const { sessionID, question, answerOptions, user } = inputs;
13353
+ if (!user) {
13354
+ throw new Error(
13355
+ "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."
13356
+ );
13357
+ }
13358
+ if (!sessionID) {
13359
+ throw new Error(
13360
+ "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."
13361
+ );
13362
+ }
13363
+ const session = await getSession({ sessionID });
13364
+ if (!session?.id) {
13365
+ throw new Error(
13366
+ "Session with ID " + sessionID + " not found in the question ask tool."
13367
+ );
13368
+ }
13369
+ const hasAccessToSession = await checkRecordAccess(session, "read", user);
13370
+ if (!hasAccessToSession) {
13371
+ throw new Error("You don't have access to this session " + session.id + ".");
13372
+ }
13373
+ const answerOptionsWithIds = answerOptions.map((text) => ({
13374
+ id: randomUUID6(),
13375
+ text
13376
+ }));
13377
+ answerOptionsWithIds.push({
13378
+ id: randomUUID6(),
13379
+ text: "None of the above..."
13380
+ });
13381
+ const newQuestion = {
13382
+ id: randomUUID6(),
13383
+ question,
13384
+ answerOptions: answerOptionsWithIds,
13385
+ status: "pending"
13386
+ };
13387
+ await addQuestion({
13388
+ session,
13389
+ question: newQuestion
13390
+ });
13391
+ return {
13392
+ result: JSON.stringify(
13393
+ {
13394
+ questionId: newQuestion.id,
13395
+ question: newQuestion.question,
13396
+ answerOptions: newQuestion.answerOptions,
13397
+ status: newQuestion.status
13398
+ },
13399
+ null,
13400
+ 2
13401
+ )
13402
+ };
13403
+ }
13404
+ });
13405
+ var QuestionReadTool = new ExuluTool({
13406
+ id: "question_read",
13407
+ name: "Question Read",
13408
+ description: "Use this tool to read questions and their answers",
13409
+ inputSchema: z11.object({}),
13410
+ type: "function",
13411
+ category: "question",
13412
+ config: [
13413
+ {
13414
+ name: "description",
13415
+ description: "The description of the question read tool, if set overwrites the default description.",
13416
+ type: "string",
13417
+ default: questionread_default
13418
+ }
13419
+ ],
13420
+ execute: async (inputs) => {
13421
+ const { sessionID } = inputs;
13422
+ const questions = await getQuestions(sessionID);
13423
+ return {
13424
+ result: JSON.stringify(questions, null, 2)
13425
+ };
13426
+ }
13427
+ });
13428
+ async function addQuestion(input) {
13429
+ const metadata = input.session.metadata ?? {};
13430
+ metadata["questions"] ??= [];
13431
+ metadata["questions"].push(input.question);
13432
+ const { db: db2 } = await postgresClient();
13433
+ await db2.from("agent_sessions").where({ id: input.session.id }).update({
13434
+ metadata
13435
+ });
13436
+ return input.session;
13437
+ }
13438
+ async function getQuestions(sessionID) {
13439
+ const { db: db2 } = await postgresClient();
13440
+ const session = await db2.from("agent_sessions").where({ id: sessionID }).first();
13441
+ if (!session) {
13442
+ throw new Error("Session not found for session ID: " + sessionID);
13443
+ }
13444
+ return session.metadata?.questions ?? [];
13445
+ }
13446
+ var questionTools = [QuestionAskTool, QuestionReadTool];
13447
+
13194
13448
  // src/templates/tools/perplexity.ts
13195
13449
  import z12 from "zod";
13196
13450
  import Perplexity from "@perplexity-ai/perplexity_ai";
@@ -13301,14 +13555,29 @@ var isValidPostgresName = (id) => {
13301
13555
 
13302
13556
  // src/exulu/app/index.ts
13303
13557
  var isDev = process.env.NODE_ENV !== "production";
13558
+ var lineLimitFormat = winston2.format((info) => {
13559
+ if (typeof info.message === "string") {
13560
+ const lines = info.message.split("\n");
13561
+ if (lines.length > 50) {
13562
+ const truncatedLines = lines.slice(0, 50);
13563
+ truncatedLines.push(`... (${lines.length - 50} more lines omitted)`);
13564
+ info.message = truncatedLines.join("\n");
13565
+ }
13566
+ }
13567
+ return info;
13568
+ });
13304
13569
  var consoleTransport = new winston2.transports.Console({
13305
13570
  format: isDev ? winston2.format.combine(
13571
+ lineLimitFormat(),
13306
13572
  winston2.format.colorize(),
13307
13573
  winston2.format.timestamp({ format: "HH:mm:ss" }),
13308
13574
  winston2.format.printf(({ timestamp, level, message }) => {
13309
13575
  return `${timestamp} [${level}] ${message}`;
13310
13576
  })
13311
- ) : winston2.format.json()
13577
+ ) : winston2.format.combine(
13578
+ lineLimitFormat(),
13579
+ winston2.format.json()
13580
+ )
13312
13581
  });
13313
13582
  var formatArg = (arg) => typeof arg === "object" ? util.inspect(arg, { depth: null, colors: isDev }) : String(arg);
13314
13583
  var createLogMethod = (logger, logLevel) => {
@@ -13378,6 +13647,7 @@ var ExuluApp2 = class {
13378
13647
  this._tools = [
13379
13648
  ...tools ?? [],
13380
13649
  ...todoTools,
13650
+ ...questionTools,
13381
13651
  ...perplexityTools,
13382
13652
  // Add contexts as tools
13383
13653
  ...Object.values(contexts || {}).map((context) => context.tool()).filter(Boolean)
@@ -15920,6 +16190,7 @@ var MarkdownChunker = class {
15920
16190
  if (currentSlice.length === 0) {
15921
16191
  currentPosition++;
15922
16192
  targetPosition = currentPosition + chunkSize * this._CHARS_PER_TOKEN;
16193
+ contentLeft = text.length - currentPosition;
15923
16194
  }
15924
16195
  }
15925
16196
  const mergedChunks = [];
@@ -16161,7 +16432,7 @@ import * as path from "path";
16161
16432
  import { generateText as generateText3, Output as Output3 } from "ai";
16162
16433
  import { z as z13 } from "zod";
16163
16434
  import pLimit from "p-limit";
16164
- import { randomUUID as randomUUID6 } from "crypto";
16435
+ import { randomUUID as randomUUID7 } from "crypto";
16165
16436
  import * as mammoth from "mammoth";
16166
16437
  import TurndownService from "turndown";
16167
16438
  import WordExtractor from "word-extractor";
@@ -16667,7 +16938,8 @@ var getMistralApiKey = async () => {
16667
16938
  if (process.env.MISTRAL_API_KEY) {
16668
16939
  return process.env.MISTRAL_API_KEY;
16669
16940
  } else {
16670
- return await ExuluVariables.get("MISTRAL_API_KEY");
16941
+ const variable = await ExuluVariables.get("MISTRAL_API_KEY");
16942
+ return variable;
16671
16943
  }
16672
16944
  };
16673
16945
  async function processPdf(buffer, paths, config, verbose = false) {
@@ -16726,7 +16998,7 @@ ${setupResult.output || ""}`);
16726
16998
  }];
16727
16999
  } else if (config?.processor.name === "mistral") {
16728
17000
  const MISTRAL_API_KEY = await getMistralApiKey();
16729
- if (MISTRAL_API_KEY) {
17001
+ if (!MISTRAL_API_KEY) {
16730
17002
  throw new Error('[EXULU] MISTRAL_API_KEY is not set, please set it in the environment variable via process.env or via an Exulu variable named "MISTRAL_API_KEY".');
16731
17003
  }
16732
17004
  await new Promise((resolve3) => setTimeout(resolve3, Math.floor(Math.random() * 4e3) + 1e3));
@@ -16858,7 +17130,7 @@ var loadFile = async (file, name, tempDir) => {
16858
17130
  if (!fileType) {
16859
17131
  throw new Error("[EXULU] File name does not include extension, extension is required for document processing.");
16860
17132
  }
16861
- const UUID = randomUUID6();
17133
+ const UUID = randomUUID7();
16862
17134
  let buffer;
16863
17135
  if (Buffer.isBuffer(file)) {
16864
17136
  filePath = path.join(tempDir, `${UUID}.${fileType}`);
@@ -16888,7 +17160,7 @@ async function documentProcessor({
16888
17160
  if (!license["advanced-document-processing"]) {
16889
17161
  throw new Error("Advanced document processing is an enterprise feature, please add a valid Exulu enterprise license key to use it.");
16890
17162
  }
16891
- const uuid = randomUUID6();
17163
+ const uuid = randomUUID7();
16892
17164
  const tempDir = path.join(process.cwd(), "temp", uuid);
16893
17165
  const localFilesAndFoldersToDelete = [tempDir];
16894
17166
  console.log(`[EXULU] Temporary directory for processing document ${name}: ${tempDir}`);
@@ -715,6 +715,7 @@ export class MarkdownChunker {
715
715
  if (currentSlice.length === 0) {
716
716
  currentPosition++;
717
717
  targetPosition = currentPosition + (chunkSize * this._CHARS_PER_TOKEN);
718
+ contentLeft = text.length - currentPosition;
718
719
  }
719
720
  }
720
721
 
@@ -591,7 +591,8 @@ const getMistralApiKey = async () => {
591
591
  if (process.env.MISTRAL_API_KEY) {
592
592
  return process.env.MISTRAL_API_KEY;
593
593
  } else {
594
- return await ExuluVariables.get("MISTRAL_API_KEY");
594
+ const variable = await ExuluVariables.get("MISTRAL_API_KEY");
595
+ return variable;
595
596
  }
596
597
  }
597
598
 
@@ -667,7 +668,7 @@ async function processPdf(
667
668
  } else if (config?.processor.name === "mistral") {
668
669
 
669
670
  const MISTRAL_API_KEY = await getMistralApiKey();
670
- if (MISTRAL_API_KEY) {
671
+ if (!MISTRAL_API_KEY) {
671
672
  throw new Error('[EXULU] MISTRAL_API_KEY is not set, please set it in the environment variable via process.env or via an Exulu variable named "MISTRAL_API_KEY".');
672
673
  }
673
674
 
package/ee/workers.ts CHANGED
@@ -358,7 +358,33 @@ export const createWorkers = async (
358
358
 
359
359
  const exuluStorage = new ExuluStorage({ config });
360
360
 
361
- console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD");
361
+ if (context.processor.filter) {
362
+ const result = await context.processor.filter({
363
+ item: data.inputs,
364
+ user: data.user,
365
+ role: data.role,
366
+ utils: {
367
+ storage: exuluStorage,
368
+ },
369
+ exuluConfig: config,
370
+ });
371
+
372
+ if (!result) {
373
+ console.log("[EXULU] Item filtered out by processor, skipping processing execution...");
374
+ return {
375
+ result: "Item filtered out by processor, skipping processing execution...", // last message
376
+ metadata: {
377
+ item: {
378
+ name: data.inputs?.name,
379
+ id: data.inputs?.id,
380
+ external_id: data.inputs?.external_id
381
+ }
382
+ },
383
+ };
384
+ }
385
+ }
386
+
387
+ console.log("[EXULU] POS 2 -- EXULU CONTEXT PROCESS FIELD", data.inputs);
362
388
  let processorResult = await context.processor.execute({
363
389
  item: data.inputs,
364
390
  user: data.user,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "1.51.1",
4
+ "version": "1.53.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {
@@ -90,6 +90,7 @@
90
90
  "@ai-sdk/cerebras": "^2.0.29",
91
91
  "@ai-sdk/google-vertex": "^4.0.28",
92
92
  "@ai-sdk/openai": "^3.0.18",
93
+ "@ai-sdk/openai-compatible": "^2.0.37",
93
94
  "@anthropic-ai/sdk": "^0.56.0",
94
95
  "@apollo/server": "^5.4.0",
95
96
  "@as-integrations/express5": "^1.0.0",