@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.
@@ -15422,7 +15422,7 @@ var init_portalLlm = __esm({
15422
15422
  RESPONSE_FORMAT_OK = /* @__PURE__ */ new Set(["openai", "inclusionai", "deepseek"]);
15423
15423
  RESPONSE_SCHEMA_OK = /* @__PURE__ */ new Set(["openai"]);
15424
15424
  JSON_CONTRACT_REMINDER = "Output ONLY the strict JSON object requested. No prose, no explanation, no markdown.";
15425
- RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6"];
15425
+ RESPONSES_ONLY_MODEL_PREFIXES = ["gpt-5.6", "gpt-6-sol", "gpt-6-luna", "gpt-6-astra"];
15426
15426
  CHAT_TO_RESPONSES_FIELDS = {
15427
15427
  max_completion_tokens: "max_output_tokens"
15428
15428
  };
@@ -16461,6 +16461,7 @@ __export(index_exports, {
16461
16461
  extractEntitiesForMemories: () => extractEntitiesForMemories,
16462
16462
  extractFacts: () => extractFacts,
16463
16463
  findMatchingTools: () => findMatchingTools,
16464
+ formatFileProcessingNotes: () => formatFileProcessingNotes,
16464
16465
  generateConversationId: () => generateConversationId,
16465
16466
  generateEmbedding: () => generateEmbedding,
16466
16467
  generateEmbeddings: () => generateEmbeddings,
@@ -20216,6 +20217,15 @@ async function assembleDecryptedMessage(message, address) {
20216
20217
  // src/lib/db/chat/types.ts
20217
20218
  var import_uuid = require("uuid");
20218
20219
 
20220
+ // src/lib/chat/fileContext.ts
20221
+ var ATTACHED_FILES_OPEN_TAG = "<attached_files>";
20222
+ var ATTACHED_FILES_CLOSE_TAG = "</attached_files>";
20223
+ var ATTACHED_FILES_PREFIX = `${ATTACHED_FILES_OPEN_TAG}
20224
+ 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.`;
20225
+ function isAttachedFilesText(text12) {
20226
+ return typeof text12 === "string" && text12.startsWith(ATTACHED_FILES_PREFIX) && text12.endsWith(ATTACHED_FILES_CLOSE_TAG);
20227
+ }
20228
+
20219
20229
  // src/lib/chat/useChat/strategies/types.ts
20220
20230
  function isChatCompletionResponse(r) {
20221
20231
  if (r.object === "response") return false;
@@ -33902,14 +33912,37 @@ init_logger();
33902
33912
  if (typeof process !== "undefined" && typeof process.umask !== "function") {
33903
33913
  process.umask = (_mask) => 18;
33904
33914
  }
33915
+ var MAX_ROWS_PER_SHEET = 2e3;
33916
+ function csvField(value) {
33917
+ return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
33918
+ }
33919
+ function uniqueHeaders(raw, columnCount) {
33920
+ const bases = [];
33921
+ for (let col = 1; col <= columnCount; col++) bases.push(raw[col]?.trim() || `Column${col}`);
33922
+ const taken = new Set(bases);
33923
+ const emitted = /* @__PURE__ */ new Set();
33924
+ return bases.map((base) => {
33925
+ if (!emitted.has(base)) {
33926
+ emitted.add(base);
33927
+ return base;
33928
+ }
33929
+ let n = 2;
33930
+ while (taken.has(`${base}_${n}`)) n++;
33931
+ const name = `${base}_${n}`;
33932
+ taken.add(name);
33933
+ emitted.add(name);
33934
+ return name;
33935
+ });
33936
+ }
33905
33937
  var ExcelProcessor = class {
33906
- constructor() {
33938
+ constructor(options = {}) {
33907
33939
  this.name = "excel";
33908
33940
  this.supportedMimeTypes = [
33909
33941
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
33910
33942
  // .xlsx
33911
33943
  ];
33912
33944
  this.supportedExtensions = [".xlsx"];
33945
+ this.maxRowsPerSheet = options.maxRowsPerSheet ?? MAX_ROWS_PER_SHEET;
33913
33946
  }
33914
33947
  async loadExcelJS() {
33915
33948
  const mod = await import("exceljs");
@@ -33924,31 +33957,51 @@ var ExcelProcessor = class {
33924
33957
  if (workbook.worksheets.length === 0) {
33925
33958
  return null;
33926
33959
  }
33927
- const jsonData = {};
33960
+ let truncated = false;
33961
+ const sections = [];
33928
33962
  for (const worksheet of workbook.worksheets) {
33929
- const rows = [];
33930
- const headerRow = worksheet.getRow(1);
33931
- const headers = [];
33932
- headerRow.eachCell({ includeEmpty: true }, (cell, colNumber) => {
33933
- headers[colNumber] = cell.text || `Column${colNumber}`;
33963
+ const rawHeaders = [];
33964
+ let columnCount = 0;
33965
+ worksheet.getRow(1).eachCell({ includeEmpty: true }, (cell, colNumber) => {
33966
+ rawHeaders[colNumber] = cell.text;
33967
+ columnCount = Math.max(columnCount, colNumber);
33934
33968
  });
33969
+ const rows = [];
33970
+ let totalRows = 0;
33935
33971
  worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
33936
33972
  if (rowNumber === 1) return;
33937
- const rowData = {};
33973
+ totalRows++;
33974
+ if (rows.length >= this.maxRowsPerSheet) return;
33975
+ const values = [];
33938
33976
  row.eachCell({ includeEmpty: true }, (cell, colNumber) => {
33939
- const key = headers[colNumber] || `Column${colNumber}`;
33940
- rowData[key] = this.resolveCellValue(cell.value);
33977
+ values[colNumber - 1] = String(this.resolveCellValue(cell.value));
33978
+ columnCount = Math.max(columnCount, colNumber);
33941
33979
  });
33942
- rows.push(rowData);
33980
+ rows.push(values);
33943
33981
  });
33944
- jsonData[worksheet.name] = rows;
33982
+ const headers = uniqueHeaders(rawHeaders, columnCount);
33983
+ const lines = [headers, ...rows].map(
33984
+ (values) => headers.map((_, i) => csvField(values[i] ?? "")).join(",")
33985
+ );
33986
+ let section = `## Sheet: ${worksheet.name}
33987
+
33988
+ \`\`\`csv
33989
+ ${lines.join("\n")}
33990
+ \`\`\``;
33991
+ if (totalRows > rows.length) {
33992
+ truncated = true;
33993
+ section += `
33994
+ [truncated: showing the first ${rows.length} of ${totalRows} data rows of sheet "${worksheet.name}"]`;
33995
+ }
33996
+ sections.push(section);
33945
33997
  }
33946
33998
  return {
33947
- extractedText: JSON.stringify(jsonData, null, 2),
33948
- format: "json",
33999
+ extractedText: sections.join("\n\n"),
34000
+ format: "markdown",
33949
34001
  metadata: {
33950
34002
  sheetCount: workbook.worksheets.length,
33951
- sheetNames: workbook.worksheets.map((ws) => ws.name)
34003
+ sheetNames: workbook.worksheets.map((ws) => ws.name),
34004
+ truncated
33952
34005
  }
33953
34006
  };
33954
34007
  } catch (error) {
@@ -33977,6 +34030,9 @@ init_logger();
33977
34030
  // src/lib/pdf.ts
33978
34031
  var pdfjsModule = null;
33979
34032
  var workerConfigured = false;
34033
+ var RENDER_SCALE = 1.5;
34034
+ var MAX_RENDER_SIDE_PX = 1600;
34035
+ var JPEG_QUALITY = 0.8;
33980
34036
  async function getPdfjs() {
33981
34037
  if (!pdfjsModule) {
33982
34038
  pdfjsModule = await import("pdfjs-dist");
@@ -33987,46 +34043,126 @@ async function getPdfjs() {
33987
34043
  }
33988
34044
  return pdfjsModule;
33989
34045
  }
33990
- async function extractTextFromPdf(pdfDataUrl) {
34046
+ async function withPdfDocument(pdfDataUrl, fn) {
33991
34047
  const pdfjs = await getPdfjs();
33992
34048
  const loadingTask = pdfjs.getDocument(pdfDataUrl);
33993
- const pdf = await loadingTask.promise;
33994
- const textParts = [];
33995
- for (let i = 1; i <= pdf.numPages; i++) {
33996
- const page = await pdf.getPage(i);
33997
- const textContent = await page.getTextContent();
33998
- const pageText = textContent.items.map((item) => "str" in item ? item.str : "").join(" ");
33999
- if (pageText.trim()) {
34000
- textParts.push(pageText);
34001
- }
34049
+ let pdf;
34050
+ try {
34051
+ pdf = await loadingTask.promise;
34052
+ } catch (error) {
34053
+ await loadingTask.destroy().catch(() => void 0);
34054
+ throw error;
34055
+ }
34056
+ try {
34057
+ return await fn(pdf);
34058
+ } finally {
34059
+ await pdf.destroy().catch(() => void 0);
34002
34060
  }
34003
- return textParts.join("\n\n");
34004
34061
  }
34005
- async function convertPdfToImages(pdfDataUrl, maxPages) {
34006
- const images = [];
34007
- const pdfjs = await getPdfjs();
34008
- const loadingTask = pdfjs.getDocument(pdfDataUrl);
34009
- const pdf = await loadingTask.promise;
34010
- const pageLimit = maxPages !== void 0 ? Math.min(maxPages, pdf.numPages) : pdf.numPages;
34011
- for (let i = 1; i <= pageLimit; i++) {
34012
- const page = await pdf.getPage(i);
34013
- const viewport = page.getViewport({ scale: 1.5 });
34014
- const canvas = document.createElement("canvas");
34015
- const context = canvas.getContext("2d");
34016
- if (!context) continue;
34017
- canvas.height = viewport.height;
34018
- canvas.width = viewport.width;
34019
- await page.render({
34020
- canvasContext: context,
34021
- viewport
34022
- }).promise;
34023
- images.push(canvas.toDataURL("image/png"));
34024
- }
34025
- return images;
34062
+ function joinPdfTextItems(items) {
34063
+ let text12 = "";
34064
+ for (const item of items) {
34065
+ if (!("str" in item)) continue;
34066
+ const { str, hasEOL } = item;
34067
+ text12 += str + (hasEOL ? "\n" : " ");
34068
+ }
34069
+ return text12.replace(/[ \t]+\n/g, "\n").trim();
34070
+ }
34071
+ function computeRenderScale(width, height) {
34072
+ const longest = Math.max(width, height);
34073
+ if (!(longest > 0)) return RENDER_SCALE;
34074
+ return Math.min(RENDER_SCALE, MAX_RENDER_SIDE_PX / longest);
34075
+ }
34076
+ async function extractPdfPageTexts(pdfDataUrl) {
34077
+ return withPdfDocument(pdfDataUrl, async (pdf) => {
34078
+ const pageTexts = [];
34079
+ for (let i = 1; i <= pdf.numPages; i++) {
34080
+ const page = await pdf.getPage(i);
34081
+ const textContent = await page.getTextContent();
34082
+ pageTexts.push(joinPdfTextItems(textContent.items));
34083
+ }
34084
+ return pageTexts;
34085
+ });
34086
+ }
34087
+ async function renderPdfPages(pdfDataUrl, maxPages, pageNumbers) {
34088
+ return withPdfDocument(pdfDataUrl, async (pdf) => {
34089
+ const requested = pageNumbers?.filter((n) => n >= 1 && n <= pdf.numPages) ?? Array.from(
34090
+ { length: maxPages !== void 0 ? Math.min(maxPages, pdf.numPages) : pdf.numPages },
34091
+ (_, i) => i + 1
34092
+ );
34093
+ const pages = [];
34094
+ for (const pageNumber of requested) {
34095
+ const page = await pdf.getPage(pageNumber);
34096
+ const base = page.getViewport({ scale: 1 });
34097
+ const viewport = page.getViewport({ scale: computeRenderScale(base.width, base.height) });
34098
+ const canvas = document.createElement("canvas");
34099
+ const context = canvas.getContext("2d");
34100
+ if (!context) continue;
34101
+ canvas.height = Math.floor(viewport.height);
34102
+ canvas.width = Math.floor(viewport.width);
34103
+ context.fillStyle = "#ffffff";
34104
+ context.fillRect(0, 0, canvas.width, canvas.height);
34105
+ await page.render({
34106
+ canvasContext: context,
34107
+ viewport,
34108
+ background: "#ffffff"
34109
+ }).promise;
34110
+ pages.push({ pageNumber, dataUrl: canvas.toDataURL("image/jpeg", JPEG_QUALITY) });
34111
+ canvas.width = 0;
34112
+ canvas.height = 0;
34113
+ }
34114
+ return { pages, pageCount: pdf.numPages };
34115
+ });
34026
34116
  }
34027
34117
 
34028
34118
  // src/lib/processors/PdfProcessor.ts
34029
34119
  var MAX_IMAGE_PAGES = 20;
34120
+ var MIN_PAGE_TEXT_CHARS = 20;
34121
+ function selectPdfImagePages(pageTexts, maxImagePages = MAX_IMAGE_PAGES) {
34122
+ const needImage = [];
34123
+ pageTexts.forEach((text12, index) => {
34124
+ if (text12.trim().length < MIN_PAGE_TEXT_CHARS) needImage.push(index + 1);
34125
+ });
34126
+ const limit = Math.max(0, maxImagePages);
34127
+ return { rendered: needImage.slice(0, limit), omitted: needImage.slice(limit) };
34128
+ }
34129
+ function formatPageList(pages) {
34130
+ const ranges = [];
34131
+ for (let i = 0; i < pages.length; i++) {
34132
+ const start = pages[i];
34133
+ let end = start;
34134
+ while (i + 1 < pages.length && pages[i + 1] === end + 1) end = pages[++i];
34135
+ ranges.push(start === end ? `${start}` : `${start}-${end}`);
34136
+ }
34137
+ return `${pages.length === 1 ? "page" : "pages"} ${ranges.join(", ")}`;
34138
+ }
34139
+ function buildPdfImageNote(fileName, includedPages, omittedPages, pageCount) {
34140
+ const imageOnlyCount = includedPages.length + omittedPages.length;
34141
+ const allPages = [...includedPages, ...omittedPages].sort((a, b) => a - b);
34142
+ 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)})`;
34143
+ if (includedPages.length === 0) {
34144
+ return `[${subject} \u2014 ${formatPageList(omittedPages)} could not be included as images because of the image limit, so their content is not available]`;
34145
+ }
34146
+ const included = `${formatPageList(includedPages)} rendered as images and included in this message for visual analysis`;
34147
+ if (omittedPages.length === 0) return `[${subject} \u2014 ${included}]`;
34148
+ return `[${subject} \u2014 ${included}; ${formatPageList(omittedPages)} not included because of the image limit, so their content is not available]`;
34149
+ }
34150
+ function rewriteImageNote(result, fileName, keptImages) {
34151
+ const images = result.imageDataUrls ?? [];
34152
+ const note = result.metadata?.imageNote;
34153
+ if (!note || keptImages >= images.length) return result.extractedText;
34154
+ const imagePages = result.metadata?.imagePages ?? [];
34155
+ const omittedPages = result.metadata?.omittedImagePages ?? [];
34156
+ const pageCount = result.metadata?.pageCount;
34157
+ const kept = Math.max(0, keptImages);
34158
+ const newNote = buildPdfImageNote(
34159
+ fileName,
34160
+ imagePages.slice(0, kept),
34161
+ [...imagePages.slice(kept), ...omittedPages].sort((a, b) => a - b),
34162
+ pageCount
34163
+ );
34164
+ return result.extractedText.replace(note, () => newNote);
34165
+ }
34030
34166
  var PdfProcessor = class {
34031
34167
  constructor() {
34032
34168
  this.name = "pdf";
@@ -34035,43 +34171,65 @@ var PdfProcessor = class {
34035
34171
  }
34036
34172
  async process(file) {
34037
34173
  const logger = getLogger();
34174
+ let pageTexts = null;
34038
34175
  try {
34039
- const text12 = await extractTextFromPdf(file.dataUrl);
34040
- if (text12 && text12.trim().length > 0) {
34041
- return {
34042
- extractedText: text12,
34043
- format: "plain"
34044
- };
34045
- }
34046
- logger.info(
34047
- `[PdfProcessor] Text extraction returned empty for "${file.name}" \u2014 falling back to image conversion`
34048
- );
34176
+ pageTexts = await extractPdfPageTexts(file.dataUrl);
34049
34177
  } catch (textError) {
34050
34178
  logger.warn(
34051
- `[PdfProcessor] Text extraction failed for "${file.name}" \u2014 falling back to image conversion:`,
34179
+ "[PdfProcessor] Text extraction failed \u2014 falling back to image conversion:",
34052
34180
  textError
34053
34181
  );
34054
34182
  }
34183
+ const text12 = (pageTexts ?? []).filter((t) => t.trim()).join("\n\n");
34184
+ const { rendered, omitted } = pageTexts ? selectPdfImagePages(pageTexts) : { rendered: [], omitted: [] };
34185
+ if (pageTexts && rendered.length === 0 && omitted.length === 0) {
34186
+ return text12.trim() ? { extractedText: text12, format: "plain" } : null;
34187
+ }
34188
+ let renderedPages;
34189
+ let pageCount = pageTexts?.length;
34055
34190
  try {
34056
- const images = await convertPdfToImages(file.dataUrl, MAX_IMAGE_PAGES);
34057
- if (images.length === 0) {
34058
- logger.warn(`[PdfProcessor] Image conversion also returned empty for "${file.name}"`);
34059
- return null;
34191
+ if (pageTexts) {
34192
+ renderedPages = (await renderPdfPages(file.dataUrl, void 0, rendered)).pages;
34193
+ } else {
34194
+ const result = await renderPdfPages(file.dataUrl, MAX_IMAGE_PAGES);
34195
+ renderedPages = result.pages;
34196
+ pageCount = result.pageCount;
34060
34197
  }
34061
- logger.info(`[PdfProcessor] Converted ${images.length} page(s) of "${file.name}" to images`);
34062
- return {
34063
- // Contextual note for the LLM — keep it self-contained since the
34064
- // images are only injected in the current request and not persisted
34065
- // for follow-up turns.
34066
- extractedText: `[${file.name}: scanned/image-based PDF \u2014 ${images.length} page(s) rendered as images and included in this message for visual analysis]`,
34067
- format: "plain",
34068
- imageDataUrls: images,
34069
- metadata: { pageCount: images.length }
34070
- };
34071
34198
  } catch (imageError) {
34072
- logger.error(`[PdfProcessor] Image conversion also failed for "${file.name}":`, imageError);
34073
- throw imageError;
34199
+ if (!text12.trim()) {
34200
+ logger.error("[PdfProcessor] Image conversion also failed:", imageError);
34201
+ throw imageError;
34202
+ }
34203
+ logger.warn("[PdfProcessor] Image conversion failed \u2014 keeping text only:", imageError);
34204
+ renderedPages = [];
34205
+ }
34206
+ const images = renderedPages.map((p) => p.dataUrl);
34207
+ const imagePages = renderedPages.map((p) => p.pageNumber);
34208
+ const included = new Set(imagePages);
34209
+ const needImage = pageTexts ? [...rendered, ...omitted] : Array.from({ length: pageCount ?? 0 }, (_, i) => i + 1);
34210
+ const omittedPages = needImage.filter((n) => !included.has(n)).sort((a, b) => a - b);
34211
+ if (images.length === 0 && !text12.trim()) {
34212
+ logger.warn("[PdfProcessor] Image conversion also returned empty");
34213
+ return null;
34074
34214
  }
34215
+ logger.info(
34216
+ `[PdfProcessor] Rendered ${images.length} page(s) as images, ${omittedPages.length} image-only page(s) omitted`
34217
+ );
34218
+ const imageNote = buildPdfImageNote(file.name, imagePages, omittedPages, pageCount);
34219
+ return {
34220
+ extractedText: text12.trim() ? `${imageNote}
34221
+
34222
+ ${text12}` : imageNote,
34223
+ format: "plain",
34224
+ imageDataUrls: images.length > 0 ? images : void 0,
34225
+ metadata: {
34226
+ pageCount,
34227
+ imageNote,
34228
+ imagePages,
34229
+ omittedImagePages: omittedPages,
34230
+ truncated: omittedPages.length > 0
34231
+ }
34232
+ };
34075
34233
  }
34076
34234
  };
34077
34235
 
@@ -34113,6 +34271,21 @@ var EXT = {
34113
34271
  CFG: ".cfg",
34114
34272
  CONF: ".conf"
34115
34273
  };
34274
+ function decodeText(bytes) {
34275
+ if (bytes[0] === 255 && bytes[1] === 254) {
34276
+ return new TextDecoder("utf-16le", { fatal: false }).decode(bytes.subarray(2));
34277
+ }
34278
+ if (bytes[0] === 254 && bytes[1] === 255) {
34279
+ const swapped = new Uint8Array(bytes.length - 2);
34280
+ for (let i = 2; i + 1 < bytes.length; i += 2) {
34281
+ swapped[i - 2] = bytes[i + 1];
34282
+ swapped[i - 1] = bytes[i];
34283
+ }
34284
+ return new TextDecoder("utf-16le", { fatal: false }).decode(swapped);
34285
+ }
34286
+ const hasUtf8Bom = bytes[0] === 239 && bytes[1] === 187 && bytes[2] === 191;
34287
+ return new TextDecoder("utf-8", { fatal: false }).decode(hasUtf8Bom ? bytes.subarray(3) : bytes);
34288
+ }
34116
34289
  var TextProcessor = class {
34117
34290
  constructor() {
34118
34291
  this.name = "text";
@@ -34122,7 +34295,7 @@ var TextProcessor = class {
34122
34295
  async process(file) {
34123
34296
  try {
34124
34297
  const arrayBuffer = await dataUrlToArrayBuffer(file.dataUrl);
34125
- const text12 = new TextDecoder("utf-8", { fatal: false }).decode(arrayBuffer);
34298
+ const text12 = decodeText(new Uint8Array(arrayBuffer));
34126
34299
  if (!text12.trim()) {
34127
34300
  return null;
34128
34301
  }
@@ -34145,7 +34318,7 @@ var TextProcessor = class {
34145
34318
  * rather than prose).
34146
34319
  */
34147
34320
  resolveFormat(file) {
34148
- const type = file.type?.toLowerCase() ?? "";
34321
+ const type = file.type?.split(";")[0].trim().toLowerCase() ?? "";
34149
34322
  const name = file.name?.toLowerCase() ?? "";
34150
34323
  if (type === MIME.MARKDOWN || type === MIME.X_MARKDOWN || name.endsWith(EXT.MD) || name.endsWith(EXT.MARKDOWN)) {
34151
34324
  return "markdown";
@@ -34203,6 +34376,12 @@ var WordProcessor = class {
34203
34376
  // src/lib/processors/ZipProcessor.ts
34204
34377
  var import_jszip = __toESM(require("jszip"));
34205
34378
  init_logger();
34379
+ var MAX_ZIP_ENTRIES = 1e3;
34380
+ var MAX_TOTAL_UNCOMPRESSED_BYTES = 50 * 1024 * 1024;
34381
+ function declaredUncompressedSize(zipObject) {
34382
+ const size = zipObject._data?.uncompressedSize;
34383
+ return typeof size === "number" ? size : void 0;
34384
+ }
34206
34385
  var ZipProcessor = class {
34207
34386
  constructor(options = {}) {
34208
34387
  this.name = "zip";
@@ -34237,14 +34416,35 @@ var ZipProcessor = class {
34237
34416
  zipObject: zipEntry
34238
34417
  });
34239
34418
  });
34240
- const filteredEntries = this.includeHidden ? entries : entries.filter((entry) => !this.isHidden(entry.path));
34241
- filteredEntries.sort((a, b) => {
34419
+ const visibleEntries = this.includeHidden ? entries : entries.filter((entry) => !this.isHidden(entry.path));
34420
+ visibleEntries.sort((a, b) => {
34242
34421
  if (a.isDirectory !== b.isDirectory) {
34243
34422
  return a.isDirectory ? -1 : 1;
34244
34423
  }
34245
34424
  return a.path.localeCompare(b.path);
34246
34425
  });
34247
- for (const entry of filteredEntries) {
34426
+ const visibleDirs = visibleEntries.filter((e) => e.isDirectory);
34427
+ const visibleFiles = visibleEntries.filter((e) => !e.isDirectory);
34428
+ const listedDirs = visibleDirs.slice(0, MAX_ZIP_ENTRIES);
34429
+ const listedFiles = visibleFiles.slice(0, MAX_ZIP_ENTRIES);
34430
+ const filteredEntries = [...listedDirs, ...listedFiles];
34431
+ const notes = [];
34432
+ if (visibleFiles.length > listedFiles.length) {
34433
+ notes.push(
34434
+ `[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]`
34435
+ );
34436
+ }
34437
+ if (visibleDirs.length > listedDirs.length) {
34438
+ notes.push(
34439
+ `[truncated: listing the first ${listedDirs.length} of ${visibleDirs.length} directories]`
34440
+ );
34441
+ }
34442
+ let totalBytes = 0;
34443
+ let byteBudgetHit = false;
34444
+ let truncated = notes.length > 0;
34445
+ const logger = getLogger();
34446
+ for (let index = 0; index < filteredEntries.length; index++) {
34447
+ const entry = filteredEntries[index];
34248
34448
  if (entry.isDirectory) continue;
34249
34449
  const fileName = entry.path.split("/").pop() || entry.path;
34250
34450
  const extension = this.getFileExtension(fileName);
@@ -34258,9 +34458,20 @@ var ZipProcessor = class {
34258
34458
  };
34259
34459
  const processor = this.registry?.findProcessor(fileMetadata);
34260
34460
  if (!processor || processor.name === "zip") continue;
34461
+ const declared = declaredUncompressedSize(entry.zipObject);
34462
+ if (declared !== void 0 && declared > this.maxFileSize) continue;
34463
+ if (totalBytes + (declared ?? 0) > MAX_TOTAL_UNCOMPRESSED_BYTES) {
34464
+ byteBudgetHit = true;
34465
+ break;
34466
+ }
34261
34467
  try {
34262
34468
  const data = await entry.zipObject.async("uint8array");
34469
+ totalBytes += data.length;
34263
34470
  if (data.length > this.maxFileSize) continue;
34471
+ if (totalBytes > MAX_TOTAL_UNCOMPRESSED_BYTES) {
34472
+ byteBudgetHit = true;
34473
+ break;
34474
+ }
34264
34475
  const base64 = uint8ArrayToBase64(data);
34265
34476
  const dataUrl = `data:${mimeType};base64,${base64}`;
34266
34477
  const fileWithData = {
@@ -34270,23 +34481,36 @@ var ZipProcessor = class {
34270
34481
  };
34271
34482
  const result = await processor.process(fileWithData);
34272
34483
  if (result && result.extractedText.trim()) {
34484
+ if (result.metadata?.truncated || result.imageDataUrls?.length) truncated = true;
34273
34485
  processedContents.push({
34274
34486
  path: entry.path,
34275
34487
  processorName: processor.name,
34276
- result
34488
+ // Page images of nested files are not forwarded, so their note must not claim them.
34489
+ result: { ...result, extractedText: rewriteImageNote(result, fileName, 0) }
34277
34490
  });
34278
34491
  }
34279
- } catch {
34492
+ } catch (error) {
34493
+ logger.warn(
34494
+ `[ZipProcessor] Failed to process archive entry #${index + 1} (${processor.name}, ${mimeType})`,
34495
+ error
34496
+ );
34280
34497
  }
34281
34498
  }
34282
- const output = this.formatOutput(filteredEntries, processedContents);
34499
+ if (byteBudgetHit) {
34500
+ truncated = true;
34501
+ notes.push(
34502
+ `[truncated: stopped reading the archive after ${Math.round(MAX_TOTAL_UNCOMPRESSED_BYTES / 1024 / 1024)} MB of decompressed content; later files were not extracted]`
34503
+ );
34504
+ }
34505
+ const output = [this.formatOutput(filteredEntries, processedContents), ...notes].join("\n");
34283
34506
  return {
34284
34507
  extractedText: output,
34285
34508
  format: "markdown",
34286
34509
  metadata: {
34287
34510
  fileCount: filteredEntries.filter((e) => !e.isDirectory).length,
34288
34511
  directoryCount: filteredEntries.filter((e) => e.isDirectory).length,
34289
- processedFiles: processedContents.length
34512
+ processedFiles: processedContents.length,
34513
+ truncated
34290
34514
  }
34291
34515
  };
34292
34516
  } catch (error) {
@@ -34388,9 +34612,10 @@ var ProcessorRegistry = class {
34388
34612
  * @returns The matching processor, or null if none found
34389
34613
  */
34390
34614
  findProcessor(file) {
34391
- if (file.type) {
34615
+ const mimeType = file.type?.split(";")[0].trim().toLowerCase();
34616
+ if (mimeType) {
34392
34617
  for (const processor of this.processors.values()) {
34393
- if (processor.supportedMimeTypes.includes(file.type)) {
34618
+ if (processor.supportedMimeTypes.includes(mimeType)) {
34394
34619
  return processor;
34395
34620
  }
34396
34621
  }
@@ -34457,9 +34682,55 @@ var ProcessorRegistry = class {
34457
34682
  }
34458
34683
  };
34459
34684
 
34685
+ // src/lib/processors/fileStatusNotes.ts
34686
+ var DEFAULT_MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024;
34687
+ function formatMegabytes(bytes) {
34688
+ const mb = bytes / (1024 * 1024);
34689
+ return `${Number.isInteger(mb) ? mb : mb.toFixed(1)} MB`;
34690
+ }
34691
+ function formatFileProcessingNotes(statuses, options = {}) {
34692
+ const maxFileSizeBytes = options.maxFileSizeBytes ?? DEFAULT_MAX_FILE_SIZE_BYTES;
34693
+ const why = {
34694
+ too_large: `the file is larger than ${formatMegabytes(maxFileSizeBytes)}`,
34695
+ unsupported_type: "its file type is not supported for reading",
34696
+ no_data: "its contents were not available to the app",
34697
+ empty: "no text or images could be extracted from it (it may be blank)",
34698
+ timeout: "reading it took too long and was stopped",
34699
+ error: "its text could not be extracted (it may be password-protected or damaged)"
34700
+ };
34701
+ const lines = statuses.filter((s) => s.status === "skipped" || s.status === "failed").map((s) => `[${s.fileName} could not be read: ${why[s.reason ?? "error"]}]`);
34702
+ return lines.length > 0 ? lines.join("\n") : null;
34703
+ }
34704
+
34460
34705
  // src/lib/processors/preprocessor.ts
34461
34706
  init_logger();
34462
34707
  var MAX_TOTAL_IMAGES = 20;
34708
+ var DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE = 1e5;
34709
+ var DEFAULT_MAX_EXTRACTED_CHARS_TOTAL = 2e5;
34710
+ var ProcessingTimeoutError = class extends Error {
34711
+ constructor(timeoutMs) {
34712
+ super(`Timed out processing file after ${timeoutMs}ms`);
34713
+ this.name = "ProcessingTimeoutError";
34714
+ }
34715
+ };
34716
+ function truncationMarker(kept, total, fileName) {
34717
+ return `
34718
+ [truncated: showing the first ${kept} of ${total} characters of ${fileName}]`;
34719
+ }
34720
+ function truncateText(text12, limit, fileName) {
34721
+ if (text12.length <= limit) return { text: text12, truncated: false };
34722
+ let end = Math.max(0, limit);
34723
+ const code = text12.charCodeAt(end - 1);
34724
+ if (end > 0 && code >= 55296 && code <= 56319) end--;
34725
+ return {
34726
+ text: `${text12.slice(0, end)}${truncationMarker(end, text12.length, fileName)}`,
34727
+ truncated: true
34728
+ };
34729
+ }
34730
+ var FILE_SEPARATOR = "\n\n---\n\n";
34731
+ function isImageFile(file) {
34732
+ return (file.type ?? "").trim().toLowerCase().startsWith("image/");
34733
+ }
34463
34734
  function createDefaultRegistry() {
34464
34735
  const registry = new ProcessorRegistry();
34465
34736
  registry.register(new PdfProcessor());
@@ -34509,10 +34780,11 @@ async function preprocessFiles(files, options = {}) {
34509
34780
  processors = void 0,
34510
34781
  // undefined means use defaults
34511
34782
  keepOriginalFiles = true,
34512
- maxFileSizeBytes = 10 * 1024 * 1024,
34513
- // 10MB
34783
+ maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES,
34514
34784
  timeoutMs = 3e4,
34515
34785
  // 30s per file
34786
+ maxExtractedCharsPerFile = DEFAULT_MAX_EXTRACTED_CHARS_PER_FILE,
34787
+ maxExtractedCharsTotal = DEFAULT_MAX_EXTRACTED_CHARS_TOTAL,
34516
34788
  onProgress,
34517
34789
  onError
34518
34790
  } = options;
@@ -34522,23 +34794,23 @@ async function preprocessFiles(files, options = {}) {
34522
34794
  extractedContent: null,
34523
34795
  originalFiles: files,
34524
34796
  preprocessedFileIds: [],
34797
+ fileStatuses: [],
34525
34798
  metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
34526
34799
  };
34527
34800
  }
34528
- if (processors !== void 0 && processors !== null && processors.length === 0) {
34529
- return {
34530
- extractedContent: null,
34531
- originalFiles: files,
34532
- preprocessedFileIds: [],
34533
- metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
34534
- };
34535
- }
34536
- if (processors === null) {
34801
+ if (processors === null || processors?.length === 0) {
34802
+ const fileStatuses2 = files.filter((file) => !isImageFile(file)).map((file) => ({
34803
+ fileId: file.id,
34804
+ fileName: file.name,
34805
+ status: "skipped",
34806
+ reason: "unsupported_type"
34807
+ }));
34537
34808
  return {
34538
34809
  extractedContent: null,
34539
34810
  originalFiles: files,
34540
34811
  preprocessedFileIds: [],
34541
- metadata: { processedCount: 0, skippedCount: 0, errorCount: 0 }
34812
+ fileStatuses: fileStatuses2,
34813
+ metadata: { processedCount: 0, skippedCount: fileStatuses2.length, errorCount: 0 }
34542
34814
  };
34543
34815
  }
34544
34816
  let registry;
@@ -34556,28 +34828,38 @@ async function preprocessFiles(files, options = {}) {
34556
34828
  const extractedTexts = [];
34557
34829
  const allImageUrls = [];
34558
34830
  const preprocessedFileIds = [];
34831
+ const fileStatuses = [];
34559
34832
  let processedCount = 0;
34560
34833
  let skippedCount = 0;
34561
34834
  let errorCount = 0;
34835
+ let totalChars = 0;
34836
+ const overBudgetFiles = [];
34562
34837
  for (let i = 0; i < files.length; i++) {
34563
34838
  const file = files[i];
34839
+ const label = `#${i + 1} (${file.type || "unknown type"}, ${file.size} bytes)`;
34840
+ const skip = (reason) => {
34841
+ skippedCount++;
34842
+ fileStatuses.push({ fileId: file.id, fileName: file.name, status: "skipped", reason });
34843
+ };
34564
34844
  onProgress?.(i + 1, files.length, file.name);
34845
+ const processor = registry.findProcessor(file);
34846
+ if (!processor && isImageFile(file)) continue;
34565
34847
  if (file.size > maxFileSizeBytes) {
34566
34848
  logger.info(
34567
- `[preprocessFiles] Skipping "${file.name}" \u2014 exceeds ${maxFileSizeBytes} byte limit`
34849
+ `[preprocessFiles] Skipping file ${label} \u2014 exceeds ${maxFileSizeBytes} byte limit`
34568
34850
  );
34569
- skippedCount++;
34851
+ skip("too_large");
34570
34852
  continue;
34571
34853
  }
34572
- const processor = registry.findProcessor(file);
34573
34854
  if (!processor) {
34574
- skippedCount++;
34855
+ skip("unsupported_type");
34575
34856
  continue;
34576
34857
  }
34858
+ let timer;
34577
34859
  try {
34578
34860
  if (!file.url) {
34579
- logger.info(`[preprocessFiles] Skipping "${file.name}" \u2014 no data URL available`);
34580
- skippedCount++;
34861
+ logger.info(`[preprocessFiles] Skipping file ${label} \u2014 no data URL available`);
34862
+ skip("no_data");
34581
34863
  continue;
34582
34864
  }
34583
34865
  const fileWithData = {
@@ -34586,40 +34868,76 @@ async function preprocessFiles(files, options = {}) {
34586
34868
  };
34587
34869
  const result = await Promise.race([
34588
34870
  processor.process(fileWithData),
34589
- new Promise(
34590
- (_, reject) => setTimeout(() => reject(new Error(`Timed out processing ${file.name}`)), timeoutMs)
34591
- )
34871
+ new Promise((_, reject) => {
34872
+ timer = setTimeout(() => reject(new ProcessingTimeoutError(timeoutMs)), timeoutMs);
34873
+ })
34592
34874
  ]);
34593
34875
  if (result && result.extractedText.trim()) {
34594
- const formattedContent = formatExtractedContent(
34595
- file.name,
34596
- result.extractedText,
34597
- result.format
34598
- );
34599
- extractedTexts.push(formattedContent);
34876
+ const separatorLength = extractedTexts.length > 0 ? FILE_SEPARATOR.length : 0;
34877
+ const remaining = maxExtractedCharsTotal - totalChars - separatorLength - formatExtractedContent(file.name, "", result.format).length;
34600
34878
  preprocessedFileIds.push(file.id);
34601
34879
  processedCount++;
34602
- if (result.imageDataUrls && result.imageDataUrls.length > 0) {
34603
- const remaining = MAX_TOTAL_IMAGES - allImageUrls.length;
34604
- if (remaining > 0) {
34605
- allImageUrls.push(...result.imageDataUrls.slice(0, remaining));
34606
- }
34880
+ const fullLength = result.extractedText.length;
34881
+ if (fullLength > remaining && remaining - truncationMarker(fullLength, fullLength, file.name).length <= 0) {
34882
+ overBudgetFiles.push(file.name);
34883
+ fileStatuses.push({ fileId: file.id, fileName: file.name, status: "truncated" });
34884
+ continue;
34607
34885
  }
34886
+ const images = result.imageDataUrls ?? [];
34887
+ const keptImages = Math.min(
34888
+ images.length,
34889
+ Math.max(0, MAX_TOTAL_IMAGES - allImageUrls.length)
34890
+ );
34891
+ allImageUrls.push(...images.slice(0, keptImages));
34892
+ const text12 = rewriteImageNote(result, file.name, keptImages);
34893
+ const fitsWhole = text12.length <= Math.min(maxExtractedCharsPerFile, remaining);
34894
+ const limit = fitsWhole ? text12.length : Math.min(
34895
+ maxExtractedCharsPerFile,
34896
+ remaining - truncationMarker(text12.length, text12.length, file.name).length
34897
+ );
34898
+ const capped = truncateText(text12, Math.max(0, limit), file.name);
34899
+ const formattedContent = formatExtractedContent(file.name, capped.text, result.format);
34900
+ totalChars += separatorLength + formattedContent.length;
34901
+ extractedTexts.push(formattedContent);
34902
+ const truncated = capped.truncated || keptImages < images.length || result.metadata?.truncated === true;
34903
+ fileStatuses.push({
34904
+ fileId: file.id,
34905
+ fileName: file.name,
34906
+ status: truncated ? "truncated" : keptImages > 0 ? "rendered_as_images" : "extracted"
34907
+ });
34608
34908
  } else {
34609
- skippedCount++;
34909
+ skip("empty");
34610
34910
  }
34611
34911
  } catch (error) {
34612
34912
  errorCount++;
34613
- logger.error(`[preprocessFiles] Error processing "${file.name}":`, error);
34913
+ const timedOut = error instanceof ProcessingTimeoutError;
34914
+ fileStatuses.push({
34915
+ fileId: file.id,
34916
+ fileName: file.name,
34917
+ status: "failed",
34918
+ reason: timedOut ? "timeout" : "error"
34919
+ });
34920
+ logger.error(
34921
+ `[preprocessFiles] Error processing file ${label} with ${processor.name}:`,
34922
+ error
34923
+ );
34614
34924
  onError?.(file.name, error instanceof Error ? error : new Error(String(error)));
34925
+ } finally {
34926
+ clearTimeout(timer);
34615
34927
  }
34616
34928
  }
34617
- const extractedContent = extractedTexts.length > 0 ? extractedTexts.join("\n\n---\n\n") : null;
34929
+ if (overBudgetFiles.length > 0) {
34930
+ extractedTexts.push(
34931
+ `[truncated: the attachment text limit (${maxExtractedCharsTotal} characters) was reached; the contents of ${overBudgetFiles.join(", ")} were not included]`
34932
+ );
34933
+ }
34934
+ const extractedContent = extractedTexts.length > 0 ? extractedTexts.join(FILE_SEPARATOR) : null;
34618
34935
  return {
34619
34936
  extractedContent,
34620
34937
  imageContentUrls: allImageUrls.length > 0 ? allImageUrls : void 0,
34621
34938
  originalFiles: keepOriginalFiles ? files : void 0,
34622
34939
  preprocessedFileIds,
34940
+ fileStatuses,
34623
34941
  metadata: { processedCount, skippedCount, errorCount }
34624
34942
  };
34625
34943
  }
@@ -36777,7 +37095,7 @@ function extractLastUserText(messages) {
36777
37095
  const content = lastUserMsg.content;
36778
37096
  if (typeof content === "string") return content;
36779
37097
  if (Array.isArray(content)) {
36780
- return content.filter((c) => c?.type === "text").map((c) => c.text ?? "").join(" ");
37098
+ return content.filter((c) => c?.type === "text" && !isAttachedFilesText(c.text)).map((c) => c.text ?? "").join(" ");
36781
37099
  }
36782
37100
  return "";
36783
37101
  }
@@ -38452,6 +38770,7 @@ function buildSystemPrompt(ctx, options) {
38452
38770
  extractEntitiesForMemories,
38453
38771
  extractFacts,
38454
38772
  findMatchingTools,
38773
+ formatFileProcessingNotes,
38455
38774
  generateConversationId,
38456
38775
  generateEmbedding,
38457
38776
  generateEmbeddings,