@juspay/neurolink 10.4.4 → 10.5.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.
@@ -283,6 +283,8 @@ export const EXTENSION_MIME_MAP = {
283
283
  ".webp": IMAGE_MIME_TYPES.WEBP,
284
284
  ".bmp": IMAGE_MIME_TYPES.BMP,
285
285
  ".ico": IMAGE_MIME_TYPES.ICO,
286
+ ".heic": IMAGE_MIME_TYPES.HEIC,
287
+ ".heif": IMAGE_MIME_TYPES.HEIF,
286
288
  // Source code
287
289
  ".js": TEXT_MIME_TYPES.JAVASCRIPT,
288
290
  ".mjs": TEXT_MIME_TYPES.JAVASCRIPT,
@@ -23,6 +23,7 @@ async function getArchiveProcessor() {
23
23
  import { tracers, ATTR, withSpan } from "../telemetry/index.js";
24
24
  import { CSVProcessor } from "./csvProcessor.js";
25
25
  import { ImageProcessor } from "./imageProcessor.js";
26
+ import { detectIsoBmffImageMimeType, hasFtypBoxSignature } from "./isoBmff.js";
26
27
  import { logger } from "./logger.js";
27
28
  import { withTimeout } from "./errorHandling.js";
28
29
  import { normalizeUrlForCache, redactUrlForError, sanitizeErrorCause, } from "./logSanitize.js";
@@ -1659,17 +1660,35 @@ class MagicBytesStrategy {
1659
1660
  if (this.isWebP(input)) {
1660
1661
  return this.result("image", "image/webp", 95);
1661
1662
  }
1663
+ if (input.length >= 2 && input[0] === 0x42 && input[1] === 0x4d) {
1664
+ return this.result("image", "image/bmp", 95);
1665
+ }
1666
+ if (input.length >= 4 &&
1667
+ ((input[0] === 0x49 &&
1668
+ input[1] === 0x49 &&
1669
+ input[2] === 0x2a &&
1670
+ input[3] === 0x00) ||
1671
+ (input[0] === 0x4d &&
1672
+ input[1] === 0x4d &&
1673
+ input[2] === 0x00 &&
1674
+ input[3] === 0x2a))) {
1675
+ return this.result("image", "image/tiff", 95);
1676
+ }
1677
+ if (input.length >= 4 &&
1678
+ input[0] === 0x00 &&
1679
+ input[1] === 0x00 &&
1680
+ input[2] === 0x01 &&
1681
+ input[3] === 0x00 &&
1682
+ !hasFtypBoxSignature(input)) {
1683
+ return this.result("image", "image/x-icon", 95);
1684
+ }
1662
1685
  if (this.isPDF(input)) {
1663
1686
  return this.result("pdf", "application/pdf", 95);
1664
1687
  }
1665
1688
  // ISO-BMFF ("ftyp" at offset 4): MP4 video, QuickTime MOV, or M4A/M4B/M4P
1666
1689
  // audio all share this box — disambiguate by the major brand at offset 8-11,
1667
1690
  // otherwise an .m4a audio file is misrouted to the video pipeline.
1668
- if (input.length >= 8 &&
1669
- input[4] === 0x66 &&
1670
- input[5] === 0x74 &&
1671
- input[6] === 0x79 &&
1672
- input[7] === 0x70) {
1691
+ if (hasFtypBoxSignature(input)) {
1673
1692
  const brand = input.length >= 12 ? input.toString("latin1", 8, 12) : "";
1674
1693
  // AVIF images share the ISO-BMFF ftyp box with MP4/MOV; the major brand
1675
1694
  // ('avif' still, 'avis' sequence, 'avio' intra-only AV1 image/sequence
@@ -1677,8 +1696,9 @@ class MagicBytesStrategy {
1677
1696
  // major_brand by real encoders) distinguishes them. Detect before the
1678
1697
  // audio/video branches so an AVIF buffer isn't misrouted to the video
1679
1698
  // pipeline (#286).
1680
- if (/^(avif|avis|avio)/.test(brand)) {
1681
- return this.result("image", "image/avif", 95);
1699
+ const imageMimeType = detectIsoBmffImageMimeType(input);
1700
+ if (imageMimeType) {
1701
+ return this.result("image", imageMimeType, 95);
1682
1702
  }
1683
1703
  if (/^(M4A|M4B|M4P|F4A|F4B)/.test(brand)) {
1684
1704
  return this.result("audio", "audio/mp4", 95);
@@ -1775,6 +1795,16 @@ class MagicBytesStrategy {
1775
1795
  if (input.length >= 2 && input[0] === 0x1f && input[1] === 0x8b) {
1776
1796
  return this.result("archive", "application/gzip", 90);
1777
1797
  }
1798
+ // 7z: 37 7A BC AF 27 1C
1799
+ if (input.length >= 6 &&
1800
+ input[0] === 0x37 &&
1801
+ input[1] === 0x7a &&
1802
+ input[2] === 0xbc &&
1803
+ input[3] === 0xaf &&
1804
+ input[4] === 0x27 &&
1805
+ input[5] === 0x1c) {
1806
+ return this.result("archive", "application/x-7z-compressed", 95);
1807
+ }
1778
1808
  // RAR: "Rar!"
1779
1809
  if (input.length >= 4 &&
1780
1810
  input[0] === 0x52 &&
@@ -2006,6 +2036,8 @@ class ExtensionStrategy {
2006
2036
  // AI providers don't support SVG format, so we process it as sanitized text
2007
2037
  svg: "svg",
2008
2038
  avif: "image",
2039
+ heic: "image",
2040
+ heif: "image",
2009
2041
  pdf: "pdf",
2010
2042
  // Video formats
2011
2043
  mp4: "video",
@@ -2179,6 +2211,8 @@ class ExtensionStrategy {
2179
2211
  tif: "image/tiff",
2180
2212
  svg: "image/svg+xml",
2181
2213
  avif: "image/avif",
2214
+ heic: "image/heic",
2215
+ heif: "image/heif",
2182
2216
  pdf: "application/pdf",
2183
2217
  // Video MIME types
2184
2218
  mp4: "video/mp4",
@@ -11,6 +11,7 @@ import { SYSTEM_LIMITS } from "../core/constants.js";
11
11
  import { SIZE_LIMITS_BYTES } from "../processors/config/sizeLimits.js";
12
12
  import { getImageCache } from "./imageCache.js";
13
13
  import { ErrorFactory, NeuroLinkError } from "./errorHandling.js";
14
+ import { detectIsoBmffImageMimeType, hasFtypBoxSignature } from "./isoBmff.js";
14
15
  /**
15
16
  * Minimum bytes required just to detect an image format from magic bytes (#293).
16
17
  */
@@ -104,7 +105,7 @@ function isRetryableDownloadError(error) {
104
105
  }
105
106
  /**
106
107
  * Reject `detectImageType()`'s `application/octet-stream` sentinel — the
107
- * honest "no known image signature (PNG/JPEG/GIF/WebP/BMP/TIFF/SVG/AVIF)
108
+ * honest "no known image signature (PNG/JPEG/GIF/WebP/BMP/TIFF/ICO/SVG/AVIF/HEIC/HEIF)
108
109
  * matched" fallback (#261/#286). Packaging that sentinel into a data URI
109
110
  * hands vision providers (OpenAI/Anthropic/Google) a MIME type they reject
110
111
  * outright with an HTTP 400, so every public conversion path that turns
@@ -118,7 +119,7 @@ function assertKnownImageType(mediaType) {
118
119
  if (mediaType === "application/octet-stream") {
119
120
  logger.error("Unable to detect a supported image format from file content");
120
121
  throw new Error("Unsupported or corrupted image: no known image signature " +
121
- "(PNG/JPEG/GIF/WebP/BMP/TIFF/SVG/AVIF) was found in the file content.");
122
+ "(PNG/JPEG/GIF/WebP/BMP/TIFF/ICO/SVG/AVIF/HEIC/HEIF) was found in the file content.");
122
123
  }
123
124
  }
124
125
  /**
@@ -457,24 +458,22 @@ export class ImageProcessor {
457
458
  return "image/svg+xml";
458
459
  }
459
460
  }
460
- // AVIF (ISOBMFF): "ftyp" box at offset 4, major brand at offset 8.
461
- // Accept the AVIF-spec brand variants 'avif' (still image), 'avis'
462
- // (image sequence), and 'avio' (intra-only AV1 image/sequence the
463
- // spec lists it under compatible_brands rather than major_brand, but
464
- // real-world encoders emit it as major_brand too, so it's accepted
465
- // here) and compare bytes directly rather than via Buffer.toString()
466
- // (which would utf-8-decode non-ASCII bytes) so the check is exact
467
- // (#286).
468
- if (input.length >= 12) {
469
- const isFtyp = input[4] === 0x66 && // 'f'
470
- input[5] === 0x74 && // 't'
471
- input[6] === 0x79 && // 'y'
472
- input[7] === 0x70; // 'p'
473
- const brand = String.fromCharCode(input[8], input[9], input[10], input[11]);
474
- if (isFtyp &&
475
- (brand === "avif" || brand === "avis" || brand === "avio")) {
476
- return "image/avif";
477
- }
461
+ // ISO-BMFF images: "ftyp" box at offset 4, major brand at offset 8.
462
+ // AVIF has codec-specific major brands. HEIC uses HEVC image/sequence
463
+ // brands, while generic HEIF structural brands (mif1/msf1) need a
464
+ // compatible codec brand to avoid mistaking AVIF or generic MP4 data
465
+ // for HEIF.
466
+ const isoBmffMimeType = detectIsoBmffImageMimeType(input);
467
+ if (isoBmffMimeType) {
468
+ return isoBmffMimeType;
469
+ }
470
+ // ICO: 00 00 01 00 (icon type=1)
471
+ if (input[0] === 0x00 &&
472
+ input[1] === 0x00 &&
473
+ input[2] === 0x01 &&
474
+ input[3] === 0x00 &&
475
+ !hasFtypBoxSignature(input)) {
476
+ return "image/x-icon";
478
477
  }
479
478
  // BMP: "BM" magic (0x42 0x4D)
480
479
  if (input[0] === 0x42 && input[1] === 0x4d) {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared ISO-BMFF (`ftyp`) image-brand detection.
3
+ *
4
+ * FileDetector and ImageProcessor must use the same brand tables and declared
5
+ * box boundary when classifying AVIF, HEIC, and structural HEIF brands. Keeping
6
+ * the scan here prevents the two consumers from drifting as new brands are
7
+ * added.
8
+ */
9
+ export declare const AVIF_FTYP_BRANDS: Set<string>;
10
+ export declare const HEIC_FTYP_BRANDS: Set<string>;
11
+ export declare const HEIF_STRUCTURAL_FTYP_BRANDS: Set<string>;
12
+ /**
13
+ * Whether the buffer starts with an ISO-BMFF `ftyp` box signature.
14
+ */
15
+ export declare function hasFtypBoxSignature(input: Buffer): boolean;
16
+ /**
17
+ * Read compatible brands without scanning past the declared `ftyp` box.
18
+ */
19
+ export declare function getCompatibleFtypBrands(input: Buffer): Set<string>;
20
+ /**
21
+ * Return the image MIME type encoded by an ISO-BMFF `ftyp` box, or null when
22
+ * the box belongs to a non-image ISO-BMFF flavor.
23
+ */
24
+ export declare function detectIsoBmffImageMimeType(input: Buffer): string | null;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Shared ISO-BMFF (`ftyp`) image-brand detection.
3
+ *
4
+ * FileDetector and ImageProcessor must use the same brand tables and declared
5
+ * box boundary when classifying AVIF, HEIC, and structural HEIF brands. Keeping
6
+ * the scan here prevents the two consumers from drifting as new brands are
7
+ * added.
8
+ */
9
+ export const AVIF_FTYP_BRANDS = new Set(["avif", "avis", "avio"]);
10
+ export const HEIC_FTYP_BRANDS = new Set([
11
+ "heic",
12
+ "heix",
13
+ "hevc",
14
+ "hevx",
15
+ "heim",
16
+ "heis",
17
+ "hevm",
18
+ "hevs",
19
+ ]);
20
+ export const HEIF_STRUCTURAL_FTYP_BRANDS = new Set(["mif1", "msf1"]);
21
+ /**
22
+ * Whether the buffer starts with an ISO-BMFF `ftyp` box signature.
23
+ */
24
+ export function hasFtypBoxSignature(input) {
25
+ return (input.length >= 8 &&
26
+ input[4] === 0x66 &&
27
+ input[5] === 0x74 &&
28
+ input[6] === 0x79 &&
29
+ input[7] === 0x70);
30
+ }
31
+ /**
32
+ * Read compatible brands without scanning past the declared `ftyp` box.
33
+ */
34
+ export function getCompatibleFtypBrands(input) {
35
+ const compatibleBrands = new Set();
36
+ if (input.length < 20) {
37
+ return compatibleBrands;
38
+ }
39
+ const declaredBoxSize = input.readUInt32BE(0);
40
+ const boxEnd = declaredBoxSize === 0
41
+ ? input.length
42
+ : Math.min(input.length, declaredBoxSize);
43
+ for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
44
+ compatibleBrands.add(input.toString("latin1", offset, offset + 4));
45
+ }
46
+ return compatibleBrands;
47
+ }
48
+ function hasFtypBrand(compatibleBrands, candidates) {
49
+ for (const brand of compatibleBrands) {
50
+ if (candidates.has(brand)) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ /**
57
+ * Return the image MIME type encoded by an ISO-BMFF `ftyp` box, or null when
58
+ * the box belongs to a non-image ISO-BMFF flavor.
59
+ */
60
+ export function detectIsoBmffImageMimeType(input) {
61
+ if (input.length < 12 || !hasFtypBoxSignature(input)) {
62
+ return null;
63
+ }
64
+ const brand = input.toString("latin1", 8, 12);
65
+ if (AVIF_FTYP_BRANDS.has(brand)) {
66
+ return "image/avif";
67
+ }
68
+ if (HEIC_FTYP_BRANDS.has(brand)) {
69
+ return "image/heic";
70
+ }
71
+ if (!HEIF_STRUCTURAL_FTYP_BRANDS.has(brand)) {
72
+ return null;
73
+ }
74
+ const compatibleBrands = getCompatibleFtypBrands(input);
75
+ if (hasFtypBrand(compatibleBrands, HEIC_FTYP_BRANDS)) {
76
+ return "image/heif";
77
+ }
78
+ if (hasFtypBrand(compatibleBrands, AVIF_FTYP_BRANDS)) {
79
+ return "image/avif";
80
+ }
81
+ return null;
82
+ }
83
+ //# sourceMappingURL=isoBmff.js.map
@@ -283,6 +283,8 @@ export const EXTENSION_MIME_MAP = {
283
283
  ".webp": IMAGE_MIME_TYPES.WEBP,
284
284
  ".bmp": IMAGE_MIME_TYPES.BMP,
285
285
  ".ico": IMAGE_MIME_TYPES.ICO,
286
+ ".heic": IMAGE_MIME_TYPES.HEIC,
287
+ ".heif": IMAGE_MIME_TYPES.HEIF,
286
288
  // Source code
287
289
  ".js": TEXT_MIME_TYPES.JAVASCRIPT,
288
290
  ".mjs": TEXT_MIME_TYPES.JAVASCRIPT,
@@ -23,6 +23,7 @@ async function getArchiveProcessor() {
23
23
  import { tracers, ATTR, withSpan } from "../telemetry/index.js";
24
24
  import { CSVProcessor } from "./csvProcessor.js";
25
25
  import { ImageProcessor } from "./imageProcessor.js";
26
+ import { detectIsoBmffImageMimeType, hasFtypBoxSignature } from "./isoBmff.js";
26
27
  import { logger } from "./logger.js";
27
28
  import { withTimeout } from "./errorHandling.js";
28
29
  import { normalizeUrlForCache, redactUrlForError, sanitizeErrorCause, } from "./logSanitize.js";
@@ -1659,17 +1660,35 @@ class MagicBytesStrategy {
1659
1660
  if (this.isWebP(input)) {
1660
1661
  return this.result("image", "image/webp", 95);
1661
1662
  }
1663
+ if (input.length >= 2 && input[0] === 0x42 && input[1] === 0x4d) {
1664
+ return this.result("image", "image/bmp", 95);
1665
+ }
1666
+ if (input.length >= 4 &&
1667
+ ((input[0] === 0x49 &&
1668
+ input[1] === 0x49 &&
1669
+ input[2] === 0x2a &&
1670
+ input[3] === 0x00) ||
1671
+ (input[0] === 0x4d &&
1672
+ input[1] === 0x4d &&
1673
+ input[2] === 0x00 &&
1674
+ input[3] === 0x2a))) {
1675
+ return this.result("image", "image/tiff", 95);
1676
+ }
1677
+ if (input.length >= 4 &&
1678
+ input[0] === 0x00 &&
1679
+ input[1] === 0x00 &&
1680
+ input[2] === 0x01 &&
1681
+ input[3] === 0x00 &&
1682
+ !hasFtypBoxSignature(input)) {
1683
+ return this.result("image", "image/x-icon", 95);
1684
+ }
1662
1685
  if (this.isPDF(input)) {
1663
1686
  return this.result("pdf", "application/pdf", 95);
1664
1687
  }
1665
1688
  // ISO-BMFF ("ftyp" at offset 4): MP4 video, QuickTime MOV, or M4A/M4B/M4P
1666
1689
  // audio all share this box — disambiguate by the major brand at offset 8-11,
1667
1690
  // otherwise an .m4a audio file is misrouted to the video pipeline.
1668
- if (input.length >= 8 &&
1669
- input[4] === 0x66 &&
1670
- input[5] === 0x74 &&
1671
- input[6] === 0x79 &&
1672
- input[7] === 0x70) {
1691
+ if (hasFtypBoxSignature(input)) {
1673
1692
  const brand = input.length >= 12 ? input.toString("latin1", 8, 12) : "";
1674
1693
  // AVIF images share the ISO-BMFF ftyp box with MP4/MOV; the major brand
1675
1694
  // ('avif' still, 'avis' sequence, 'avio' intra-only AV1 image/sequence
@@ -1677,8 +1696,9 @@ class MagicBytesStrategy {
1677
1696
  // major_brand by real encoders) distinguishes them. Detect before the
1678
1697
  // audio/video branches so an AVIF buffer isn't misrouted to the video
1679
1698
  // pipeline (#286).
1680
- if (/^(avif|avis|avio)/.test(brand)) {
1681
- return this.result("image", "image/avif", 95);
1699
+ const imageMimeType = detectIsoBmffImageMimeType(input);
1700
+ if (imageMimeType) {
1701
+ return this.result("image", imageMimeType, 95);
1682
1702
  }
1683
1703
  if (/^(M4A|M4B|M4P|F4A|F4B)/.test(brand)) {
1684
1704
  return this.result("audio", "audio/mp4", 95);
@@ -1775,6 +1795,16 @@ class MagicBytesStrategy {
1775
1795
  if (input.length >= 2 && input[0] === 0x1f && input[1] === 0x8b) {
1776
1796
  return this.result("archive", "application/gzip", 90);
1777
1797
  }
1798
+ // 7z: 37 7A BC AF 27 1C
1799
+ if (input.length >= 6 &&
1800
+ input[0] === 0x37 &&
1801
+ input[1] === 0x7a &&
1802
+ input[2] === 0xbc &&
1803
+ input[3] === 0xaf &&
1804
+ input[4] === 0x27 &&
1805
+ input[5] === 0x1c) {
1806
+ return this.result("archive", "application/x-7z-compressed", 95);
1807
+ }
1778
1808
  // RAR: "Rar!"
1779
1809
  if (input.length >= 4 &&
1780
1810
  input[0] === 0x52 &&
@@ -2006,6 +2036,8 @@ class ExtensionStrategy {
2006
2036
  // AI providers don't support SVG format, so we process it as sanitized text
2007
2037
  svg: "svg",
2008
2038
  avif: "image",
2039
+ heic: "image",
2040
+ heif: "image",
2009
2041
  pdf: "pdf",
2010
2042
  // Video formats
2011
2043
  mp4: "video",
@@ -2179,6 +2211,8 @@ class ExtensionStrategy {
2179
2211
  tif: "image/tiff",
2180
2212
  svg: "image/svg+xml",
2181
2213
  avif: "image/avif",
2214
+ heic: "image/heic",
2215
+ heif: "image/heif",
2182
2216
  pdf: "application/pdf",
2183
2217
  // Video MIME types
2184
2218
  mp4: "video/mp4",
@@ -11,6 +11,7 @@ import { SYSTEM_LIMITS } from "../core/constants.js";
11
11
  import { SIZE_LIMITS_BYTES } from "../processors/config/sizeLimits.js";
12
12
  import { getImageCache } from "./imageCache.js";
13
13
  import { ErrorFactory, NeuroLinkError } from "./errorHandling.js";
14
+ import { detectIsoBmffImageMimeType, hasFtypBoxSignature } from "./isoBmff.js";
14
15
  /**
15
16
  * Minimum bytes required just to detect an image format from magic bytes (#293).
16
17
  */
@@ -104,7 +105,7 @@ function isRetryableDownloadError(error) {
104
105
  }
105
106
  /**
106
107
  * Reject `detectImageType()`'s `application/octet-stream` sentinel — the
107
- * honest "no known image signature (PNG/JPEG/GIF/WebP/BMP/TIFF/SVG/AVIF)
108
+ * honest "no known image signature (PNG/JPEG/GIF/WebP/BMP/TIFF/ICO/SVG/AVIF/HEIC/HEIF)
108
109
  * matched" fallback (#261/#286). Packaging that sentinel into a data URI
109
110
  * hands vision providers (OpenAI/Anthropic/Google) a MIME type they reject
110
111
  * outright with an HTTP 400, so every public conversion path that turns
@@ -118,7 +119,7 @@ function assertKnownImageType(mediaType) {
118
119
  if (mediaType === "application/octet-stream") {
119
120
  logger.error("Unable to detect a supported image format from file content");
120
121
  throw new Error("Unsupported or corrupted image: no known image signature " +
121
- "(PNG/JPEG/GIF/WebP/BMP/TIFF/SVG/AVIF) was found in the file content.");
122
+ "(PNG/JPEG/GIF/WebP/BMP/TIFF/ICO/SVG/AVIF/HEIC/HEIF) was found in the file content.");
122
123
  }
123
124
  }
124
125
  /**
@@ -457,24 +458,22 @@ export class ImageProcessor {
457
458
  return "image/svg+xml";
458
459
  }
459
460
  }
460
- // AVIF (ISOBMFF): "ftyp" box at offset 4, major brand at offset 8.
461
- // Accept the AVIF-spec brand variants 'avif' (still image), 'avis'
462
- // (image sequence), and 'avio' (intra-only AV1 image/sequence the
463
- // spec lists it under compatible_brands rather than major_brand, but
464
- // real-world encoders emit it as major_brand too, so it's accepted
465
- // here) and compare bytes directly rather than via Buffer.toString()
466
- // (which would utf-8-decode non-ASCII bytes) so the check is exact
467
- // (#286).
468
- if (input.length >= 12) {
469
- const isFtyp = input[4] === 0x66 && // 'f'
470
- input[5] === 0x74 && // 't'
471
- input[6] === 0x79 && // 'y'
472
- input[7] === 0x70; // 'p'
473
- const brand = String.fromCharCode(input[8], input[9], input[10], input[11]);
474
- if (isFtyp &&
475
- (brand === "avif" || brand === "avis" || brand === "avio")) {
476
- return "image/avif";
477
- }
461
+ // ISO-BMFF images: "ftyp" box at offset 4, major brand at offset 8.
462
+ // AVIF has codec-specific major brands. HEIC uses HEVC image/sequence
463
+ // brands, while generic HEIF structural brands (mif1/msf1) need a
464
+ // compatible codec brand to avoid mistaking AVIF or generic MP4 data
465
+ // for HEIF.
466
+ const isoBmffMimeType = detectIsoBmffImageMimeType(input);
467
+ if (isoBmffMimeType) {
468
+ return isoBmffMimeType;
469
+ }
470
+ // ICO: 00 00 01 00 (icon type=1)
471
+ if (input[0] === 0x00 &&
472
+ input[1] === 0x00 &&
473
+ input[2] === 0x01 &&
474
+ input[3] === 0x00 &&
475
+ !hasFtypBoxSignature(input)) {
476
+ return "image/x-icon";
478
477
  }
479
478
  // BMP: "BM" magic (0x42 0x4D)
480
479
  if (input[0] === 0x42 && input[1] === 0x4d) {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared ISO-BMFF (`ftyp`) image-brand detection.
3
+ *
4
+ * FileDetector and ImageProcessor must use the same brand tables and declared
5
+ * box boundary when classifying AVIF, HEIC, and structural HEIF brands. Keeping
6
+ * the scan here prevents the two consumers from drifting as new brands are
7
+ * added.
8
+ */
9
+ export declare const AVIF_FTYP_BRANDS: Set<string>;
10
+ export declare const HEIC_FTYP_BRANDS: Set<string>;
11
+ export declare const HEIF_STRUCTURAL_FTYP_BRANDS: Set<string>;
12
+ /**
13
+ * Whether the buffer starts with an ISO-BMFF `ftyp` box signature.
14
+ */
15
+ export declare function hasFtypBoxSignature(input: Buffer): boolean;
16
+ /**
17
+ * Read compatible brands without scanning past the declared `ftyp` box.
18
+ */
19
+ export declare function getCompatibleFtypBrands(input: Buffer): Set<string>;
20
+ /**
21
+ * Return the image MIME type encoded by an ISO-BMFF `ftyp` box, or null when
22
+ * the box belongs to a non-image ISO-BMFF flavor.
23
+ */
24
+ export declare function detectIsoBmffImageMimeType(input: Buffer): string | null;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Shared ISO-BMFF (`ftyp`) image-brand detection.
3
+ *
4
+ * FileDetector and ImageProcessor must use the same brand tables and declared
5
+ * box boundary when classifying AVIF, HEIC, and structural HEIF brands. Keeping
6
+ * the scan here prevents the two consumers from drifting as new brands are
7
+ * added.
8
+ */
9
+ export const AVIF_FTYP_BRANDS = new Set(["avif", "avis", "avio"]);
10
+ export const HEIC_FTYP_BRANDS = new Set([
11
+ "heic",
12
+ "heix",
13
+ "hevc",
14
+ "hevx",
15
+ "heim",
16
+ "heis",
17
+ "hevm",
18
+ "hevs",
19
+ ]);
20
+ export const HEIF_STRUCTURAL_FTYP_BRANDS = new Set(["mif1", "msf1"]);
21
+ /**
22
+ * Whether the buffer starts with an ISO-BMFF `ftyp` box signature.
23
+ */
24
+ export function hasFtypBoxSignature(input) {
25
+ return (input.length >= 8 &&
26
+ input[4] === 0x66 &&
27
+ input[5] === 0x74 &&
28
+ input[6] === 0x79 &&
29
+ input[7] === 0x70);
30
+ }
31
+ /**
32
+ * Read compatible brands without scanning past the declared `ftyp` box.
33
+ */
34
+ export function getCompatibleFtypBrands(input) {
35
+ const compatibleBrands = new Set();
36
+ if (input.length < 20) {
37
+ return compatibleBrands;
38
+ }
39
+ const declaredBoxSize = input.readUInt32BE(0);
40
+ const boxEnd = declaredBoxSize === 0
41
+ ? input.length
42
+ : Math.min(input.length, declaredBoxSize);
43
+ for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
44
+ compatibleBrands.add(input.toString("latin1", offset, offset + 4));
45
+ }
46
+ return compatibleBrands;
47
+ }
48
+ function hasFtypBrand(compatibleBrands, candidates) {
49
+ for (const brand of compatibleBrands) {
50
+ if (candidates.has(brand)) {
51
+ return true;
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ /**
57
+ * Return the image MIME type encoded by an ISO-BMFF `ftyp` box, or null when
58
+ * the box belongs to a non-image ISO-BMFF flavor.
59
+ */
60
+ export function detectIsoBmffImageMimeType(input) {
61
+ if (input.length < 12 || !hasFtypBoxSignature(input)) {
62
+ return null;
63
+ }
64
+ const brand = input.toString("latin1", 8, 12);
65
+ if (AVIF_FTYP_BRANDS.has(brand)) {
66
+ return "image/avif";
67
+ }
68
+ if (HEIC_FTYP_BRANDS.has(brand)) {
69
+ return "image/heic";
70
+ }
71
+ if (!HEIF_STRUCTURAL_FTYP_BRANDS.has(brand)) {
72
+ return null;
73
+ }
74
+ const compatibleBrands = getCompatibleFtypBrands(input);
75
+ if (hasFtypBrand(compatibleBrands, HEIC_FTYP_BRANDS)) {
76
+ return "image/heif";
77
+ }
78
+ if (hasFtypBrand(compatibleBrands, AVIF_FTYP_BRANDS)) {
79
+ return "image/avif";
80
+ }
81
+ return null;
82
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "10.4.4",
3
+ "version": "10.5.0",
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": {
@@ -120,6 +120,7 @@
120
120
  "test:proxy": "npx tsx test/continuous-test-suite-proxy.ts",
121
121
  "test:bugfixes": "npx tsx test/continuous-test-suite-bugfixes.ts",
122
122
  "test:file-detector-extension": "npx tsx test/continuous-test-suite-file-detector-extension.ts",
123
+ "test:file-detector-magic-bytes": "npx tsx test/continuous-test-suite-file-detector-magic-bytes.ts",
123
124
  "test:json": "npx tsx test/continuous-test-suite-json.ts",
124
125
  "test:json-e2e": "npx tsx test/continuous-test-suite-json-e2e.ts",
125
126
  "test:workflow": "npx tsx test/continuous-test-suite-workflow.ts",
@@ -148,7 +149,7 @@
148
149
  "test:system-messages:vitest": "pnpm exec vitest run test/systemMessages.test.ts",
149
150
  "test:tool-routing-semantic:vitest": "pnpm exec vitest run test/toolRoutingSemantic.test.ts",
150
151
  "test:tool-routing-semantic": "pnpm run test:tool-routing-semantic:vitest && npx tsx test/continuous-test-suite-tool-routing-semantic.ts",
151
- "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:litellm-context:vitest && pnpm run test:step-budget-guard:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
152
+ "test:unit": "pnpm run test:envguard && pnpm run test:bugfixes && pnpm run test:file-detector-extension && pnpm run test:file-detector-magic-bytes && pnpm run test:mcp:infra && pnpm run test:mcp:bash && pnpm run test:mcp:limits && pnpm run test:mcp:spans && pnpm run test:autoresearch:redis && pnpm run test:unit:vitest && pnpm run test:tool-routing-cli:vitest && pnpm run test:tool-dedup:vitest && pnpm run test:model-pool:vitest && pnpm run test:litellm-context:vitest && pnpm run test:step-budget-guard:vitest && pnpm run test:system-messages:vitest && pnpm run test:tool-routing-semantic:vitest && pnpm run test:anthropic-tools-policy && pnpm run test:sagemaker-tools && pnpm run test:anthropic-multimodal && pnpm run test:excel-interop",
152
153
  "// CI tier — live providers, runs only when API keys are present (test:credentials and test:dynamic make real provider calls when keys are set, so they live here, not in test:unit)": "",
153
154
  "test:live": "pnpm run test:providers && pnpm run test:mcp:http && pnpm run test:mcp:sdk && pnpm run test:mcp:cli && pnpm run test:observability && pnpm run test:context && pnpm run test:memory && pnpm run test:tool-reliability && pnpm run test:evaluation && pnpm run test:autoresearch && pnpm run test:credentials && pnpm run test:dynamic",
154
155
  "// CI tier — product output (image/video/TTS/PPT) — costs $$ per run": "",