@juspay/neurolink 9.94.4 → 9.94.6
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 +12 -0
- package/dist/browser/neurolink.min.js +401 -401
- package/dist/core/modules/MessageBuilder.js +32 -3
- package/dist/lib/core/modules/MessageBuilder.js +32 -3
- package/dist/lib/types/generate.d.ts +1 -0
- package/dist/lib/types/multimodal.d.ts +34 -3
- package/dist/lib/types/stream.d.ts +1 -0
- package/dist/lib/utils/errorHandling.d.ts +22 -0
- package/dist/lib/utils/errorHandling.js +55 -0
- package/dist/lib/utils/fileDetector.js +210 -31
- package/dist/lib/utils/imageCache.js +11 -8
- package/dist/lib/utils/imageProcessor.d.ts +37 -0
- package/dist/lib/utils/imageProcessor.js +222 -16
- package/dist/lib/utils/logSanitize.d.ts +100 -1
- package/dist/lib/utils/logSanitize.js +152 -2
- package/dist/lib/utils/messageBuilder.js +80 -9
- package/dist/types/generate.d.ts +1 -0
- package/dist/types/multimodal.d.ts +34 -3
- package/dist/types/stream.d.ts +1 -0
- package/dist/utils/errorHandling.d.ts +22 -0
- package/dist/utils/errorHandling.js +55 -0
- package/dist/utils/fileDetector.js +210 -31
- package/dist/utils/imageCache.js +11 -8
- package/dist/utils/imageProcessor.d.ts +37 -0
- package/dist/utils/imageProcessor.js +222 -16
- package/dist/utils/logSanitize.d.ts +100 -1
- package/dist/utils/logSanitize.js +152 -2
- package/dist/utils/messageBuilder.js +80 -9
- package/package.json +1 -1
|
@@ -5,12 +5,12 @@ import { MultimodalLogger, ProviderImageAdapter, } from "../adapters/providerIma
|
|
|
5
5
|
import { CONVERSATION_INSTRUCTIONS, STRUCTURED_OUTPUT_INSTRUCTIONS, } from "../config/conversationMemory.js";
|
|
6
6
|
import { getAvailableInputTokens } from "../constants/contextWindows.js";
|
|
7
7
|
import { enforceAggregateFileBudget, FILE_READ_BUDGET_PERCENT, } from "../context/fileTokenBudget.js";
|
|
8
|
-
import { SIZE_TIER_THRESHOLDS } from "../types/index.js";
|
|
8
|
+
import { isCSVContent, SIZE_TIER_THRESHOLDS } from "../types/index.js";
|
|
9
9
|
import { tracers, ATTR, withSpan } from "../telemetry/index.js";
|
|
10
|
-
import { NeuroLinkError } from "./errorHandling.js";
|
|
10
|
+
import { ErrorFactory, NeuroLinkError, withTimeout } from "./errorHandling.js";
|
|
11
11
|
import { FileDetector } from "./fileDetector.js";
|
|
12
12
|
import { getImageCache } from "./imageCache.js";
|
|
13
|
-
import { ImageProcessor } from "./imageProcessor.js";
|
|
13
|
+
import { ImageProcessor, imageUtils } from "./imageProcessor.js";
|
|
14
14
|
import { logger } from "./logger.js";
|
|
15
15
|
import { PDFImageConverter, PDFProcessor } from "./pdfProcessor.js";
|
|
16
16
|
import { urlDownloadRateLimiter } from "./rateLimiter.js";
|
|
@@ -860,7 +860,11 @@ export async function processUnifiedFilesArray(options, maxSize, provider) {
|
|
|
860
860
|
}
|
|
861
861
|
catch (error) {
|
|
862
862
|
const errMsg = error instanceof Error ? error.message : String(error);
|
|
863
|
-
|
|
863
|
+
// #273: don't silently drop a failed file — log, then throw so the
|
|
864
|
+
// caller learns the file couldn't be processed (matches the explicit
|
|
865
|
+
// pdf/csv paths' fail-loud behavior).
|
|
866
|
+
logger.error(`[NEUROLINK] File processing failed: ${filename} — reason: ${errMsg}`);
|
|
867
|
+
throw ErrorFactory.fileProcessingFailed(filename, error instanceof Error ? error : new Error(errMsg));
|
|
864
868
|
}
|
|
865
869
|
}
|
|
866
870
|
span.setAttribute(ATTR.FILE_INCLUDED_COUNT, includedCount);
|
|
@@ -935,10 +939,12 @@ async function processExplicitCsvFiles(options) {
|
|
|
935
939
|
logger.info(`[CSV] ✅ Processed: ${filename}`);
|
|
936
940
|
}
|
|
937
941
|
catch (error) {
|
|
938
|
-
logger.error(`[CSV] ❌ Failed:`, error);
|
|
939
942
|
const filename = extractFilename(csvFile, i);
|
|
940
|
-
|
|
941
|
-
|
|
943
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
944
|
+
// #273: fail loud instead of embedding the error into the prompt text
|
|
945
|
+
// (which the model would then "analyze"). Log, then throw.
|
|
946
|
+
logger.error(`[CSV] ❌ Failed to process ${filename}: ${errMsg}`);
|
|
947
|
+
throw ErrorFactory.csvProcessingFailed(filename, error instanceof Error ? error : new Error(errMsg));
|
|
942
948
|
}
|
|
943
949
|
}
|
|
944
950
|
}
|
|
@@ -1067,6 +1073,18 @@ export async function buildMultimodalMessagesArray(options, provider, model) {
|
|
|
1067
1073
|
// local const so TypeScript sees the definite (non-optional) type in the
|
|
1068
1074
|
// rest of this function, avoiding 60+ "possibly undefined" errors.
|
|
1069
1075
|
const inp = options.input;
|
|
1076
|
+
// #284: audioFiles/videoFiles have no dedicated processor — route them
|
|
1077
|
+
// through the same auto-detecting `files` pipeline that already
|
|
1078
|
+
// understands "audio"/"video" FileDetector results (see
|
|
1079
|
+
// appendDetectedFileResult), instead of silently dropping them once
|
|
1080
|
+
// detectMultimodal() routes an audio/video-only request here.
|
|
1081
|
+
if (inp.audioFiles?.length || inp.videoFiles?.length) {
|
|
1082
|
+
inp.files = [
|
|
1083
|
+
...(inp.files || []),
|
|
1084
|
+
...(inp.audioFiles || []),
|
|
1085
|
+
...(inp.videoFiles || []),
|
|
1086
|
+
];
|
|
1087
|
+
}
|
|
1070
1088
|
// Compute provider-specific max PDF size once for consistent validation
|
|
1071
1089
|
const pdfConfig = PDFProcessor.getProviderConfig(provider);
|
|
1072
1090
|
const maxSize = pdfConfig
|
|
@@ -1088,6 +1106,13 @@ export async function buildMultimodalMessagesArray(options, provider, model) {
|
|
|
1088
1106
|
const hasPDFs = pdfFiles.length > 0;
|
|
1089
1107
|
// If no images or PDFs, use standard message building and convert to MultimodalChatMessage[]
|
|
1090
1108
|
if (!hasImages && !hasPDFs) {
|
|
1109
|
+
// #289: CSV content[] items don't need vision, so they never reach the
|
|
1110
|
+
// multimodal converter below — process them into the prompt text here
|
|
1111
|
+
// (otherwise a `content: [{type:"csv"}]`-only request silently drops it).
|
|
1112
|
+
const csvContentItems = inp.content?.filter(isCSVContent) ?? [];
|
|
1113
|
+
if (csvContentItems.length > 0) {
|
|
1114
|
+
inp.text = await appendCsvContentToText(csvContentItems, inp.text ?? "");
|
|
1115
|
+
}
|
|
1091
1116
|
if (inp.csvFiles) {
|
|
1092
1117
|
inp.csvFiles = [];
|
|
1093
1118
|
}
|
|
@@ -1216,6 +1241,47 @@ export async function buildMultimodalMessagesArray(options, provider, model) {
|
|
|
1216
1241
|
throw error;
|
|
1217
1242
|
}
|
|
1218
1243
|
}
|
|
1244
|
+
/**
|
|
1245
|
+
* Timeout for detecting/parsing an in-memory CSV `content[]` buffer (#325
|
|
1246
|
+
* review, round 2). Bounds a stalled detector rather than blocking the
|
|
1247
|
+
* request indefinitely.
|
|
1248
|
+
*/
|
|
1249
|
+
const CSV_CONTENT_DETECTION_TIMEOUT_MS = 30_000;
|
|
1250
|
+
/**
|
|
1251
|
+
* #289: process CSV `content[]` items into appended prompt text. CSV is
|
|
1252
|
+
* delivered to the model as text (like the explicit `csvFiles` path), so this
|
|
1253
|
+
* is shared by both the no-vision gate and the multimodal converter.
|
|
1254
|
+
*/
|
|
1255
|
+
async function appendCsvContentToText(csvItems, baseText) {
|
|
1256
|
+
let text = baseText;
|
|
1257
|
+
for (const csv of csvItems) {
|
|
1258
|
+
const raw = csv.data;
|
|
1259
|
+
if (raw === undefined) {
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1262
|
+
// #325: raw CSV text (e.g. "a,b\n1,2") is not base64 — decoding it as
|
|
1263
|
+
// base64 silently corrupts the content. Only treat the string as base64
|
|
1264
|
+
// when it actually validates as such; otherwise treat it as literal
|
|
1265
|
+
// UTF-8 CSV text, matching how Buffer inputs are already handled as-is.
|
|
1266
|
+
const buffer = typeof raw === "string"
|
|
1267
|
+
? imageUtils.isValidBase64(raw)
|
|
1268
|
+
? Buffer.from(raw, "base64")
|
|
1269
|
+
: Buffer.from(raw, "utf-8")
|
|
1270
|
+
: raw;
|
|
1271
|
+
const name = csv.metadata?.filename || "data.csv";
|
|
1272
|
+
// #325 review (round 2): a stalled/hung detector must not block the
|
|
1273
|
+
// request indefinitely — wrap with the project's standard withTimeout.
|
|
1274
|
+
const result = await withTimeout(FileDetector.detectAndProcess(buffer, {
|
|
1275
|
+
allowedTypes: ["csv"],
|
|
1276
|
+
csvOptions: {
|
|
1277
|
+
maxRows: csv.metadata?.maxRows,
|
|
1278
|
+
formatStyle: csv.metadata?.formatStyle,
|
|
1279
|
+
},
|
|
1280
|
+
}), CSV_CONTENT_DETECTION_TIMEOUT_MS, new Error(`Timed out processing CSV content "${name}" after ${CSV_CONTENT_DETECTION_TIMEOUT_MS}ms`));
|
|
1281
|
+
text += `${text ? "\n\n" : ""}## CSV Data from ${name}\n${result.content}`;
|
|
1282
|
+
}
|
|
1283
|
+
return text;
|
|
1284
|
+
}
|
|
1219
1285
|
/**
|
|
1220
1286
|
* Convert advanced content format to provider-specific format
|
|
1221
1287
|
*/
|
|
@@ -1223,14 +1289,19 @@ async function convertContentToProviderFormat(content, provider, _model) {
|
|
|
1223
1289
|
const textContent = content.find((c) => c.type === "text");
|
|
1224
1290
|
const imageContent = content.filter((c) => c.type === "image");
|
|
1225
1291
|
const pdfContent = content.filter((c) => c.type === "pdf");
|
|
1292
|
+
const csvContent = content.filter(isCSVContent);
|
|
1226
1293
|
// Allow empty text when multimodal content is present (enables image-only or PDF-only queries)
|
|
1227
|
-
|
|
1294
|
+
let text = textContent?.text || "";
|
|
1295
|
+
// #289: CSV content[] items were silently dropped — fold each into the text.
|
|
1296
|
+
if (csvContent.length > 0) {
|
|
1297
|
+
text = await appendCsvContentToText(csvContent, text);
|
|
1298
|
+
}
|
|
1228
1299
|
const hasMultimodal = imageContent.length > 0 || pdfContent.length > 0;
|
|
1229
1300
|
// Validate that we have at least some content
|
|
1230
1301
|
if (!hasMultimodal && !text) {
|
|
1231
1302
|
throw new Error("Content must include either text or multimodal content");
|
|
1232
1303
|
}
|
|
1233
|
-
// Text-only case
|
|
1304
|
+
// Text-only case (CSV has already been folded into `text`)
|
|
1234
1305
|
if (imageContent.length === 0 && pdfContent.length === 0) {
|
|
1235
1306
|
return text;
|
|
1236
1307
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.94.
|
|
3
|
+
"version": "9.94.6",
|
|
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": {
|