@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.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
taskTypeHeader,
|
|
19
19
|
validateEndpointOverride,
|
|
20
20
|
withInternalFlowMarker
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-LBIKGBXE.mjs";
|
|
22
22
|
import {
|
|
23
23
|
BASE_URL,
|
|
24
24
|
MCP_R2_DOMAIN,
|
|
@@ -1512,6 +1512,46 @@ async function assembleDecryptedMessage(message, address) {
|
|
|
1512
1512
|
// src/lib/db/chat/types.ts
|
|
1513
1513
|
import { v7 as uuidv7 } from "uuid";
|
|
1514
1514
|
|
|
1515
|
+
// src/lib/chat/fileContext.ts
|
|
1516
|
+
var ATTACHED_FILES_OPEN_TAG = "<attached_files>";
|
|
1517
|
+
var ATTACHED_FILES_CLOSE_TAG = "</attached_files>";
|
|
1518
|
+
var ATTACHED_FILES_PREFIX = `${ATTACHED_FILES_OPEN_TAG}
|
|
1519
|
+
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.`;
|
|
1520
|
+
function isAttachedFilesText(text12) {
|
|
1521
|
+
return typeof text12 === "string" && text12.startsWith(ATTACHED_FILES_PREFIX) && text12.endsWith(ATTACHED_FILES_CLOSE_TAG);
|
|
1522
|
+
}
|
|
1523
|
+
function buildAttachedFilesText(fileContext) {
|
|
1524
|
+
return `${ATTACHED_FILES_PREFIX}
|
|
1525
|
+
|
|
1526
|
+
${fileContext}
|
|
1527
|
+
${ATTACHED_FILES_CLOSE_TAG}`;
|
|
1528
|
+
}
|
|
1529
|
+
function attachFileContextToLastUserMessage(messages, fileContext) {
|
|
1530
|
+
if (!fileContext.trim()) return messages;
|
|
1531
|
+
let lastUserIndex = -1;
|
|
1532
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1533
|
+
if (messages[i].role === "user") {
|
|
1534
|
+
lastUserIndex = i;
|
|
1535
|
+
break;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
if (lastUserIndex === -1) return messages;
|
|
1539
|
+
const message = messages[lastUserIndex];
|
|
1540
|
+
const existing = message.content ?? [];
|
|
1541
|
+
let insertAt = 0;
|
|
1542
|
+
existing.forEach((part, index) => {
|
|
1543
|
+
if (part.type === "text") insertAt = index + 1;
|
|
1544
|
+
});
|
|
1545
|
+
const filePart = {
|
|
1546
|
+
type: "text",
|
|
1547
|
+
text: buildAttachedFilesText(fileContext)
|
|
1548
|
+
};
|
|
1549
|
+
const content = [...existing.slice(0, insertAt), filePart, ...existing.slice(insertAt)];
|
|
1550
|
+
const result = [...messages];
|
|
1551
|
+
result[lastUserIndex] = { ...message, content };
|
|
1552
|
+
return result;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1515
1555
|
// src/lib/chat/useChat/strategies/types.ts
|
|
1516
1556
|
function isChatCompletionResponse(r) {
|
|
1517
1557
|
if (r.object === "response") return false;
|
|
@@ -1633,7 +1673,7 @@ function extractUserMessageFromMessages(messages) {
|
|
|
1633
1673
|
const textParts = [];
|
|
1634
1674
|
const files = [];
|
|
1635
1675
|
for (const part of lastUserMessage.content) {
|
|
1636
|
-
if (part.type === "text" && part.text) {
|
|
1676
|
+
if (part.type === "text" && part.text && !isAttachedFilesText(part.text)) {
|
|
1637
1677
|
textParts.push(part.text);
|
|
1638
1678
|
} else if (part.type === "image_url" && part.image_url?.url) {
|
|
1639
1679
|
files.push({
|
|
@@ -6069,7 +6109,7 @@ function extractLastUserText(messages) {
|
|
|
6069
6109
|
const content = lastUserMsg.content;
|
|
6070
6110
|
if (typeof content === "string") return content;
|
|
6071
6111
|
if (Array.isArray(content)) {
|
|
6072
|
-
return content.filter((c) => c?.type === "text").map((c) => c.text ?? "").join(" ");
|
|
6112
|
+
return content.filter((c) => c?.type === "text" && !isAttachedFilesText(c.text)).map((c) => c.text ?? "").join(" ");
|
|
6073
6113
|
}
|
|
6074
6114
|
return "";
|
|
6075
6115
|
}
|
|
@@ -14932,7 +14972,7 @@ async function tryConsolidate(trimmed, ctx, options, prepared) {
|
|
|
14932
14972
|
content: m.content,
|
|
14933
14973
|
similarity: m.similarity
|
|
14934
14974
|
}));
|
|
14935
|
-
const { consolidateMemory: doConsolidate } = await import("./consolidate-
|
|
14975
|
+
const { consolidateMemory: doConsolidate } = await import("./consolidate-2NLZAEWR.mjs");
|
|
14936
14976
|
const decision = await doConsolidate(trimmed, candidates, consolidateOptions);
|
|
14937
14977
|
if (decision.action === "create") return decision.fallbackReason ? null : { create: true };
|
|
14938
14978
|
if (decision.action === "supersede" && decision.content) {
|
|
@@ -19549,14 +19589,37 @@ function createMemoryVaultTool(vaultCtx, options, embeddingOptions, cache) {
|
|
|
19549
19589
|
if (typeof process !== "undefined" && typeof process.umask !== "function") {
|
|
19550
19590
|
process.umask = (_mask) => 18;
|
|
19551
19591
|
}
|
|
19592
|
+
var MAX_ROWS_PER_SHEET = 2e3;
|
|
19593
|
+
function csvField(value) {
|
|
19594
|
+
return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
|
|
19595
|
+
}
|
|
19596
|
+
function uniqueHeaders(raw, columnCount) {
|
|
19597
|
+
const bases = [];
|
|
19598
|
+
for (let col = 1; col <= columnCount; col++) bases.push(raw[col]?.trim() || `Column${col}`);
|
|
19599
|
+
const taken = new Set(bases);
|
|
19600
|
+
const emitted = /* @__PURE__ */ new Set();
|
|
19601
|
+
return bases.map((base) => {
|
|
19602
|
+
if (!emitted.has(base)) {
|
|
19603
|
+
emitted.add(base);
|
|
19604
|
+
return base;
|
|
19605
|
+
}
|
|
19606
|
+
let n = 2;
|
|
19607
|
+
while (taken.has(`${base}_${n}`)) n++;
|
|
19608
|
+
const name = `${base}_${n}`;
|
|
19609
|
+
taken.add(name);
|
|
19610
|
+
emitted.add(name);
|
|
19611
|
+
return name;
|
|
19612
|
+
});
|
|
19613
|
+
}
|
|
19552
19614
|
var ExcelProcessor = class {
|
|
19553
|
-
constructor() {
|
|
19615
|
+
constructor(options = {}) {
|
|
19554
19616
|
this.name = "excel";
|
|
19555
19617
|
this.supportedMimeTypes = [
|
|
19556
19618
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
19557
19619
|
// .xlsx
|
|
19558
19620
|
];
|
|
19559
19621
|
this.supportedExtensions = [".xlsx"];
|
|
19622
|
+
this.maxRowsPerSheet = options.maxRowsPerSheet ?? MAX_ROWS_PER_SHEET;
|
|
19560
19623
|
}
|
|
19561
19624
|
async loadExcelJS() {
|
|
19562
19625
|
const mod = await import("exceljs");
|
|
@@ -19571,31 +19634,51 @@ var ExcelProcessor = class {
|
|
|
19571
19634
|
if (workbook.worksheets.length === 0) {
|
|
19572
19635
|
return null;
|
|
19573
19636
|
}
|
|
19574
|
-
|
|
19637
|
+
let truncated = false;
|
|
19638
|
+
const sections = [];
|
|
19575
19639
|
for (const worksheet of workbook.worksheets) {
|
|
19576
|
-
const
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19640
|
+
const rawHeaders = [];
|
|
19641
|
+
let columnCount = 0;
|
|
19642
|
+
worksheet.getRow(1).eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
19643
|
+
rawHeaders[colNumber] = cell.text;
|
|
19644
|
+
columnCount = Math.max(columnCount, colNumber);
|
|
19581
19645
|
});
|
|
19646
|
+
const rows = [];
|
|
19647
|
+
let totalRows = 0;
|
|
19582
19648
|
worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
|
|
19583
19649
|
if (rowNumber === 1) return;
|
|
19584
|
-
|
|
19650
|
+
totalRows++;
|
|
19651
|
+
if (rows.length >= this.maxRowsPerSheet) return;
|
|
19652
|
+
const values = [];
|
|
19585
19653
|
row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
|
|
19586
|
-
|
|
19587
|
-
|
|
19654
|
+
values[colNumber - 1] = String(this.resolveCellValue(cell.value));
|
|
19655
|
+
columnCount = Math.max(columnCount, colNumber);
|
|
19588
19656
|
});
|
|
19589
|
-
rows.push(
|
|
19657
|
+
rows.push(values);
|
|
19590
19658
|
});
|
|
19591
|
-
|
|
19659
|
+
const headers = uniqueHeaders(rawHeaders, columnCount);
|
|
19660
|
+
const lines = [headers, ...rows].map(
|
|
19661
|
+
(values) => headers.map((_, i) => csvField(values[i] ?? "")).join(",")
|
|
19662
|
+
);
|
|
19663
|
+
let section = `## Sheet: ${worksheet.name}
|
|
19664
|
+
|
|
19665
|
+
\`\`\`csv
|
|
19666
|
+
${lines.join("\n")}
|
|
19667
|
+
\`\`\``;
|
|
19668
|
+
if (totalRows > rows.length) {
|
|
19669
|
+
truncated = true;
|
|
19670
|
+
section += `
|
|
19671
|
+
[truncated: showing the first ${rows.length} of ${totalRows} data rows of sheet "${worksheet.name}"]`;
|
|
19672
|
+
}
|
|
19673
|
+
sections.push(section);
|
|
19592
19674
|
}
|
|
19593
19675
|
return {
|
|
19594
|
-
extractedText:
|
|
19595
|
-
format: "
|
|
19676
|
+
extractedText: sections.join("\n\n"),
|
|
19677
|
+
format: "markdown",
|
|
19596
19678
|
metadata: {
|
|
19597
19679
|
sheetCount: workbook.worksheets.length,
|
|
19598
|
-
sheetNames: workbook.worksheets.map((ws) => ws.name)
|
|
19680
|
+
sheetNames: workbook.worksheets.map((ws) => ws.name),
|
|
19681
|
+
truncated
|
|
19599
19682
|
}
|
|
19600
19683
|
};
|
|
19601
19684
|
} catch (error) {
|
|
@@ -19621,6 +19704,9 @@ var ExcelProcessor = class {
|
|
|
19621
19704
|
// src/lib/pdf.ts
|
|
19622
19705
|
var pdfjsModule = null;
|
|
19623
19706
|
var workerConfigured = false;
|
|
19707
|
+
var RENDER_SCALE = 1.5;
|
|
19708
|
+
var MAX_RENDER_SIDE_PX = 1600;
|
|
19709
|
+
var JPEG_QUALITY = 0.8;
|
|
19624
19710
|
async function getPdfjs() {
|
|
19625
19711
|
if (!pdfjsModule) {
|
|
19626
19712
|
pdfjsModule = await import("pdfjs-dist");
|
|
@@ -19631,46 +19717,134 @@ async function getPdfjs() {
|
|
|
19631
19717
|
}
|
|
19632
19718
|
return pdfjsModule;
|
|
19633
19719
|
}
|
|
19634
|
-
async function
|
|
19720
|
+
async function withPdfDocument(pdfDataUrl, fn) {
|
|
19635
19721
|
const pdfjs = await getPdfjs();
|
|
19636
19722
|
const loadingTask = pdfjs.getDocument(pdfDataUrl);
|
|
19637
|
-
|
|
19638
|
-
|
|
19639
|
-
|
|
19640
|
-
|
|
19641
|
-
|
|
19642
|
-
|
|
19643
|
-
|
|
19644
|
-
|
|
19645
|
-
|
|
19723
|
+
let pdf;
|
|
19724
|
+
try {
|
|
19725
|
+
pdf = await loadingTask.promise;
|
|
19726
|
+
} catch (error) {
|
|
19727
|
+
await loadingTask.destroy().catch(() => void 0);
|
|
19728
|
+
throw error;
|
|
19729
|
+
}
|
|
19730
|
+
try {
|
|
19731
|
+
return await fn(pdf);
|
|
19732
|
+
} finally {
|
|
19733
|
+
await pdf.destroy().catch(() => void 0);
|
|
19646
19734
|
}
|
|
19647
|
-
return textParts.join("\n\n");
|
|
19648
19735
|
}
|
|
19649
|
-
|
|
19650
|
-
|
|
19651
|
-
const
|
|
19652
|
-
|
|
19653
|
-
|
|
19654
|
-
|
|
19655
|
-
|
|
19656
|
-
|
|
19657
|
-
|
|
19658
|
-
|
|
19659
|
-
|
|
19660
|
-
|
|
19661
|
-
|
|
19662
|
-
|
|
19663
|
-
|
|
19664
|
-
|
|
19665
|
-
|
|
19666
|
-
|
|
19667
|
-
|
|
19668
|
-
|
|
19669
|
-
|
|
19736
|
+
function joinPdfTextItems(items) {
|
|
19737
|
+
let text12 = "";
|
|
19738
|
+
for (const item of items) {
|
|
19739
|
+
if (!("str" in item)) continue;
|
|
19740
|
+
const { str, hasEOL } = item;
|
|
19741
|
+
text12 += str + (hasEOL ? "\n" : " ");
|
|
19742
|
+
}
|
|
19743
|
+
return text12.replace(/[ \t]+\n/g, "\n").trim();
|
|
19744
|
+
}
|
|
19745
|
+
function computeRenderScale(width, height) {
|
|
19746
|
+
const longest = Math.max(width, height);
|
|
19747
|
+
if (!(longest > 0)) return RENDER_SCALE;
|
|
19748
|
+
return Math.min(RENDER_SCALE, MAX_RENDER_SIDE_PX / longest);
|
|
19749
|
+
}
|
|
19750
|
+
async function extractPdfPageTexts(pdfDataUrl) {
|
|
19751
|
+
return withPdfDocument(pdfDataUrl, async (pdf) => {
|
|
19752
|
+
const pageTexts = [];
|
|
19753
|
+
for (let i = 1; i <= pdf.numPages; i++) {
|
|
19754
|
+
const page = await pdf.getPage(i);
|
|
19755
|
+
const textContent = await page.getTextContent();
|
|
19756
|
+
pageTexts.push(joinPdfTextItems(textContent.items));
|
|
19757
|
+
}
|
|
19758
|
+
return pageTexts;
|
|
19759
|
+
});
|
|
19760
|
+
}
|
|
19761
|
+
async function extractTextFromPdf(pdfDataUrl) {
|
|
19762
|
+
const pageTexts = await extractPdfPageTexts(pdfDataUrl);
|
|
19763
|
+
return pageTexts.filter((text12) => text12.trim()).join("\n\n");
|
|
19764
|
+
}
|
|
19765
|
+
async function renderPdfPages(pdfDataUrl, maxPages, pageNumbers) {
|
|
19766
|
+
return withPdfDocument(pdfDataUrl, async (pdf) => {
|
|
19767
|
+
const requested = pageNumbers?.filter((n) => n >= 1 && n <= pdf.numPages) ?? Array.from(
|
|
19768
|
+
{ length: maxPages !== void 0 ? Math.min(maxPages, pdf.numPages) : pdf.numPages },
|
|
19769
|
+
(_, i) => i + 1
|
|
19770
|
+
);
|
|
19771
|
+
const pages = [];
|
|
19772
|
+
for (const pageNumber of requested) {
|
|
19773
|
+
const page = await pdf.getPage(pageNumber);
|
|
19774
|
+
const base = page.getViewport({ scale: 1 });
|
|
19775
|
+
const viewport = page.getViewport({ scale: computeRenderScale(base.width, base.height) });
|
|
19776
|
+
const canvas = document.createElement("canvas");
|
|
19777
|
+
const context = canvas.getContext("2d");
|
|
19778
|
+
if (!context) continue;
|
|
19779
|
+
canvas.height = Math.floor(viewport.height);
|
|
19780
|
+
canvas.width = Math.floor(viewport.width);
|
|
19781
|
+
context.fillStyle = "#ffffff";
|
|
19782
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
19783
|
+
await page.render({
|
|
19784
|
+
canvasContext: context,
|
|
19785
|
+
viewport,
|
|
19786
|
+
background: "#ffffff"
|
|
19787
|
+
}).promise;
|
|
19788
|
+
pages.push({ pageNumber, dataUrl: canvas.toDataURL("image/jpeg", JPEG_QUALITY) });
|
|
19789
|
+
canvas.width = 0;
|
|
19790
|
+
canvas.height = 0;
|
|
19791
|
+
}
|
|
19792
|
+
return { pages, pageCount: pdf.numPages };
|
|
19793
|
+
});
|
|
19794
|
+
}
|
|
19795
|
+
async function convertPdfToImages(pdfDataUrl, maxPages, pageNumbers) {
|
|
19796
|
+
const { pages } = await renderPdfPages(pdfDataUrl, maxPages, pageNumbers);
|
|
19797
|
+
return pages.map((p) => p.dataUrl);
|
|
19670
19798
|
}
|
|
19671
19799
|
|
|
19672
19800
|
// src/lib/processors/PdfProcessor.ts
|
|
19673
19801
|
var MAX_IMAGE_PAGES = 20;
|
|
19802
|
+
var MIN_PAGE_TEXT_CHARS = 20;
|
|
19803
|
+
function selectPdfImagePages(pageTexts, maxImagePages = MAX_IMAGE_PAGES) {
|
|
19804
|
+
const needImage = [];
|
|
19805
|
+
pageTexts.forEach((text12, index) => {
|
|
19806
|
+
if (text12.trim().length < MIN_PAGE_TEXT_CHARS) needImage.push(index + 1);
|
|
19807
|
+
});
|
|
19808
|
+
const limit = Math.max(0, maxImagePages);
|
|
19809
|
+
return { rendered: needImage.slice(0, limit), omitted: needImage.slice(limit) };
|
|
19810
|
+
}
|
|
19811
|
+
function formatPageList(pages) {
|
|
19812
|
+
const ranges = [];
|
|
19813
|
+
for (let i = 0; i < pages.length; i++) {
|
|
19814
|
+
const start = pages[i];
|
|
19815
|
+
let end = start;
|
|
19816
|
+
while (i + 1 < pages.length && pages[i + 1] === end + 1) end = pages[++i];
|
|
19817
|
+
ranges.push(start === end ? `${start}` : `${start}-${end}`);
|
|
19818
|
+
}
|
|
19819
|
+
return `${pages.length === 1 ? "page" : "pages"} ${ranges.join(", ")}`;
|
|
19820
|
+
}
|
|
19821
|
+
function buildPdfImageNote(fileName, includedPages, omittedPages, pageCount) {
|
|
19822
|
+
const imageOnlyCount = includedPages.length + omittedPages.length;
|
|
19823
|
+
const allPages = [...includedPages, ...omittedPages].sort((a, b) => a - b);
|
|
19824
|
+
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)})`;
|
|
19825
|
+
if (includedPages.length === 0) {
|
|
19826
|
+
return `[${subject} \u2014 ${formatPageList(omittedPages)} could not be included as images because of the image limit, so their content is not available]`;
|
|
19827
|
+
}
|
|
19828
|
+
const included = `${formatPageList(includedPages)} rendered as images and included in this message for visual analysis`;
|
|
19829
|
+
if (omittedPages.length === 0) return `[${subject} \u2014 ${included}]`;
|
|
19830
|
+
return `[${subject} \u2014 ${included}; ${formatPageList(omittedPages)} not included because of the image limit, so their content is not available]`;
|
|
19831
|
+
}
|
|
19832
|
+
function rewriteImageNote(result, fileName, keptImages) {
|
|
19833
|
+
const images = result.imageDataUrls ?? [];
|
|
19834
|
+
const note = result.metadata?.imageNote;
|
|
19835
|
+
if (!note || keptImages >= images.length) return result.extractedText;
|
|
19836
|
+
const imagePages = result.metadata?.imagePages ?? [];
|
|
19837
|
+
const omittedPages = result.metadata?.omittedImagePages ?? [];
|
|
19838
|
+
const pageCount = result.metadata?.pageCount;
|
|
19839
|
+
const kept = Math.max(0, keptImages);
|
|
19840
|
+
const newNote = buildPdfImageNote(
|
|
19841
|
+
fileName,
|
|
19842
|
+
imagePages.slice(0, kept),
|
|
19843
|
+
[...imagePages.slice(kept), ...omittedPages].sort((a, b) => a - b),
|
|
19844
|
+
pageCount
|
|
19845
|
+
);
|
|
19846
|
+
return result.extractedText.replace(note, () => newNote);
|
|
19847
|
+
}
|
|
19674
19848
|
var PdfProcessor = class {
|
|
19675
19849
|
constructor() {
|
|
19676
19850
|
this.name = "pdf";
|
|
@@ -19679,43 +19853,65 @@ var PdfProcessor = class {
|
|
|
19679
19853
|
}
|
|
19680
19854
|
async process(file) {
|
|
19681
19855
|
const logger = getLogger();
|
|
19856
|
+
let pageTexts = null;
|
|
19682
19857
|
try {
|
|
19683
|
-
|
|
19684
|
-
if (text12 && text12.trim().length > 0) {
|
|
19685
|
-
return {
|
|
19686
|
-
extractedText: text12,
|
|
19687
|
-
format: "plain"
|
|
19688
|
-
};
|
|
19689
|
-
}
|
|
19690
|
-
logger.info(
|
|
19691
|
-
`[PdfProcessor] Text extraction returned empty for "${file.name}" \u2014 falling back to image conversion`
|
|
19692
|
-
);
|
|
19858
|
+
pageTexts = await extractPdfPageTexts(file.dataUrl);
|
|
19693
19859
|
} catch (textError) {
|
|
19694
19860
|
logger.warn(
|
|
19695
|
-
|
|
19861
|
+
"[PdfProcessor] Text extraction failed \u2014 falling back to image conversion:",
|
|
19696
19862
|
textError
|
|
19697
19863
|
);
|
|
19698
19864
|
}
|
|
19865
|
+
const text12 = (pageTexts ?? []).filter((t) => t.trim()).join("\n\n");
|
|
19866
|
+
const { rendered, omitted } = pageTexts ? selectPdfImagePages(pageTexts) : { rendered: [], omitted: [] };
|
|
19867
|
+
if (pageTexts && rendered.length === 0 && omitted.length === 0) {
|
|
19868
|
+
return text12.trim() ? { extractedText: text12, format: "plain" } : null;
|
|
19869
|
+
}
|
|
19870
|
+
let renderedPages;
|
|
19871
|
+
let pageCount = pageTexts?.length;
|
|
19699
19872
|
try {
|
|
19700
|
-
|
|
19701
|
-
|
|
19702
|
-
|
|
19703
|
-
|
|
19873
|
+
if (pageTexts) {
|
|
19874
|
+
renderedPages = (await renderPdfPages(file.dataUrl, void 0, rendered)).pages;
|
|
19875
|
+
} else {
|
|
19876
|
+
const result = await renderPdfPages(file.dataUrl, MAX_IMAGE_PAGES);
|
|
19877
|
+
renderedPages = result.pages;
|
|
19878
|
+
pageCount = result.pageCount;
|
|
19704
19879
|
}
|
|
19705
|
-
logger.info(`[PdfProcessor] Converted ${images.length} page(s) of "${file.name}" to images`);
|
|
19706
|
-
return {
|
|
19707
|
-
// Contextual note for the LLM — keep it self-contained since the
|
|
19708
|
-
// images are only injected in the current request and not persisted
|
|
19709
|
-
// for follow-up turns.
|
|
19710
|
-
extractedText: `[${file.name}: scanned/image-based PDF \u2014 ${images.length} page(s) rendered as images and included in this message for visual analysis]`,
|
|
19711
|
-
format: "plain",
|
|
19712
|
-
imageDataUrls: images,
|
|
19713
|
-
metadata: { pageCount: images.length }
|
|
19714
|
-
};
|
|
19715
19880
|
} catch (imageError) {
|
|
19716
|
-
|
|
19717
|
-
|
|
19881
|
+
if (!text12.trim()) {
|
|
19882
|
+
logger.error("[PdfProcessor] Image conversion also failed:", imageError);
|
|
19883
|
+
throw imageError;
|
|
19884
|
+
}
|
|
19885
|
+
logger.warn("[PdfProcessor] Image conversion failed \u2014 keeping text only:", imageError);
|
|
19886
|
+
renderedPages = [];
|
|
19887
|
+
}
|
|
19888
|
+
const images = renderedPages.map((p) => p.dataUrl);
|
|
19889
|
+
const imagePages = renderedPages.map((p) => p.pageNumber);
|
|
19890
|
+
const included = new Set(imagePages);
|
|
19891
|
+
const needImage = pageTexts ? [...rendered, ...omitted] : Array.from({ length: pageCount ?? 0 }, (_, i) => i + 1);
|
|
19892
|
+
const omittedPages = needImage.filter((n) => !included.has(n)).sort((a, b) => a - b);
|
|
19893
|
+
if (images.length === 0 && !text12.trim()) {
|
|
19894
|
+
logger.warn("[PdfProcessor] Image conversion also returned empty");
|
|
19895
|
+
return null;
|
|
19718
19896
|
}
|
|
19897
|
+
logger.info(
|
|
19898
|
+
`[PdfProcessor] Rendered ${images.length} page(s) as images, ${omittedPages.length} image-only page(s) omitted`
|
|
19899
|
+
);
|
|
19900
|
+
const imageNote = buildPdfImageNote(file.name, imagePages, omittedPages, pageCount);
|
|
19901
|
+
return {
|
|
19902
|
+
extractedText: text12.trim() ? `${imageNote}
|
|
19903
|
+
|
|
19904
|
+
${text12}` : imageNote,
|
|
19905
|
+
format: "plain",
|
|
19906
|
+
imageDataUrls: images.length > 0 ? images : void 0,
|
|
19907
|
+
metadata: {
|
|
19908
|
+
pageCount,
|
|
19909
|
+
imageNote,
|
|
19910
|
+
imagePages,
|
|
19911
|
+
omittedImagePages: omittedPages,
|
|
19912
|
+
truncated: omittedPages.length > 0
|
|
19913
|
+
}
|
|
19914
|
+
};
|
|
19719
19915
|
}
|
|
19720
19916
|
};
|
|
19721
19917
|
|
|
@@ -19756,6 +19952,21 @@ var EXT = {
|
|
|
19756
19952
|
CFG: ".cfg",
|
|
19757
19953
|
CONF: ".conf"
|
|
19758
19954
|
};
|
|
19955
|
+
function decodeText(bytes) {
|
|
19956
|
+
if (bytes[0] === 255 && bytes[1] === 254) {
|
|
19957
|
+
return new TextDecoder("utf-16le", { fatal: false }).decode(bytes.subarray(2));
|
|
19958
|
+
}
|
|
19959
|
+
if (bytes[0] === 254 && bytes[1] === 255) {
|
|
19960
|
+
const swapped = new Uint8Array(bytes.length - 2);
|
|
19961
|
+
for (let i = 2; i + 1 < bytes.length; i += 2) {
|
|
19962
|
+
swapped[i - 2] = bytes[i + 1];
|
|
19963
|
+
swapped[i - 1] = bytes[i];
|
|
19964
|
+
}
|
|
19965
|
+
return new TextDecoder("utf-16le", { fatal: false }).decode(swapped);
|
|
19966
|
+
}
|
|
19967
|
+
const hasUtf8Bom = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191;
|
|
19968
|
+
return new TextDecoder("utf-8", { fatal: false }).decode(hasUtf8Bom ? bytes.subarray(3) : bytes);
|
|
19969
|
+
}
|
|
19759
19970
|
var TextProcessor = class {
|
|
19760
19971
|
constructor() {
|
|
19761
19972
|
this.name = "text";
|
|
@@ -19765,7 +19976,7 @@ var TextProcessor = class {
|
|
|
19765
19976
|
async process(file) {
|
|
19766
19977
|
try {
|
|
19767
19978
|
const arrayBuffer = await dataUrlToArrayBuffer(file.dataUrl);
|
|
19768
|
-
const text12 = new
|
|
19979
|
+
const text12 = decodeText(new Uint8Array(arrayBuffer));
|
|
19769
19980
|
if (!text12.trim()) {
|
|
19770
19981
|
return null;
|
|
19771
19982
|
}
|
|
@@ -19788,7 +19999,7 @@ var TextProcessor = class {
|
|
|
19788
19999
|
* rather than prose).
|
|
19789
20000
|
*/
|
|
19790
20001
|
resolveFormat(file) {
|
|
19791
|
-
const type = file.type?.toLowerCase() ?? "";
|
|
20002
|
+
const type = file.type?.split(";")[0].trim().toLowerCase() ?? "";
|
|
19792
20003
|
const name = file.name?.toLowerCase() ?? "";
|
|
19793
20004
|
if (type === MIME.MARKDOWN || type === MIME.X_MARKDOWN || name.endsWith(EXT.MD) || name.endsWith(EXT.MARKDOWN)) {
|
|
19794
20005
|
return "markdown";
|
|
@@ -19844,6 +20055,12 @@ var WordProcessor = class {
|
|
|
19844
20055
|
|
|
19845
20056
|
// src/lib/processors/ZipProcessor.ts
|
|
19846
20057
|
import JSZip from "jszip";
|
|
20058
|
+
var MAX_ZIP_ENTRIES = 1e3;
|
|
20059
|
+
var MAX_TOTAL_UNCOMPRESSED_BYTES = 50 * 1024 * 1024;
|
|
20060
|
+
function declaredUncompressedSize(zipObject) {
|
|
20061
|
+
const size = zipObject._data?.uncompressedSize;
|
|
20062
|
+
return typeof size === "number" ? size : void 0;
|
|
20063
|
+
}
|
|
19847
20064
|
var ZipProcessor = class {
|
|
19848
20065
|
constructor(options = {}) {
|
|
19849
20066
|
this.name = "zip";
|
|
@@ -19878,14 +20095,35 @@ var ZipProcessor = class {
|
|
|
19878
20095
|
zipObject: zipEntry
|
|
19879
20096
|
});
|
|
19880
20097
|
});
|
|
19881
|
-
const
|
|
19882
|
-
|
|
20098
|
+
const visibleEntries = this.includeHidden ? entries : entries.filter((entry) => !this.isHidden(entry.path));
|
|
20099
|
+
visibleEntries.sort((a, b) => {
|
|
19883
20100
|
if (a.isDirectory !== b.isDirectory) {
|
|
19884
20101
|
return a.isDirectory ? -1 : 1;
|
|
19885
20102
|
}
|
|
19886
20103
|
return a.path.localeCompare(b.path);
|
|
19887
20104
|
});
|
|
19888
|
-
|
|
20105
|
+
const visibleDirs = visibleEntries.filter((e) => e.isDirectory);
|
|
20106
|
+
const visibleFiles = visibleEntries.filter((e) => !e.isDirectory);
|
|
20107
|
+
const listedDirs = visibleDirs.slice(0, MAX_ZIP_ENTRIES);
|
|
20108
|
+
const listedFiles = visibleFiles.slice(0, MAX_ZIP_ENTRIES);
|
|
20109
|
+
const filteredEntries = [...listedDirs, ...listedFiles];
|
|
20110
|
+
const notes = [];
|
|
20111
|
+
if (visibleFiles.length > listedFiles.length) {
|
|
20112
|
+
notes.push(
|
|
20113
|
+
`[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]`
|
|
20114
|
+
);
|
|
20115
|
+
}
|
|
20116
|
+
if (visibleDirs.length > listedDirs.length) {
|
|
20117
|
+
notes.push(
|
|
20118
|
+
`[truncated: listing the first ${listedDirs.length} of ${visibleDirs.length} directories]`
|
|
20119
|
+
);
|
|
20120
|
+
}
|
|
20121
|
+
let totalBytes = 0;
|
|
20122
|
+
let byteBudgetHit = false;
|
|
20123
|
+
let truncated = notes.length > 0;
|
|
20124
|
+
const logger = getLogger();
|
|
20125
|
+
for (let index = 0; index < filteredEntries.length; index++) {
|
|
20126
|
+
const entry = filteredEntries[index];
|
|
19889
20127
|
if (entry.isDirectory) continue;
|
|
19890
20128
|
const fileName = entry.path.split("/").pop() || entry.path;
|
|
19891
20129
|
const extension = this.getFileExtension(fileName);
|
|
@@ -19899,9 +20137,20 @@ var ZipProcessor = class {
|
|
|
19899
20137
|
};
|
|
19900
20138
|
const processor = this.registry?.findProcessor(fileMetadata);
|
|
19901
20139
|
if (!processor || processor.name === "zip") continue;
|
|
20140
|
+
const declared = declaredUncompressedSize(entry.zipObject);
|
|
20141
|
+
if (declared !== void 0 && declared > this.maxFileSize) continue;
|
|
20142
|
+
if (totalBytes + (declared ?? 0) > MAX_TOTAL_UNCOMPRESSED_BYTES) {
|
|
20143
|
+
byteBudgetHit = true;
|
|
20144
|
+
break;
|
|
20145
|
+
}
|
|
19902
20146
|
try {
|
|
19903
20147
|
const data = await entry.zipObject.async("uint8array");
|
|
20148
|
+
totalBytes += data.length;
|
|
19904
20149
|
if (data.length > this.maxFileSize) continue;
|
|
20150
|
+
if (totalBytes > MAX_TOTAL_UNCOMPRESSED_BYTES) {
|
|
20151
|
+
byteBudgetHit = true;
|
|
20152
|
+
break;
|
|
20153
|
+
}
|
|
19905
20154
|
const base64 = uint8ArrayToBase64(data);
|
|
19906
20155
|
const dataUrl = `data:${mimeType};base64,${base64}`;
|
|
19907
20156
|
const fileWithData = {
|
|
@@ -19911,23 +20160,36 @@ var ZipProcessor = class {
|
|
|
19911
20160
|
};
|
|
19912
20161
|
const result = await processor.process(fileWithData);
|
|
19913
20162
|
if (result && result.extractedText.trim()) {
|
|
20163
|
+
if (result.metadata?.truncated || result.imageDataUrls?.length) truncated = true;
|
|
19914
20164
|
processedContents.push({
|
|
19915
20165
|
path: entry.path,
|
|
19916
20166
|
processorName: processor.name,
|
|
19917
|
-
|
|
20167
|
+
// Page images of nested files are not forwarded, so their note must not claim them.
|
|
20168
|
+
result: { ...result, extractedText: rewriteImageNote(result, fileName, 0) }
|
|
19918
20169
|
});
|
|
19919
20170
|
}
|
|
19920
|
-
} catch {
|
|
20171
|
+
} catch (error) {
|
|
20172
|
+
logger.warn(
|
|
20173
|
+
`[ZipProcessor] Failed to process archive entry #${index + 1} (${processor.name}, ${mimeType})`,
|
|
20174
|
+
error
|
|
20175
|
+
);
|
|
19921
20176
|
}
|
|
19922
20177
|
}
|
|
19923
|
-
|
|
20178
|
+
if (byteBudgetHit) {
|
|
20179
|
+
truncated = true;
|
|
20180
|
+
notes.push(
|
|
20181
|
+
`[truncated: stopped reading the archive after ${Math.round(MAX_TOTAL_UNCOMPRESSED_BYTES / 1024 / 1024)} MB of decompressed content; later files were not extracted]`
|
|
20182
|
+
);
|
|
20183
|
+
}
|
|
20184
|
+
const output = [this.formatOutput(filteredEntries, processedContents), ...notes].join("\n");
|
|
19924
20185
|
return {
|
|
19925
20186
|
extractedText: output,
|
|
19926
20187
|
format: "markdown",
|
|
19927
20188
|
metadata: {
|
|
19928
20189
|
fileCount: filteredEntries.filter((e) => !e.isDirectory).length,
|
|
19929
20190
|
directoryCount: filteredEntries.filter((e) => e.isDirectory).length,
|
|
19930
|
-
processedFiles: processedContents.length
|
|
20191
|
+
processedFiles: processedContents.length,
|
|
20192
|
+
truncated
|
|
19931
20193
|
}
|
|
19932
20194
|
};
|
|
19933
20195
|
} catch (error) {
|
|
@@ -20029,9 +20291,10 @@ var ProcessorRegistry = class {
|
|
|
20029
20291
|
* @returns The matching processor, or null if none found
|
|
20030
20292
|
*/
|
|
20031
20293
|
findProcessor(file) {
|
|
20032
|
-
|
|
20294
|
+
const mimeType = file.type?.split(";")[0].trim().toLowerCase();
|
|
20295
|
+
if (mimeType) {
|
|
20033
20296
|
for (const processor of this.processors.values()) {
|
|
20034
|
-
if (processor.supportedMimeTypes.includes(
|
|
20297
|
+
if (processor.supportedMimeTypes.includes(mimeType)) {
|
|
20035
20298
|
return processor;
|
|
20036
20299
|
}
|
|
20037
20300
|
}
|
|
@@ -20098,8 +20361,54 @@ var ProcessorRegistry = class {
|
|
|
20098
20361
|
}
|
|
20099
20362
|
};
|
|
20100
20363
|
|
|
20364
|
+
// src/lib/processors/fileStatusNotes.ts
|
|
20365
|
+
var DEFAULT_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
20366
|
+
function formatMegabytes(bytes) {
|
|
20367
|
+
const mb = bytes / (1024 * 1024);
|
|
20368
|
+
return `${Number.isInteger(mb) ? mb : mb.toFixed(1)} MB`;
|
|
20369
|
+
}
|
|
20370
|
+
function formatFileProcessingNotes(statuses, options = {}) {
|
|
20371
|
+
const maxFileSizeBytes = options.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE_BYTES;
|
|
20372
|
+
const why = {
|
|
20373
|
+
too_large: `the file is larger than ${formatMegabytes(maxFileSizeBytes)}`,
|
|
20374
|
+
unsupported_type: "its file type is not supported for reading",
|
|
20375
|
+
no_data: "its contents were not available to the app",
|
|
20376
|
+
empty: "no text or images could be extracted from it (it may be blank)",
|
|
20377
|
+
timeout: "reading it took too long and was stopped",
|
|
20378
|
+
error: "its text could not be extracted (it may be password-protected or damaged)"
|
|
20379
|
+
};
|
|
20380
|
+
const lines = statuses.filter((s) => s.status === "skipped" || s.status === "failed").map((s) => `[${s.fileName} could not be read: ${why[s.reason ?? "error"]}]`);
|
|
20381
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
20382
|
+
}
|
|
20383
|
+
|
|
20101
20384
|
// src/lib/processors/preprocessor.ts
|
|
20102
20385
|
var MAX_TOTAL_IMAGES = 20;
|
|
20386
|
+
var DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE = 1e5;
|
|
20387
|
+
var DEFAULT_MAX_EXTRACTED_CHARS_TOTAL = 2e5;
|
|
20388
|
+
var ProcessingTimeoutError = class extends Error {
|
|
20389
|
+
constructor(timeoutMs) {
|
|
20390
|
+
super(`Timed out processing file after ${timeoutMs}ms`);
|
|
20391
|
+
this.name = "ProcessingTimeoutError";
|
|
20392
|
+
}
|
|
20393
|
+
};
|
|
20394
|
+
function truncationMarker2(kept, total, fileName) {
|
|
20395
|
+
return `
|
|
20396
|
+
[truncated: showing the first ${kept} of ${total} characters of ${fileName}]`;
|
|
20397
|
+
}
|
|
20398
|
+
function truncateText(text12, limit, fileName) {
|
|
20399
|
+
if (text12.length <= limit) return { text: text12, truncated: false };
|
|
20400
|
+
let end = Math.max(0, limit);
|
|
20401
|
+
const code = text12.charCodeAt(end - 1);
|
|
20402
|
+
if (end > 0 && code >= 55296 && code <= 56319) end--;
|
|
20403
|
+
return {
|
|
20404
|
+
text: `${text12.slice(0, end)}${truncationMarker2(end, text12.length, fileName)}`,
|
|
20405
|
+
truncated: true
|
|
20406
|
+
};
|
|
20407
|
+
}
|
|
20408
|
+
var FILE_SEPARATOR = "\n\n---\n\n";
|
|
20409
|
+
function isImageFile(file) {
|
|
20410
|
+
return (file.type ?? "").trim().toLowerCase().startsWith("image/");
|
|
20411
|
+
}
|
|
20103
20412
|
function createDefaultRegistry() {
|
|
20104
20413
|
const registry2 = new ProcessorRegistry();
|
|
20105
20414
|
registry2.register(new PdfProcessor());
|
|
@@ -20149,10 +20458,11 @@ async function preprocessFiles(files, options = {}) {
|
|
|
20149
20458
|
processors = void 0,
|
|
20150
20459
|
// undefined means use defaults
|
|
20151
20460
|
keepOriginalFiles = true,
|
|
20152
|
-
maxFileSizeBytes =
|
|
20153
|
-
// 10MB
|
|
20461
|
+
maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES,
|
|
20154
20462
|
timeoutMs = 3e4,
|
|
20155
20463
|
// 30s per file
|
|
20464
|
+
maxExtractedCharsPerFile = DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE,
|
|
20465
|
+
maxExtractedCharsTotal = DEFAULT_MAX_EXTRACTED_CHARS_TOTAL,
|
|
20156
20466
|
onProgress,
|
|
20157
20467
|
onError
|
|
20158
20468
|
} = options;
|
|
@@ -20162,23 +20472,23 @@ async function preprocessFiles(files, options = {}) {
|
|
|
20162
20472
|
extractedContent: null,
|
|
20163
20473
|
originalFiles: files,
|
|
20164
20474
|
preprocessedFileIds: [],
|
|
20475
|
+
fileStatuses: [],
|
|
20165
20476
|
metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
|
|
20166
20477
|
};
|
|
20167
20478
|
}
|
|
20168
|
-
if (processors
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
20174
|
-
};
|
|
20175
|
-
}
|
|
20176
|
-
if (processors === null) {
|
|
20479
|
+
if (processors === null || processors?.length === 0) {
|
|
20480
|
+
const fileStatuses2 = files.filter((file) => !isImageFile(file)).map((file) => ({
|
|
20481
|
+
fileId: file.id,
|
|
20482
|
+
fileName: file.name,
|
|
20483
|
+
status: "skipped",
|
|
20484
|
+
reason: "unsupported_type"
|
|
20485
|
+
}));
|
|
20177
20486
|
return {
|
|
20178
20487
|
extractedContent: null,
|
|
20179
20488
|
originalFiles: files,
|
|
20180
20489
|
preprocessedFileIds: [],
|
|
20181
|
-
|
|
20490
|
+
fileStatuses: fileStatuses2,
|
|
20491
|
+
metadata: { processedCount: 0, skippedCount: fileStatuses2.length, errorCount: 0 }
|
|
20182
20492
|
};
|
|
20183
20493
|
}
|
|
20184
20494
|
let registry2;
|
|
@@ -20196,28 +20506,38 @@ async function preprocessFiles(files, options = {}) {
|
|
|
20196
20506
|
const extractedTexts = [];
|
|
20197
20507
|
const allImageUrls = [];
|
|
20198
20508
|
const preprocessedFileIds = [];
|
|
20509
|
+
const fileStatuses = [];
|
|
20199
20510
|
let processedCount = 0;
|
|
20200
20511
|
let skippedCount = 0;
|
|
20201
20512
|
let errorCount = 0;
|
|
20513
|
+
let totalChars = 0;
|
|
20514
|
+
const overBudgetFiles = [];
|
|
20202
20515
|
for (let i = 0; i < files.length; i++) {
|
|
20203
20516
|
const file = files[i];
|
|
20517
|
+
const label = `#${i + 1} (${file.type || "unknown type"}, ${file.size} bytes)`;
|
|
20518
|
+
const skip = (reason) => {
|
|
20519
|
+
skippedCount++;
|
|
20520
|
+
fileStatuses.push({ fileId: file.id, fileName: file.name, status: "skipped", reason });
|
|
20521
|
+
};
|
|
20204
20522
|
onProgress?.(i + 1, files.length, file.name);
|
|
20523
|
+
const processor = registry2.findProcessor(file);
|
|
20524
|
+
if (!processor && isImageFile(file)) continue;
|
|
20205
20525
|
if (file.size > maxFileSizeBytes) {
|
|
20206
20526
|
logger.info(
|
|
20207
|
-
`[preprocessFiles] Skipping
|
|
20527
|
+
`[preprocessFiles] Skipping file ${label} \u2014 exceeds ${maxFileSizeBytes} byte limit`
|
|
20208
20528
|
);
|
|
20209
|
-
|
|
20529
|
+
skip("too_large");
|
|
20210
20530
|
continue;
|
|
20211
20531
|
}
|
|
20212
|
-
const processor = registry2.findProcessor(file);
|
|
20213
20532
|
if (!processor) {
|
|
20214
|
-
|
|
20533
|
+
skip("unsupported_type");
|
|
20215
20534
|
continue;
|
|
20216
20535
|
}
|
|
20536
|
+
let timer;
|
|
20217
20537
|
try {
|
|
20218
20538
|
if (!file.url) {
|
|
20219
|
-
logger.info(`[preprocessFiles] Skipping
|
|
20220
|
-
|
|
20539
|
+
logger.info(`[preprocessFiles] Skipping file ${label} \u2014 no data URL available`);
|
|
20540
|
+
skip("no_data");
|
|
20221
20541
|
continue;
|
|
20222
20542
|
}
|
|
20223
20543
|
const fileWithData = {
|
|
@@ -20226,40 +20546,76 @@ async function preprocessFiles(files, options = {}) {
|
|
|
20226
20546
|
};
|
|
20227
20547
|
const result = await Promise.race([
|
|
20228
20548
|
processor.process(fileWithData),
|
|
20229
|
-
new Promise(
|
|
20230
|
-
|
|
20231
|
-
)
|
|
20549
|
+
new Promise((_, reject) => {
|
|
20550
|
+
timer = setTimeout(() => reject(new ProcessingTimeoutError(timeoutMs)), timeoutMs);
|
|
20551
|
+
})
|
|
20232
20552
|
]);
|
|
20233
20553
|
if (result && result.extractedText.trim()) {
|
|
20234
|
-
const
|
|
20235
|
-
|
|
20236
|
-
result.extractedText,
|
|
20237
|
-
result.format
|
|
20238
|
-
);
|
|
20239
|
-
extractedTexts.push(formattedContent);
|
|
20554
|
+
const separatorLength = extractedTexts.length > 0 ? FILE_SEPARATOR.length : 0;
|
|
20555
|
+
const remaining = maxExtractedCharsTotal - totalChars - separatorLength - formatExtractedContent(file.name, "", result.format).length;
|
|
20240
20556
|
preprocessedFileIds.push(file.id);
|
|
20241
20557
|
processedCount++;
|
|
20242
|
-
|
|
20243
|
-
|
|
20244
|
-
|
|
20245
|
-
|
|
20246
|
-
|
|
20558
|
+
const fullLength = result.extractedText.length;
|
|
20559
|
+
if (fullLength > remaining && remaining - truncationMarker2(fullLength, fullLength, file.name).length <= 0) {
|
|
20560
|
+
overBudgetFiles.push(file.name);
|
|
20561
|
+
fileStatuses.push({ fileId: file.id, fileName: file.name, status: "truncated" });
|
|
20562
|
+
continue;
|
|
20247
20563
|
}
|
|
20564
|
+
const images = result.imageDataUrls ?? [];
|
|
20565
|
+
const keptImages = Math.min(
|
|
20566
|
+
images.length,
|
|
20567
|
+
Math.max(0, MAX_TOTAL_IMAGES - allImageUrls.length)
|
|
20568
|
+
);
|
|
20569
|
+
allImageUrls.push(...images.slice(0, keptImages));
|
|
20570
|
+
const text12 = rewriteImageNote(result, file.name, keptImages);
|
|
20571
|
+
const fitsWhole = text12.length <= Math.min(maxExtractedCharsPerFile, remaining);
|
|
20572
|
+
const limit = fitsWhole ? text12.length : Math.min(
|
|
20573
|
+
maxExtractedCharsPerFile,
|
|
20574
|
+
remaining - truncationMarker2(text12.length, text12.length, file.name).length
|
|
20575
|
+
);
|
|
20576
|
+
const capped = truncateText(text12, Math.max(0, limit), file.name);
|
|
20577
|
+
const formattedContent = formatExtractedContent(file.name, capped.text, result.format);
|
|
20578
|
+
totalChars += separatorLength + formattedContent.length;
|
|
20579
|
+
extractedTexts.push(formattedContent);
|
|
20580
|
+
const truncated = capped.truncated || keptImages < images.length || result.metadata?.truncated === true;
|
|
20581
|
+
fileStatuses.push({
|
|
20582
|
+
fileId: file.id,
|
|
20583
|
+
fileName: file.name,
|
|
20584
|
+
status: truncated ? "truncated" : keptImages > 0 ? "rendered_as_images" : "extracted"
|
|
20585
|
+
});
|
|
20248
20586
|
} else {
|
|
20249
|
-
|
|
20587
|
+
skip("empty");
|
|
20250
20588
|
}
|
|
20251
20589
|
} catch (error) {
|
|
20252
20590
|
errorCount++;
|
|
20253
|
-
|
|
20591
|
+
const timedOut = error instanceof ProcessingTimeoutError;
|
|
20592
|
+
fileStatuses.push({
|
|
20593
|
+
fileId: file.id,
|
|
20594
|
+
fileName: file.name,
|
|
20595
|
+
status: "failed",
|
|
20596
|
+
reason: timedOut ? "timeout" : "error"
|
|
20597
|
+
});
|
|
20598
|
+
logger.error(
|
|
20599
|
+
`[preprocessFiles] Error processing file ${label} with ${processor.name}:`,
|
|
20600
|
+
error
|
|
20601
|
+
);
|
|
20254
20602
|
onError?.(file.name, error instanceof Error ? error : new Error(String(error)));
|
|
20603
|
+
} finally {
|
|
20604
|
+
clearTimeout(timer);
|
|
20255
20605
|
}
|
|
20256
20606
|
}
|
|
20257
|
-
|
|
20607
|
+
if (overBudgetFiles.length > 0) {
|
|
20608
|
+
extractedTexts.push(
|
|
20609
|
+
`[truncated: the attachment text limit (${maxExtractedCharsTotal} characters) was reached; the contents of ${overBudgetFiles.join(", ")} were not included]`
|
|
20610
|
+
);
|
|
20611
|
+
}
|
|
20612
|
+
const extractedContent = extractedTexts.length > 0 ? extractedTexts.join(FILE_SEPARATOR) : null;
|
|
20258
20613
|
return {
|
|
20259
20614
|
extractedContent,
|
|
20260
20615
|
imageContentUrls: allImageUrls.length > 0 ? allImageUrls : void 0,
|
|
20261
20616
|
originalFiles: keepOriginalFiles ? files : void 0,
|
|
20262
20617
|
preprocessedFileIds,
|
|
20618
|
+
fileStatuses,
|
|
20263
20619
|
metadata: { processedCount, skippedCount, errorCount }
|
|
20264
20620
|
};
|
|
20265
20621
|
}
|
|
@@ -21614,7 +21970,7 @@ async function storedToLlmapiMessage(stored, encryptionKey, resolveMediaByIds) {
|
|
|
21614
21970
|
} else if (encryptionKey && isOPFSSupported()) {
|
|
21615
21971
|
try {
|
|
21616
21972
|
const result = await readEncryptedFile(file.id, encryptionKey);
|
|
21617
|
-
if (result) {
|
|
21973
|
+
if (result && result.blob.type.startsWith("image/")) {
|
|
21618
21974
|
const dataUri = await blobToDataUri(result.blob);
|
|
21619
21975
|
imageParts.push({
|
|
21620
21976
|
type: "image_url",
|
|
@@ -22555,7 +22911,8 @@ function useChatStorage(options) {
|
|
|
22555
22911
|
conversationId: explicitConversationId,
|
|
22556
22912
|
parentMessageId,
|
|
22557
22913
|
assistantUniqueId,
|
|
22558
|
-
piiRedaction: requestPiiRedaction
|
|
22914
|
+
piiRedaction: requestPiiRedaction,
|
|
22915
|
+
onFileProcessingResult
|
|
22559
22916
|
} = args;
|
|
22560
22917
|
const resolvePiiForCall = (conversationIdForCall) => {
|
|
22561
22918
|
const { redactor, forInnerSend } = resolveCallPii(
|
|
@@ -22751,9 +23108,12 @@ function useChatStorage(options) {
|
|
|
22751
23108
|
const contentForStorage = resolveStoredUserContent(storedUserContent, extracted.content);
|
|
22752
23109
|
const filesForStorage = files ?? extracted.files;
|
|
22753
23110
|
let fileContextForRequest;
|
|
23111
|
+
let fileContextIsCurrentTurn = false;
|
|
22754
23112
|
let preprocessedFileIds = [];
|
|
22755
23113
|
let imageContentUrls;
|
|
23114
|
+
let fileProcessingNotes = null;
|
|
22756
23115
|
if (filesForStorage && filesForStorage.length > 0) {
|
|
23116
|
+
let fileStatuses;
|
|
22757
23117
|
try {
|
|
22758
23118
|
const preprocessingResult = await preprocessFiles(filesForStorage, {
|
|
22759
23119
|
processors: fileProcessors,
|
|
@@ -22761,16 +23121,27 @@ function useChatStorage(options) {
|
|
|
22761
23121
|
});
|
|
22762
23122
|
if (preprocessingResult.extractedContent) {
|
|
22763
23123
|
fileContextForRequest = preprocessingResult.extractedContent;
|
|
23124
|
+
fileContextIsCurrentTurn = true;
|
|
22764
23125
|
preprocessedFileIds = preprocessingResult.preprocessedFileIds;
|
|
22765
23126
|
}
|
|
22766
23127
|
if (preprocessingResult.imageContentUrls?.length) {
|
|
22767
23128
|
imageContentUrls = preprocessingResult.imageContentUrls;
|
|
22768
23129
|
}
|
|
23130
|
+
fileStatuses = preprocessingResult.fileStatuses;
|
|
22769
23131
|
} catch (err) {
|
|
22770
23132
|
getLogger().error(
|
|
22771
23133
|
"[sendMessage] File preprocessing failed \u2014 continuing without file context:",
|
|
22772
23134
|
err
|
|
22773
23135
|
);
|
|
23136
|
+
fileStatuses = filesForStorage.filter((f) => !(f.type ?? "").toLowerCase().startsWith("image/")).map((f) => ({ fileId: f.id, fileName: f.name, status: "failed", reason: "error" }));
|
|
23137
|
+
}
|
|
23138
|
+
fileProcessingNotes = formatFileProcessingNotes(fileStatuses, {
|
|
23139
|
+
maxFileSizeBytes: fileProcessingOptions?.maxFileSizeBytes
|
|
23140
|
+
});
|
|
23141
|
+
try {
|
|
23142
|
+
onFileProcessingResult?.(fileStatuses);
|
|
23143
|
+
} catch (err) {
|
|
23144
|
+
getLogger().warn("[sendMessage] onFileProcessingResult threw \u2014 ignoring:", err);
|
|
22774
23145
|
}
|
|
22775
23146
|
}
|
|
22776
23147
|
let convId;
|
|
@@ -22944,7 +23315,11 @@ function useChatStorage(options) {
|
|
|
22944
23315
|
if (part.type === "text") return true;
|
|
22945
23316
|
if (part.type === "input_file" && part.file) {
|
|
22946
23317
|
const fileId = part.file.file_id;
|
|
22947
|
-
|
|
23318
|
+
if (fileId) return !preprocessedFileIds.includes(fileId);
|
|
23319
|
+
const { file_data: fileData, file_url: fileUrl } = part.file;
|
|
23320
|
+
return !filesForStorage?.some(
|
|
23321
|
+
(f) => preprocessedFileIds.includes(f.id) && !!f.url && (f.url === fileData || f.url === fileUrl)
|
|
23322
|
+
);
|
|
22948
23323
|
}
|
|
22949
23324
|
if (part.type === "image_url" && part.image_url?.url) {
|
|
22950
23325
|
const matchesPreprocessed = filesForStorage?.some(
|
|
@@ -22979,6 +23354,13 @@ function useChatStorage(options) {
|
|
|
22979
23354
|
};
|
|
22980
23355
|
}
|
|
22981
23356
|
}
|
|
23357
|
+
const currentTurnFileText = [
|
|
23358
|
+
fileContextIsCurrentTurn ? fileContextForRequest : void 0,
|
|
23359
|
+
fileProcessingNotes
|
|
23360
|
+
].filter(Boolean).join("\n\n");
|
|
23361
|
+
if (currentTurnFileText) {
|
|
23362
|
+
messagesToSend = attachFileContextToLastUserMessage(messagesToSend, currentTurnFileText);
|
|
23363
|
+
}
|
|
22982
23364
|
let userFileIds = [];
|
|
22983
23365
|
if (filesForStorage && filesForStorage.length > 0 && walletAddress && isEncryptionReady()) {
|
|
22984
23366
|
userFileIds = await storeUserFilesInOPFS(filesForStorage, walletAddress, convId);
|
|
@@ -22989,7 +23371,11 @@ function useChatStorage(options) {
|
|
|
22989
23371
|
content: contentForStorage,
|
|
22990
23372
|
fileIds: userFileIds.length > 0 ? userFileIds : void 0,
|
|
22991
23373
|
model,
|
|
22992
|
-
// Store
|
|
23374
|
+
// Store the file context — this turn's, or the one recalled from an earlier row — in the
|
|
23375
|
+
// thinking field for retrieval in follow-up messages. Re-storing recalled context is the
|
|
23376
|
+
// carry-forward: recall only scans the last `maxHistoryMessages` rows, so without it a
|
|
23377
|
+
// file's text is lost once the turn that attached it scrolls out of the window. Failure
|
|
23378
|
+
// notes are never stored: only `[Extracted content from …]` text, which recall keys on.
|
|
22993
23379
|
thinking: fileContextForRequest,
|
|
22994
23380
|
parentMessageId
|
|
22995
23381
|
};
|
|
@@ -23147,7 +23533,7 @@ function useChatStorage(options) {
|
|
|
23147
23533
|
headers,
|
|
23148
23534
|
memoryContext,
|
|
23149
23535
|
searchContext,
|
|
23150
|
-
fileContext: fileContextForRequest,
|
|
23536
|
+
fileContext: fileContextIsCurrentTurn ? void 0 : fileContextForRequest,
|
|
23151
23537
|
toolGuidance: computeToolGuidance(
|
|
23152
23538
|
filteredServerTools,
|
|
23153
23539
|
filteredClientTools,
|
|
@@ -34428,8 +34814,10 @@ export {
|
|
|
34428
34814
|
appFileToStored,
|
|
34429
34815
|
archiveVaultMemoryOp,
|
|
34430
34816
|
assembleMemoryContext,
|
|
34817
|
+
attachFileContextToLastUserMessage,
|
|
34431
34818
|
backfillMemoryEntityUserIdsOp,
|
|
34432
34819
|
backfillMemoryTopicsOp,
|
|
34820
|
+
buildAttachedFilesText,
|
|
34433
34821
|
capHopsForDensity,
|
|
34434
34822
|
chatStorageMigrations,
|
|
34435
34823
|
chatStorageSchema,
|
|
@@ -34535,6 +34923,7 @@ export {
|
|
|
34535
34923
|
findById,
|
|
34536
34924
|
findMatchingTools,
|
|
34537
34925
|
findParentOfId,
|
|
34926
|
+
formatFileProcessingNotes,
|
|
34538
34927
|
generateConversationId,
|
|
34539
34928
|
generateEmbedding,
|
|
34540
34929
|
generateEmbeddings,
|
|
@@ -34634,6 +35023,7 @@ export {
|
|
|
34634
35023
|
insertAfterId,
|
|
34635
35024
|
insertChild,
|
|
34636
35025
|
isAnumaTag,
|
|
35026
|
+
isAttachedFilesText,
|
|
34637
35027
|
isCalendarCallback,
|
|
34638
35028
|
isDegradedTopicSkip,
|
|
34639
35029
|
isDriveCallback,
|