@juspay/neurolink 10.10.8 → 10.10.10

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.
@@ -4,6 +4,7 @@ import { getGlobalDispatcher, interceptors, request } from "undici";
4
4
  import { MultimodalLogger, ProviderImageAdapter, } from "../adapters/providerImageAdapter.js";
5
5
  import { CONVERSATION_INSTRUCTIONS, STRUCTURED_OUTPUT_INSTRUCTIONS, } from "../config/conversationMemory.js";
6
6
  import { getAvailableInputTokens } from "../constants/contextWindows.js";
7
+ import { PDF_LIMITS } from "../core/constants.js";
7
8
  import { enforceAggregateFileBudget, FILE_READ_BUDGET_PERCENT, } from "../context/fileTokenBudget.js";
8
9
  import { isCSVContent, SIZE_TIER_THRESHOLDS } from "../types/index.js";
9
10
  import { tracers, ATTR, withSpan } from "../telemetry/index.js";
@@ -1150,6 +1151,10 @@ async function processExplicitPdfFiles(options, maxSize, provider) {
1150
1151
  // #260: carry the per-page canvas-pixel ceiling so the caller can
1151
1152
  // raise (or lower) the memory guard for the image-fallback render.
1152
1153
  maxCanvasPixels: options.pdfOptions?.maxCanvasPixels,
1154
+ // #297: render scale / page ceiling, so the lowered default is
1155
+ // actually reachable and callers can trade sharpness for memory.
1156
+ scale: options.pdfOptions?.scale,
1157
+ maxPages: options.pdfOptions?.maxPages,
1153
1158
  });
1154
1159
  logger.info(`[PDF] ✅ Queued for multimodal: ${filename} (${result.metadata?.estimatedPages ?? "unknown"} pages)`);
1155
1160
  }
@@ -1458,6 +1463,8 @@ async function convertContentToProviderFormat(content, provider, _model, pdfOpti
1458
1463
  // guard as the `input.pdfFiles` path (see `processExplicitPdfFiles`).
1459
1464
  password: pdfOptions?.password,
1460
1465
  maxCanvasPixels: pdfOptions?.maxCanvasPixels,
1466
+ scale: pdfOptions?.scale,
1467
+ maxPages: pdfOptions?.maxPages,
1461
1468
  }));
1462
1469
  // #309: same aggregate ceiling as `input.pdfFiles`. Without this, moving an
1463
1470
  // over-limit payload from `input.pdfFiles` to `input.content` skipped the
