@anuma/sdk 6.26.0 → 6.26.2

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.
@@ -18539,6 +18539,15 @@ async function assembleDecryptedMessage(message, address) {
18539
18539
  // src/lib/db/chat/types.ts
18540
18540
  import { v7 as uuidv7 } from "uuid";
18541
18541
 
18542
+ // src/lib/chat/fileContext.ts
18543
+ var ATTACHED_FILES_OPEN_TAG = "<attached_files>";
18544
+ var ATTACHED_FILES_CLOSE_TAG = "</attached_files>";
18545
+ var ATTACHED_FILES_PREFIX = `${ATTACHED_FILES_OPEN_TAG}
18546
+ 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.`;
18547
+ function isAttachedFilesText(text12) {
18548
+ return typeof text12 === "string" && text12.startsWith(ATTACHED_FILES_PREFIX) && text12.endsWith(ATTACHED_FILES_CLOSE_TAG);
18549
+ }
18550
+
18542
18551
  // src/lib/chat/useChat/strategies/types.ts
18543
18552
  function isChatCompletionResponse(r) {
18544
18553
  if (r.object === "response") return false;
@@ -32166,14 +32175,37 @@ var WalletPoller = class {
32166
32175
  if (typeof process !== "undefined" && typeof process.umask !== "function") {
32167
32176
  process.umask = (_mask) => 18;
32168
32177
  }
32178
+ var MAX_ROWS_PER_SHEET = 2e3;
32179
+ function csvField(value) {
32180
+ return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
32181
+ }
32182
+ function uniqueHeaders(raw, columnCount) {
32183
+ const bases = [];
32184
+ for (let col = 1; col <= columnCount; col++) bases.push(raw[col]?.trim() || `Column${col}`);
32185
+ const taken = new Set(bases);
32186
+ const emitted = /* @__PURE__ */ new Set();
32187
+ return bases.map((base) => {
32188
+ if (!emitted.has(base)) {
32189
+ emitted.add(base);
32190
+ return base;
32191
+ }
32192
+ let n = 2;
32193
+ while (taken.has(`${base}_${n}`)) n++;
32194
+ const name = `${base}_${n}`;
32195
+ taken.add(name);
32196
+ emitted.add(name);
32197
+ return name;
32198
+ });
32199
+ }
32169
32200
  var ExcelProcessor = class {
32170
- constructor() {
32201
+ constructor(options = {}) {
32171
32202
  this.name = "excel";
32172
32203
  this.supportedMimeTypes = [
32173
32204
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
32174
32205
  // .xlsx
32175
32206
  ];
32176
32207
  this.supportedExtensions = [".xlsx"];
32208
+ this.maxRowsPerSheet = options.maxRowsPerSheet ?? MAX_ROWS_PER_SHEET;
32177
32209
  }
32178
32210
  async loadExcelJS() {
32179
32211
  const mod = await import("exceljs");
@@ -32188,31 +32220,51 @@ var ExcelProcessor = class {
32188
32220
  if (workbook.worksheets.length === 0) {
32189
32221
  return null;
32190
32222
  }
32191
- const jsonData = {};
32223
+ let truncated = false;
32224
+ const sections = [];
32192
32225
  for (const worksheet of workbook.worksheets) {
32193
- const rows = [];
32194
- const headerRow = worksheet.getRow(1);
32195
- const headers = [];
32196
- headerRow.eachCell({ includeEmpty: true }, (cell, colNumber) => {
32197
- headers[colNumber] = cell.text || `Column${colNumber}`;
32226
+ const rawHeaders = [];
32227
+ let columnCount = 0;
32228
+ worksheet.getRow(1).eachCell({ includeEmpty: true }, (cell, colNumber) => {
32229
+ rawHeaders[colNumber] = cell.text;
32230
+ columnCount = Math.max(columnCount, colNumber);
32198
32231
  });
32232
+ const rows = [];
32233
+ let totalRows = 0;
32199
32234
  worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
32200
32235
  if (rowNumber === 1) return;
32201
- const rowData = {};
32236
+ totalRows++;
32237
+ if (rows.length >= this.maxRowsPerSheet) return;
32238
+ const values = [];
32202
32239
  row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
32203
- const key = headers[colNumber] || `Column${colNumber}`;
32204
- rowData[key] = this.resolveCellValue(cell.value);
32240
+ values[colNumber - 1] = String(this.resolveCellValue(cell.value));
32241
+ columnCount = Math.max(columnCount, colNumber);
32205
32242
  });
32206
- rows.push(rowData);
32243
+ rows.push(values);
32207
32244
  });
32208
- jsonData[worksheet.name] = rows;
32245
+ const headers = uniqueHeaders(rawHeaders, columnCount);
32246
+ const lines = [headers, ...rows].map(
32247
+ (values) => headers.map((_, i) => csvField(values[i] ?? "")).join(",")
32248
+ );
32249
+ let section = `## Sheet: ${worksheet.name}
32250
+
32251
+ \`\`\`csv
32252
+ ${lines.join("\n")}
32253
+ \`\`\``;
32254
+ if (totalRows > rows.length) {
32255
+ truncated = true;
32256
+ section += `
32257
+ [truncated: showing the first ${rows.length} of ${totalRows} data rows of sheet "${worksheet.name}"]`;
32258
+ }
32259
+ sections.push(section);
32209
32260
  }
32210
32261
  return {
32211
- extractedText: JSON.stringify(jsonData, null, 2),
32212
- format: "json",
32262
+ extractedText: sections.join("\n\n"),
32263
+ format: "markdown",
32213
32264
  metadata: {
32214
32265
  sheetCount: workbook.worksheets.length,
32215
- sheetNames: workbook.worksheets.map((ws) => ws.name)
32266
+ sheetNames: workbook.worksheets.map((ws) => ws.name),
32267
+ truncated
32216
32268
  }
32217
32269
  };
32218
32270
  } catch (error) {
@@ -32238,6 +32290,9 @@ var ExcelProcessor = class {
32238
32290
  // src/lib/pdf.ts
32239
32291
  var pdfjsModule = null;
32240
32292
  var workerConfigured = false;
32293
+ var RENDER_SCALE = 1.5;
32294
+ var MAX_RENDER_SIDE_PX = 1600;
32295
+ var JPEG_QUALITY = 0.8;
32241
32296
  async function getPdfjs() {
32242
32297
  if (!pdfjsModule) {
32243
32298
  pdfjsModule = await import("pdfjs-dist");
@@ -32248,46 +32303,126 @@ async function getPdfjs() {
32248
32303
  }
32249
32304
  return pdfjsModule;
32250
32305
  }
32251
- async function extractTextFromPdf(pdfDataUrl) {
32306
+ async function withPdfDocument(pdfDataUrl, fn) {
32252
32307
  const pdfjs = await getPdfjs();
32253
32308
  const loadingTask = pdfjs.getDocument(pdfDataUrl);
32254
- const pdf = await loadingTask.promise;
32255
- const textParts = [];
32256
- for (let i = 1; i <= pdf.numPages; i++) {
32257
- const page = await pdf.getPage(i);
32258
- const textContent = await page.getTextContent();
32259
- const pageText = textContent.items.map((item) => "str" in item ? item.str : "").join(" ");
32260
- if (pageText.trim()) {
32261
- textParts.push(pageText);
32262
- }
32309
+ let pdf;
32310
+ try {
32311
+ pdf = await loadingTask.promise;
32312
+ } catch (error) {
32313
+ await loadingTask.destroy().catch(() => void 0);
32314
+ throw error;
32315
+ }
32316
+ try {
32317
+ return await fn(pdf);
32318
+ } finally {
32319
+ await pdf.destroy().catch(() => void 0);
32263
32320
  }
32264
- return textParts.join("\n\n");
32265
32321
  }
32266
- async function convertPdfToImages(pdfDataUrl, maxPages) {
32267
- const images = [];
32268
- const pdfjs = await getPdfjs();
32269
- const loadingTask = pdfjs.getDocument(pdfDataUrl);
32270
- const pdf = await loadingTask.promise;
32271
- const pageLimit = maxPages !== void 0 ? Math.min(maxPages, pdf.numPages) : pdf.numPages;
32272
- for (let i = 1; i <= pageLimit; i++) {
32273
- const page = await pdf.getPage(i);
32274
- const viewport = page.getViewport({ scale: 1.5 });
32275
- const canvas = document.createElement("canvas");
32276
- const context = canvas.getContext("2d");
32277
- if (!context) continue;
32278
- canvas.height = viewport.height;
32279
- canvas.width = viewport.width;
32280
- await page.render({
32281
- canvasContext: context,
32282
- viewport
32283
- }).promise;
32284
- images.push(canvas.toDataURL("image/png"));
32285
- }
32286
- return images;
32322
+ function joinPdfTextItems(items) {
32323
+ let text12 = "";
32324
+ for (const item of items) {
32325
+ if (!("str" in item)) continue;
32326
+ const { str, hasEOL } = item;
32327
+ text12 += str + (hasEOL ? "\n" : " ");
32328
+ }
32329
+ return text12.replace(/[ \t]+\n/g, "\n").trim();
32330
+ }
32331
+ function computeRenderScale(width, height) {
32332
+ const longest = Math.max(width, height);
32333
+ if (!(longest > 0)) return RENDER_SCALE;
32334
+ return Math.min(RENDER_SCALE, MAX_RENDER_SIDE_PX / longest);
32335
+ }
32336
+ async function extractPdfPageTexts(pdfDataUrl) {
32337
+ return withPdfDocument(pdfDataUrl, async (pdf) => {
32338
+ const pageTexts = [];
32339
+ for (let i = 1; i <= pdf.numPages; i++) {
32340
+ const page = await pdf.getPage(i);
32341
+ const textContent = await page.getTextContent();
32342
+ pageTexts.push(joinPdfTextItems(textContent.items));
32343
+ }
32344
+ return pageTexts;
32345
+ });
32346
+ }
32347
+ async function renderPdfPages(pdfDataUrl, maxPages, pageNumbers) {
32348
+ return withPdfDocument(pdfDataUrl, async (pdf) => {
32349
+ const requested = pageNumbers?.filter((n) => n >= 1 && n <= pdf.numPages) ?? Array.from(
32350
+ { length: maxPages !== void 0 ? Math.min(maxPages, pdf.numPages) : pdf.numPages },
32351
+ (_, i) => i + 1
32352
+ );
32353
+ const pages = [];
32354
+ for (const pageNumber of requested) {
32355
+ const page = await pdf.getPage(pageNumber);
32356
+ const base = page.getViewport({ scale: 1 });
32357
+ const viewport = page.getViewport({ scale: computeRenderScale(base.width, base.height) });
32358
+ const canvas = document.createElement("canvas");
32359
+ const context = canvas.getContext("2d");
32360
+ if (!context) continue;
32361
+ canvas.height = Math.floor(viewport.height);
32362
+ canvas.width = Math.floor(viewport.width);
32363
+ context.fillStyle = "#ffffff";
32364
+ context.fillRect(0, 0, canvas.width, canvas.height);
32365
+ await page.render({
32366
+ canvasContext: context,
32367
+ viewport,
32368
+ background: "#ffffff"
32369
+ }).promise;
32370
+ pages.push({ pageNumber, dataUrl: canvas.toDataURL("image/jpeg", JPEG_QUALITY) });
32371
+ canvas.width = 0;
32372
+ canvas.height = 0;
32373
+ }
32374
+ return { pages, pageCount: pdf.numPages };
32375
+ });
32287
32376
  }
32288
32377
 
32289
32378
  // src/lib/processors/PdfProcessor.ts
32290
32379
  var MAX_IMAGE_PAGES = 20;
32380
+ var MIN_PAGE_TEXT_CHARS = 20;
32381
+ function selectPdfImagePages(pageTexts, maxImagePages = MAX_IMAGE_PAGES) {
32382
+ const needImage = [];
32383
+ pageTexts.forEach((text12, index) => {
32384
+ if (text12.trim().length < MIN_PAGE_TEXT_CHARS) needImage.push(index + 1);
32385
+ });
32386
+ const limit = Math.max(0, maxImagePages);
32387
+ return { rendered: needImage.slice(0, limit), omitted: needImage.slice(limit) };
32388
+ }
32389
+ function formatPageList(pages) {
32390
+ const ranges = [];
32391
+ for (let i = 0; i < pages.length; i++) {
32392
+ const start = pages[i];
32393
+ let end = start;
32394
+ while (i + 1 < pages.length && pages[i + 1] === end + 1) end = pages[++i];
32395
+ ranges.push(start === end ? `${start}` : `${start}-${end}`);
32396
+ }
32397
+ return `${pages.length === 1 ? "page" : "pages"} ${ranges.join(", ")}`;
32398
+ }
32399
+ function buildPdfImageNote(fileName, includedPages, omittedPages, pageCount) {
32400
+ const imageOnlyCount = includedPages.length + omittedPages.length;
32401
+ const allPages = [...includedPages, ...omittedPages].sort((a, b) => a - b);
32402
+ 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)})`;
32403
+ if (includedPages.length === 0) {
32404
+ return `[${subject} \u2014 ${formatPageList(omittedPages)} could not be included as images because of the image limit, so their content is not available]`;
32405
+ }
32406
+ const included = `${formatPageList(includedPages)} rendered as images and included in this message for visual analysis`;
32407
+ if (omittedPages.length === 0) return `[${subject} \u2014 ${included}]`;
32408
+ return `[${subject} \u2014 ${included}; ${formatPageList(omittedPages)} not included because of the image limit, so their content is not available]`;
32409
+ }
32410
+ function rewriteImageNote(result, fileName, keptImages) {
32411
+ const images = result.imageDataUrls ?? [];
32412
+ const note = result.metadata?.imageNote;
32413
+ if (!note || keptImages >= images.length) return result.extractedText;
32414
+ const imagePages = result.metadata?.imagePages ?? [];
32415
+ const omittedPages = result.metadata?.omittedImagePages ?? [];
32416
+ const pageCount = result.metadata?.pageCount;
32417
+ const kept = Math.max(0, keptImages);
32418
+ const newNote = buildPdfImageNote(
32419
+ fileName,
32420
+ imagePages.slice(0, kept),
32421
+ [...imagePages.slice(kept), ...omittedPages].sort((a, b) => a - b),
32422
+ pageCount
32423
+ );
32424
+ return result.extractedText.replace(note, () => newNote);
32425
+ }
32291
32426
  var PdfProcessor = class {
32292
32427
  constructor() {
32293
32428
  this.name = "pdf";
@@ -32296,43 +32431,65 @@ var PdfProcessor = class {
32296
32431
  }
32297
32432
  async process(file) {
32298
32433
  const logger = getLogger();
32434
+ let pageTexts = null;
32299
32435
  try {
32300
- const text12 = await extractTextFromPdf(file.dataUrl);
32301
- if (text12 && text12.trim().length > 0) {
32302
- return {
32303
- extractedText: text12,
32304
- format: "plain"
32305
- };
32306
- }
32307
- logger.info(
32308
- `[PdfProcessor] Text extraction returned empty for "${file.name}" \u2014 falling back to image conversion`
32309
- );
32436
+ pageTexts = await extractPdfPageTexts(file.dataUrl);
32310
32437
  } catch (textError) {
32311
32438
  logger.warn(
32312
- `[PdfProcessor] Text extraction failed for "${file.name}" \u2014 falling back to image conversion:`,
32439
+ "[PdfProcessor] Text extraction failed \u2014 falling back to image conversion:",
32313
32440
  textError
32314
32441
  );
32315
32442
  }
32443
+ const text12 = (pageTexts ?? []).filter((t) => t.trim()).join("\n\n");
32444
+ const { rendered, omitted } = pageTexts ? selectPdfImagePages(pageTexts) : { rendered: [], omitted: [] };
32445
+ if (pageTexts && rendered.length === 0 && omitted.length === 0) {
32446
+ return text12.trim() ? { extractedText: text12, format: "plain" } : null;
32447
+ }
32448
+ let renderedPages;
32449
+ let pageCount = pageTexts?.length;
32316
32450
  try {
32317
- const images = await convertPdfToImages(file.dataUrl, MAX_IMAGE_PAGES);
32318
- if (images.length === 0) {
32319
- logger.warn(`[PdfProcessor] Image conversion also returned empty for "${file.name}"`);
32320
- return null;
32451
+ if (pageTexts) {
32452
+ renderedPages = (await renderPdfPages(file.dataUrl, void 0, rendered)).pages;
32453
+ } else {
32454
+ const result = await renderPdfPages(file.dataUrl, MAX_IMAGE_PAGES);
32455
+ renderedPages = result.pages;
32456
+ pageCount = result.pageCount;
32321
32457
  }
32322
- logger.info(`[PdfProcessor] Converted ${images.length} page(s) of "${file.name}" to images`);
32323
- return {
32324
- // Contextual note for the LLM — keep it self-contained since the
32325
- // images are only injected in the current request and not persisted
32326
- // for follow-up turns.
32327
- extractedText: `[${file.name}: scanned/image-based PDF \u2014 ${images.length} page(s) rendered as images and included in this message for visual analysis]`,
32328
- format: "plain",
32329
- imageDataUrls: images,
32330
- metadata: { pageCount: images.length }
32331
- };
32332
32458
  } catch (imageError) {
32333
- logger.error(`[PdfProcessor] Image conversion also failed for "${file.name}":`, imageError);
32334
- throw imageError;
32459
+ if (!text12.trim()) {
32460
+ logger.error("[PdfProcessor] Image conversion also failed:", imageError);
32461
+ throw imageError;
32462
+ }
32463
+ logger.warn("[PdfProcessor] Image conversion failed \u2014 keeping text only:", imageError);
32464
+ renderedPages = [];
32465
+ }
32466
+ const images = renderedPages.map((p) => p.dataUrl);
32467
+ const imagePages = renderedPages.map((p) => p.pageNumber);
32468
+ const included = new Set(imagePages);
32469
+ const needImage = pageTexts ? [...rendered, ...omitted] : Array.from({ length: pageCount ?? 0 }, (_, i) => i + 1);
32470
+ const omittedPages = needImage.filter((n) => !included.has(n)).sort((a, b) => a - b);
32471
+ if (images.length === 0 && !text12.trim()) {
32472
+ logger.warn("[PdfProcessor] Image conversion also returned empty");
32473
+ return null;
32335
32474
  }
32475
+ logger.info(
32476
+ `[PdfProcessor] Rendered ${images.length} page(s) as images, ${omittedPages.length} image-only page(s) omitted`
32477
+ );
32478
+ const imageNote = buildPdfImageNote(file.name, imagePages, omittedPages, pageCount);
32479
+ return {
32480
+ extractedText: text12.trim() ? `${imageNote}
32481
+
32482
+ ${text12}` : imageNote,
32483
+ format: "plain",
32484
+ imageDataUrls: images.length > 0 ? images : void 0,
32485
+ metadata: {
32486
+ pageCount,
32487
+ imageNote,
32488
+ imagePages,
32489
+ omittedImagePages: omittedPages,
32490
+ truncated: omittedPages.length > 0
32491
+ }
32492
+ };
32336
32493
  }
32337
32494
  };
32338
32495
 
@@ -32373,6 +32530,21 @@ var EXT = {
32373
32530
  CFG: ".cfg",
32374
32531
  CONF: ".conf"
32375
32532
  };
32533
+ function decodeText(bytes) {
32534
+ if (bytes[0] === 255 && bytes[1] === 254) {
32535
+ return new TextDecoder("utf-16le", { fatal: false }).decode(bytes.subarray(2));
32536
+ }
32537
+ if (bytes[0] === 254 && bytes[1] === 255) {
32538
+ const swapped = new Uint8Array(bytes.length - 2);
32539
+ for (let i = 2; i + 1 < bytes.length; i += 2) {
32540
+ swapped[i - 2] = bytes[i + 1];
32541
+ swapped[i - 1] = bytes[i];
32542
+ }
32543
+ return new TextDecoder("utf-16le", { fatal: false }).decode(swapped);
32544
+ }
32545
+ const hasUtf8Bom = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191;
32546
+ return new TextDecoder("utf-8", { fatal: false }).decode(hasUtf8Bom ? bytes.subarray(3) : bytes);
32547
+ }
32376
32548
  var TextProcessor = class {
32377
32549
  constructor() {
32378
32550
  this.name = "text";
@@ -32382,7 +32554,7 @@ var TextProcessor = class {
32382
32554
  async process(file) {
32383
32555
  try {
32384
32556
  const arrayBuffer = await dataUrlToArrayBuffer(file.dataUrl);
32385
- const text12 = new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
32557
+ const text12 = decodeText(new Uint8Array(arrayBuffer));
32386
32558
  if (!text12.trim()) {
32387
32559
  return null;
32388
32560
  }
@@ -32405,7 +32577,7 @@ var TextProcessor = class {
32405
32577
  * rather than prose).
32406
32578
  */
32407
32579
  resolveFormat(file) {
32408
- const type = file.type?.toLowerCase() ?? "";
32580
+ const type = file.type?.split(";")[0].trim().toLowerCase() ?? "";
32409
32581
  const name = file.name?.toLowerCase() ?? "";
32410
32582
  if (type === MIME.MARKDOWN || type === MIME.X_MARKDOWN || name.endsWith(EXT.MD) || name.endsWith(EXT.MARKDOWN)) {
32411
32583
  return "markdown";
@@ -32461,6 +32633,12 @@ var WordProcessor = class {
32461
32633
 
32462
32634
  // src/lib/processors/ZipProcessor.ts
32463
32635
  import JSZip from "jszip";
32636
+ var MAX_ZIP_ENTRIES = 1e3;
32637
+ var MAX_TOTAL_UNCOMPRESSED_BYTES = 50 * 1024 * 1024;
32638
+ function declaredUncompressedSize(zipObject) {
32639
+ const size = zipObject._data?.uncompressedSize;
32640
+ return typeof size === "number" ? size : void 0;
32641
+ }
32464
32642
  var ZipProcessor = class {
32465
32643
  constructor(options = {}) {
32466
32644
  this.name = "zip";
@@ -32495,14 +32673,35 @@ var ZipProcessor = class {
32495
32673
  zipObject: zipEntry
32496
32674
  });
32497
32675
  });
32498
- const filteredEntries = this.includeHidden ? entries : entries.filter((entry) => !this.isHidden(entry.path));
32499
- filteredEntries.sort((a, b) => {
32676
+ const visibleEntries = this.includeHidden ? entries : entries.filter((entry) => !this.isHidden(entry.path));
32677
+ visibleEntries.sort((a, b) => {
32500
32678
  if (a.isDirectory !== b.isDirectory) {
32501
32679
  return a.isDirectory ? -1 : 1;
32502
32680
  }
32503
32681
  return a.path.localeCompare(b.path);
32504
32682
  });
32505
- for (const entry of filteredEntries) {
32683
+ const visibleDirs = visibleEntries.filter((e) => e.isDirectory);
32684
+ const visibleFiles = visibleEntries.filter((e) => !e.isDirectory);
32685
+ const listedDirs = visibleDirs.slice(0, MAX_ZIP_ENTRIES);
32686
+ const listedFiles = visibleFiles.slice(0, MAX_ZIP_ENTRIES);
32687
+ const filteredEntries = [...listedDirs, ...listedFiles];
32688
+ const notes = [];
32689
+ if (visibleFiles.length > listedFiles.length) {
32690
+ notes.push(
32691
+ `[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]`
32692
+ );
32693
+ }
32694
+ if (visibleDirs.length > listedDirs.length) {
32695
+ notes.push(
32696
+ `[truncated: listing the first ${listedDirs.length} of ${visibleDirs.length} directories]`
32697
+ );
32698
+ }
32699
+ let totalBytes = 0;
32700
+ let byteBudgetHit = false;
32701
+ let truncated = notes.length > 0;
32702
+ const logger = getLogger();
32703
+ for (let index = 0; index < filteredEntries.length; index++) {
32704
+ const entry = filteredEntries[index];
32506
32705
  if (entry.isDirectory) continue;
32507
32706
  const fileName = entry.path.split("/").pop() || entry.path;
32508
32707
  const extension = this.getFileExtension(fileName);
@@ -32516,9 +32715,20 @@ var ZipProcessor = class {
32516
32715
  };
32517
32716
  const processor = this.registry?.findProcessor(fileMetadata);
32518
32717
  if (!processor || processor.name === "zip") continue;
32718
+ const declared = declaredUncompressedSize(entry.zipObject);
32719
+ if (declared !== void 0 && declared > this.maxFileSize) continue;
32720
+ if (totalBytes + (declared ?? 0) > MAX_TOTAL_UNCOMPRESSED_BYTES) {
32721
+ byteBudgetHit = true;
32722
+ break;
32723
+ }
32519
32724
  try {
32520
32725
  const data = await entry.zipObject.async("uint8array");
32726
+ totalBytes += data.length;
32521
32727
  if (data.length > this.maxFileSize) continue;
32728
+ if (totalBytes > MAX_TOTAL_UNCOMPRESSED_BYTES) {
32729
+ byteBudgetHit = true;
32730
+ break;
32731
+ }
32522
32732
  const base64 = uint8ArrayToBase64(data);
32523
32733
  const dataUrl = `data:${mimeType};base64,${base64}`;
32524
32734
  const fileWithData = {
@@ -32528,23 +32738,36 @@ var ZipProcessor = class {
32528
32738
  };
32529
32739
  const result = await processor.process(fileWithData);
32530
32740
  if (result && result.extractedText.trim()) {
32741
+ if (result.metadata?.truncated || result.imageDataUrls?.length) truncated = true;
32531
32742
  processedContents.push({
32532
32743
  path: entry.path,
32533
32744
  processorName: processor.name,
32534
- result
32745
+ // Page images of nested files are not forwarded, so their note must not claim them.
32746
+ result: { ...result, extractedText: rewriteImageNote(result, fileName, 0) }
32535
32747
  });
32536
32748
  }
32537
- } catch {
32749
+ } catch (error) {
32750
+ logger.warn(
32751
+ `[ZipProcessor] Failed to process archive entry #${index + 1} (${processor.name}, ${mimeType})`,
32752
+ error
32753
+ );
32538
32754
  }
32539
32755
  }
32540
- const output = this.formatOutput(filteredEntries, processedContents);
32756
+ if (byteBudgetHit) {
32757
+ truncated = true;
32758
+ notes.push(
32759
+ `[truncated: stopped reading the archive after ${Math.round(MAX_TOTAL_UNCOMPRESSED_BYTES / 1024 / 1024)} MB of decompressed content; later files were not extracted]`
32760
+ );
32761
+ }
32762
+ const output = [this.formatOutput(filteredEntries, processedContents), ...notes].join("\n");
32541
32763
  return {
32542
32764
  extractedText: output,
32543
32765
  format: "markdown",
32544
32766
  metadata: {
32545
32767
  fileCount: filteredEntries.filter((e) => !e.isDirectory).length,
32546
32768
  directoryCount: filteredEntries.filter((e) => e.isDirectory).length,
32547
- processedFiles: processedContents.length
32769
+ processedFiles: processedContents.length,
32770
+ truncated
32548
32771
  }
32549
32772
  };
32550
32773
  } catch (error) {
@@ -32646,9 +32869,10 @@ var ProcessorRegistry = class {
32646
32869
  * @returns The matching processor, or null if none found
32647
32870
  */
32648
32871
  findProcessor(file) {
32649
- if (file.type) {
32872
+ const mimeType = file.type?.split(";")[0].trim().toLowerCase();
32873
+ if (mimeType) {
32650
32874
  for (const processor of this.processors.values()) {
32651
- if (processor.supportedMimeTypes.includes(file.type)) {
32875
+ if (processor.supportedMimeTypes.includes(mimeType)) {
32652
32876
  return processor;
32653
32877
  }
32654
32878
  }
@@ -32715,8 +32939,54 @@ var ProcessorRegistry = class {
32715
32939
  }
32716
32940
  };
32717
32941
 
32942
+ // src/lib/processors/fileStatusNotes.ts
32943
+ var DEFAULT_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
32944
+ function formatMegabytes(bytes) {
32945
+ const mb = bytes / (1024 * 1024);
32946
+ return `${Number.isInteger(mb) ? mb : mb.toFixed(1)} MB`;
32947
+ }
32948
+ function formatFileProcessingNotes(statuses, options = {}) {
32949
+ const maxFileSizeBytes = options.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE_BYTES;
32950
+ const why = {
32951
+ too_large: `the file is larger than ${formatMegabytes(maxFileSizeBytes)}`,
32952
+ unsupported_type: "its file type is not supported for reading",
32953
+ no_data: "its contents were not available to the app",
32954
+ empty: "no text or images could be extracted from it (it may be blank)",
32955
+ timeout: "reading it took too long and was stopped",
32956
+ error: "its text could not be extracted (it may be password-protected or damaged)"
32957
+ };
32958
+ const lines = statuses.filter((s) => s.status === "skipped" || s.status === "failed").map((s) => `[${s.fileName} could not be read: ${why[s.reason ?? "error"]}]`);
32959
+ return lines.length > 0 ? lines.join("\n") : null;
32960
+ }
32961
+
32718
32962
  // src/lib/processors/preprocessor.ts
32719
32963
  var MAX_TOTAL_IMAGES = 20;
32964
+ var DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE = 1e5;
32965
+ var DEFAULT_MAX_EXTRACTED_CHARS_TOTAL = 2e5;
32966
+ var ProcessingTimeoutError = class extends Error {
32967
+ constructor(timeoutMs) {
32968
+ super(`Timed out processing file after ${timeoutMs}ms`);
32969
+ this.name = "ProcessingTimeoutError";
32970
+ }
32971
+ };
32972
+ function truncationMarker(kept, total, fileName) {
32973
+ return `
32974
+ [truncated: showing the first ${kept} of ${total} characters of ${fileName}]`;
32975
+ }
32976
+ function truncateText(text12, limit, fileName) {
32977
+ if (text12.length <= limit) return { text: text12, truncated: false };
32978
+ let end = Math.max(0, limit);
32979
+ const code = text12.charCodeAt(end - 1);
32980
+ if (end > 0 && code >= 55296 && code <= 56319) end--;
32981
+ return {
32982
+ text: `${text12.slice(0, end)}${truncationMarker(end, text12.length, fileName)}`,
32983
+ truncated: true
32984
+ };
32985
+ }
32986
+ var FILE_SEPARATOR = "\n\n---\n\n";
32987
+ function isImageFile(file) {
32988
+ return (file.type ?? "").trim().toLowerCase().startsWith("image/");
32989
+ }
32720
32990
  function createDefaultRegistry() {
32721
32991
  const registry = new ProcessorRegistry();
32722
32992
  registry.register(new PdfProcessor());
@@ -32766,10 +33036,11 @@ async function preprocessFiles(files, options = {}) {
32766
33036
  processors = void 0,
32767
33037
  // undefined means use defaults
32768
33038
  keepOriginalFiles = true,
32769
- maxFileSizeBytes = 10 * 1024 * 1024,
32770
- // 10MB
33039
+ maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES,
32771
33040
  timeoutMs = 3e4,
32772
33041
  // 30s per file
33042
+ maxExtractedCharsPerFile = DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE,
33043
+ maxExtractedCharsTotal = DEFAULT_MAX_EXTRACTED_CHARS_TOTAL,
32773
33044
  onProgress,
32774
33045
  onError
32775
33046
  } = options;
@@ -32779,23 +33050,23 @@ async function preprocessFiles(files, options = {}) {
32779
33050
  extractedContent: null,
32780
33051
  originalFiles: files,
32781
33052
  preprocessedFileIds: [],
33053
+ fileStatuses: [],
32782
33054
  metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
32783
33055
  };
32784
33056
  }
32785
- if (processors !== void 0 && processors !== null && processors.length === 0) {
32786
- return {
32787
- extractedContent: null,
32788
- originalFiles: files,
32789
- preprocessedFileIds: [],
32790
- metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
32791
- };
32792
- }
32793
- if (processors === null) {
33057
+ if (processors === null || processors?.length === 0) {
33058
+ const fileStatuses2 = files.filter((file) => !isImageFile(file)).map((file) => ({
33059
+ fileId: file.id,
33060
+ fileName: file.name,
33061
+ status: "skipped",
33062
+ reason: "unsupported_type"
33063
+ }));
32794
33064
  return {
32795
33065
  extractedContent: null,
32796
33066
  originalFiles: files,
32797
33067
  preprocessedFileIds: [],
32798
- metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
33068
+ fileStatuses: fileStatuses2,
33069
+ metadata: { processedCount: 0, skippedCount: fileStatuses2.length, errorCount: 0 }
32799
33070
  };
32800
33071
  }
32801
33072
  let registry;
@@ -32813,28 +33084,38 @@ async function preprocessFiles(files, options = {}) {
32813
33084
  const extractedTexts = [];
32814
33085
  const allImageUrls = [];
32815
33086
  const preprocessedFileIds = [];
33087
+ const fileStatuses = [];
32816
33088
  let processedCount = 0;
32817
33089
  let skippedCount = 0;
32818
33090
  let errorCount = 0;
33091
+ let totalChars = 0;
33092
+ const overBudgetFiles = [];
32819
33093
  for (let i = 0; i < files.length; i++) {
32820
33094
  const file = files[i];
33095
+ const label = `#${i + 1} (${file.type || "unknown type"}, ${file.size} bytes)`;
33096
+ const skip = (reason) => {
33097
+ skippedCount++;
33098
+ fileStatuses.push({ fileId: file.id, fileName: file.name, status: "skipped", reason });
33099
+ };
32821
33100
  onProgress?.(i + 1, files.length, file.name);
33101
+ const processor = registry.findProcessor(file);
33102
+ if (!processor && isImageFile(file)) continue;
32822
33103
  if (file.size > maxFileSizeBytes) {
32823
33104
  logger.info(
32824
- `[preprocessFiles] Skipping "${file.name}" \u2014 exceeds ${maxFileSizeBytes} byte limit`
33105
+ `[preprocessFiles] Skipping file ${label} \u2014 exceeds ${maxFileSizeBytes} byte limit`
32825
33106
  );
32826
- skippedCount++;
33107
+ skip("too_large");
32827
33108
  continue;
32828
33109
  }
32829
- const processor = registry.findProcessor(file);
32830
33110
  if (!processor) {
32831
- skippedCount++;
33111
+ skip("unsupported_type");
32832
33112
  continue;
32833
33113
  }
33114
+ let timer;
32834
33115
  try {
32835
33116
  if (!file.url) {
32836
- logger.info(`[preprocessFiles] Skipping "${file.name}" \u2014 no data URL available`);
32837
- skippedCount++;
33117
+ logger.info(`[preprocessFiles] Skipping file ${label} \u2014 no data URL available`);
33118
+ skip("no_data");
32838
33119
  continue;
32839
33120
  }
32840
33121
  const fileWithData = {
@@ -32843,40 +33124,76 @@ async function preprocessFiles(files, options = {}) {
32843
33124
  };
32844
33125
  const result = await Promise.race([
32845
33126
  processor.process(fileWithData),
32846
- new Promise(
32847
- (_, reject) => setTimeout(() => reject(new Error(`Timed out processing ${file.name}`)), timeoutMs)
32848
- )
33127
+ new Promise((_, reject) => {
33128
+ timer = setTimeout(() => reject(new ProcessingTimeoutError(timeoutMs)), timeoutMs);
33129
+ })
32849
33130
  ]);
32850
33131
  if (result && result.extractedText.trim()) {
32851
- const formattedContent = formatExtractedContent(
32852
- file.name,
32853
- result.extractedText,
32854
- result.format
32855
- );
32856
- extractedTexts.push(formattedContent);
33132
+ const separatorLength = extractedTexts.length > 0 ? FILE_SEPARATOR.length : 0;
33133
+ const remaining = maxExtractedCharsTotal - totalChars - separatorLength - formatExtractedContent(file.name, "", result.format).length;
32857
33134
  preprocessedFileIds.push(file.id);
32858
33135
  processedCount++;
32859
- if (result.imageDataUrls && result.imageDataUrls.length > 0) {
32860
- const remaining = MAX_TOTAL_IMAGES - allImageUrls.length;
32861
- if (remaining > 0) {
32862
- allImageUrls.push(...result.imageDataUrls.slice(0, remaining));
32863
- }
33136
+ const fullLength = result.extractedText.length;
33137
+ if (fullLength > remaining && remaining - truncationMarker(fullLength, fullLength, file.name).length <= 0) {
33138
+ overBudgetFiles.push(file.name);
33139
+ fileStatuses.push({ fileId: file.id, fileName: file.name, status: "truncated" });
33140
+ continue;
32864
33141
  }
33142
+ const images = result.imageDataUrls ?? [];
33143
+ const keptImages = Math.min(
33144
+ images.length,
33145
+ Math.max(0, MAX_TOTAL_IMAGES - allImageUrls.length)
33146
+ );
33147
+ allImageUrls.push(...images.slice(0, keptImages));
33148
+ const text12 = rewriteImageNote(result, file.name, keptImages);
33149
+ const fitsWhole = text12.length <= Math.min(maxExtractedCharsPerFile, remaining);
33150
+ const limit = fitsWhole ? text12.length : Math.min(
33151
+ maxExtractedCharsPerFile,
33152
+ remaining - truncationMarker(text12.length, text12.length, file.name).length
33153
+ );
33154
+ const capped = truncateText(text12, Math.max(0, limit), file.name);
33155
+ const formattedContent = formatExtractedContent(file.name, capped.text, result.format);
33156
+ totalChars += separatorLength + formattedContent.length;
33157
+ extractedTexts.push(formattedContent);
33158
+ const truncated = capped.truncated || keptImages < images.length || result.metadata?.truncated === true;
33159
+ fileStatuses.push({
33160
+ fileId: file.id,
33161
+ fileName: file.name,
33162
+ status: truncated ? "truncated" : keptImages > 0 ? "rendered_as_images" : "extracted"
33163
+ });
32865
33164
  } else {
32866
- skippedCount++;
33165
+ skip("empty");
32867
33166
  }
32868
33167
  } catch (error) {
32869
33168
  errorCount++;
32870
- logger.error(`[preprocessFiles] Error processing "${file.name}":`, error);
33169
+ const timedOut = error instanceof ProcessingTimeoutError;
33170
+ fileStatuses.push({
33171
+ fileId: file.id,
33172
+ fileName: file.name,
33173
+ status: "failed",
33174
+ reason: timedOut ? "timeout" : "error"
33175
+ });
33176
+ logger.error(
33177
+ `[preprocessFiles] Error processing file ${label} with ${processor.name}:`,
33178
+ error
33179
+ );
32871
33180
  onError?.(file.name, error instanceof Error ? error : new Error(String(error)));
33181
+ } finally {
33182
+ clearTimeout(timer);
32872
33183
  }
32873
33184
  }
32874
- const extractedContent = extractedTexts.length > 0 ? extractedTexts.join("\n\n---\n\n") : null;
33185
+ if (overBudgetFiles.length > 0) {
33186
+ extractedTexts.push(
33187
+ `[truncated: the attachment text limit (${maxExtractedCharsTotal} characters) was reached; the contents of ${overBudgetFiles.join(", ")} were not included]`
33188
+ );
33189
+ }
33190
+ const extractedContent = extractedTexts.length > 0 ? extractedTexts.join(FILE_SEPARATOR) : null;
32875
33191
  return {
32876
33192
  extractedContent,
32877
33193
  imageContentUrls: allImageUrls.length > 0 ? allImageUrls : void 0,
32878
33194
  originalFiles: keepOriginalFiles ? files : void 0,
32879
33195
  preprocessedFileIds,
33196
+ fileStatuses,
32880
33197
  metadata: { processedCount, skippedCount, errorCount }
32881
33198
  };
32882
33199
  }
@@ -35025,7 +35342,7 @@ function extractLastUserText(messages) {
35025
35342
  const content = lastUserMsg.content;
35026
35343
  if (typeof content === "string") return content;
35027
35344
  if (Array.isArray(content)) {
35028
- return content.filter((c) => c?.type === "text").map((c) => c.text ?? "").join(" ");
35345
+ return content.filter((c) => c?.type === "text" && !isAttachedFilesText(c.text)).map((c) => c.text ?? "").join(" ");
35029
35346
  }
35030
35347
  return "";
35031
35348
  }
@@ -36699,6 +37016,7 @@ export {
36699
37016
  extractEntitiesForMemories,
36700
37017
  extractFacts,
36701
37018
  findMatchingTools,
37019
+ formatFileProcessingNotes,
36702
37020
  generateConversationId,
36703
37021
  generateEmbedding,
36704
37022
  generateEmbeddings,