@exulu/backend 1.37.0 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/dist/index.cjs +77 -5
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +77 -5
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# [1.
|
|
1
|
+
# [1.38.0](https://github.com/Qventu/exulu-backend/compare/v1.37.0...v1.38.0) (2025-11-30)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Features
|
|
5
5
|
|
|
6
|
-
* add
|
|
6
|
+
* add document parsing for AI agent file handling with JWT secret fix ([fc97b5f](https://github.com/Qventu/exulu-backend/commit/fc97b5ff14c617350a130a12a6077c5fc23fda8b))
|
package/dist/index.cjs
CHANGED
|
@@ -463,7 +463,9 @@ var getToken = async (authHeader) => {
|
|
|
463
463
|
}
|
|
464
464
|
try {
|
|
465
465
|
const secret = process.env.NEXTAUTH_SECRET;
|
|
466
|
-
const
|
|
466
|
+
const secretBuffer = Buffer.from(secret, "utf-8");
|
|
467
|
+
const base64Secret = secretBuffer.toString("base64url");
|
|
468
|
+
const jwk = await (0, import_jose.importJWK)({ k: base64Secret, alg: "HS256", kty: "oct" });
|
|
467
469
|
const { payload } = await (0, import_jose.jwtVerify)(token, jwk);
|
|
468
470
|
return payload;
|
|
469
471
|
} catch (error) {
|
|
@@ -4969,6 +4971,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4969
4971
|
};
|
|
4970
4972
|
|
|
4971
4973
|
// src/registry/classes.ts
|
|
4974
|
+
var import_officeparser = require("officeparser");
|
|
4972
4975
|
var s3Client2;
|
|
4973
4976
|
function sanitizeToolName(name) {
|
|
4974
4977
|
if (typeof name !== "string") return "";
|
|
@@ -5482,6 +5485,71 @@ var ExuluAgent2 = class {
|
|
|
5482
5485
|
}
|
|
5483
5486
|
return "";
|
|
5484
5487
|
};
|
|
5488
|
+
/**
|
|
5489
|
+
* Convert file parts in messages to OpenAI Responses API compatible format.
|
|
5490
|
+
* The OpenAI Responses API doesn't support inline file parts with type 'file'.
|
|
5491
|
+
* This function converts:
|
|
5492
|
+
* - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
|
|
5493
|
+
* - Image files -> image parts (which ARE supported by Responses API)
|
|
5494
|
+
*/
|
|
5495
|
+
async processFilePartsInMessages(messages) {
|
|
5496
|
+
const processedMessages = await Promise.all(messages.map(async (message) => {
|
|
5497
|
+
console.log("[EXULU] Processing file parts in messages: " + JSON.stringify(message, null, 2));
|
|
5498
|
+
if (message.role !== "user" || !Array.isArray(message.parts)) {
|
|
5499
|
+
return message;
|
|
5500
|
+
}
|
|
5501
|
+
const processedParts = await Promise.all(message.parts.map(async (part) => {
|
|
5502
|
+
if (part.type !== "file") {
|
|
5503
|
+
return part;
|
|
5504
|
+
}
|
|
5505
|
+
const { mediaType, url, filename } = part;
|
|
5506
|
+
const imageTypes = ["image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp"];
|
|
5507
|
+
if (imageTypes.includes(mediaType)) {
|
|
5508
|
+
console.log(`[EXULU] Converting file part to image part: ${filename}`);
|
|
5509
|
+
return {
|
|
5510
|
+
type: "image",
|
|
5511
|
+
image: url,
|
|
5512
|
+
mimeType: mediaType
|
|
5513
|
+
};
|
|
5514
|
+
}
|
|
5515
|
+
console.log(`[EXULU] Converting file part to text using officeparser: ${filename}`);
|
|
5516
|
+
try {
|
|
5517
|
+
const response = await fetch(url);
|
|
5518
|
+
if (!response.ok) {
|
|
5519
|
+
console.error(`[EXULU] Failed to fetch file: ${filename}, status: ${response.status}`);
|
|
5520
|
+
return {
|
|
5521
|
+
type: "text",
|
|
5522
|
+
text: `[Error: Could not load file ${filename}]`
|
|
5523
|
+
};
|
|
5524
|
+
}
|
|
5525
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
5526
|
+
const extractedText = await (0, import_officeparser.parseOfficeAsync)(arrayBuffer, {
|
|
5527
|
+
outputErrorToConsole: false,
|
|
5528
|
+
newlineDelimiter: "\n"
|
|
5529
|
+
});
|
|
5530
|
+
return {
|
|
5531
|
+
type: "text",
|
|
5532
|
+
text: `<file name="${filename}">
|
|
5533
|
+
${extractedText}
|
|
5534
|
+
</file>`
|
|
5535
|
+
};
|
|
5536
|
+
} catch (error) {
|
|
5537
|
+
console.error(`[EXULU] Error processing file ${filename}:`, error);
|
|
5538
|
+
return {
|
|
5539
|
+
type: "text",
|
|
5540
|
+
text: `[Error extracting text from file ${filename}: ${error instanceof Error ? error.message : "Unknown error"}]`
|
|
5541
|
+
};
|
|
5542
|
+
}
|
|
5543
|
+
}));
|
|
5544
|
+
const result = {
|
|
5545
|
+
...message,
|
|
5546
|
+
parts: processedParts
|
|
5547
|
+
};
|
|
5548
|
+
console.log("[EXULU] Result: " + JSON.stringify(result, null, 2));
|
|
5549
|
+
return result;
|
|
5550
|
+
}));
|
|
5551
|
+
return processedMessages;
|
|
5552
|
+
}
|
|
5485
5553
|
generateStream = async ({
|
|
5486
5554
|
user,
|
|
5487
5555
|
session,
|
|
@@ -5529,6 +5597,11 @@ var ExuluAgent2 = class {
|
|
|
5529
5597
|
// append the new message to the previous messages:
|
|
5530
5598
|
messages: [...previousMessagesContent, message]
|
|
5531
5599
|
});
|
|
5600
|
+
messages = messages.filter(
|
|
5601
|
+
(message2, index, self) => index === self.findIndex((t) => t.id === message2.id)
|
|
5602
|
+
);
|
|
5603
|
+
console.log("[EXULU] Processing file parts in messages for OpenAI Responses API compatibility");
|
|
5604
|
+
messages = await this.processFilePartsInMessages(messages);
|
|
5532
5605
|
const genericContext = "IMPORTANT: \n\n The current date is " + (/* @__PURE__ */ new Date()).toLocaleDateString() + " and the current time is " + (/* @__PURE__ */ new Date()).toLocaleTimeString() + ". If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume they are talking with the current date in mind as a reference.";
|
|
5533
5606
|
let system = instructions || "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.";
|
|
5534
5607
|
system += "\n\n" + genericContext;
|
|
@@ -5593,7 +5666,7 @@ var getSession = async ({ sessionID }) => {
|
|
|
5593
5666
|
};
|
|
5594
5667
|
var saveChat = async ({ session, user, messages }) => {
|
|
5595
5668
|
const { db: db3 } = await postgresClient();
|
|
5596
|
-
const
|
|
5669
|
+
for (const message of messages) {
|
|
5597
5670
|
const mutation = db3.from("agent_messages").insert({
|
|
5598
5671
|
session,
|
|
5599
5672
|
user,
|
|
@@ -5602,9 +5675,8 @@ var saveChat = async ({ session, user, messages }) => {
|
|
|
5602
5675
|
title: message.role === "user" ? "User" : "Assistant"
|
|
5603
5676
|
}).returning("id");
|
|
5604
5677
|
mutation.onConflict("message_id").merge();
|
|
5605
|
-
|
|
5606
|
-
}
|
|
5607
|
-
await Promise.all(promises);
|
|
5678
|
+
await mutation;
|
|
5679
|
+
}
|
|
5608
5680
|
};
|
|
5609
5681
|
var ExuluEmbedder = class {
|
|
5610
5682
|
id;
|
package/dist/index.d.cts
CHANGED
|
@@ -398,6 +398,14 @@ declare class ExuluAgent {
|
|
|
398
398
|
instructions?: string;
|
|
399
399
|
outputSchema?: z.ZodType;
|
|
400
400
|
}) => Promise<string | any>;
|
|
401
|
+
/**
|
|
402
|
+
* Convert file parts in messages to OpenAI Responses API compatible format.
|
|
403
|
+
* The OpenAI Responses API doesn't support inline file parts with type 'file'.
|
|
404
|
+
* This function converts:
|
|
405
|
+
* - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
|
|
406
|
+
* - Image files -> image parts (which ARE supported by Responses API)
|
|
407
|
+
*/
|
|
408
|
+
private processFilePartsInMessages;
|
|
401
409
|
generateStream: ({ user, session, message, previousMessages, currentTools, allExuluTools, toolConfigs, providerapikey, contexts, exuluConfig, instructions, req, }: {
|
|
402
410
|
user?: User;
|
|
403
411
|
session?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -398,6 +398,14 @@ declare class ExuluAgent {
|
|
|
398
398
|
instructions?: string;
|
|
399
399
|
outputSchema?: z.ZodType;
|
|
400
400
|
}) => Promise<string | any>;
|
|
401
|
+
/**
|
|
402
|
+
* Convert file parts in messages to OpenAI Responses API compatible format.
|
|
403
|
+
* The OpenAI Responses API doesn't support inline file parts with type 'file'.
|
|
404
|
+
* This function converts:
|
|
405
|
+
* - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
|
|
406
|
+
* - Image files -> image parts (which ARE supported by Responses API)
|
|
407
|
+
*/
|
|
408
|
+
private processFilePartsInMessages;
|
|
401
409
|
generateStream: ({ user, session, message, previousMessages, currentTools, allExuluTools, toolConfigs, providerapikey, contexts, exuluConfig, instructions, req, }: {
|
|
402
410
|
user?: User;
|
|
403
411
|
session?: string;
|
package/dist/index.js
CHANGED
|
@@ -411,7 +411,9 @@ var getToken = async (authHeader) => {
|
|
|
411
411
|
}
|
|
412
412
|
try {
|
|
413
413
|
const secret = process.env.NEXTAUTH_SECRET;
|
|
414
|
-
const
|
|
414
|
+
const secretBuffer = Buffer.from(secret, "utf-8");
|
|
415
|
+
const base64Secret = secretBuffer.toString("base64url");
|
|
416
|
+
const jwk = await importJWK({ k: base64Secret, alg: "HS256", kty: "oct" });
|
|
415
417
|
const { payload } = await jwtVerify(token, jwk);
|
|
416
418
|
return payload;
|
|
417
419
|
} catch (error) {
|
|
@@ -4936,6 +4938,7 @@ var createUppyRoutes = async (app, config) => {
|
|
|
4936
4938
|
};
|
|
4937
4939
|
|
|
4938
4940
|
// src/registry/classes.ts
|
|
4941
|
+
import { parseOfficeAsync } from "officeparser";
|
|
4939
4942
|
var s3Client2;
|
|
4940
4943
|
function sanitizeToolName(name) {
|
|
4941
4944
|
if (typeof name !== "string") return "";
|
|
@@ -5449,6 +5452,71 @@ var ExuluAgent2 = class {
|
|
|
5449
5452
|
}
|
|
5450
5453
|
return "";
|
|
5451
5454
|
};
|
|
5455
|
+
/**
|
|
5456
|
+
* Convert file parts in messages to OpenAI Responses API compatible format.
|
|
5457
|
+
* The OpenAI Responses API doesn't support inline file parts with type 'file'.
|
|
5458
|
+
* This function converts:
|
|
5459
|
+
* - Document files (PDF, DOCX, etc.) -> text parts with extracted content using officeparser
|
|
5460
|
+
* - Image files -> image parts (which ARE supported by Responses API)
|
|
5461
|
+
*/
|
|
5462
|
+
async processFilePartsInMessages(messages) {
|
|
5463
|
+
const processedMessages = await Promise.all(messages.map(async (message) => {
|
|
5464
|
+
console.log("[EXULU] Processing file parts in messages: " + JSON.stringify(message, null, 2));
|
|
5465
|
+
if (message.role !== "user" || !Array.isArray(message.parts)) {
|
|
5466
|
+
return message;
|
|
5467
|
+
}
|
|
5468
|
+
const processedParts = await Promise.all(message.parts.map(async (part) => {
|
|
5469
|
+
if (part.type !== "file") {
|
|
5470
|
+
return part;
|
|
5471
|
+
}
|
|
5472
|
+
const { mediaType, url, filename } = part;
|
|
5473
|
+
const imageTypes = ["image/png", "image/jpeg", "image/jpg", "image/gif", "image/webp"];
|
|
5474
|
+
if (imageTypes.includes(mediaType)) {
|
|
5475
|
+
console.log(`[EXULU] Converting file part to image part: ${filename}`);
|
|
5476
|
+
return {
|
|
5477
|
+
type: "image",
|
|
5478
|
+
image: url,
|
|
5479
|
+
mimeType: mediaType
|
|
5480
|
+
};
|
|
5481
|
+
}
|
|
5482
|
+
console.log(`[EXULU] Converting file part to text using officeparser: ${filename}`);
|
|
5483
|
+
try {
|
|
5484
|
+
const response = await fetch(url);
|
|
5485
|
+
if (!response.ok) {
|
|
5486
|
+
console.error(`[EXULU] Failed to fetch file: ${filename}, status: ${response.status}`);
|
|
5487
|
+
return {
|
|
5488
|
+
type: "text",
|
|
5489
|
+
text: `[Error: Could not load file ${filename}]`
|
|
5490
|
+
};
|
|
5491
|
+
}
|
|
5492
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
5493
|
+
const extractedText = await parseOfficeAsync(arrayBuffer, {
|
|
5494
|
+
outputErrorToConsole: false,
|
|
5495
|
+
newlineDelimiter: "\n"
|
|
5496
|
+
});
|
|
5497
|
+
return {
|
|
5498
|
+
type: "text",
|
|
5499
|
+
text: `<file name="${filename}">
|
|
5500
|
+
${extractedText}
|
|
5501
|
+
</file>`
|
|
5502
|
+
};
|
|
5503
|
+
} catch (error) {
|
|
5504
|
+
console.error(`[EXULU] Error processing file ${filename}:`, error);
|
|
5505
|
+
return {
|
|
5506
|
+
type: "text",
|
|
5507
|
+
text: `[Error extracting text from file ${filename}: ${error instanceof Error ? error.message : "Unknown error"}]`
|
|
5508
|
+
};
|
|
5509
|
+
}
|
|
5510
|
+
}));
|
|
5511
|
+
const result = {
|
|
5512
|
+
...message,
|
|
5513
|
+
parts: processedParts
|
|
5514
|
+
};
|
|
5515
|
+
console.log("[EXULU] Result: " + JSON.stringify(result, null, 2));
|
|
5516
|
+
return result;
|
|
5517
|
+
}));
|
|
5518
|
+
return processedMessages;
|
|
5519
|
+
}
|
|
5452
5520
|
generateStream = async ({
|
|
5453
5521
|
user,
|
|
5454
5522
|
session,
|
|
@@ -5496,6 +5564,11 @@ var ExuluAgent2 = class {
|
|
|
5496
5564
|
// append the new message to the previous messages:
|
|
5497
5565
|
messages: [...previousMessagesContent, message]
|
|
5498
5566
|
});
|
|
5567
|
+
messages = messages.filter(
|
|
5568
|
+
(message2, index, self) => index === self.findIndex((t) => t.id === message2.id)
|
|
5569
|
+
);
|
|
5570
|
+
console.log("[EXULU] Processing file parts in messages for OpenAI Responses API compatibility");
|
|
5571
|
+
messages = await this.processFilePartsInMessages(messages);
|
|
5499
5572
|
const genericContext = "IMPORTANT: \n\n The current date is " + (/* @__PURE__ */ new Date()).toLocaleDateString() + " and the current time is " + (/* @__PURE__ */ new Date()).toLocaleTimeString() + ". If the user does not explicitly provide the current date, for examle when saying ' this weekend', you should assume they are talking with the current date in mind as a reference.";
|
|
5500
5573
|
let system = instructions || "You are a helpful assistant. When you use a tool to answer a question do not explicitly comment on the result of the tool call unless the user has explicitly you to do something with the result.";
|
|
5501
5574
|
system += "\n\n" + genericContext;
|
|
@@ -5560,7 +5633,7 @@ var getSession = async ({ sessionID }) => {
|
|
|
5560
5633
|
};
|
|
5561
5634
|
var saveChat = async ({ session, user, messages }) => {
|
|
5562
5635
|
const { db: db3 } = await postgresClient();
|
|
5563
|
-
const
|
|
5636
|
+
for (const message of messages) {
|
|
5564
5637
|
const mutation = db3.from("agent_messages").insert({
|
|
5565
5638
|
session,
|
|
5566
5639
|
user,
|
|
@@ -5569,9 +5642,8 @@ var saveChat = async ({ session, user, messages }) => {
|
|
|
5569
5642
|
title: message.role === "user" ? "User" : "Assistant"
|
|
5570
5643
|
}).returning("id");
|
|
5571
5644
|
mutation.onConflict("message_id").merge();
|
|
5572
|
-
|
|
5573
|
-
}
|
|
5574
|
-
await Promise.all(promises);
|
|
5645
|
+
await mutation;
|
|
5646
|
+
}
|
|
5575
5647
|
};
|
|
5576
5648
|
var ExuluEmbedder = class {
|
|
5577
5649
|
id;
|
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.38.0",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"private": false,
|
|
7
7
|
"publishConfig": {
|
|
@@ -90,6 +90,7 @@
|
|
|
90
90
|
"jsonwebtoken": "^9.0.2",
|
|
91
91
|
"knex": "^3.1.0",
|
|
92
92
|
"link": "^2.1.1",
|
|
93
|
+
"officeparser": "^5.2.2",
|
|
93
94
|
"openai": "^5.21.0",
|
|
94
95
|
"papaparse": "^5.5.2",
|
|
95
96
|
"pg": "^8.16.3",
|