@anuma/sdk 6.25.3 → 6.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/expo/{chunk-EDABJLYN.mjs → chunk-N5CKISI3.mjs} +1 -1
- package/dist/expo/{consolidate-5WM3CFKK.mjs → consolidate-N56RXPCW.mjs} +1 -1
- package/dist/expo/index.cjs +71 -3
- package/dist/expo/index.d.mts +91 -1
- package/dist/expo/index.d.ts +91 -1
- package/dist/expo/index.mjs +68 -4
- package/dist/index.cjs +68 -0
- package/dist/index.d.mts +80 -1
- package/dist/index.d.ts +80 -1
- package/dist/index.mjs +64 -0
- package/dist/react/{chunk-BPMINZPO.mjs → chunk-LBIKGBXE.mjs} +1 -1
- package/dist/react/{consolidate-A2X3M47N.mjs → consolidate-2NLZAEWR.mjs} +1 -1
- package/dist/react/index.cjs +522 -128
- package/dist/react/index.d.mts +331 -206
- package/dist/react/index.d.ts +331 -206
- package/dist/react/index.mjs +519 -129
- package/dist/server/{chunk-DWNEDXA5.mjs → chunk-Z2QBL4HB.mjs} +1 -1
- package/dist/server/{consolidate-53I4CTD7.mjs → consolidate-QWVI25WQ.mjs} +1 -1
- package/dist/server/index.cjs +441 -122
- package/dist/server/index.d.mts +85 -7
- package/dist/server/index.d.ts +85 -7
- package/dist/server/index.mjs +441 -123
- package/package.json +1 -1
package/dist/react/index.cjs
CHANGED
|
@@ -1174,7 +1174,7 @@ var init_portalLlm = __esm({
|
|
|
1174
1174
|
RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
|
|
1175
1175
|
RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
|
|
1176
1176
|
JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
|
|
1177
|
-
RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6"];
|
|
1177
|
+
RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6", "gpt-6-sol", "gpt-6-luna", "gpt-6-astra"];
|
|
1178
1178
|
CHAT_TO_RESPONSES_FIELDS = {
|
|
1179
1179
|
max_completion_tokens: "max_output_tokens"
|
|
1180
1180
|
};
|
|
@@ -1504,8 +1504,10 @@ __export(index_exports, {
|
|
|
1504
1504
|
appFileToStored: () => appFileToStored,
|
|
1505
1505
|
archiveVaultMemoryOp: () => archiveVaultMemoryOp,
|
|
1506
1506
|
assembleMemoryContext: () => assembleMemoryContext,
|
|
1507
|
+
attachFileContextToLastUserMessage: () => attachFileContextToLastUserMessage,
|
|
1507
1508
|
backfillMemoryEntityUserIdsOp: () => backfillMemoryEntityUserIdsOp,
|
|
1508
1509
|
backfillMemoryTopicsOp: () => backfillMemoryTopicsOp,
|
|
1510
|
+
buildAttachedFilesText: () => buildAttachedFilesText,
|
|
1509
1511
|
capHopsForDensity: () => capHopsForDensity,
|
|
1510
1512
|
chatStorageMigrations: () => chatStorageMigrations,
|
|
1511
1513
|
chatStorageSchema: () => chatStorageSchema,
|
|
@@ -1611,6 +1613,7 @@ __export(index_exports, {
|
|
|
1611
1613
|
findById: () => findById,
|
|
1612
1614
|
findMatchingTools: () => findMatchingTools,
|
|
1613
1615
|
findParentOfId: () => findParentOfId,
|
|
1616
|
+
formatFileProcessingNotes: () => formatFileProcessingNotes,
|
|
1614
1617
|
generateConversationId: () => generateConversationId,
|
|
1615
1618
|
generateEmbedding: () => generateEmbedding,
|
|
1616
1619
|
generateEmbeddings: () => generateEmbeddings,
|
|
@@ -1710,6 +1713,7 @@ __export(index_exports, {
|
|
|
1710
1713
|
insertAfterId: () => insertAfterId,
|
|
1711
1714
|
insertChild: () => insertChild,
|
|
1712
1715
|
isAnumaTag: () => isAnumaTag,
|
|
1716
|
+
isAttachedFilesText: () => isAttachedFilesText,
|
|
1713
1717
|
isCalendarCallback: () => isCalendarCallback,
|
|
1714
1718
|
isDegradedTopicSkip: () => isDegradedTopicSkip,
|
|
1715
1719
|
isDriveCallback: () => isDriveCallback,
|
|
@@ -3359,6 +3363,46 @@ async function assembleDecryptedMessage(message, address) {
|
|
|
3359
3363
|
// src/lib/db/chat/types.ts
|
|
3360
3364
|
var import_uuid = require("uuid");
|
|
3361
3365
|
|
|
3366
|
+
// src/lib/chat/fileContext.ts
|
|
3367
|
+
var ATTACHED_FILES_OPEN_TAG = "<attached_files>";
|
|
3368
|
+
var ATTACHED_FILES_CLOSE_TAG = "</attached_files>";
|
|
3369
|
+
var ATTACHED_FILES_PREFIX = `${ATTACHED_FILES_OPEN_TAG}
|
|
3370
|
+
The user attached the following file(s) to this message. Their extracted contents are below; when the user refers to "the attached file", "this file" or "the document", they mean these.`;
|
|
3371
|
+
function isAttachedFilesText(text12) {
|
|
3372
|
+
return typeof text12 === "string" && text12.startsWith(ATTACHED_FILES_PREFIX) && text12.endsWith(ATTACHED_FILES_CLOSE_TAG);
|
|
3373
|
+
}
|
|
3374
|
+
function buildAttachedFilesText(fileContext) {
|
|
3375
|
+
return `${ATTACHED_FILES_PREFIX}
|
|
3376
|
+
|
|
3377
|
+
${fileContext}
|
|
3378
|
+
${ATTACHED_FILES_CLOSE_TAG}`;
|
|
3379
|
+
}
|
|
3380
|
+
function attachFileContextToLastUserMessage(messages, fileContext) {
|
|
3381
|
+
if (!fileContext.trim()) return messages;
|
|
3382
|
+
let lastUserIndex = -1;
|
|
3383
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
3384
|
+
if (messages[i].role === "user") {
|
|
3385
|
+
lastUserIndex = i;
|
|
3386
|
+
break;
|
|
3387
|
+
}
|
|
3388
|
+
}
|
|
3389
|
+
if (lastUserIndex === -1) return messages;
|
|
3390
|
+
const message = messages[lastUserIndex];
|
|
3391
|
+
const existing = message.content ?? [];
|
|
3392
|
+
let insertAt = 0;
|
|
3393
|
+
existing.forEach((part, index) => {
|
|
3394
|
+
if (part.type === "text") insertAt = index + 1;
|
|
3395
|
+
});
|
|
3396
|
+
const filePart = {
|
|
3397
|
+
type: "text",
|
|
3398
|
+
text: buildAttachedFilesText(fileContext)
|
|
3399
|
+
};
|
|
3400
|
+
const content = [...existing.slice(0, insertAt), filePart, ...existing.slice(insertAt)];
|
|
3401
|
+
const result = [...messages];
|
|
3402
|
+
result[lastUserIndex] = { ...message, content };
|
|
3403
|
+
return result;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3362
3406
|
// src/lib/chat/useChat/strategies/types.ts
|
|
3363
3407
|
function isChatCompletionResponse(r) {
|
|
3364
3408
|
if (r.object === "response") return false;
|
|
@@ -3480,7 +3524,7 @@ function extractUserMessageFromMessages(messages) {
|
|
|
3480
3524
|
const textParts = [];
|
|
3481
3525
|
const files = [];
|
|
3482
3526
|
for (const part of lastUserMessage.content) {
|
|
3483
|
-
if (part.type === "text" && part.text) {
|
|
3527
|
+
if (part.type === "text" && part.text && !isAttachedFilesText(part.text)) {
|
|
3484
3528
|
textParts.push(part.text);
|
|
3485
3529
|
} else if (part.type === "image_url" && part.image_url?.url) {
|
|
3486
3530
|
files.push({
|
|
@@ -7926,7 +7970,7 @@ function extractLastUserText(messages) {
|
|
|
7926
7970
|
const content = lastUserMsg.content;
|
|
7927
7971
|
if (typeof content === "string") return content;
|
|
7928
7972
|
if (Array.isArray(content)) {
|
|
7929
|
-
return content.filter((c) => c?.type === "text").map((c) => c.text ?? "").join(" ");
|
|
7973
|
+
return content.filter((c) => c?.type === "text" && !isAttachedFilesText(c.text)).map((c) => c.text ?? "").join(" ");
|
|
7930
7974
|
}
|
|
7931
7975
|
return "";
|
|
7932
7976
|
}
|
|
@@ -21459,14 +21503,37 @@ init_logger();
|
|
|
21459
21503
|
if (typeof process !== "undefined" && typeof process.umask !== "function") {
|
|
21460
21504
|
process.umask = (_mask) => 18;
|
|
21461
21505
|
}
|
|
21506
|
+
var MAX_ROWS_PER_SHEET = 2e3;
|
|
21507
|
+
function csvField(value) {
|
|
21508
|
+
return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
21509
|
+
}
|
|
21510
|
+
function uniqueHeaders(raw, columnCount) {
|
|
21511
|
+
const bases = [];
|
|
21512
|
+
for (let col = 1; col <= columnCount; col++) bases.push(raw[col]?.trim() || `Column${col}`);
|
|
21513
|
+
const taken = new Set(bases);
|
|
21514
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
21515
|
+
return bases.map((base) => {
|
|
21516
|
+
if (!emitted.has(base)) {
|
|
21517
|
+
emitted.add(base);
|
|
21518
|
+
return base;
|
|
21519
|
+
}
|
|
21520
|
+
let n = 2;
|
|
21521
|
+
while (taken.has(`${base}_${n}`)) n++;
|
|
21522
|
+
const name = `${base}_${n}`;
|
|
21523
|
+
taken.add(name);
|
|
21524
|
+
emitted.add(name);
|
|
21525
|
+
return name;
|
|
21526
|
+
});
|
|
21527
|
+
}
|
|
21462
21528
|
var ExcelProcessor = class {
|
|
21463
|
-
constructor() {
|
|
21529
|
+
constructor(options = {}) {
|
|
21464
21530
|
this.name = "excel";
|
|
21465
21531
|
this.supportedMimeTypes = [
|
|
21466
21532
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
21467
21533
|
// .xlsx
|
|
21468
21534
|
];
|
|
21469
21535
|
this.supportedExtensions = [".xlsx"];
|
|
21536
|
+
this.maxRowsPerSheet = options.maxRowsPerSheet ?? MAX_ROWS_PER_SHEET;
|
|
21470
21537
|
}
|
|
21471
21538
|
async loadExcelJS() {
|
|
21472
21539
|
const mod = await import("exceljs");
|
|
@@ -21481,31 +21548,51 @@ var ExcelProcessor = class {
|
|
|
21481
21548
|
if (workbook.worksheets.length === 0) {
|
|
21482
21549
|
return null;
|
|
21483
21550
|
}
|
|
21484
|
-
|
|
21551
|
+
let truncated = false;
|
|
21552
|
+
const sections = [];
|
|
21485
21553
|
for (const worksheet of workbook.worksheets) {
|
|
21486
|
-
const
|
|
21487
|
-
|
|
21488
|
-
|
|
21489
|
-
|
|
21490
|
-
|
|
21554
|
+
const rawHeaders = [];
|
|
21555
|
+
let columnCount = 0;
|
|
21556
|
+
worksheet.getRow(1).eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
21557
|
+
rawHeaders[colNumber] = cell.text;
|
|
21558
|
+
columnCount = Math.max(columnCount, colNumber);
|
|
21491
21559
|
});
|
|
21560
|
+
const rows = [];
|
|
21561
|
+
let totalRows = 0;
|
|
21492
21562
|
worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
|
|
21493
21563
|
if (rowNumber === 1) return;
|
|
21494
|
-
|
|
21564
|
+
totalRows++;
|
|
21565
|
+
if (rows.length >= this.maxRowsPerSheet) return;
|
|
21566
|
+
const values = [];
|
|
21495
21567
|
row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
21496
|
-
|
|
21497
|
-
|
|
21568
|
+
values[colNumber - 1] = String(this.resolveCellValue(cell.value));
|
|
21569
|
+
columnCount = Math.max(columnCount, colNumber);
|
|
21498
21570
|
});
|
|
21499
|
-
rows.push(
|
|
21571
|
+
rows.push(values);
|
|
21500
21572
|
});
|
|
21501
|
-
|
|
21573
|
+
const headers = uniqueHeaders(rawHeaders, columnCount);
|
|
21574
|
+
const lines = [headers, ...rows].map(
|
|
21575
|
+
(values) => headers.map((_, i) => csvField(values[i] ?? "")).join(",")
|
|
21576
|
+
);
|
|
21577
|
+
let section = `## Sheet: ${worksheet.name}
|
|
21578
|
+
|
|
21579
|
+
\`\`\`csv
|
|
21580
|
+
${lines.join("\n")}
|
|
21581
|
+
\`\`\``;
|
|
21582
|
+
if (totalRows > rows.length) {
|
|
21583
|
+
truncated = true;
|
|
21584
|
+
section += `
|
|
21585
|
+
[truncated: showing the first ${rows.length} of ${totalRows} data rows of sheet "${worksheet.name}"]`;
|
|
21586
|
+
}
|
|
21587
|
+
sections.push(section);
|
|
21502
21588
|
}
|
|
21503
21589
|
return {
|
|
21504
|
-
extractedText:
|
|
21505
|
-
format: "
|
|
21590
|
+
extractedText: sections.join("\n\n"),
|
|
21591
|
+
format: "markdown",
|
|
21506
21592
|
metadata: {
|
|
21507
21593
|
sheetCount: workbook.worksheets.length,
|
|
21508
|
-
sheetNames: workbook.worksheets.map((ws) => ws.name)
|
|
21594
|
+
sheetNames: workbook.worksheets.map((ws) => ws.name),
|
|
21595
|
+
truncated
|
|
21509
21596
|
}
|
|
21510
21597
|
};
|
|
21511
21598
|
} catch (error) {
|
|
@@ -21534,6 +21621,9 @@ init_logger();
|
|
|
21534
21621
|
// src/lib/pdf.ts
|
|
21535
21622
|
var pdfjsModule = null;
|
|
21536
21623
|
var workerConfigured = false;
|
|
21624
|
+
var RENDER_SCALE = 1.5;
|
|
21625
|
+
var MAX_RENDER_SIDE_PX = 1600;
|
|
21626
|
+
var JPEG_QUALITY = 0.8;
|
|
21537
21627
|
async function getPdfjs() {
|
|
21538
21628
|
if (!pdfjsModule) {
|
|
21539
21629
|
pdfjsModule = await import("pdfjs-dist");
|
|
@@ -21544,46 +21634,134 @@ async function getPdfjs() {
|
|
|
21544
21634
|
}
|
|
21545
21635
|
return pdfjsModule;
|
|
21546
21636
|
}
|
|
21547
|
-
async function
|
|
21637
|
+
async function withPdfDocument(pdfDataUrl, fn) {
|
|
21548
21638
|
const pdfjs = await getPdfjs();
|
|
21549
21639
|
const loadingTask = pdfjs.getDocument(pdfDataUrl);
|
|
21550
|
-
|
|
21551
|
-
|
|
21552
|
-
|
|
21553
|
-
|
|
21554
|
-
|
|
21555
|
-
|
|
21556
|
-
|
|
21557
|
-
|
|
21558
|
-
|
|
21640
|
+
let pdf;
|
|
21641
|
+
try {
|
|
21642
|
+
pdf = await loadingTask.promise;
|
|
21643
|
+
} catch (error) {
|
|
21644
|
+
await loadingTask.destroy().catch(() => void 0);
|
|
21645
|
+
throw error;
|
|
21646
|
+
}
|
|
21647
|
+
try {
|
|
21648
|
+
return await fn(pdf);
|
|
21649
|
+
} finally {
|
|
21650
|
+
await pdf.destroy().catch(() => void 0);
|
|
21559
21651
|
}
|
|
21560
|
-
return textParts.join("\n\n");
|
|
21561
21652
|
}
|
|
21562
|
-
|
|
21563
|
-
|
|
21564
|
-
const
|
|
21565
|
-
|
|
21566
|
-
|
|
21567
|
-
|
|
21568
|
-
|
|
21569
|
-
|
|
21570
|
-
|
|
21571
|
-
|
|
21572
|
-
|
|
21573
|
-
|
|
21574
|
-
|
|
21575
|
-
|
|
21576
|
-
|
|
21577
|
-
|
|
21578
|
-
|
|
21579
|
-
|
|
21580
|
-
|
|
21581
|
-
|
|
21582
|
-
|
|
21653
|
+
function joinPdfTextItems(items) {
|
|
21654
|
+
let text12 = "";
|
|
21655
|
+
for (const item of items) {
|
|
21656
|
+
if (!("str" in item)) continue;
|
|
21657
|
+
const { str, hasEOL } = item;
|
|
21658
|
+
text12 += str + (hasEOL ? "\n" : " ");
|
|
21659
|
+
}
|
|
21660
|
+
return text12.replace(/[ \t]+\n/g, "\n").trim();
|
|
21661
|
+
}
|
|
21662
|
+
function computeRenderScale(width, height) {
|
|
21663
|
+
const longest = Math.max(width, height);
|
|
21664
|
+
if (!(longest > 0)) return RENDER_SCALE;
|
|
21665
|
+
return Math.min(RENDER_SCALE, MAX_RENDER_SIDE_PX / longest);
|
|
21666
|
+
}
|
|
21667
|
+
async function extractPdfPageTexts(pdfDataUrl) {
|
|
21668
|
+
return withPdfDocument(pdfDataUrl, async (pdf) => {
|
|
21669
|
+
const pageTexts = [];
|
|
21670
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
21671
|
+
const page = await pdf.getPage(i);
|
|
21672
|
+
const textContent = await page.getTextContent();
|
|
21673
|
+
pageTexts.push(joinPdfTextItems(textContent.items));
|
|
21674
|
+
}
|
|
21675
|
+
return pageTexts;
|
|
21676
|
+
});
|
|
21677
|
+
}
|
|
21678
|
+
async function extractTextFromPdf(pdfDataUrl) {
|
|
21679
|
+
const pageTexts = await extractPdfPageTexts(pdfDataUrl);
|
|
21680
|
+
return pageTexts.filter((text12) => text12.trim()).join("\n\n");
|
|
21681
|
+
}
|
|
21682
|
+
async function renderPdfPages(pdfDataUrl, maxPages, pageNumbers) {
|
|
21683
|
+
return withPdfDocument(pdfDataUrl, async (pdf) => {
|
|
21684
|
+
const requested = pageNumbers?.filter((n) => n >= 1 && n <= pdf.numPages) ?? Array.from(
|
|
21685
|
+
{ length: maxPages !== void 0 ? Math.min(maxPages, pdf.numPages) : pdf.numPages },
|
|
21686
|
+
(_, i) => i + 1
|
|
21687
|
+
);
|
|
21688
|
+
const pages = [];
|
|
21689
|
+
for (const pageNumber of requested) {
|
|
21690
|
+
const page = await pdf.getPage(pageNumber);
|
|
21691
|
+
const base = page.getViewport({ scale: 1 });
|
|
21692
|
+
const viewport = page.getViewport({ scale: computeRenderScale(base.width, base.height) });
|
|
21693
|
+
const canvas = document.createElement("canvas");
|
|
21694
|
+
const context = canvas.getContext("2d");
|
|
21695
|
+
if (!context) continue;
|
|
21696
|
+
canvas.height = Math.floor(viewport.height);
|
|
21697
|
+
canvas.width = Math.floor(viewport.width);
|
|
21698
|
+
context.fillStyle = "#ffffff";
|
|
21699
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
21700
|
+
await page.render({
|
|
21701
|
+
canvasContext: context,
|
|
21702
|
+
viewport,
|
|
21703
|
+
background: "#ffffff"
|
|
21704
|
+
}).promise;
|
|
21705
|
+
pages.push({ pageNumber, dataUrl: canvas.toDataURL("image/jpeg", JPEG_QUALITY) });
|
|
21706
|
+
canvas.width = 0;
|
|
21707
|
+
canvas.height = 0;
|
|
21708
|
+
}
|
|
21709
|
+
return { pages, pageCount: pdf.numPages };
|
|
21710
|
+
});
|
|
21711
|
+
}
|
|
21712
|
+
async function convertPdfToImages(pdfDataUrl, maxPages, pageNumbers) {
|
|
21713
|
+
const { pages } = await renderPdfPages(pdfDataUrl, maxPages, pageNumbers);
|
|
21714
|
+
return pages.map((p) => p.dataUrl);
|
|
21583
21715
|
}
|
|
21584
21716
|
|
|
21585
21717
|
// src/lib/processors/PdfProcessor.ts
|
|
21586
21718
|
var MAX_IMAGE_PAGES = 20;
|
|
21719
|
+
var MIN_PAGE_TEXT_CHARS = 20;
|
|
21720
|
+
function selectPdfImagePages(pageTexts, maxImagePages = MAX_IMAGE_PAGES) {
|
|
21721
|
+
const needImage = [];
|
|
21722
|
+
pageTexts.forEach((text12, index) => {
|
|
21723
|
+
if (text12.trim().length < MIN_PAGE_TEXT_CHARS) needImage.push(index + 1);
|
|
21724
|
+
});
|
|
21725
|
+
const limit = Math.max(0, maxImagePages);
|
|
21726
|
+
return { rendered: needImage.slice(0, limit), omitted: needImage.slice(limit) };
|
|
21727
|
+
}
|
|
21728
|
+
function formatPageList(pages) {
|
|
21729
|
+
const ranges = [];
|
|
21730
|
+
for (let i = 0; i < pages.length; i++) {
|
|
21731
|
+
const start = pages[i];
|
|
21732
|
+
let end = start;
|
|
21733
|
+
while (i + 1 < pages.length && pages[i + 1] === end + 1) end = pages[++i];
|
|
21734
|
+
ranges.push(start === end ? `${start}` : `${start}-${end}`);
|
|
21735
|
+
}
|
|
21736
|
+
return `${pages.length === 1 ? "page" : "pages"} ${ranges.join(", ")}`;
|
|
21737
|
+
}
|
|
21738
|
+
function buildPdfImageNote(fileName, includedPages, omittedPages, pageCount) {
|
|
21739
|
+
const imageOnlyCount = includedPages.length + omittedPages.length;
|
|
21740
|
+
const allPages = [...includedPages, ...omittedPages].sort((a, b) => a - b);
|
|
21741
|
+
const subject = pageCount === void 0 || imageOnlyCount >= pageCount ? `${fileName}: scanned/image-based PDF (no extractable text)` : `${fileName}: ${imageOnlyCount} of ${pageCount} pages have no extractable text (${formatPageList(allPages)})`;
|
|
21742
|
+
if (includedPages.length === 0) {
|
|
21743
|
+
return `[${subject} \u2014 ${formatPageList(omittedPages)} could not be included as images because of the image limit, so their content is not available]`;
|
|
21744
|
+
}
|
|
21745
|
+
const included = `${formatPageList(includedPages)} rendered as images and included in this message for visual analysis`;
|
|
21746
|
+
if (omittedPages.length === 0) return `[${subject} \u2014 ${included}]`;
|
|
21747
|
+
return `[${subject} \u2014 ${included}; ${formatPageList(omittedPages)} not included because of the image limit, so their content is not available]`;
|
|
21748
|
+
}
|
|
21749
|
+
function rewriteImageNote(result, fileName, keptImages) {
|
|
21750
|
+
const images = result.imageDataUrls ?? [];
|
|
21751
|
+
const note = result.metadata?.imageNote;
|
|
21752
|
+
if (!note || keptImages >= images.length) return result.extractedText;
|
|
21753
|
+
const imagePages = result.metadata?.imagePages ?? [];
|
|
21754
|
+
const omittedPages = result.metadata?.omittedImagePages ?? [];
|
|
21755
|
+
const pageCount = result.metadata?.pageCount;
|
|
21756
|
+
const kept = Math.max(0, keptImages);
|
|
21757
|
+
const newNote = buildPdfImageNote(
|
|
21758
|
+
fileName,
|
|
21759
|
+
imagePages.slice(0, kept),
|
|
21760
|
+
[...imagePages.slice(kept), ...omittedPages].sort((a, b) => a - b),
|
|
21761
|
+
pageCount
|
|
21762
|
+
);
|
|
21763
|
+
return result.extractedText.replace(note, () => newNote);
|
|
21764
|
+
}
|
|
21587
21765
|
var PdfProcessor = class {
|
|
21588
21766
|
constructor() {
|
|
21589
21767
|
this.name = "pdf";
|
|
@@ -21592,43 +21770,65 @@ var PdfProcessor = class {
|
|
|
21592
21770
|
}
|
|
21593
21771
|
async process(file) {
|
|
21594
21772
|
const logger = getLogger();
|
|
21773
|
+
let pageTexts = null;
|
|
21595
21774
|
try {
|
|
21596
|
-
|
|
21597
|
-
if (text12 && text12.trim().length > 0) {
|
|
21598
|
-
return {
|
|
21599
|
-
extractedText: text12,
|
|
21600
|
-
format: "plain"
|
|
21601
|
-
};
|
|
21602
|
-
}
|
|
21603
|
-
logger.info(
|
|
21604
|
-
`[PdfProcessor] Text extraction returned empty for "${file.name}" \u2014 falling back to image conversion`
|
|
21605
|
-
);
|
|
21775
|
+
pageTexts = await extractPdfPageTexts(file.dataUrl);
|
|
21606
21776
|
} catch (textError) {
|
|
21607
21777
|
logger.warn(
|
|
21608
|
-
|
|
21778
|
+
"[PdfProcessor] Text extraction failed \u2014 falling back to image conversion:",
|
|
21609
21779
|
textError
|
|
21610
21780
|
);
|
|
21611
21781
|
}
|
|
21782
|
+
const text12 = (pageTexts ?? []).filter((t) => t.trim()).join("\n\n");
|
|
21783
|
+
const { rendered, omitted } = pageTexts ? selectPdfImagePages(pageTexts) : { rendered: [], omitted: [] };
|
|
21784
|
+
if (pageTexts && rendered.length === 0 && omitted.length === 0) {
|
|
21785
|
+
return text12.trim() ? { extractedText: text12, format: "plain" } : null;
|
|
21786
|
+
}
|
|
21787
|
+
let renderedPages;
|
|
21788
|
+
let pageCount = pageTexts?.length;
|
|
21612
21789
|
try {
|
|
21613
|
-
|
|
21614
|
-
|
|
21615
|
-
|
|
21616
|
-
|
|
21790
|
+
if (pageTexts) {
|
|
21791
|
+
renderedPages = (await renderPdfPages(file.dataUrl, void 0, rendered)).pages;
|
|
21792
|
+
} else {
|
|
21793
|
+
const result = await renderPdfPages(file.dataUrl, MAX_IMAGE_PAGES);
|
|
21794
|
+
renderedPages = result.pages;
|
|
21795
|
+
pageCount = result.pageCount;
|
|
21617
21796
|
}
|
|
21618
|
-
logger.info(`[PdfProcessor] Converted ${images.length} page(s) of "${file.name}" to images`);
|
|
21619
|
-
return {
|
|
21620
|
-
// Contextual note for the LLM — keep it self-contained since the
|
|
21621
|
-
// images are only injected in the current request and not persisted
|
|
21622
|
-
// for follow-up turns.
|
|
21623
|
-
extractedText: `[${file.name}: scanned/image-based PDF \u2014 ${images.length} page(s) rendered as images and included in this message for visual analysis]`,
|
|
21624
|
-
format: "plain",
|
|
21625
|
-
imageDataUrls: images,
|
|
21626
|
-
metadata: { pageCount: images.length }
|
|
21627
|
-
};
|
|
21628
21797
|
} catch (imageError) {
|
|
21629
|
-
|
|
21630
|
-
|
|
21798
|
+
if (!text12.trim()) {
|
|
21799
|
+
logger.error("[PdfProcessor] Image conversion also failed:", imageError);
|
|
21800
|
+
throw imageError;
|
|
21801
|
+
}
|
|
21802
|
+
logger.warn("[PdfProcessor] Image conversion failed \u2014 keeping text only:", imageError);
|
|
21803
|
+
renderedPages = [];
|
|
21804
|
+
}
|
|
21805
|
+
const images = renderedPages.map((p) => p.dataUrl);
|
|
21806
|
+
const imagePages = renderedPages.map((p) => p.pageNumber);
|
|
21807
|
+
const included = new Set(imagePages);
|
|
21808
|
+
const needImage = pageTexts ? [...rendered, ...omitted] : Array.from({ length: pageCount ?? 0 }, (_, i) => i + 1);
|
|
21809
|
+
const omittedPages = needImage.filter((n) => !included.has(n)).sort((a, b) => a - b);
|
|
21810
|
+
if (images.length === 0 && !text12.trim()) {
|
|
21811
|
+
logger.warn("[PdfProcessor] Image conversion also returned empty");
|
|
21812
|
+
return null;
|
|
21631
21813
|
}
|
|
21814
|
+
logger.info(
|
|
21815
|
+
`[PdfProcessor] Rendered ${images.length} page(s) as images, ${omittedPages.length} image-only page(s) omitted`
|
|
21816
|
+
);
|
|
21817
|
+
const imageNote = buildPdfImageNote(file.name, imagePages, omittedPages, pageCount);
|
|
21818
|
+
return {
|
|
21819
|
+
extractedText: text12.trim() ? `${imageNote}
|
|
21820
|
+
|
|
21821
|
+
${text12}` : imageNote,
|
|
21822
|
+
format: "plain",
|
|
21823
|
+
imageDataUrls: images.length > 0 ? images : void 0,
|
|
21824
|
+
metadata: {
|
|
21825
|
+
pageCount,
|
|
21826
|
+
imageNote,
|
|
21827
|
+
imagePages,
|
|
21828
|
+
omittedImagePages: omittedPages,
|
|
21829
|
+
truncated: omittedPages.length > 0
|
|
21830
|
+
}
|
|
21831
|
+
};
|
|
21632
21832
|
}
|
|
21633
21833
|
};
|
|
21634
21834
|
|
|
@@ -21670,6 +21870,21 @@ var EXT = {
|
|
|
21670
21870
|
CFG: ".cfg",
|
|
21671
21871
|
CONF: ".conf"
|
|
21672
21872
|
};
|
|
21873
|
+
function decodeText(bytes) {
|
|
21874
|
+
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
21875
|
+
return new TextDecoder("utf-16le", { fatal: false }).decode(bytes.subarray(2));
|
|
21876
|
+
}
|
|
21877
|
+
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
21878
|
+
const swapped = new Uint8Array(bytes.length - 2);
|
|
21879
|
+
for (let i = 2; i + 1 < bytes.length; i += 2) {
|
|
21880
|
+
swapped[i - 2] = bytes[i + 1];
|
|
21881
|
+
swapped[i - 1] = bytes[i];
|
|
21882
|
+
}
|
|
21883
|
+
return new TextDecoder("utf-16le", { fatal: false }).decode(swapped);
|
|
21884
|
+
}
|
|
21885
|
+
const hasUtf8Bom = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191;
|
|
21886
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(hasUtf8Bom ? bytes.subarray(3) : bytes);
|
|
21887
|
+
}
|
|
21673
21888
|
var TextProcessor = class {
|
|
21674
21889
|
constructor() {
|
|
21675
21890
|
this.name = "text";
|
|
@@ -21679,7 +21894,7 @@ var TextProcessor = class {
|
|
|
21679
21894
|
async process(file) {
|
|
21680
21895
|
try {
|
|
21681
21896
|
const arrayBuffer = await dataUrlToArrayBuffer(file.dataUrl);
|
|
21682
|
-
const text12 = new
|
|
21897
|
+
const text12 = decodeText(new Uint8Array(arrayBuffer));
|
|
21683
21898
|
if (!text12.trim()) {
|
|
21684
21899
|
return null;
|
|
21685
21900
|
}
|
|
@@ -21702,7 +21917,7 @@ var TextProcessor = class {
|
|
|
21702
21917
|
* rather than prose).
|
|
21703
21918
|
*/
|
|
21704
21919
|
resolveFormat(file) {
|
|
21705
|
-
const type = file.type?.toLowerCase() ?? "";
|
|
21920
|
+
const type = file.type?.split(";")[0].trim().toLowerCase() ?? "";
|
|
21706
21921
|
const name = file.name?.toLowerCase() ?? "";
|
|
21707
21922
|
if (type === MIME.MARKDOWN || type === MIME.X_MARKDOWN || name.endsWith(EXT.MD) || name.endsWith(EXT.MARKDOWN)) {
|
|
21708
21923
|
return "markdown";
|
|
@@ -21760,6 +21975,12 @@ var WordProcessor = class {
|
|
|
21760
21975
|
// src/lib/processors/ZipProcessor.ts
|
|
21761
21976
|
var import_jszip = __toESM(require("jszip"));
|
|
21762
21977
|
init_logger();
|
|
21978
|
+
var MAX_ZIP_ENTRIES = 1e3;
|
|
21979
|
+
var MAX_TOTAL_UNCOMPRESSED_BYTES = 50 * 1024 * 1024;
|
|
21980
|
+
function declaredUncompressedSize(zipObject) {
|
|
21981
|
+
const size = zipObject._data?.uncompressedSize;
|
|
21982
|
+
return typeof size === "number" ? size : void 0;
|
|
21983
|
+
}
|
|
21763
21984
|
var ZipProcessor = class {
|
|
21764
21985
|
constructor(options = {}) {
|
|
21765
21986
|
this.name = "zip";
|
|
@@ -21794,14 +22015,35 @@ var ZipProcessor = class {
|
|
|
21794
22015
|
zipObject: zipEntry
|
|
21795
22016
|
});
|
|
21796
22017
|
});
|
|
21797
|
-
const
|
|
21798
|
-
|
|
22018
|
+
const visibleEntries = this.includeHidden ? entries : entries.filter((entry) => !this.isHidden(entry.path));
|
|
22019
|
+
visibleEntries.sort((a, b) => {
|
|
21799
22020
|
if (a.isDirectory !== b.isDirectory) {
|
|
21800
22021
|
return a.isDirectory ? -1 : 1;
|
|
21801
22022
|
}
|
|
21802
22023
|
return a.path.localeCompare(b.path);
|
|
21803
22024
|
});
|
|
21804
|
-
|
|
22025
|
+
const visibleDirs = visibleEntries.filter((e) => e.isDirectory);
|
|
22026
|
+
const visibleFiles = visibleEntries.filter((e) => !e.isDirectory);
|
|
22027
|
+
const listedDirs = visibleDirs.slice(0, MAX_ZIP_ENTRIES);
|
|
22028
|
+
const listedFiles = visibleFiles.slice(0, MAX_ZIP_ENTRIES);
|
|
22029
|
+
const filteredEntries = [...listedDirs, ...listedFiles];
|
|
22030
|
+
const notes = [];
|
|
22031
|
+
if (visibleFiles.length > listedFiles.length) {
|
|
22032
|
+
notes.push(
|
|
22033
|
+
`[truncated: the archive has ${visibleFiles.length} files; only the first ${listedFiles.length} were listed and considered for extraction, the other ${visibleFiles.length - listedFiles.length} files were dropped]`
|
|
22034
|
+
);
|
|
22035
|
+
}
|
|
22036
|
+
if (visibleDirs.length > listedDirs.length) {
|
|
22037
|
+
notes.push(
|
|
22038
|
+
`[truncated: listing the first ${listedDirs.length} of ${visibleDirs.length} directories]`
|
|
22039
|
+
);
|
|
22040
|
+
}
|
|
22041
|
+
let totalBytes = 0;
|
|
22042
|
+
let byteBudgetHit = false;
|
|
22043
|
+
let truncated = notes.length > 0;
|
|
22044
|
+
const logger = getLogger();
|
|
22045
|
+
for (let index = 0; index < filteredEntries.length; index++) {
|
|
22046
|
+
const entry = filteredEntries[index];
|
|
21805
22047
|
if (entry.isDirectory) continue;
|
|
21806
22048
|
const fileName = entry.path.split("/").pop() || entry.path;
|
|
21807
22049
|
const extension = this.getFileExtension(fileName);
|
|
@@ -21815,9 +22057,20 @@ var ZipProcessor = class {
|
|
|
21815
22057
|
};
|
|
21816
22058
|
const processor = this.registry?.findProcessor(fileMetadata);
|
|
21817
22059
|
if (!processor || processor.name === "zip") continue;
|
|
22060
|
+
const declared = declaredUncompressedSize(entry.zipObject);
|
|
22061
|
+
if (declared !== void 0 && declared > this.maxFileSize) continue;
|
|
22062
|
+
if (totalBytes + (declared ?? 0) > MAX_TOTAL_UNCOMPRESSED_BYTES) {
|
|
22063
|
+
byteBudgetHit = true;
|
|
22064
|
+
break;
|
|
22065
|
+
}
|
|
21818
22066
|
try {
|
|
21819
22067
|
const data = await entry.zipObject.async("uint8array");
|
|
22068
|
+
totalBytes += data.length;
|
|
21820
22069
|
if (data.length > this.maxFileSize) continue;
|
|
22070
|
+
if (totalBytes > MAX_TOTAL_UNCOMPRESSED_BYTES) {
|
|
22071
|
+
byteBudgetHit = true;
|
|
22072
|
+
break;
|
|
22073
|
+
}
|
|
21821
22074
|
const base64 = uint8ArrayToBase64(data);
|
|
21822
22075
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
21823
22076
|
const fileWithData = {
|
|
@@ -21827,23 +22080,36 @@ var ZipProcessor = class {
|
|
|
21827
22080
|
};
|
|
21828
22081
|
const result = await processor.process(fileWithData);
|
|
21829
22082
|
if (result && result.extractedText.trim()) {
|
|
22083
|
+
if (result.metadata?.truncated || result.imageDataUrls?.length) truncated = true;
|
|
21830
22084
|
processedContents.push({
|
|
21831
22085
|
path: entry.path,
|
|
21832
22086
|
processorName: processor.name,
|
|
21833
|
-
|
|
22087
|
+
// Page images of nested files are not forwarded, so their note must not claim them.
|
|
22088
|
+
result: { ...result, extractedText: rewriteImageNote(result, fileName, 0) }
|
|
21834
22089
|
});
|
|
21835
22090
|
}
|
|
21836
|
-
} catch {
|
|
22091
|
+
} catch (error) {
|
|
22092
|
+
logger.warn(
|
|
22093
|
+
`[ZipProcessor] Failed to process archive entry #${index + 1} (${processor.name}, ${mimeType})`,
|
|
22094
|
+
error
|
|
22095
|
+
);
|
|
21837
22096
|
}
|
|
21838
22097
|
}
|
|
21839
|
-
|
|
22098
|
+
if (byteBudgetHit) {
|
|
22099
|
+
truncated = true;
|
|
22100
|
+
notes.push(
|
|
22101
|
+
`[truncated: stopped reading the archive after ${Math.round(MAX_TOTAL_UNCOMPRESSED_BYTES / 1024 / 1024)} MB of decompressed content; later files were not extracted]`
|
|
22102
|
+
);
|
|
22103
|
+
}
|
|
22104
|
+
const output = [this.formatOutput(filteredEntries, processedContents), ...notes].join("\n");
|
|
21840
22105
|
return {
|
|
21841
22106
|
extractedText: output,
|
|
21842
22107
|
format: "markdown",
|
|
21843
22108
|
metadata: {
|
|
21844
22109
|
fileCount: filteredEntries.filter((e) => !e.isDirectory).length,
|
|
21845
22110
|
directoryCount: filteredEntries.filter((e) => e.isDirectory).length,
|
|
21846
|
-
processedFiles: processedContents.length
|
|
22111
|
+
processedFiles: processedContents.length,
|
|
22112
|
+
truncated
|
|
21847
22113
|
}
|
|
21848
22114
|
};
|
|
21849
22115
|
} catch (error) {
|
|
@@ -21945,9 +22211,10 @@ var ProcessorRegistry = class {
|
|
|
21945
22211
|
* @returns The matching processor, or null if none found
|
|
21946
22212
|
*/
|
|
21947
22213
|
findProcessor(file) {
|
|
21948
|
-
|
|
22214
|
+
const mimeType = file.type?.split(";")[0].trim().toLowerCase();
|
|
22215
|
+
if (mimeType) {
|
|
21949
22216
|
for (const processor of this.processors.values()) {
|
|
21950
|
-
if (processor.supportedMimeTypes.includes(
|
|
22217
|
+
if (processor.supportedMimeTypes.includes(mimeType)) {
|
|
21951
22218
|
return processor;
|
|
21952
22219
|
}
|
|
21953
22220
|
}
|
|
@@ -22014,9 +22281,55 @@ var ProcessorRegistry = class {
|
|
|
22014
22281
|
}
|
|
22015
22282
|
};
|
|
22016
22283
|
|
|
22284
|
+
// src/lib/processors/fileStatusNotes.ts
|
|
22285
|
+
var DEFAULT_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
22286
|
+
function formatMegabytes(bytes) {
|
|
22287
|
+
const mb = bytes / (1024 * 1024);
|
|
22288
|
+
return `${Number.isInteger(mb) ? mb : mb.toFixed(1)} MB`;
|
|
22289
|
+
}
|
|
22290
|
+
function formatFileProcessingNotes(statuses, options = {}) {
|
|
22291
|
+
const maxFileSizeBytes = options.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE_BYTES;
|
|
22292
|
+
const why = {
|
|
22293
|
+
too_large: `the file is larger than ${formatMegabytes(maxFileSizeBytes)}`,
|
|
22294
|
+
unsupported_type: "its file type is not supported for reading",
|
|
22295
|
+
no_data: "its contents were not available to the app",
|
|
22296
|
+
empty: "no text or images could be extracted from it (it may be blank)",
|
|
22297
|
+
timeout: "reading it took too long and was stopped",
|
|
22298
|
+
error: "its text could not be extracted (it may be password-protected or damaged)"
|
|
22299
|
+
};
|
|
22300
|
+
const lines = statuses.filter((s) => s.status === "skipped" || s.status === "failed").map((s) => `[${s.fileName} could not be read: ${why[s.reason ?? "error"]}]`);
|
|
22301
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
22302
|
+
}
|
|
22303
|
+
|
|
22017
22304
|
// src/lib/processors/preprocessor.ts
|
|
22018
22305
|
init_logger();
|
|
22019
22306
|
var MAX_TOTAL_IMAGES = 20;
|
|
22307
|
+
var DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE = 1e5;
|
|
22308
|
+
var DEFAULT_MAX_EXTRACTED_CHARS_TOTAL = 2e5;
|
|
22309
|
+
var ProcessingTimeoutError = class extends Error {
|
|
22310
|
+
constructor(timeoutMs) {
|
|
22311
|
+
super(`Timed out processing file after ${timeoutMs}ms`);
|
|
22312
|
+
this.name = "ProcessingTimeoutError";
|
|
22313
|
+
}
|
|
22314
|
+
};
|
|
22315
|
+
function truncationMarker2(kept, total, fileName) {
|
|
22316
|
+
return `
|
|
22317
|
+
[truncated: showing the first ${kept} of ${total} characters of ${fileName}]`;
|
|
22318
|
+
}
|
|
22319
|
+
function truncateText(text12, limit, fileName) {
|
|
22320
|
+
if (text12.length <= limit) return { text: text12, truncated: false };
|
|
22321
|
+
let end = Math.max(0, limit);
|
|
22322
|
+
const code = text12.charCodeAt(end - 1);
|
|
22323
|
+
if (end > 0 && code >= 55296 && code <= 56319) end--;
|
|
22324
|
+
return {
|
|
22325
|
+
text: `${text12.slice(0, end)}${truncationMarker2(end, text12.length, fileName)}`,
|
|
22326
|
+
truncated: true
|
|
22327
|
+
};
|
|
22328
|
+
}
|
|
22329
|
+
var FILE_SEPARATOR = "\n\n---\n\n";
|
|
22330
|
+
function isImageFile(file) {
|
|
22331
|
+
return (file.type ?? "").trim().toLowerCase().startsWith("image/");
|
|
22332
|
+
}
|
|
22020
22333
|
function createDefaultRegistry() {
|
|
22021
22334
|
const registry2 = new ProcessorRegistry();
|
|
22022
22335
|
registry2.register(new PdfProcessor());
|
|
@@ -22066,10 +22379,11 @@ async function preprocessFiles(files, options = {}) {
|
|
|
22066
22379
|
processors = void 0,
|
|
22067
22380
|
// undefined means use defaults
|
|
22068
22381
|
keepOriginalFiles = true,
|
|
22069
|
-
maxFileSizeBytes =
|
|
22070
|
-
// 10MB
|
|
22382
|
+
maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES,
|
|
22071
22383
|
timeoutMs = 3e4,
|
|
22072
22384
|
// 30s per file
|
|
22385
|
+
maxExtractedCharsPerFile = DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE,
|
|
22386
|
+
maxExtractedCharsTotal = DEFAULT_MAX_EXTRACTED_CHARS_TOTAL,
|
|
22073
22387
|
onProgress,
|
|
22074
22388
|
onError
|
|
22075
22389
|
} = options;
|
|
@@ -22079,23 +22393,23 @@ async function preprocessFiles(files, options = {}) {
|
|
|
22079
22393
|
extractedContent: null,
|
|
22080
22394
|
originalFiles: files,
|
|
22081
22395
|
preprocessedFileIds: [],
|
|
22396
|
+
fileStatuses: [],
|
|
22082
22397
|
metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
|
|
22083
22398
|
};
|
|
22084
22399
|
}
|
|
22085
|
-
if (processors
|
|
22086
|
-
|
|
22087
|
-
|
|
22088
|
-
|
|
22089
|
-
|
|
22090
|
-
|
|
22091
|
-
};
|
|
22092
|
-
}
|
|
22093
|
-
if (processors === null) {
|
|
22400
|
+
if (processors === null || processors?.length === 0) {
|
|
22401
|
+
const fileStatuses2 = files.filter((file) => !isImageFile(file)).map((file) => ({
|
|
22402
|
+
fileId: file.id,
|
|
22403
|
+
fileName: file.name,
|
|
22404
|
+
status: "skipped",
|
|
22405
|
+
reason: "unsupported_type"
|
|
22406
|
+
}));
|
|
22094
22407
|
return {
|
|
22095
22408
|
extractedContent: null,
|
|
22096
22409
|
originalFiles: files,
|
|
22097
22410
|
preprocessedFileIds: [],
|
|
22098
|
-
|
|
22411
|
+
fileStatuses: fileStatuses2,
|
|
22412
|
+
metadata: { processedCount: 0, skippedCount: fileStatuses2.length, errorCount: 0 }
|
|
22099
22413
|
};
|
|
22100
22414
|
}
|
|
22101
22415
|
let registry2;
|
|
@@ -22113,28 +22427,38 @@ async function preprocessFiles(files, options = {}) {
|
|
|
22113
22427
|
const extractedTexts = [];
|
|
22114
22428
|
const allImageUrls = [];
|
|
22115
22429
|
const preprocessedFileIds = [];
|
|
22430
|
+
const fileStatuses = [];
|
|
22116
22431
|
let processedCount = 0;
|
|
22117
22432
|
let skippedCount = 0;
|
|
22118
22433
|
let errorCount = 0;
|
|
22434
|
+
let totalChars = 0;
|
|
22435
|
+
const overBudgetFiles = [];
|
|
22119
22436
|
for (let i = 0; i < files.length; i++) {
|
|
22120
22437
|
const file = files[i];
|
|
22438
|
+
const label = `#${i + 1} (${file.type || "unknown type"}, ${file.size} bytes)`;
|
|
22439
|
+
const skip = (reason) => {
|
|
22440
|
+
skippedCount++;
|
|
22441
|
+
fileStatuses.push({ fileId: file.id, fileName: file.name, status: "skipped", reason });
|
|
22442
|
+
};
|
|
22121
22443
|
onProgress?.(i + 1, files.length, file.name);
|
|
22444
|
+
const processor = registry2.findProcessor(file);
|
|
22445
|
+
if (!processor && isImageFile(file)) continue;
|
|
22122
22446
|
if (file.size > maxFileSizeBytes) {
|
|
22123
22447
|
logger.info(
|
|
22124
|
-
`[preprocessFiles] Skipping
|
|
22448
|
+
`[preprocessFiles] Skipping file ${label} \u2014 exceeds ${maxFileSizeBytes} byte limit`
|
|
22125
22449
|
);
|
|
22126
|
-
|
|
22450
|
+
skip("too_large");
|
|
22127
22451
|
continue;
|
|
22128
22452
|
}
|
|
22129
|
-
const processor = registry2.findProcessor(file);
|
|
22130
22453
|
if (!processor) {
|
|
22131
|
-
|
|
22454
|
+
skip("unsupported_type");
|
|
22132
22455
|
continue;
|
|
22133
22456
|
}
|
|
22457
|
+
let timer;
|
|
22134
22458
|
try {
|
|
22135
22459
|
if (!file.url) {
|
|
22136
|
-
logger.info(`[preprocessFiles] Skipping
|
|
22137
|
-
|
|
22460
|
+
logger.info(`[preprocessFiles] Skipping file ${label} \u2014 no data URL available`);
|
|
22461
|
+
skip("no_data");
|
|
22138
22462
|
continue;
|
|
22139
22463
|
}
|
|
22140
22464
|
const fileWithData = {
|
|
@@ -22143,40 +22467,76 @@ async function preprocessFiles(files, options = {}) {
|
|
|
22143
22467
|
};
|
|
22144
22468
|
const result = await Promise.race([
|
|
22145
22469
|
processor.process(fileWithData),
|
|
22146
|
-
new Promise(
|
|
22147
|
-
|
|
22148
|
-
)
|
|
22470
|
+
new Promise((_, reject) => {
|
|
22471
|
+
timer = setTimeout(() => reject(new ProcessingTimeoutError(timeoutMs)), timeoutMs);
|
|
22472
|
+
})
|
|
22149
22473
|
]);
|
|
22150
22474
|
if (result && result.extractedText.trim()) {
|
|
22151
|
-
const
|
|
22152
|
-
|
|
22153
|
-
result.extractedText,
|
|
22154
|
-
result.format
|
|
22155
|
-
);
|
|
22156
|
-
extractedTexts.push(formattedContent);
|
|
22475
|
+
const separatorLength = extractedTexts.length > 0 ? FILE_SEPARATOR.length : 0;
|
|
22476
|
+
const remaining = maxExtractedCharsTotal - totalChars - separatorLength - formatExtractedContent(file.name, "", result.format).length;
|
|
22157
22477
|
preprocessedFileIds.push(file.id);
|
|
22158
22478
|
processedCount++;
|
|
22159
|
-
|
|
22160
|
-
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22479
|
+
const fullLength = result.extractedText.length;
|
|
22480
|
+
if (fullLength > remaining && remaining - truncationMarker2(fullLength, fullLength, file.name).length <= 0) {
|
|
22481
|
+
overBudgetFiles.push(file.name);
|
|
22482
|
+
fileStatuses.push({ fileId: file.id, fileName: file.name, status: "truncated" });
|
|
22483
|
+
continue;
|
|
22164
22484
|
}
|
|
22485
|
+
const images = result.imageDataUrls ?? [];
|
|
22486
|
+
const keptImages = Math.min(
|
|
22487
|
+
images.length,
|
|
22488
|
+
Math.max(0, MAX_TOTAL_IMAGES - allImageUrls.length)
|
|
22489
|
+
);
|
|
22490
|
+
allImageUrls.push(...images.slice(0, keptImages));
|
|
22491
|
+
const text12 = rewriteImageNote(result, file.name, keptImages);
|
|
22492
|
+
const fitsWhole = text12.length <= Math.min(maxExtractedCharsPerFile, remaining);
|
|
22493
|
+
const limit = fitsWhole ? text12.length : Math.min(
|
|
22494
|
+
maxExtractedCharsPerFile,
|
|
22495
|
+
remaining - truncationMarker2(text12.length, text12.length, file.name).length
|
|
22496
|
+
);
|
|
22497
|
+
const capped = truncateText(text12, Math.max(0, limit), file.name);
|
|
22498
|
+
const formattedContent = formatExtractedContent(file.name, capped.text, result.format);
|
|
22499
|
+
totalChars += separatorLength + formattedContent.length;
|
|
22500
|
+
extractedTexts.push(formattedContent);
|
|
22501
|
+
const truncated = capped.truncated || keptImages < images.length || result.metadata?.truncated === true;
|
|
22502
|
+
fileStatuses.push({
|
|
22503
|
+
fileId: file.id,
|
|
22504
|
+
fileName: file.name,
|
|
22505
|
+
status: truncated ? "truncated" : keptImages > 0 ? "rendered_as_images" : "extracted"
|
|
22506
|
+
});
|
|
22165
22507
|
} else {
|
|
22166
|
-
|
|
22508
|
+
skip("empty");
|
|
22167
22509
|
}
|
|
22168
22510
|
} catch (error) {
|
|
22169
22511
|
errorCount++;
|
|
22170
|
-
|
|
22512
|
+
const timedOut = error instanceof ProcessingTimeoutError;
|
|
22513
|
+
fileStatuses.push({
|
|
22514
|
+
fileId: file.id,
|
|
22515
|
+
fileName: file.name,
|
|
22516
|
+
status: "failed",
|
|
22517
|
+
reason: timedOut ? "timeout" : "error"
|
|
22518
|
+
});
|
|
22519
|
+
logger.error(
|
|
22520
|
+
`[preprocessFiles] Error processing file ${label} with ${processor.name}:`,
|
|
22521
|
+
error
|
|
22522
|
+
);
|
|
22171
22523
|
onError?.(file.name, error instanceof Error ? error : new Error(String(error)));
|
|
22524
|
+
} finally {
|
|
22525
|
+
clearTimeout(timer);
|
|
22172
22526
|
}
|
|
22173
22527
|
}
|
|
22174
|
-
|
|
22528
|
+
if (overBudgetFiles.length > 0) {
|
|
22529
|
+
extractedTexts.push(
|
|
22530
|
+
`[truncated: the attachment text limit (${maxExtractedCharsTotal} characters) was reached; the contents of ${overBudgetFiles.join(", ")} were not included]`
|
|
22531
|
+
);
|
|
22532
|
+
}
|
|
22533
|
+
const extractedContent = extractedTexts.length > 0 ? extractedTexts.join(FILE_SEPARATOR) : null;
|
|
22175
22534
|
return {
|
|
22176
22535
|
extractedContent,
|
|
22177
22536
|
imageContentUrls: allImageUrls.length > 0 ? allImageUrls : void 0,
|
|
22178
22537
|
originalFiles: keepOriginalFiles ? files : void 0,
|
|
22179
22538
|
preprocessedFileIds,
|
|
22539
|
+
fileStatuses,
|
|
22180
22540
|
metadata: { processedCount, skippedCount, errorCount }
|
|
22181
22541
|
};
|
|
22182
22542
|
}
|
|
@@ -23533,7 +23893,7 @@ async function storedToLlmapiMessage(stored, encryptionKey, resolveMediaByIds) {
|
|
|
23533
23893
|
} else if (encryptionKey && isOPFSSupported()) {
|
|
23534
23894
|
try {
|
|
23535
23895
|
const result = await readEncryptedFile(file.id, encryptionKey);
|
|
23536
|
-
if (result) {
|
|
23896
|
+
if (result && result.blob.type.startsWith("image/")) {
|
|
23537
23897
|
const dataUri = await blobToDataUri(result.blob);
|
|
23538
23898
|
imageParts.push({
|
|
23539
23899
|
type: "image_url",
|
|
@@ -24474,7 +24834,8 @@ function useChatStorage(options) {
|
|
|
24474
24834
|
conversationId: explicitConversationId,
|
|
24475
24835
|
parentMessageId,
|
|
24476
24836
|
assistantUniqueId,
|
|
24477
|
-
piiRedaction: requestPiiRedaction
|
|
24837
|
+
piiRedaction: requestPiiRedaction,
|
|
24838
|
+
onFileProcessingResult
|
|
24478
24839
|
} = args;
|
|
24479
24840
|
const resolvePiiForCall = (conversationIdForCall) => {
|
|
24480
24841
|
const { redactor, forInnerSend } = resolveCallPii(
|
|
@@ -24670,9 +25031,12 @@ function useChatStorage(options) {
|
|
|
24670
25031
|
const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
|
|
24671
25032
|
const filesForStorage = files ?? extracted.files;
|
|
24672
25033
|
let fileContextForRequest;
|
|
25034
|
+
let fileContextIsCurrentTurn = false;
|
|
24673
25035
|
let preprocessedFileIds = [];
|
|
24674
25036
|
let imageContentUrls;
|
|
25037
|
+
let fileProcessingNotes = null;
|
|
24675
25038
|
if (filesForStorage && filesForStorage.length > 0) {
|
|
25039
|
+
let fileStatuses;
|
|
24676
25040
|
try {
|
|
24677
25041
|
const preprocessingResult = await preprocessFiles(filesForStorage, {
|
|
24678
25042
|
processors: fileProcessors,
|
|
@@ -24680,16 +25044,27 @@ function useChatStorage(options) {
|
|
|
24680
25044
|
});
|
|
24681
25045
|
if (preprocessingResult.extractedContent) {
|
|
24682
25046
|
fileContextForRequest = preprocessingResult.extractedContent;
|
|
25047
|
+
fileContextIsCurrentTurn = true;
|
|
24683
25048
|
preprocessedFileIds = preprocessingResult.preprocessedFileIds;
|
|
24684
25049
|
}
|
|
24685
25050
|
if (preprocessingResult.imageContentUrls?.length) {
|
|
24686
25051
|
imageContentUrls = preprocessingResult.imageContentUrls;
|
|
24687
25052
|
}
|
|
25053
|
+
fileStatuses = preprocessingResult.fileStatuses;
|
|
24688
25054
|
} catch (err) {
|
|
24689
25055
|
getLogger().error(
|
|
24690
25056
|
"[sendMessage] File preprocessing failed \u2014 continuing without file context:",
|
|
24691
25057
|
err
|
|
24692
25058
|
);
|
|
25059
|
+
fileStatuses = filesForStorage.filter((f) => !(f.type ?? "").toLowerCase().startsWith("image/")).map((f) => ({ fileId: f.id, fileName: f.name, status: "failed", reason: "error" }));
|
|
25060
|
+
}
|
|
25061
|
+
fileProcessingNotes = formatFileProcessingNotes(fileStatuses, {
|
|
25062
|
+
maxFileSizeBytes: fileProcessingOptions?.maxFileSizeBytes
|
|
25063
|
+
});
|
|
25064
|
+
try {
|
|
25065
|
+
onFileProcessingResult?.(fileStatuses);
|
|
25066
|
+
} catch (err) {
|
|
25067
|
+
getLogger().warn("[sendMessage] onFileProcessingResult threw \u2014 ignoring:", err);
|
|
24693
25068
|
}
|
|
24694
25069
|
}
|
|
24695
25070
|
let convId;
|
|
@@ -24863,7 +25238,11 @@ function useChatStorage(options) {
|
|
|
24863
25238
|
if (part.type === "text") return true;
|
|
24864
25239
|
if (part.type === "input_file" && part.file) {
|
|
24865
25240
|
const fileId = part.file.file_id;
|
|
24866
|
-
|
|
25241
|
+
if (fileId) return !preprocessedFileIds.includes(fileId);
|
|
25242
|
+
const { file_data: fileData, file_url: fileUrl } = part.file;
|
|
25243
|
+
return !filesForStorage?.some(
|
|
25244
|
+
(f) => preprocessedFileIds.includes(f.id) && !!f.url && (f.url === fileData || f.url === fileUrl)
|
|
25245
|
+
);
|
|
24867
25246
|
}
|
|
24868
25247
|
if (part.type === "image_url" && part.image_url?.url) {
|
|
24869
25248
|
const matchesPreprocessed = filesForStorage?.some(
|
|
@@ -24898,6 +25277,13 @@ function useChatStorage(options) {
|
|
|
24898
25277
|
};
|
|
24899
25278
|
}
|
|
24900
25279
|
}
|
|
25280
|
+
const currentTurnFileText = [
|
|
25281
|
+
fileContextIsCurrentTurn ? fileContextForRequest : void 0,
|
|
25282
|
+
fileProcessingNotes
|
|
25283
|
+
].filter(Boolean).join("\n\n");
|
|
25284
|
+
if (currentTurnFileText) {
|
|
25285
|
+
messagesToSend = attachFileContextToLastUserMessage(messagesToSend, currentTurnFileText);
|
|
25286
|
+
}
|
|
24901
25287
|
let userFileIds = [];
|
|
24902
25288
|
if (filesForStorage && filesForStorage.length > 0 && walletAddress && isEncryptionReady()) {
|
|
24903
25289
|
userFileIds = await storeUserFilesInOPFS(filesForStorage, walletAddress, convId);
|
|
@@ -24908,7 +25294,11 @@ function useChatStorage(options) {
|
|
|
24908
25294
|
content: contentForStorage,
|
|
24909
25295
|
fileIds: userFileIds.length > 0 ? userFileIds : void 0,
|
|
24910
25296
|
model,
|
|
24911
|
-
// Store
|
|
25297
|
+
// Store the file context — this turn's, or the one recalled from an earlier row — in the
|
|
25298
|
+
// thinking field for retrieval in follow-up messages. Re-storing recalled context is the
|
|
25299
|
+
// carry-forward: recall only scans the last `maxHistoryMessages` rows, so without it a
|
|
25300
|
+
// file's text is lost once the turn that attached it scrolls out of the window. Failure
|
|
25301
|
+
// notes are never stored: only `[Extracted content from …]` text, which recall keys on.
|
|
24912
25302
|
thinking: fileContextForRequest,
|
|
24913
25303
|
parentMessageId
|
|
24914
25304
|
};
|
|
@@ -25066,7 +25456,7 @@ function useChatStorage(options) {
|
|
|
25066
25456
|
headers,
|
|
25067
25457
|
memoryContext,
|
|
25068
25458
|
searchContext,
|
|
25069
|
-
fileContext: fileContextForRequest,
|
|
25459
|
+
fileContext: fileContextIsCurrentTurn ? void 0 : fileContextForRequest,
|
|
25070
25460
|
toolGuidance: computeToolGuidance(
|
|
25071
25461
|
filteredServerTools,
|
|
25072
25462
|
filteredClientTools,
|
|
@@ -36339,8 +36729,10 @@ function renderAnumaJsx(jsx3) {
|
|
|
36339
36729
|
appFileToStored,
|
|
36340
36730
|
archiveVaultMemoryOp,
|
|
36341
36731
|
assembleMemoryContext,
|
|
36732
|
+
attachFileContextToLastUserMessage,
|
|
36342
36733
|
backfillMemoryEntityUserIdsOp,
|
|
36343
36734
|
backfillMemoryTopicsOp,
|
|
36735
|
+
buildAttachedFilesText,
|
|
36344
36736
|
capHopsForDensity,
|
|
36345
36737
|
chatStorageMigrations,
|
|
36346
36738
|
chatStorageSchema,
|
|
@@ -36446,6 +36838,7 @@ function renderAnumaJsx(jsx3) {
|
|
|
36446
36838
|
findById,
|
|
36447
36839
|
findMatchingTools,
|
|
36448
36840
|
findParentOfId,
|
|
36841
|
+
formatFileProcessingNotes,
|
|
36449
36842
|
generateConversationId,
|
|
36450
36843
|
generateEmbedding,
|
|
36451
36844
|
generateEmbeddings,
|
|
@@ -36545,6 +36938,7 @@ function renderAnumaJsx(jsx3) {
|
|
|
36545
36938
|
insertAfterId,
|
|
36546
36939
|
insertChild,
|
|
36547
36940
|
isAnumaTag,
|
|
36941
|
+
isAttachedFilesText,
|
|
36548
36942
|
isCalendarCallback,
|
|
36549
36943
|
isDegradedTopicSkip,
|
|
36550
36944
|
isDriveCallback,
|