@juspay/neurolink 9.94.1 → 9.94.3

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 +12 -0
  2. package/dist/agent/directTools.js +3 -1
  3. package/dist/browser/neurolink.min.js +404 -403
  4. package/dist/cli/factories/commandFactory.d.ts +6 -0
  5. package/dist/cli/factories/commandFactory.js +36 -8
  6. package/dist/lib/agent/directTools.js +3 -1
  7. package/dist/lib/neurolink.js +5 -0
  8. package/dist/lib/rag/document/loaders.d.ts +12 -1
  9. package/dist/lib/rag/document/loaders.js +64 -34
  10. package/dist/lib/types/file.d.ts +43 -0
  11. package/dist/lib/types/generate.d.ts +3 -11
  12. package/dist/lib/types/rag.d.ts +7 -0
  13. package/dist/lib/types/stream.d.ts +2 -6
  14. package/dist/lib/utils/csvProcessor.d.ts +121 -5
  15. package/dist/lib/utils/csvProcessor.js +563 -114
  16. package/dist/lib/utils/csvUtils.d.ts +26 -0
  17. package/dist/lib/utils/csvUtils.js +85 -0
  18. package/dist/lib/utils/errorHandling.d.ts +23 -0
  19. package/dist/lib/utils/errorHandling.js +65 -0
  20. package/dist/lib/utils/multimodalOptionsBuilder.d.ts +1 -5
  21. package/dist/lib/utils/textEncoding.d.ts +25 -0
  22. package/dist/lib/utils/textEncoding.js +141 -0
  23. package/dist/neurolink.js +5 -0
  24. package/dist/rag/document/loaders.d.ts +12 -1
  25. package/dist/rag/document/loaders.js +64 -34
  26. package/dist/types/file.d.ts +43 -0
  27. package/dist/types/generate.d.ts +3 -11
  28. package/dist/types/rag.d.ts +7 -0
  29. package/dist/types/stream.d.ts +2 -6
  30. package/dist/utils/csvProcessor.d.ts +121 -5
  31. package/dist/utils/csvProcessor.js +562 -113
  32. package/dist/utils/csvUtils.d.ts +26 -0
  33. package/dist/utils/csvUtils.js +84 -0
  34. package/dist/utils/errorHandling.d.ts +23 -0
  35. package/dist/utils/errorHandling.js +65 -0
  36. package/dist/utils/multimodalOptionsBuilder.d.ts +1 -5
  37. package/dist/utils/textEncoding.d.ts +25 -0
  38. package/dist/utils/textEncoding.js +140 -0
  39. package/package.json +3 -1
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared, RFC-4180-aware delimiter-separated-value helpers.
3
+ *
4
+ * Consolidates quote-aware field counting/splitting that was previously
5
+ * duplicated across csvProcessor.ts and fileDetector.ts (#359), and adds
6
+ * delimiter detection so TSV / semicolon / pipe files parse correctly (#361).
7
+ * Keeping these in one module prevents the same quoting edge case from having
8
+ * to be fixed in three places and drifting out of sync.
9
+ */
10
+ /** Delimiter candidates, in tie-break preference order (comma first). */
11
+ export declare const CANDIDATE_DELIMITERS: readonly [",", "\t", ";", "|"];
12
+ /**
13
+ * Split a single line into fields, respecting RFC-4180 quoting: a delimiter
14
+ * inside a `"…"` quoted field does not split, and `""` is an escaped quote.
15
+ */
16
+ export declare function splitCSVFields(line: string, delimiter?: string): string[];
17
+ /** Count fields in a single line, respecting RFC-4180 quoting. */
18
+ export declare function countCSVColumns(line: string, delimiter?: string): number;
19
+ /**
20
+ * Detect the delimiter of DSV content by testing comma/tab/semicolon/pipe
21
+ * against up to 5 sampled non-empty lines and keeping the candidate with the
22
+ * most consistent (>1) column count. Comma wins ties and the "nothing splits"
23
+ * case, so plain CSVs are never reclassified. An optional extension hint
24
+ * (`tsv` → tab, `psv` → pipe) breaks ambiguity in the file's favor.
25
+ */
26
+ export declare function detectDelimiter(lines: string[], extensionHint?: string): string;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shared, RFC-4180-aware delimiter-separated-value helpers.
3
+ *
4
+ * Consolidates quote-aware field counting/splitting that was previously
5
+ * duplicated across csvProcessor.ts and fileDetector.ts (#359), and adds
6
+ * delimiter detection so TSV / semicolon / pipe files parse correctly (#361).
7
+ * Keeping these in one module prevents the same quoting edge case from having
8
+ * to be fixed in three places and drifting out of sync.
9
+ */
10
+ /** Delimiter candidates, in tie-break preference order (comma first). */
11
+ export const CANDIDATE_DELIMITERS = [",", "\t", ";", "|"];
12
+ /**
13
+ * Split a single line into fields, respecting RFC-4180 quoting: a delimiter
14
+ * inside a `"…"` quoted field does not split, and `""` is an escaped quote.
15
+ */
16
+ export function splitCSVFields(line, delimiter = ",") {
17
+ const fields = [];
18
+ let field = "";
19
+ let inQuotes = false;
20
+ for (let i = 0; i < line.length; i++) {
21
+ const ch = line[i];
22
+ if (ch === '"') {
23
+ if (inQuotes && line[i + 1] === '"') {
24
+ field += '"';
25
+ i++; // consume the escaped quote
26
+ continue;
27
+ }
28
+ inQuotes = !inQuotes;
29
+ }
30
+ else if (ch === delimiter && !inQuotes) {
31
+ fields.push(field);
32
+ field = "";
33
+ }
34
+ else {
35
+ field += ch;
36
+ }
37
+ }
38
+ fields.push(field);
39
+ return fields;
40
+ }
41
+ /** Count fields in a single line, respecting RFC-4180 quoting. */
42
+ export function countCSVColumns(line, delimiter = ",") {
43
+ return splitCSVFields(line, delimiter).length;
44
+ }
45
+ /**
46
+ * Detect the delimiter of DSV content by testing comma/tab/semicolon/pipe
47
+ * against up to 5 sampled non-empty lines and keeping the candidate with the
48
+ * most consistent (>1) column count. Comma wins ties and the "nothing splits"
49
+ * case, so plain CSVs are never reclassified. An optional extension hint
50
+ * (`tsv` → tab, `psv` → pipe) breaks ambiguity in the file's favor.
51
+ */
52
+ export function detectDelimiter(lines, extensionHint) {
53
+ const sample = lines.filter((l) => l.trim() !== "").slice(0, 5);
54
+ if (sample.length === 0) {
55
+ return ",";
56
+ }
57
+ const hint = extensionHint?.toLowerCase().replace(/^\./, "");
58
+ const hinted = hint === "tsv" ? "\t" : hint === "psv" ? "|" : undefined;
59
+ let best = ",";
60
+ let bestScore = -1;
61
+ for (const delim of CANDIDATE_DELIMITERS) {
62
+ const counts = sample.map((l) => countCSVColumns(l, delim));
63
+ const headerCols = counts[0];
64
+ if (headerCols <= 1) {
65
+ continue; // this delimiter doesn't split the header — not the delimiter
66
+ }
67
+ const consistent = counts.every((c) => c === headerCols);
68
+ // Prefer: a matching extension hint, then consistency, then more columns,
69
+ // with a small comma bias so comma stays the default on ties.
70
+ const score = (delim === hinted ? 1000 : 0) +
71
+ (consistent ? 100 : 0) +
72
+ headerCols +
73
+ (delim === "," ? 0.5 : 0);
74
+ if (score > bestScore) {
75
+ bestScore = score;
76
+ best = delim;
77
+ }
78
+ }
79
+ // Nothing split multi-column, but the extension says otherwise — honor it.
80
+ if (bestScore < 0 && hinted) {
81
+ return hinted;
82
+ }
83
+ return best;
84
+ }
@@ -51,6 +51,10 @@ export declare const ERROR_CODES: {
51
51
  readonly INVALID_PPT_LOGO_PATH: "INVALID_PPT_LOGO_PATH";
52
52
  readonly INVALID_PPT_MODE: "INVALID_PPT_MODE";
53
53
  readonly INVALID_PPT_PROMPT: "INVALID_PPT_PROMPT";
54
+ readonly CSV_INVALID_INPUT: "CSV_INVALID_INPUT";
55
+ readonly CSV_ROW_INVALID: "CSV_ROW_INVALID";
56
+ readonly CSV_FILE_ACCESS_FAILED: "CSV_FILE_ACCESS_FAILED";
57
+ readonly CSV_PARSE_FAILED: "CSV_PARSE_FAILED";
54
58
  };
55
59
  /**
56
60
  * Enhanced error class with structured information
@@ -128,6 +132,25 @@ export declare class ErrorFactory {
128
132
  * Create an invalid configuration error (e.g., NaN for numeric values)
129
133
  */
130
134
  static invalidConfiguration(configName: string, reason: string, context?: Record<string, unknown>): NeuroLinkError;
135
+ /**
136
+ * Create an invalid CSV input error (e.g. an empty filePath/csvString argument).
137
+ */
138
+ static csvInvalidInput(message: string): NeuroLinkError;
139
+ /**
140
+ * Create a CSV row-shape violation error (#384) — a parsed row that isn't a
141
+ * string-keyed object with string values.
142
+ */
143
+ static csvRowInvalid(message: string, rowNumber: number): NeuroLinkError;
144
+ /**
145
+ * Create a CSV file access error (bad path, permissions, ENOENT/EACCES) (#375).
146
+ */
147
+ static csvFileAccessFailed(message: string, filePath: string, originalError?: Error): NeuroLinkError;
148
+ /**
149
+ * Create a CSV read/parse failure error (#375) — source stream errors and
150
+ * csv-parser errors both route through this, carrying the enriched
151
+ * `buildCsvParseErrorMessage` context in `message`.
152
+ */
153
+ static csvParseFailed(message: string, context: Record<string, unknown>, originalError?: Error): NeuroLinkError;
131
154
  /**
132
155
  * Create an invalid video resolution error
133
156
  */
@@ -66,6 +66,11 @@ export const ERROR_CODES = {
66
66
  INVALID_PPT_LOGO_PATH: "INVALID_PPT_LOGO_PATH",
67
67
  INVALID_PPT_MODE: "INVALID_PPT_MODE",
68
68
  INVALID_PPT_PROMPT: "INVALID_PPT_PROMPT",
69
+ // CSV validation/parsing errors (#1199)
70
+ CSV_INVALID_INPUT: "CSV_INVALID_INPUT",
71
+ CSV_ROW_INVALID: "CSV_ROW_INVALID",
72
+ CSV_FILE_ACCESS_FAILED: "CSV_FILE_ACCESS_FAILED",
73
+ CSV_PARSE_FAILED: "CSV_PARSE_FAILED",
69
74
  };
70
75
  /**
71
76
  * Enhanced error class with structured information
@@ -259,6 +264,66 @@ export class ErrorFactory {
259
264
  });
260
265
  }
261
266
  // ============================================================================
267
+ // CSV VALIDATION/PARSING ERRORS (#1199)
268
+ // ============================================================================
269
+ /**
270
+ * Create an invalid CSV input error (e.g. an empty filePath/csvString argument).
271
+ */
272
+ static csvInvalidInput(message) {
273
+ return new NeuroLinkError({
274
+ code: ERROR_CODES.CSV_INVALID_INPUT,
275
+ message,
276
+ category: ErrorCategory.VALIDATION,
277
+ severity: ErrorSeverity.MEDIUM,
278
+ retriable: false,
279
+ context: {},
280
+ });
281
+ }
282
+ /**
283
+ * Create a CSV row-shape violation error (#384) — a parsed row that isn't a
284
+ * string-keyed object with string values.
285
+ */
286
+ static csvRowInvalid(message, rowNumber) {
287
+ return new NeuroLinkError({
288
+ code: ERROR_CODES.CSV_ROW_INVALID,
289
+ message,
290
+ category: ErrorCategory.VALIDATION,
291
+ severity: ErrorSeverity.MEDIUM,
292
+ retriable: false,
293
+ context: { rowNumber },
294
+ });
295
+ }
296
+ /**
297
+ * Create a CSV file access error (bad path, permissions, ENOENT/EACCES) (#375).
298
+ */
299
+ static csvFileAccessFailed(message, filePath, originalError) {
300
+ return new NeuroLinkError({
301
+ code: ERROR_CODES.CSV_FILE_ACCESS_FAILED,
302
+ message,
303
+ category: ErrorCategory.RESOURCE,
304
+ severity: ErrorSeverity.HIGH,
305
+ retriable: false,
306
+ context: { filePath },
307
+ originalError,
308
+ });
309
+ }
310
+ /**
311
+ * Create a CSV read/parse failure error (#375) — source stream errors and
312
+ * csv-parser errors both route through this, carrying the enriched
313
+ * `buildCsvParseErrorMessage` context in `message`.
314
+ */
315
+ static csvParseFailed(message, context, originalError) {
316
+ return new NeuroLinkError({
317
+ code: ERROR_CODES.CSV_PARSE_FAILED,
318
+ message,
319
+ category: ErrorCategory.EXECUTION,
320
+ severity: ErrorSeverity.MEDIUM,
321
+ retriable: false,
322
+ context,
323
+ originalError,
324
+ });
325
+ }
326
+ // ============================================================================
262
327
  // VIDEO VALIDATION ERRORS
263
328
  // ============================================================================
264
329
  /**
@@ -50,11 +50,7 @@ export declare function buildMultimodalOptions(options: StreamOptions, providerN
50
50
  csvFiles: (string | Buffer<ArrayBufferLike>)[] | undefined;
51
51
  pdfFiles: (string | Buffer<ArrayBufferLike>)[] | undefined;
52
52
  };
53
- csvOptions: {
54
- maxRows?: number;
55
- formatStyle?: "raw" | "markdown" | "json";
56
- includeHeaders?: boolean;
57
- } | undefined;
53
+ csvOptions: import("../index.js").CSVProcessorOptions | undefined;
58
54
  systemPrompt: string | undefined;
59
55
  conversationHistory: import("../index.js").ChatMessage[] | undefined;
60
56
  provider: string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Buffer → string decoding with encoding detection (#362).
3
+ *
4
+ * Order of resolution:
5
+ * 1. Explicit override (any iconv-lite label).
6
+ * 2. Deterministic BOM sniff (UTF-8 / UTF-16 LE / UTF-16 BE).
7
+ * 3. `chardet` statistical detection, mapped to an iconv-lite label.
8
+ * 4. Fallback to UTF-8 (preserves the historical hard-coded default).
9
+ *
10
+ * Decoding always goes through `iconv-lite`, and any residual BOM is stripped
11
+ * from the decoded string so it can't glue onto the first token.
12
+ */
13
+ import type { DecodedBuffer } from "../types/index.js";
14
+ /** Strip a leading BOM (U+FEFF) from an already-decoded string. */
15
+ export declare function stripBomString(text: string): string;
16
+ /**
17
+ * Decode a buffer to text, detecting the encoding unless overridden.
18
+ *
19
+ * @param isCompleteBuffer - Whether `buffer` is the entire source (default
20
+ * `true`). Pass `false` when `buffer` is only a peeked prefix (e.g. a
21
+ * streaming head-sniff) — the ASCII fast path still commits to "utf-8" for
22
+ * the peeked bytes (unchanged behavior), but reports a lower confidence
23
+ * since later bytes not yet seen could still be non-ASCII (#1199).
24
+ */
25
+ export declare function decodeBuffer(buffer: Buffer, override?: string, isCompleteBuffer?: boolean): DecodedBuffer;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Buffer → string decoding with encoding detection (#362).
3
+ *
4
+ * Order of resolution:
5
+ * 1. Explicit override (any iconv-lite label).
6
+ * 2. Deterministic BOM sniff (UTF-8 / UTF-16 LE / UTF-16 BE).
7
+ * 3. `chardet` statistical detection, mapped to an iconv-lite label.
8
+ * 4. Fallback to UTF-8 (preserves the historical hard-coded default).
9
+ *
10
+ * Decoding always goes through `iconv-lite`, and any residual BOM is stripped
11
+ * from the decoded string so it can't glue onto the first token.
12
+ */
13
+ import chardet from "chardet";
14
+ import iconv from "iconv-lite";
15
+ import { logger } from "./logger.js";
16
+ /** Strip a leading BOM (U+FEFF) from an already-decoded string. */
17
+ export function stripBomString(text) {
18
+ return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
19
+ }
20
+ /** Deterministic BOM detection from the raw bytes. Returns null when absent. */
21
+ function sniffBom(buffer) {
22
+ if (buffer.length >= 3 &&
23
+ buffer[0] === 0xef &&
24
+ buffer[1] === 0xbb &&
25
+ buffer[2] === 0xbf) {
26
+ return "utf-8";
27
+ }
28
+ if (buffer.length >= 2 && buffer[0] === 0xff && buffer[1] === 0xfe) {
29
+ return "utf-16le";
30
+ }
31
+ if (buffer.length >= 2 && buffer[0] === 0xfe && buffer[1] === 0xff) {
32
+ return "utf-16be";
33
+ }
34
+ return null;
35
+ }
36
+ /**
37
+ * Map a chardet label to an iconv-lite-supported label. Returns null when the
38
+ * label is unknown/unsupported so the caller falls back to UTF-8.
39
+ */
40
+ function toIconvLabel(detected) {
41
+ if (!detected) {
42
+ return null;
43
+ }
44
+ const normalized = detected.toLowerCase().replace(/[^a-z0-9]/g, "");
45
+ const map = {
46
+ utf8: "utf-8",
47
+ utf16le: "utf-16le",
48
+ utf16be: "utf-16be",
49
+ ascii: "utf-8", // ASCII is a strict UTF-8 subset
50
+ windows1252: "windows-1252",
51
+ iso88591: "latin1",
52
+ latin1: "latin1",
53
+ iso88592: "iso-8859-2",
54
+ windows1251: "windows-1251",
55
+ koi8r: "koi8-r",
56
+ gb18030: "gb18030",
57
+ big5: "big5",
58
+ shiftjis: "shift_jis",
59
+ euckr: "euc-kr",
60
+ eucjp: "euc-jp",
61
+ };
62
+ const label = map[normalized] ?? detected;
63
+ return iconv.encodingExists(label) ? label : null;
64
+ }
65
+ /**
66
+ * Decode a buffer to text, detecting the encoding unless overridden.
67
+ *
68
+ * @param isCompleteBuffer - Whether `buffer` is the entire source (default
69
+ * `true`). Pass `false` when `buffer` is only a peeked prefix (e.g. a
70
+ * streaming head-sniff) — the ASCII fast path still commits to "utf-8" for
71
+ * the peeked bytes (unchanged behavior), but reports a lower confidence
72
+ * since later bytes not yet seen could still be non-ASCII (#1199).
73
+ */
74
+ export function decodeBuffer(buffer, override, isCompleteBuffer = true) {
75
+ // 1. Explicit override wins outright.
76
+ if (override && iconv.encodingExists(override)) {
77
+ return {
78
+ text: stripBomString(iconv.decode(buffer, override)),
79
+ encoding: override,
80
+ confidence: 100,
81
+ };
82
+ }
83
+ if (override && !iconv.encodingExists(override)) {
84
+ logger.warn(`[textEncoding] Unsupported encoding override "${override}"; falling back to detection.`);
85
+ }
86
+ // 2. BOM is authoritative when present.
87
+ const bom = sniffBom(buffer);
88
+ if (bom) {
89
+ return {
90
+ text: stripBomString(iconv.decode(buffer, bom)),
91
+ encoding: bom,
92
+ confidence: 100,
93
+ };
94
+ }
95
+ // 2b. Pure-ASCII fast path — chardet often reports ASCII as ISO-8859-1, but
96
+ // ASCII is a strict UTF-8 subset and decodes identically, so report "utf-8"
97
+ // to preserve the historical default for plain-ASCII files.
98
+ let ascii = true;
99
+ for (let i = 0; i < buffer.length; i++) {
100
+ if (buffer[i] >= 0x80) {
101
+ ascii = false;
102
+ break;
103
+ }
104
+ }
105
+ if (ascii) {
106
+ return {
107
+ text: stripBomString(iconv.decode(buffer, "utf-8")),
108
+ encoding: "utf-8",
109
+ // A partial peek being pure-ASCII only proves the peeked prefix is
110
+ // ASCII, not the whole file — a later chunk could still switch to a
111
+ // legacy encoding. Only report full certainty when `buffer` is known
112
+ // to be the complete source.
113
+ confidence: isCompleteBuffer ? 100 : 60,
114
+ };
115
+ }
116
+ // 3. Statistical detection.
117
+ let detected = null;
118
+ try {
119
+ detected = chardet.detect(buffer);
120
+ }
121
+ catch (error) {
122
+ logger.debug(`[textEncoding] chardet failed: ${String(error)}`);
123
+ }
124
+ const label = toIconvLabel(detected);
125
+ if (label) {
126
+ return {
127
+ text: stripBomString(iconv.decode(buffer, label)),
128
+ encoding: label,
129
+ // chardet gives a winner but not a numeric score via detect(); treat a
130
+ // confident non-UTF-8 hit as reasonably (not fully) certain.
131
+ confidence: label === "utf-8" ? 100 : 80,
132
+ };
133
+ }
134
+ // 4. Fallback: historical UTF-8 default.
135
+ return {
136
+ text: stripBomString(iconv.decode(buffer, "utf-8")),
137
+ encoding: "utf-8",
138
+ confidence: 0,
139
+ };
140
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.94.1",
3
+ "version": "9.94.3",
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": {
@@ -358,6 +358,7 @@
358
358
  "adm-zip": "^0.5.16",
359
359
  "ai": "^6.0.134",
360
360
  "chalk": "^5.6.2",
361
+ "chardet": "2.1.1",
361
362
  "croner": "^9.1.0",
362
363
  "csv-parser": "^3.2.0",
363
364
  "dotenv": "^17.3.1",
@@ -365,6 +366,7 @@
365
366
  "fast-xml-parser": "^5.7.0",
366
367
  "google-auth-library": "^10.6.1",
367
368
  "hono": "^4.12.21",
369
+ "iconv-lite": "0.7.2",
368
370
  "inquirer": "^13.3.0",
369
371
  "jose": "^6.1.3",
370
372
  "js-yaml": "^4.2.0",