@exulu/backend 1.52.0 → 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 +281 -23
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +284 -26
- package/ee/workers.ts +27 -1
- package/package.json +2 -1
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
|
-
|
|
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 {
|
|
@@ -11778,7 +11804,7 @@ var import_types2 = require("@modelcontextprotocol/sdk/types.js");
|
|
|
11778
11804
|
var import_express4 = require("express");
|
|
11779
11805
|
var import_api3 = require("@opentelemetry/api");
|
|
11780
11806
|
var import_crypto_js7 = __toESM(require("crypto-js"), 1);
|
|
11781
|
-
var
|
|
11807
|
+
var import_zod8 = require("zod");
|
|
11782
11808
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
11783
11809
|
var ExuluMCP = class {
|
|
11784
11810
|
server = {};
|
|
@@ -11861,7 +11887,7 @@ var ExuluMCP = class {
|
|
|
11861
11887
|
title: tool3.name + " agent",
|
|
11862
11888
|
description: tool3.description,
|
|
11863
11889
|
inputSchema: {
|
|
11864
|
-
inputs: tool3.inputSchema ||
|
|
11890
|
+
inputs: tool3.inputSchema || import_zod8.z.object({})
|
|
11865
11891
|
}
|
|
11866
11892
|
},
|
|
11867
11893
|
async ({ inputs }, args) => {
|
|
@@ -11913,7 +11939,7 @@ var ExuluMCP = class {
|
|
|
11913
11939
|
title: "Get List of Prompt Templates",
|
|
11914
11940
|
description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
|
|
11915
11941
|
inputSchema: {
|
|
11916
|
-
inputs:
|
|
11942
|
+
inputs: import_zod8.z.object({})
|
|
11917
11943
|
}
|
|
11918
11944
|
},
|
|
11919
11945
|
async ({ inputs }, args) => {
|
|
@@ -11959,8 +11985,8 @@ var ExuluMCP = class {
|
|
|
11959
11985
|
title: "Get Prompt Template Details",
|
|
11960
11986
|
description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
|
|
11961
11987
|
inputSchema: {
|
|
11962
|
-
inputs:
|
|
11963
|
-
id:
|
|
11988
|
+
inputs: import_zod8.z.object({
|
|
11989
|
+
id: import_zod8.z.string().describe("The ID of the prompt template to retrieve")
|
|
11964
11990
|
})
|
|
11965
11991
|
}
|
|
11966
11992
|
},
|
|
@@ -12868,7 +12894,7 @@ var ExuluEval = class {
|
|
|
12868
12894
|
};
|
|
12869
12895
|
|
|
12870
12896
|
// src/templates/evals/index.ts
|
|
12871
|
-
var
|
|
12897
|
+
var import_zod9 = require("zod");
|
|
12872
12898
|
var llmAsJudgeEval = () => {
|
|
12873
12899
|
if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
|
|
12874
12900
|
return new ExuluEval({
|
|
@@ -12913,8 +12939,8 @@ var llmAsJudgeEval = () => {
|
|
|
12913
12939
|
contexts: [],
|
|
12914
12940
|
rerankers: [],
|
|
12915
12941
|
prompt,
|
|
12916
|
-
outputSchema:
|
|
12917
|
-
score:
|
|
12942
|
+
outputSchema: import_zod9.z.object({
|
|
12943
|
+
score: import_zod9.z.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
12918
12944
|
}),
|
|
12919
12945
|
providerapikey
|
|
12920
12946
|
});
|
|
@@ -13142,12 +13168,12 @@ Usage:
|
|
|
13142
13168
|
- If no todos exist yet, an empty list will be returned`;
|
|
13143
13169
|
|
|
13144
13170
|
// src/templates/tools/todo/todo.ts
|
|
13145
|
-
var
|
|
13146
|
-
var TodoSchema =
|
|
13147
|
-
content:
|
|
13148
|
-
status:
|
|
13149
|
-
priority:
|
|
13150
|
-
id:
|
|
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")
|
|
13151
13177
|
});
|
|
13152
13178
|
var TodoWriteTool = new ExuluTool({
|
|
13153
13179
|
id: "todo_write",
|
|
@@ -13163,8 +13189,8 @@ var TodoWriteTool = new ExuluTool({
|
|
|
13163
13189
|
default: todowrite_default
|
|
13164
13190
|
}
|
|
13165
13191
|
],
|
|
13166
|
-
inputSchema:
|
|
13167
|
-
todos:
|
|
13192
|
+
inputSchema: import_zod10.default.object({
|
|
13193
|
+
todos: import_zod10.default.array(TodoSchema).describe("The updated todo list")
|
|
13168
13194
|
}),
|
|
13169
13195
|
execute: async (inputs) => {
|
|
13170
13196
|
const { sessionID, todos, user } = inputs;
|
|
@@ -13199,7 +13225,7 @@ var TodoReadTool = new ExuluTool({
|
|
|
13199
13225
|
id: "todo_read",
|
|
13200
13226
|
name: "Todo Read",
|
|
13201
13227
|
description: "Use this tool to read your todo list",
|
|
13202
|
-
inputSchema:
|
|
13228
|
+
inputSchema: import_zod10.default.object({}),
|
|
13203
13229
|
type: "function",
|
|
13204
13230
|
category: "todo",
|
|
13205
13231
|
config: [
|
|
@@ -13237,6 +13263,222 @@ async function getTodos(sessionID) {
|
|
|
13237
13263
|
}
|
|
13238
13264
|
var todoTools = [TodoWriteTool, TodoReadTool];
|
|
13239
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
|
+
|
|
13240
13482
|
// src/templates/tools/perplexity.ts
|
|
13241
13483
|
var import_zod12 = __toESM(require("zod"), 1);
|
|
13242
13484
|
var import_perplexity_ai = __toESM(require("@perplexity-ai/perplexity_ai"), 1);
|
|
@@ -13347,14 +13589,29 @@ var isValidPostgresName = (id) => {
|
|
|
13347
13589
|
|
|
13348
13590
|
// src/exulu/app/index.ts
|
|
13349
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
|
+
});
|
|
13350
13603
|
var consoleTransport = new import_winston2.default.transports.Console({
|
|
13351
13604
|
format: isDev ? import_winston2.default.format.combine(
|
|
13605
|
+
lineLimitFormat(),
|
|
13352
13606
|
import_winston2.default.format.colorize(),
|
|
13353
13607
|
import_winston2.default.format.timestamp({ format: "HH:mm:ss" }),
|
|
13354
13608
|
import_winston2.default.format.printf(({ timestamp, level, message }) => {
|
|
13355
13609
|
return `${timestamp} [${level}] ${message}`;
|
|
13356
13610
|
})
|
|
13357
|
-
) : import_winston2.default.format.
|
|
13611
|
+
) : import_winston2.default.format.combine(
|
|
13612
|
+
lineLimitFormat(),
|
|
13613
|
+
import_winston2.default.format.json()
|
|
13614
|
+
)
|
|
13358
13615
|
});
|
|
13359
13616
|
var formatArg = (arg) => typeof arg === "object" ? import_util.default.inspect(arg, { depth: null, colors: isDev }) : String(arg);
|
|
13360
13617
|
var createLogMethod = (logger, logLevel) => {
|
|
@@ -13424,6 +13681,7 @@ var ExuluApp2 = class {
|
|
|
13424
13681
|
this._tools = [
|
|
13425
13682
|
...tools ?? [],
|
|
13426
13683
|
...todoTools,
|
|
13684
|
+
...questionTools,
|
|
13427
13685
|
...perplexityTools,
|
|
13428
13686
|
// Add contexts as tools
|
|
13429
13687
|
...Object.values(contexts || {}).map((context) => context.tool()).filter(Boolean)
|
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
|
|
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
|
|
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
|
-
|
|
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 {
|
|
@@ -11744,7 +11770,7 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
|
11744
11770
|
import "express";
|
|
11745
11771
|
import "@opentelemetry/api";
|
|
11746
11772
|
import CryptoJS7 from "crypto-js";
|
|
11747
|
-
import { z as
|
|
11773
|
+
import { z as z8 } from "zod";
|
|
11748
11774
|
var SESSION_ID_HEADER = "mcp-session-id";
|
|
11749
11775
|
var ExuluMCP = class {
|
|
11750
11776
|
server = {};
|
|
@@ -11827,7 +11853,7 @@ var ExuluMCP = class {
|
|
|
11827
11853
|
title: tool3.name + " agent",
|
|
11828
11854
|
description: tool3.description,
|
|
11829
11855
|
inputSchema: {
|
|
11830
|
-
inputs: tool3.inputSchema ||
|
|
11856
|
+
inputs: tool3.inputSchema || z8.object({})
|
|
11831
11857
|
}
|
|
11832
11858
|
},
|
|
11833
11859
|
async ({ inputs }, args) => {
|
|
@@ -11879,7 +11905,7 @@ var ExuluMCP = class {
|
|
|
11879
11905
|
title: "Get List of Prompt Templates",
|
|
11880
11906
|
description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
|
|
11881
11907
|
inputSchema: {
|
|
11882
|
-
inputs:
|
|
11908
|
+
inputs: z8.object({})
|
|
11883
11909
|
}
|
|
11884
11910
|
},
|
|
11885
11911
|
async ({ inputs }, args) => {
|
|
@@ -11925,8 +11951,8 @@ var ExuluMCP = class {
|
|
|
11925
11951
|
title: "Get Prompt Template Details",
|
|
11926
11952
|
description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
|
|
11927
11953
|
inputSchema: {
|
|
11928
|
-
inputs:
|
|
11929
|
-
id:
|
|
11954
|
+
inputs: z8.object({
|
|
11955
|
+
id: z8.string().describe("The ID of the prompt template to retrieve")
|
|
11930
11956
|
})
|
|
11931
11957
|
}
|
|
11932
11958
|
},
|
|
@@ -12834,7 +12860,7 @@ var ExuluEval = class {
|
|
|
12834
12860
|
};
|
|
12835
12861
|
|
|
12836
12862
|
// src/templates/evals/index.ts
|
|
12837
|
-
import { z as
|
|
12863
|
+
import { z as z9 } from "zod";
|
|
12838
12864
|
var llmAsJudgeEval = () => {
|
|
12839
12865
|
if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
|
|
12840
12866
|
return new ExuluEval({
|
|
@@ -12879,8 +12905,8 @@ var llmAsJudgeEval = () => {
|
|
|
12879
12905
|
contexts: [],
|
|
12880
12906
|
rerankers: [],
|
|
12881
12907
|
prompt,
|
|
12882
|
-
outputSchema:
|
|
12883
|
-
score:
|
|
12908
|
+
outputSchema: z9.object({
|
|
12909
|
+
score: z9.number().min(0).max(100).describe("The score between 0 and 100.")
|
|
12884
12910
|
}),
|
|
12885
12911
|
providerapikey
|
|
12886
12912
|
});
|
|
@@ -13108,12 +13134,12 @@ Usage:
|
|
|
13108
13134
|
- If no todos exist yet, an empty list will be returned`;
|
|
13109
13135
|
|
|
13110
13136
|
// src/templates/tools/todo/todo.ts
|
|
13111
|
-
import
|
|
13112
|
-
var TodoSchema =
|
|
13113
|
-
content:
|
|
13114
|
-
status:
|
|
13115
|
-
priority:
|
|
13116
|
-
id:
|
|
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")
|
|
13117
13143
|
});
|
|
13118
13144
|
var TodoWriteTool = new ExuluTool({
|
|
13119
13145
|
id: "todo_write",
|
|
@@ -13129,8 +13155,8 @@ var TodoWriteTool = new ExuluTool({
|
|
|
13129
13155
|
default: todowrite_default
|
|
13130
13156
|
}
|
|
13131
13157
|
],
|
|
13132
|
-
inputSchema:
|
|
13133
|
-
todos:
|
|
13158
|
+
inputSchema: z10.object({
|
|
13159
|
+
todos: z10.array(TodoSchema).describe("The updated todo list")
|
|
13134
13160
|
}),
|
|
13135
13161
|
execute: async (inputs) => {
|
|
13136
13162
|
const { sessionID, todos, user } = inputs;
|
|
@@ -13165,7 +13191,7 @@ var TodoReadTool = new ExuluTool({
|
|
|
13165
13191
|
id: "todo_read",
|
|
13166
13192
|
name: "Todo Read",
|
|
13167
13193
|
description: "Use this tool to read your todo list",
|
|
13168
|
-
inputSchema:
|
|
13194
|
+
inputSchema: z10.object({}),
|
|
13169
13195
|
type: "function",
|
|
13170
13196
|
category: "todo",
|
|
13171
13197
|
config: [
|
|
@@ -13203,6 +13229,222 @@ async function getTodos(sessionID) {
|
|
|
13203
13229
|
}
|
|
13204
13230
|
var todoTools = [TodoWriteTool, TodoReadTool];
|
|
13205
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
|
+
|
|
13206
13448
|
// src/templates/tools/perplexity.ts
|
|
13207
13449
|
import z12 from "zod";
|
|
13208
13450
|
import Perplexity from "@perplexity-ai/perplexity_ai";
|
|
@@ -13313,14 +13555,29 @@ var isValidPostgresName = (id) => {
|
|
|
13313
13555
|
|
|
13314
13556
|
// src/exulu/app/index.ts
|
|
13315
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
|
+
});
|
|
13316
13569
|
var consoleTransport = new winston2.transports.Console({
|
|
13317
13570
|
format: isDev ? winston2.format.combine(
|
|
13571
|
+
lineLimitFormat(),
|
|
13318
13572
|
winston2.format.colorize(),
|
|
13319
13573
|
winston2.format.timestamp({ format: "HH:mm:ss" }),
|
|
13320
13574
|
winston2.format.printf(({ timestamp, level, message }) => {
|
|
13321
13575
|
return `${timestamp} [${level}] ${message}`;
|
|
13322
13576
|
})
|
|
13323
|
-
) : winston2.format.
|
|
13577
|
+
) : winston2.format.combine(
|
|
13578
|
+
lineLimitFormat(),
|
|
13579
|
+
winston2.format.json()
|
|
13580
|
+
)
|
|
13324
13581
|
});
|
|
13325
13582
|
var formatArg = (arg) => typeof arg === "object" ? util.inspect(arg, { depth: null, colors: isDev }) : String(arg);
|
|
13326
13583
|
var createLogMethod = (logger, logLevel) => {
|
|
@@ -13390,6 +13647,7 @@ var ExuluApp2 = class {
|
|
|
13390
13647
|
this._tools = [
|
|
13391
13648
|
...tools ?? [],
|
|
13392
13649
|
...todoTools,
|
|
13650
|
+
...questionTools,
|
|
13393
13651
|
...perplexityTools,
|
|
13394
13652
|
// Add contexts as tools
|
|
13395
13653
|
...Object.values(contexts || {}).map((context) => context.tool()).filter(Boolean)
|
|
@@ -16174,7 +16432,7 @@ import * as path from "path";
|
|
|
16174
16432
|
import { generateText as generateText3, Output as Output3 } from "ai";
|
|
16175
16433
|
import { z as z13 } from "zod";
|
|
16176
16434
|
import pLimit from "p-limit";
|
|
16177
|
-
import { randomUUID as
|
|
16435
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
16178
16436
|
import * as mammoth from "mammoth";
|
|
16179
16437
|
import TurndownService from "turndown";
|
|
16180
16438
|
import WordExtractor from "word-extractor";
|
|
@@ -16872,7 +17130,7 @@ var loadFile = async (file, name, tempDir) => {
|
|
|
16872
17130
|
if (!fileType) {
|
|
16873
17131
|
throw new Error("[EXULU] File name does not include extension, extension is required for document processing.");
|
|
16874
17132
|
}
|
|
16875
|
-
const UUID =
|
|
17133
|
+
const UUID = randomUUID7();
|
|
16876
17134
|
let buffer;
|
|
16877
17135
|
if (Buffer.isBuffer(file)) {
|
|
16878
17136
|
filePath = path.join(tempDir, `${UUID}.${fileType}`);
|
|
@@ -16902,7 +17160,7 @@ async function documentProcessor({
|
|
|
16902
17160
|
if (!license["advanced-document-processing"]) {
|
|
16903
17161
|
throw new Error("Advanced document processing is an enterprise feature, please add a valid Exulu enterprise license key to use it.");
|
|
16904
17162
|
}
|
|
16905
|
-
const uuid =
|
|
17163
|
+
const uuid = randomUUID7();
|
|
16906
17164
|
const tempDir = path.join(process.cwd(), "temp", uuid);
|
|
16907
17165
|
const localFilesAndFoldersToDelete = [tempDir];
|
|
16908
17166
|
console.log(`[EXULU] Temporary directory for processing document ${name}: ${tempDir}`);
|
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
|
-
|
|
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.
|
|
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",
|