@@ -1809,7 +1816,10 @@ async function convertSimpleImagesToProviderFormat(text, images, provider, _mode
1809
1816
  /**
1810
1817
  * Convert multimodal content (images + PDFs) to provider format
1811
1818
  */
1812
- async function convertMultimodalToProviderFormat(text, images, pdfFiles, provider, model) {
1819
+ async function convertMultimodalToProviderFormat(text, images,
1820
+ // The canonical entry shape (#309) rather than a fourth copy of it inline —
1821
+ // which is what let the render knobs stop short of this function.
1822
+ pdfFiles, provider, model) {
1813
1823
  const content = [
1814
1824
  { type: "text", text },
1815
1825
  ];
@@ -1843,14 +1853,41 @@ async function convertMultimodalToProviderFormat(text, images, pdfFiles, provide
1843
1853
  logger.info(`[PDF→Image] Provider ${provider} doesn't support native PDF. Converting ${pdfFiles.length} PDF(s) to images...`);
1844
1854
  for (const pdf of pdfFiles) {
1845
1855
  try {
1856
+ const effectiveMaxPages = pdf.maxPages ?? PDF_LIMITS.DEFAULT_MAX_PAGES;
1846
1857
  const conversionResult = await PDFImageConverter.convertToImages(pdf.buffer, {
1847
- scale: 2.0, // High quality for OCR/analysis
1848
- maxPages: 20, // Limit pages to prevent token overflow
1858
+ // #297: this is the only PDF→image call the product actually makes,
1859
+ // and it used to hardcode scale 2.0 — silently overriding the
1860
+ // lowered PDF_LIMITS.DEFAULT_SCALE and keeping the memory cost the
1861
+ // issue reports (a 100-page render at 2.0 is ~776MB; 1.5 is ~44%
1862
+ // fewer pixels per page). Callers can raise it back per request.
1863
+ scale: pdf.scale ?? PDF_LIMITS.DEFAULT_SCALE,
1864
+ // Page ceiling guards token overflow; also now caller-adjustable
1865
+ // rather than a constant nothing could reach.
1866
+ maxPages: effectiveMaxPages,
1849
1867
  ...(pdf.password ? { password: pdf.password } : {}), // #258
1850
1868
  ...(pdf.maxCanvasPixels
1851
1869
  ? { maxCanvasPixels: pdf.maxCanvasPixels }
1852
1870
  : {}), // #260
1853
1871
  });
1872
+ // The renderer stops at maxPages, so a longer document is silently
1873
+ // truncated — say so rather than letting the model answer from a
1874
+ // partial document as though it had the whole thing.
1875
+ //
1876
+ // Keyed on the cap being reached, not on pdf.pageCount: that field is
1877
+ // null whenever `input.content` omits `metadata.pages`, which is the
1878
+ // common case, so a page-count comparison would simply never fire
1879
+ // there. Reaching the cap is also unambiguous — a short count caused by
1880
+ // per-page render failures (#294 isolates those into `errors`) would
1881
+ // otherwise be misreported as a maxPages truncation.
1882
+ if (conversionResult.pageCount >= effectiveMaxPages) {
1883
+ logger.warn(`[PDF→Image] ${safeBasename(pdf.filename)} hit the ${effectiveMaxPages}-page ` +
1884
+ `conversion limit. Any pages beyond that were not sent — the model may be ` +
1885
+ `answering from a partial document. Raise pdfOptions.maxPages or split the file.`);
1886
+ }
1887
+ if (conversionResult.errors && conversionResult.errors.length > 0) {
1888
+ logger.warn(`[PDF→Image] ${safeBasename(pdf.filename)}: ${conversionResult.errors.length} page(s) ` +
1889
+ `failed to render and were omitted (page ${conversionResult.errors.map((e) => e.page).join(", ")}).`);
1890
+ }
1854
1891
  logger.info(`[PDF→Image] ✅ Converted ${pdf.filename}: ${conversionResult.pageCount} page(s) → images`);
1855
1892
  // Add each page as an ImagePart (raw base64, not data: URI — see SSRF note above)
1856
1893
  conversionResult.images.forEach((base64Image, pageIndex) => {
@@ -58,6 +58,8 @@ export declare function buildMultimodalOptions(options: StreamOptions, providerN
58
58
  pdfOptions: {
59
59
  password?: string;
60
60
  maxCanvasPixels?: number;
61
+ scale?: number;
62
+ maxPages?: number;
61
63
  } | undefined;
62
64
  systemPrompt: string | undefined;
63
65
  conversationHistory: import("../index.js").ChatMessage[] | undefined;
@@ -377,6 +377,7 @@ export class PDFProcessor {
377
377
  format,
378
378
  scale,
379
379
  maxCanvasPixels,
380
+ maxPages,
380
381
  });
381
382
  logger.debug("[PDF→Image] ✅ PDF validation passed", {
382
383
  bufferSize: pdfBuffer.length,
@@ -535,6 +536,14 @@ export class PDFProcessor {
535
536
  if (!Number.isFinite(opts.maxCanvasPixels) || opts.maxCanvasPixels <= 0) {
536
537
  throw new Error(`Invalid maxCanvasPixels: ${opts.maxCanvasPixels}. Must be a finite number greater than 0.`);
537
538
  }
539
+ // #297: maxPages became caller-controlled, and an unvalidated 0/-1/NaN
540
+ // silently converts nothing, surfacing later as a misleading
541
+ // "PDF has 0 pages" from deep inside the renderer. Reject it here where
542
+ // the message can still name the offending option.
543
+ if (opts.maxPages !== undefined &&
544
+ (!Number.isInteger(opts.maxPages) || opts.maxPages < 1)) {
545
+ throw new Error(`Invalid maxPages: ${opts.maxPages}. Must be a whole number of at least 1.`);
546
+ }
538
547
  if (!pdfBuffer || pdfBuffer.length < 5) {
539
548
  throw new Error("Invalid PDF: Buffer is too small or empty. " +
540
549
  "A valid PDF must be at least 5 bytes (PDF header).");
@@ -570,6 +579,7 @@ export class PDFProcessor {
570
579
  format,
571
580
  scale,
572
581
  maxCanvasPixels,
582
+ maxPages,
573
583
  });
574
584
  const pdfToImgModule = await import("pdf-to-img");
575
585
  const pdf = pdfToImgModule.pdf;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.10.8",
3
+ "version": "10.10.10",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {