@juspay/neurolink 12.22.0 → 12.23.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +6 -3
  2. package/dist/adapters/providerImageAdapter.js +16 -11
  3. package/dist/browser/neurolink.min.js +379 -379
  4. package/dist/cli/factories/commandFactory.js +26 -3
  5. package/dist/constants/enums.d.ts +10 -0
  6. package/dist/constants/enums.js +11 -0
  7. package/dist/core/constants.js +1 -1
  8. package/dist/factories/providerDescriptors.d.ts +1 -1
  9. package/dist/factories/providerDescriptors.js +15 -13
  10. package/dist/image-gen/ImageGenService.js +16 -14
  11. package/dist/index.d.ts +1 -0
  12. package/dist/index.js +1 -0
  13. package/dist/mcp/externalServerManager.d.ts +9 -0
  14. package/dist/mcp/externalServerManager.js +88 -16
  15. package/dist/neurolink.js +18 -11
  16. package/dist/providers/amazonBedrock/client.js +30 -1
  17. package/dist/providers/catalog/friendli.json +130 -0
  18. package/dist/providers/catalog/index.generated.d.ts +1 -1
  19. package/dist/providers/catalog/index.generated.js +3 -0
  20. package/dist/providers/catalog/mistral.json +1 -2
  21. package/dist/providers/catalog/schema.js +34 -0
  22. package/dist/providers/googleAiStudio/client.js +18 -2
  23. package/dist/providers/googleVertex/client.js +13 -4
  24. package/dist/providers/openaiChatCompletionsClient.d.ts +4 -0
  25. package/dist/providers/openaiChatCompletionsClient.js +20 -5
  26. package/dist/providers/recraft.js +8 -1
  27. package/dist/types/cli.d.ts +2 -0
  28. package/dist/types/externalMcp.d.ts +20 -2
  29. package/dist/types/file.d.ts +13 -0
  30. package/dist/types/generate.d.ts +6 -1
  31. package/dist/types/mcp.d.ts +17 -0
  32. package/dist/types/providerCatalog.generated.d.ts +2 -2
  33. package/dist/types/providers.d.ts +4 -0
  34. package/dist/utils/imageDetection.d.ts +10 -0
  35. package/dist/utils/imageDetection.js +38 -12
  36. package/dist/utils/pdfProcessor.d.ts +21 -3
  37. package/dist/utils/pdfProcessor.js +124 -48
  38. package/docs-site/static/search-index.json +1 -1
  39. package/package.json +3 -4
@@ -5,7 +5,10 @@
5
5
  * - Native PDF support for providers that accept PDF directly (Google AI, Vertex, OpenAI, Anthropic, Bedrock)
6
6
  * - PDF → Image conversion for providers that don't support native PDF (Azure, Mistral, Ollama)
7
7
  *
8
- * The conversion uses pdf-to-img package (MuPDF-based) for high-quality conversion.
8
+ * Page counting, text extraction and page rendering all go through pdf-parse,
9
+ * so the process only ever loads pdf-parse's pdfjs-dist. pdfjs keeps its Node
10
+ * worker in a process-wide global, and a second pdfjs version fails its
11
+ * API-vs-Worker version check against whichever copy loaded first.
9
12
  */
10
13
  import type { FileProcessingResult, PDFProcessorOptions, PDFProviderConfig, PDFImageConversionOptions, PDFImageConversionResult, PDFImagePage } from "../types/index.js";
11
14
  export declare class PDFProcessor {
@@ -40,6 +43,21 @@ export declare class PDFProcessor {
40
43
  * falls back to the regex estimate.
41
44
  */
42
45
  private static getAccuratePageCount;
46
+ /**
47
+ * Font and CMap directories of the pdfjs-dist copy pdf-parse itself loads,
48
+ * so non-embedded standard fonts and CJK text render instead of dropping
49
+ * out. Resolved from pdf-parse's location, never our own, to stay on that
50
+ * single copy. Undefined when resolution fails; rendering still works, with
51
+ * pdfjs falling back to its built-in font substitution.
52
+ */
53
+ private static pdfjsAssetDirs;
54
+ private static resolvePdfjsAssetDirs;
55
+ /**
56
+ * Open a PDF for page-by-page PNG rendering through pdf-parse. Rejects with
57
+ * pdf-parse's PasswordException for encrypted PDFs (#258), which the callers
58
+ * map to typed errors.
59
+ */
60
+ private static openForRendering;
43
61
  static estimateTokens(pageCount: number, mode?: "text-only" | "visual"): number;
44
62
  /**
45
63
  * Estimate the largest page's rendered pixel count from the PDF's MediaBox
@@ -96,7 +114,7 @@ export declare class PDFProcessor {
96
114
  *
97
115
  * NOT a wrapper of/over {@link convertToImages}, despite the similar
98
116
  * contract — the two are independent, parallel implementations (each does
99
- * its own `pdf-to-img` import, downscale calculation, and page loop)
117
+ * its own document open, downscale calculation, and page loop)
100
118
  * rather than one delegating to the other. Keep behavior changes (page
101
119
  * isolation, downscale, password handling) in sync across both by hand.
102
120
  */
@@ -113,7 +131,7 @@ export declare class PDFProcessor {
113
131
  * Check if PDF to image conversion is available
114
132
  * Useful for feature detection
115
133
  *
116
- * @returns true if pdf-to-img package is available
134
+ * @returns true if the pdf-parse renderer is available
117
135
  */
118
136
  static isImageConversionAvailable(): Promise<boolean>;
119
137
  /**
@@ -5,8 +5,13 @@
5
5
  * - Native PDF support for providers that accept PDF directly (Google AI, Vertex, OpenAI, Anthropic, Bedrock)
6
6
  * - PDF → Image conversion for providers that don't support native PDF (Azure, Mistral, Ollama)
7
7
  *
8
- * The conversion uses pdf-to-img package (MuPDF-based) for high-quality conversion.
8
+ * Page counting, text extraction and page rendering all go through pdf-parse,
9
+ * so the process only ever loads pdf-parse's pdfjs-dist. pdfjs keeps its Node
10
+ * worker in a process-wide global, and a second pdfjs version fails its
11
+ * API-vs-Worker version check against whichever copy loaded first.
9
12
  */
13
+ import { createRequire } from "node:module";
14
+ import path from "node:path";
10
15
  import { PDF_LIMITS } from "../core/constants.js";
11
16
  import { ErrorFactory } from "./errorHandling.js";
12
17
  import { logger } from "./logger.js";
@@ -297,6 +302,76 @@ export class PDFProcessor {
297
302
  return null;
298
303
  }
299
304
  }
305
+ /**
306
+ * Font and CMap directories of the pdfjs-dist copy pdf-parse itself loads,
307
+ * so non-embedded standard fonts and CJK text render instead of dropping
308
+ * out. Resolved from pdf-parse's location, never our own, to stay on that
309
+ * single copy. Undefined when resolution fails; rendering still works, with
310
+ * pdfjs falling back to its built-in font substitution.
311
+ */
312
+ static pdfjsAssetDirs;
313
+ static resolvePdfjsAssetDirs() {
314
+ if (PDFProcessor.pdfjsAssetDirs === undefined) {
315
+ try {
316
+ const pdfParseEntry = createRequire(import.meta.url).resolve("pdf-parse");
317
+ const pdfjsRoot = path.dirname(createRequire(pdfParseEntry).resolve("pdfjs-dist/package.json"));
318
+ PDFProcessor.pdfjsAssetDirs = {
319
+ standardFontDataUrl: path.join(pdfjsRoot, "standard_fonts") + path.sep,
320
+ cMapUrl: path.join(pdfjsRoot, "cmaps") + path.sep,
321
+ };
322
+ }
323
+ catch (error) {
324
+ logger.debug(`[PDF→Image] pdfjs font/CMap directories unavailable: ${error instanceof Error ? error.message : String(error)}`);
325
+ PDFProcessor.pdfjsAssetDirs = null;
326
+ }
327
+ }
328
+ return PDFProcessor.pdfjsAssetDirs ?? undefined;
329
+ }
330
+ /**
331
+ * Open a PDF for page-by-page PNG rendering through pdf-parse. Rejects with
332
+ * pdf-parse's PasswordException for encrypted PDFs (#258), which the callers
333
+ * map to typed errors.
334
+ */
335
+ static async openForRendering(pdfBuffer, scale, password) {
336
+ const { PDFParse } = await import("pdf-parse");
337
+ const parser = new PDFParse({
338
+ data: new Uint8Array(pdfBuffer),
339
+ isEvalSupported: false,
340
+ cMapPacked: true,
341
+ ...PDFProcessor.resolvePdfjsAssetDirs(),
342
+ ...(password ? { password } : {}),
343
+ });
344
+ try {
345
+ const { total } = await parser.getInfo();
346
+ return {
347
+ length: total,
348
+ getPage: async (pageNumber) => {
349
+ const shot = await parser.getScreenshot({
350
+ partial: [pageNumber],
351
+ scale,
352
+ imageBuffer: true,
353
+ imageDataUrl: false,
354
+ });
355
+ const page = shot.pages[0];
356
+ if (!page || page.data.length === 0) {
357
+ throw new Error(`page ${pageNumber} rendered no image data`);
358
+ }
359
+ return Buffer.from(page.data);
360
+ },
361
+ destroy: async () => {
362
+ await parser.destroy().catch(() => {
363
+ /* cleanup - ignore destroy errors */
364
+ });
365
+ },
366
+ };
367
+ }
368
+ catch (error) {
369
+ await parser.destroy().catch(() => {
370
+ /* cleanup - ignore destroy errors */
371
+ });
372
+ throw error;
373
+ }
374
+ }
300
375
  static estimateTokens(pageCount, mode = "visual") {
301
376
  if (mode === "text-only") {
302
377
  return Math.ceil((pageCount / 3) * 1000);
@@ -395,16 +470,14 @@ export class PDFProcessor {
395
470
  estimatedMemoryMB,
396
471
  sizeMB: Number(sizeMB.toFixed(2)),
397
472
  });
473
+ let document;
398
474
  try {
399
- // Dynamic import to avoid loading MuPDF binaries until needed
400
- const pdfToImgModule = await import("pdf-to-img");
401
- const pdf = pdfToImgModule.pdf;
402
475
  logger.debug("[PDF→Image] Starting PDF to image conversion", {
403
476
  bufferSize: pdfBuffer.length,
404
477
  scale,
405
478
  maxPages: maxPages || "all",
406
479
  });
407
- // #260: pre-flight page-size check WITHOUT rendering. pdf-to-img applies
480
+ // #260: pre-flight page-size check WITHOUT rendering. The renderer applies
408
481
  // `scale` uniformly with no per-page hook and no pixel guard, so a very
409
482
  // large page (e.g. an architectural drawing with a huge MediaBox) can
410
483
  // allocate gigabytes of canvas. Read the largest MediaBox from the PDF
@@ -416,7 +489,7 @@ export class PDFProcessor {
416
489
  const downscale = Math.sqrt(maxCanvasPixels / largestPixels);
417
490
  // Floor the result: an astronomically large (but now finite, see
418
491
  // `largestPagePixels`) pixel estimate would otherwise push `downscale`
419
- // — and therefore `effectiveScale` — toward 0, handing `pdf-to-img` a
492
+ // — and therefore `effectiveScale` — toward 0, handing the renderer a
420
493
  // degenerate viewport instead of a small-but-renderable page.
421
494
  effectiveScale = Math.max(PDF_LIMITS.MIN_EFFECTIVE_SCALE, scale * downscale);
422
495
  // Recompute the ratio actually applied (may differ from `downscale`
@@ -431,14 +504,9 @@ export class PDFProcessor {
431
504
  logger.warn(`[PDF→Image] ⚠️ ${msg}`);
432
505
  warnings.push(msg);
433
506
  }
434
- // Create PDF document (password forwarded for encrypted PDFs, #258).
435
- // pdf-to-img resolves `.length` (numPages) synchronously here and exposes
436
- // `.getPage(n)`, so we drive an indexed loop rather than the async
437
- // iterator — that lets one bad page be isolated instead of aborting all.
438
- const document = await pdf(pdfBuffer, {
439
- scale: effectiveScale,
440
- ...(password ? { password } : {}),
441
- });
507
+ // Password forwarded for encrypted PDFs (#258). An indexed getPage()
508
+ // loop lets one bad page be isolated instead of aborting all.
509
+ document = await PDFProcessor.openForRendering(pdfBuffer, effectiveScale, password);
442
510
  const totalPages = document.length;
443
511
  // #294: convert page-by-page with per-page isolation. A single page whose
444
512
  // canvas render throws (e.g. a degenerate MediaBox) no longer discards
@@ -518,6 +586,9 @@ export class PDFProcessor {
518
586
  cause: error,
519
587
  });
520
588
  }
589
+ finally {
590
+ await document?.destroy();
591
+ }
521
592
  }
522
593
  /**
523
594
  * Shared input validation for the image-conversion paths. Returns the PDF
@@ -568,7 +639,7 @@ export class PDFProcessor {
568
639
  *
569
640
  * NOT a wrapper of/over {@link convertToImages}, despite the similar
570
641
  * contract — the two are independent, parallel implementations (each does
571
- * its own `pdf-to-img` import, downscale calculation, and page loop)
642
+ * its own document open, downscale calculation, and page loop)
572
643
  * rather than one delegating to the other. Keep behavior changes (page
573
644
  * isolation, downscale, password handling) in sync across both by hand.
574
645
  */
@@ -581,8 +652,6 @@ export class PDFProcessor {
581
652
  maxCanvasPixels,
582
653
  maxPages,
583
654
  });
584
- const pdfToImgModule = await import("pdf-to-img");
585
- const pdf = pdfToImgModule.pdf;
586
655
  // #260: uniform downscale so the largest page stays under maxCanvasPixels.
587
656
  let effectiveScale = scale;
588
657
  const largestPixels = PDFProcessor.largestPagePixels(pdfBuffer, scale);
@@ -596,10 +665,7 @@ export class PDFProcessor {
596
665
  // being yielded as a page error.
597
666
  let document;
598
667
  try {
599
- document = await pdf(pdfBuffer, {
600
- scale: effectiveScale,
601
- ...(password ? { password } : {}),
602
- });
668
+ document = await PDFProcessor.openForRendering(pdfBuffer, effectiveScale, password);
603
669
  }
604
670
  catch (error) {
605
671
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -617,33 +683,43 @@ export class PDFProcessor {
617
683
  }
618
684
  const totalPages = document.length;
619
685
  let converted = 0;
620
- for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
621
- if (maxPages !== undefined && pageNum - 1 >= maxPages) {
622
- break;
623
- }
624
- try {
625
- const page = await document.getPage(pageNum);
626
- const base64Image = page.toString("base64");
627
- converted++;
628
- if (onProgress) {
629
- await onProgress({
630
- pagesConverted: converted,
631
- totalPages,
632
- elapsedMs: Date.now() - startTime,
633
- });
686
+ try {
687
+ for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
688
+ if (maxPages !== undefined && pageNum - 1 >= maxPages) {
689
+ break;
690
+ }
691
+ try {
692
+ const page = await document.getPage(pageNum);
693
+ const base64Image = page.toString("base64");
694
+ converted++;
695
+ if (onProgress) {
696
+ await onProgress({
697
+ pagesConverted: converted,
698
+ totalPages,
699
+ elapsedMs: Date.now() - startTime,
700
+ });
701
+ }
702
+ yield {
703
+ pageIndex: pageNum,
704
+ image: base64Image,
705
+ imageSizeBytes: page.length,
706
+ };
707
+ }
708
+ catch (pageError) {
709
+ const msg = pageError instanceof Error ? pageError.message : String(pageError);
710
+ logger.warn(`[PDF→Image] ⚠️ page ${pageNum} failed to render (stream): ${msg}`);
711
+ yield {
712
+ pageIndex: pageNum,
713
+ image: "",
714
+ imageSizeBytes: 0,
715
+ error: msg,
716
+ };
634
717
  }
635
- yield {
636
- pageIndex: pageNum,
637
- image: base64Image,
638
- imageSizeBytes: page.length,
639
- };
640
- }
641
- catch (pageError) {
642
- const msg = pageError instanceof Error ? pageError.message : String(pageError);
643
- logger.warn(`[PDF→Image] ⚠️ page ${pageNum} failed to render (stream): ${msg}`);
644
- yield { pageIndex: pageNum, image: "", imageSizeBytes: 0, error: msg };
645
718
  }
646
719
  }
720
+ finally {
721
+ await document.destroy();
722
+ }
647
723
  }
648
724
  /**
649
725
  * Convert a PDF file path to an array of base64 PNG images
@@ -661,11 +737,11 @@ export class PDFProcessor {
661
737
  * Check if PDF to image conversion is available
662
738
  * Useful for feature detection
663
739
  *
664
- * @returns true if pdf-to-img package is available
740
+ * @returns true if the pdf-parse renderer is available
665
741
  */
666
742
  static async isImageConversionAvailable() {
667
743
  try {
668
- await import("pdf-to-img");
744
+ await import("pdf-parse");
669
745
  return true;
670
746
  }
671
747
  catch {
@@ -683,7 +759,7 @@ export class PDFProcessor {
683
759
  static estimateConversionMemoryUsage(pdfSizeBytes, pageCount, scale = PDF_LIMITS.DEFAULT_SCALE) {
684
760
  // Rough estimation:
685
761
  // - Each page at scale 2 produces ~1-3MB PNG
686
- // - MuPDF needs ~2x PDF size for processing
762
+ // - pdfjs needs ~2x PDF size for processing
687
763
  // - Output images need ~2MB per page on average
688
764
  const pdfProcessingMB = (pdfSizeBytes / (1024 * 1024)) * 2;
689
765
  const outputImagesMB = pageCount * 2 * scale;