@juspay/neurolink 10.10.11 → 10.10.12
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.
- package/CHANGELOG.md +6 -0
- package/dist/browser/neurolink.min.js +348 -348
- package/dist/lib/utils/messageBuilder.js +134 -1
- package/dist/utils/messageBuilder.js +134 -1
- package/package.json +1 -1
|
@@ -45,13 +45,32 @@ const EXTENSION_TYPE_MAP = {
|
|
|
45
45
|
// Image
|
|
46
46
|
jpg: "image",
|
|
47
47
|
jpeg: "image",
|
|
48
|
+
jpe: "image",
|
|
49
|
+
jfif: "image",
|
|
48
50
|
png: "image",
|
|
51
|
+
apng: "image",
|
|
49
52
|
gif: "image",
|
|
50
53
|
webp: "image",
|
|
51
54
|
bmp: "image",
|
|
52
55
|
tiff: "image",
|
|
53
56
|
tif: "image",
|
|
54
57
|
avif: "image",
|
|
58
|
+
// Below this line the entries exist because this map now decides whether an
|
|
59
|
+
// image is processed eagerly (see isEagerMultimodalFile), not merely how its
|
|
60
|
+
// tokens are estimated. An extension missing here classifies as `undefined`
|
|
61
|
+
// and takes the lazy path, which drops the pixels — so a gap is a dropped
|
|
62
|
+
// photo, not a slightly-off estimate. HEIC is the one that matters most:
|
|
63
|
+
// it is what an iPhone writes by default.
|
|
64
|
+
heic: "image",
|
|
65
|
+
heif: "image",
|
|
66
|
+
ico: "image",
|
|
67
|
+
jp2: "image",
|
|
68
|
+
jpx: "image",
|
|
69
|
+
// NB: `svg` is deliberately absent here — it is mapped to its own "svg"
|
|
70
|
+
// routing type further down, and listing it twice is a duplicate key whose
|
|
71
|
+
// second entry silently wins. `isEagerMultimodalFile` accepts both types
|
|
72
|
+
// instead, so SVG takes the eager path without this map having to lie about
|
|
73
|
+
// which processor handles it.
|
|
55
74
|
// Archive
|
|
56
75
|
zip: "archive",
|
|
57
76
|
tar: "archive",
|
|
@@ -101,6 +120,19 @@ function inferFileTypeFromBuffer(buf) {
|
|
|
101
120
|
if (buf.length < 4) {
|
|
102
121
|
return undefined;
|
|
103
122
|
}
|
|
123
|
+
// SVG is markup and has no magic number, so every signature check below
|
|
124
|
+
// misses it and a raw SVG Buffer classified as `undefined` — which meant the
|
|
125
|
+
// lazy path, which previews markup away. Checked first because the sniff is
|
|
126
|
+
// a cheap look at the head and cannot collide with a binary signature.
|
|
127
|
+
//
|
|
128
|
+
// Deliberately a substring scan over the head rather than a prolog-stripping
|
|
129
|
+
// regex: the obvious pattern for skipping comments and a DOCTYPE is two
|
|
130
|
+
// nested quantifiers, which is a ReDoS waiting to happen inside what is
|
|
131
|
+
// supposed to be a cheap type check. Over-matching is harmless here — the
|
|
132
|
+
// only consequence of a false positive is that a file is processed eagerly.
|
|
133
|
+
if (buf.subarray(0, 1024).toString("latin1").includes("<svg")) {
|
|
134
|
+
return "svg";
|
|
135
|
+
}
|
|
104
136
|
// PNG
|
|
105
137
|
if (buf[0] === 0x89 &&
|
|
106
138
|
buf[1] === 0x50 &&
|
|
@@ -863,7 +895,9 @@ export async function processUnifiedFilesArray(options, maxSize, provider) {
|
|
|
863
895
|
try {
|
|
864
896
|
// ─── Lazy file registration path ──────────────────────────────
|
|
865
897
|
const fileSize = fileRegistry ? getFileSize(file) : 0;
|
|
866
|
-
if (fileRegistry &&
|
|
898
|
+
if (fileRegistry &&
|
|
899
|
+
fileSize > SIZE_TIER_THRESHOLDS.TINY_MAX &&
|
|
900
|
+
!isEagerMultimodalFile(file)) {
|
|
867
901
|
const registered = await tryRegisterFileReference(file, fileSize, fileRegistry, fileIdx);
|
|
868
902
|
if (registered) {
|
|
869
903
|
logger.info(`[NEUROLINK] File lazily registered: ${filename} (${fileSize} bytes) — deferred processing`);
|
|
@@ -2027,6 +2061,105 @@ function getFileSource(file) {
|
|
|
2027
2061
|
}
|
|
2028
2062
|
return "buffer";
|
|
2029
2063
|
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Whether a file must be processed eagerly rather than lazily referenced.
|
|
2066
|
+
*
|
|
2067
|
+
* The lazy path registers a file and injects a short textual *preview* in place
|
|
2068
|
+
* of the file itself. Whether that is an acceptable trade depends entirely on
|
|
2069
|
+
* whether a description of the file can answer questions about it:
|
|
2070
|
+
*
|
|
2071
|
+
* pdf lazy -> preview carries the extracted text ✔ content survives
|
|
2072
|
+
* image lazy -> preview carries ~98 chars of prose ✘ the pixels are gone
|
|
2073
|
+
*
|
|
2074
|
+
* An image is the case where the bytes ARE the content: no prose summary
|
|
2075
|
+
* substitutes for them, so the model receives a description of a file instead
|
|
2076
|
+
* of the file. Measured end-to-end on `release`, asking "what number is
|
|
2077
|
+
* written in this image?":
|
|
2078
|
+
*
|
|
2079
|
+
* tiny.png 1.6 KB -> "7391" (under TINY_MAX, eager, correct)
|
|
2080
|
+
* big.png 11 KB -> NOTHING_RECEIVED (lazy, image never arrived)
|
|
2081
|
+
* big.jpg 19 KB -> NOTHING_RECEIVED
|
|
2082
|
+
*
|
|
2083
|
+
* 10 KB is far below any real photo, so this affected essentially every image
|
|
2084
|
+
* attached by path — an ordinary JPEG, not just unusual formats.
|
|
2085
|
+
*
|
|
2086
|
+
* Scoped to images deliberately, and audio deserves spelling out because the
|
|
2087
|
+
* reason is not the one it appears to be. An audio file's message contains only
|
|
2088
|
+
* a metadata block — duration, codec, sample rate — on BOTH paths; no audio
|
|
2089
|
+
* bytes are handed to the provider either way. Moving audio to the eager path
|
|
2090
|
+
* would therefore change nothing about what the model receives. That audio
|
|
2091
|
+
* content never reaches the model at all is a separate and pre-existing gap,
|
|
2092
|
+
* not something this threshold decision can repair, and it is easy to mistake
|
|
2093
|
+
* for working code because the metadata block answers exactly the questions
|
|
2094
|
+
* ("how long is it?", "what sample rate?") a test is most tempted to ask.
|
|
2095
|
+
* Video is left alone for the opposite reason: its frames are measurably
|
|
2096
|
+
* present in the message and a model reads them correctly.
|
|
2097
|
+
*
|
|
2098
|
+
* Note this costs no extra memory: `tryRegisterFileReference` already calls
|
|
2099
|
+
* `getFileBuffer()` and reads the whole file to register it. The lazy path was
|
|
2100
|
+
* never lazy about reading — only about processing — so the difference here is
|
|
2101
|
+
* simply whether the bytes survive.
|
|
2102
|
+
*/
|
|
2103
|
+
function isEagerMultimodalFile(file) {
|
|
2104
|
+
if (typeof file === "string") {
|
|
2105
|
+
return isImageLikeType(inferFileTypeFromExtension(file));
|
|
2106
|
+
}
|
|
2107
|
+
if (Buffer.isBuffer(file)) {
|
|
2108
|
+
return isImageLikeType(inferFileTypeFromBuffer(file));
|
|
2109
|
+
}
|
|
2110
|
+
// A `FileWithMetadata` carries two independent declarations, and either one
|
|
2111
|
+
// alone is enough: the shape exists for Slack/Curator-style uploads that
|
|
2112
|
+
// arrive as bytes plus a mimetype, so its `filename` may be extensionless or
|
|
2113
|
+
// simply wrong. Reading them as a `??` chain meant the first *recognised*
|
|
2114
|
+
// name won outright — `upload.pdf` with `mimetype: "image/png"` classified as
|
|
2115
|
+
// a PDF and lost its pixels down the lazy path. Any declaration of an image
|
|
2116
|
+
// is therefore decisive.
|
|
2117
|
+
const declared = [
|
|
2118
|
+
inferFileTypeFromExtension(file.filename),
|
|
2119
|
+
inferFileTypeFromMimetype(file.mimetype),
|
|
2120
|
+
];
|
|
2121
|
+
if (declared.some(isImageLikeType)) {
|
|
2122
|
+
return true;
|
|
2123
|
+
}
|
|
2124
|
+
// The buffer sniff is the last resort rather than a third vote, because it is
|
|
2125
|
+
// a substring scan: an HTML page with an inline `<svg>` icon in its head
|
|
2126
|
+
// would otherwise be pulled onto the eager path and sent in full, which is
|
|
2127
|
+
// the opposite of what the size tiers are for. It only speaks when nothing
|
|
2128
|
+
// else did.
|
|
2129
|
+
return declared.every((type) => type === undefined)
|
|
2130
|
+
? isImageLikeType(inferFileTypeFromBuffer(file.buffer))
|
|
2131
|
+
: false;
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Whether a routing type should have its bytes preserved rather than previewed.
|
|
2135
|
+
*
|
|
2136
|
+
* "svg" is a separate routing type rather than a sub-case of "image" (it goes
|
|
2137
|
+
* to the sanitizer, not to a vision encoder), but it is still an image as far
|
|
2138
|
+
* as this decision is concerned: its markup IS its content, and previewing it
|
|
2139
|
+
* away leaves the model with nothing. Accepting both keeps this correct
|
|
2140
|
+
* whichever of the two type vocabularies the caller's map uses.
|
|
2141
|
+
*/
|
|
2142
|
+
function isImageLikeType(type) {
|
|
2143
|
+
return type === "image" || type === "svg";
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Infer a routing type from a caller-declared mimetype.
|
|
2147
|
+
*
|
|
2148
|
+
* Only images matter here — this exists so the eager/lazy decision can read a
|
|
2149
|
+
* mimetype hint — and "application/octet-stream" is deliberately ignored,
|
|
2150
|
+
* because it is the opaque sentinel a caller sends when it knows nothing, not
|
|
2151
|
+
* a claim about content.
|
|
2152
|
+
*/
|
|
2153
|
+
function inferFileTypeFromMimetype(mimetype) {
|
|
2154
|
+
if (!mimetype) {
|
|
2155
|
+
return undefined;
|
|
2156
|
+
}
|
|
2157
|
+
const normalized = mimetype.split(";")[0].trim().toLowerCase();
|
|
2158
|
+
if (normalized === "image/svg+xml") {
|
|
2159
|
+
return "svg";
|
|
2160
|
+
}
|
|
2161
|
+
return normalized.startsWith("image/") ? "image" : undefined;
|
|
2162
|
+
}
|
|
2030
2163
|
/**
|
|
2031
2164
|
* Try to register a file with the FileReferenceRegistry for lazy processing.
|
|
2032
2165
|
* Returns true if registration succeeded, false if it failed (caller should
|
|
@@ -45,13 +45,32 @@ const EXTENSION_TYPE_MAP = {
|
|
|
45
45
|
// Image
|
|
46
46
|
jpg: "image",
|
|
47
47
|
jpeg: "image",
|
|
48
|
+
jpe: "image",
|
|
49
|
+
jfif: "image",
|
|
48
50
|
png: "image",
|
|
51
|
+
apng: "image",
|
|
49
52
|
gif: "image",
|
|
50
53
|
webp: "image",
|
|
51
54
|
bmp: "image",
|
|
52
55
|
tiff: "image",
|
|
53
56
|
tif: "image",
|
|
54
57
|
avif: "image",
|
|
58
|
+
// Below this line the entries exist because this map now decides whether an
|
|
59
|
+
// image is processed eagerly (see isEagerMultimodalFile), not merely how its
|
|
60
|
+
// tokens are estimated. An extension missing here classifies as `undefined`
|
|
61
|
+
// and takes the lazy path, which drops the pixels — so a gap is a dropped
|
|
62
|
+
// photo, not a slightly-off estimate. HEIC is the one that matters most:
|
|
63
|
+
// it is what an iPhone writes by default.
|
|
64
|
+
heic: "image",
|
|
65
|
+
heif: "image",
|
|
66
|
+
ico: "image",
|
|
67
|
+
jp2: "image",
|
|
68
|
+
jpx: "image",
|
|
69
|
+
// NB: `svg` is deliberately absent here — it is mapped to its own "svg"
|
|
70
|
+
// routing type further down, and listing it twice is a duplicate key whose
|
|
71
|
+
// second entry silently wins. `isEagerMultimodalFile` accepts both types
|
|
72
|
+
// instead, so SVG takes the eager path without this map having to lie about
|
|
73
|
+
// which processor handles it.
|
|
55
74
|
// Archive
|
|
56
75
|
zip: "archive",
|
|
57
76
|
tar: "archive",
|
|
@@ -101,6 +120,19 @@ function inferFileTypeFromBuffer(buf) {
|
|
|
101
120
|
if (buf.length < 4) {
|
|
102
121
|
return undefined;
|
|
103
122
|
}
|
|
123
|
+
// SVG is markup and has no magic number, so every signature check below
|
|
124
|
+
// misses it and a raw SVG Buffer classified as `undefined` — which meant the
|
|
125
|
+
// lazy path, which previews markup away. Checked first because the sniff is
|
|
126
|
+
// a cheap look at the head and cannot collide with a binary signature.
|
|
127
|
+
//
|
|
128
|
+
// Deliberately a substring scan over the head rather than a prolog-stripping
|
|
129
|
+
// regex: the obvious pattern for skipping comments and a DOCTYPE is two
|
|
130
|
+
// nested quantifiers, which is a ReDoS waiting to happen inside what is
|
|
131
|
+
// supposed to be a cheap type check. Over-matching is harmless here — the
|
|
132
|
+
// only consequence of a false positive is that a file is processed eagerly.
|
|
133
|
+
if (buf.subarray(0, 1024).toString("latin1").includes("<svg")) {
|
|
134
|
+
return "svg";
|
|
135
|
+
}
|
|
104
136
|
// PNG
|
|
105
137
|
if (buf[0] === 0x89 &&
|
|
106
138
|
buf[1] === 0x50 &&
|
|
@@ -863,7 +895,9 @@ export async function processUnifiedFilesArray(options, maxSize, provider) {
|
|
|
863
895
|
try {
|
|
864
896
|
// ─── Lazy file registration path ──────────────────────────────
|
|
865
897
|
const fileSize = fileRegistry ? getFileSize(file) : 0;
|
|
866
|
-
if (fileRegistry &&
|
|
898
|
+
if (fileRegistry &&
|
|
899
|
+
fileSize > SIZE_TIER_THRESHOLDS.TINY_MAX &&
|
|
900
|
+
!isEagerMultimodalFile(file)) {
|
|
867
901
|
const registered = await tryRegisterFileReference(file, fileSize, fileRegistry, fileIdx);
|
|
868
902
|
if (registered) {
|
|
869
903
|
logger.info(`[NEUROLINK] File lazily registered: ${filename} (${fileSize} bytes) — deferred processing`);
|
|
@@ -2027,6 +2061,105 @@ function getFileSource(file) {
|
|
|
2027
2061
|
}
|
|
2028
2062
|
return "buffer";
|
|
2029
2063
|
}
|
|
2064
|
+
/**
|
|
2065
|
+
* Whether a file must be processed eagerly rather than lazily referenced.
|
|
2066
|
+
*
|
|
2067
|
+
* The lazy path registers a file and injects a short textual *preview* in place
|
|
2068
|
+
* of the file itself. Whether that is an acceptable trade depends entirely on
|
|
2069
|
+
* whether a description of the file can answer questions about it:
|
|
2070
|
+
*
|
|
2071
|
+
* pdf lazy -> preview carries the extracted text ✔ content survives
|
|
2072
|
+
* image lazy -> preview carries ~98 chars of prose ✘ the pixels are gone
|
|
2073
|
+
*
|
|
2074
|
+
* An image is the case where the bytes ARE the content: no prose summary
|
|
2075
|
+
* substitutes for them, so the model receives a description of a file instead
|
|
2076
|
+
* of the file. Measured end-to-end on `release`, asking "what number is
|
|
2077
|
+
* written in this image?":
|
|
2078
|
+
*
|
|
2079
|
+
* tiny.png 1.6 KB -> "7391" (under TINY_MAX, eager, correct)
|
|
2080
|
+
* big.png 11 KB -> NOTHING_RECEIVED (lazy, image never arrived)
|
|
2081
|
+
* big.jpg 19 KB -> NOTHING_RECEIVED
|
|
2082
|
+
*
|
|
2083
|
+
* 10 KB is far below any real photo, so this affected essentially every image
|
|
2084
|
+
* attached by path — an ordinary JPEG, not just unusual formats.
|
|
2085
|
+
*
|
|
2086
|
+
* Scoped to images deliberately, and audio deserves spelling out because the
|
|
2087
|
+
* reason is not the one it appears to be. An audio file's message contains only
|
|
2088
|
+
* a metadata block — duration, codec, sample rate — on BOTH paths; no audio
|
|
2089
|
+
* bytes are handed to the provider either way. Moving audio to the eager path
|
|
2090
|
+
* would therefore change nothing about what the model receives. That audio
|
|
2091
|
+
* content never reaches the model at all is a separate and pre-existing gap,
|
|
2092
|
+
* not something this threshold decision can repair, and it is easy to mistake
|
|
2093
|
+
* for working code because the metadata block answers exactly the questions
|
|
2094
|
+
* ("how long is it?", "what sample rate?") a test is most tempted to ask.
|
|
2095
|
+
* Video is left alone for the opposite reason: its frames are measurably
|
|
2096
|
+
* present in the message and a model reads them correctly.
|
|
2097
|
+
*
|
|
2098
|
+
* Note this costs no extra memory: `tryRegisterFileReference` already calls
|
|
2099
|
+
* `getFileBuffer()` and reads the whole file to register it. The lazy path was
|
|
2100
|
+
* never lazy about reading — only about processing — so the difference here is
|
|
2101
|
+
* simply whether the bytes survive.
|
|
2102
|
+
*/
|
|
2103
|
+
function isEagerMultimodalFile(file) {
|
|
2104
|
+
if (typeof file === "string") {
|
|
2105
|
+
return isImageLikeType(inferFileTypeFromExtension(file));
|
|
2106
|
+
}
|
|
2107
|
+
if (Buffer.isBuffer(file)) {
|
|
2108
|
+
return isImageLikeType(inferFileTypeFromBuffer(file));
|
|
2109
|
+
}
|
|
2110
|
+
// A `FileWithMetadata` carries two independent declarations, and either one
|
|
2111
|
+
// alone is enough: the shape exists for Slack/Curator-style uploads that
|
|
2112
|
+
// arrive as bytes plus a mimetype, so its `filename` may be extensionless or
|
|
2113
|
+
// simply wrong. Reading them as a `??` chain meant the first *recognised*
|
|
2114
|
+
// name won outright — `upload.pdf` with `mimetype: "image/png"` classified as
|
|
2115
|
+
// a PDF and lost its pixels down the lazy path. Any declaration of an image
|
|
2116
|
+
// is therefore decisive.
|
|
2117
|
+
const declared = [
|
|
2118
|
+
inferFileTypeFromExtension(file.filename),
|
|
2119
|
+
inferFileTypeFromMimetype(file.mimetype),
|
|
2120
|
+
];
|
|
2121
|
+
if (declared.some(isImageLikeType)) {
|
|
2122
|
+
return true;
|
|
2123
|
+
}
|
|
2124
|
+
// The buffer sniff is the last resort rather than a third vote, because it is
|
|
2125
|
+
// a substring scan: an HTML page with an inline `<svg>` icon in its head
|
|
2126
|
+
// would otherwise be pulled onto the eager path and sent in full, which is
|
|
2127
|
+
// the opposite of what the size tiers are for. It only speaks when nothing
|
|
2128
|
+
// else did.
|
|
2129
|
+
return declared.every((type) => type === undefined)
|
|
2130
|
+
? isImageLikeType(inferFileTypeFromBuffer(file.buffer))
|
|
2131
|
+
: false;
|
|
2132
|
+
}
|
|
2133
|
+
/**
|
|
2134
|
+
* Whether a routing type should have its bytes preserved rather than previewed.
|
|
2135
|
+
*
|
|
2136
|
+
* "svg" is a separate routing type rather than a sub-case of "image" (it goes
|
|
2137
|
+
* to the sanitizer, not to a vision encoder), but it is still an image as far
|
|
2138
|
+
* as this decision is concerned: its markup IS its content, and previewing it
|
|
2139
|
+
* away leaves the model with nothing. Accepting both keeps this correct
|
|
2140
|
+
* whichever of the two type vocabularies the caller's map uses.
|
|
2141
|
+
*/
|
|
2142
|
+
function isImageLikeType(type) {
|
|
2143
|
+
return type === "image" || type === "svg";
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Infer a routing type from a caller-declared mimetype.
|
|
2147
|
+
*
|
|
2148
|
+
* Only images matter here — this exists so the eager/lazy decision can read a
|
|
2149
|
+
* mimetype hint — and "application/octet-stream" is deliberately ignored,
|
|
2150
|
+
* because it is the opaque sentinel a caller sends when it knows nothing, not
|
|
2151
|
+
* a claim about content.
|
|
2152
|
+
*/
|
|
2153
|
+
function inferFileTypeFromMimetype(mimetype) {
|
|
2154
|
+
if (!mimetype) {
|
|
2155
|
+
return undefined;
|
|
2156
|
+
}
|
|
2157
|
+
const normalized = mimetype.split(";")[0].trim().toLowerCase();
|
|
2158
|
+
if (normalized === "image/svg+xml") {
|
|
2159
|
+
return "svg";
|
|
2160
|
+
}
|
|
2161
|
+
return normalized.startsWith("image/") ? "image" : undefined;
|
|
2162
|
+
}
|
|
2030
2163
|
/**
|
|
2031
2164
|
* Try to register a file with the FileReferenceRegistry for lazy processing.
|
|
2032
2165
|
* Returns true if registration succeeded, false if it failed (caller should
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.10.
|
|
3
|
+
"version": "10.10.12",
|
|
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": {
|